1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.wifi.rtt; 18 19 import android.annotation.FlaggedApi; 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.net.wifi.ScanResult; 24 import android.net.wifi.WifiSsid; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 28 import com.android.modules.utils.build.SdkLevel; 29 import com.android.wifi.flags.Flags; 30 31 import java.lang.annotation.Retention; 32 import java.lang.annotation.RetentionPolicy; 33 import java.util.Arrays; 34 import java.util.HashMap; 35 import java.util.Map; 36 import java.util.Objects; 37 38 /** 39 * Pre-association security negotiation (PASN) configuration. 40 * <p> 41 * PASN configuration in IEEE 802.11az focuses on securing the ranging process before a device 42 * fully associates with a Wi-Fi network. IEEE 802.11az supports various based AKMs as in 43 * {@code AKM_*} for PASN and cipher as in {@code CIPHER_*}. Password is also another input to 44 * some base AKMs. 45 * <p> 46 * Once PASN is initiated, the AP and the client device exchange messages to authenticate each 47 * other and establish security keys. This process ensures that only authorized devices can 48 * participate in ranging. 49 * <p> 50 * After successful PASN authentication, ranging operations are performed using the established 51 * secure channel. This protects the ranging measurements from eavesdropping and tampering. 52 * <p> 53 * The keys derived during the PASN process are used to protect the LTFs exchanged during ranging. 54 * This ensures that the LTFs are encrypted and authenticated, preventing unauthorized access 55 * and manipulation. 56 */ 57 @FlaggedApi(Flags.FLAG_SECURE_RANGING) 58 public final class PasnConfig implements Parcelable { 59 60 /** 61 * Various base Authentication and Key Management (AKM) protocol supported by the PASN. 62 * 63 * @hide 64 */ 65 @IntDef(prefix = {"AKM_"}, flag = true, value = { 66 AKM_NONE, 67 AKM_PASN, 68 AKM_SAE, 69 AKM_FT_EAP_SHA256, 70 AKM_FT_PSK_SHA256, 71 AKM_FT_EAP_SHA384, 72 AKM_FT_PSK_SHA384, 73 AKM_FILS_EAP_SHA256, 74 AKM_FILS_EAP_SHA384}) 75 @Retention(RetentionPolicy.SOURCE) 76 public @interface AkmType { 77 } 78 79 /** 80 * No authentication and key management. 81 */ 82 public static final int AKM_NONE = 0; 83 /** 84 * Pre-association security negotiation (PASN). 85 */ 86 public static final int AKM_PASN = 1 << 0; 87 /** 88 * Simultaneous authentication of equals (SAE). 89 */ 90 public static final int AKM_SAE = 1 << 1; 91 /** 92 * Fast BSS Transition (FT) with Extensible Authentication Protocol (EAP) and SHA-256. 93 */ 94 public static final int AKM_FT_EAP_SHA256 = 1 << 2; 95 /** 96 * Fast BSS Transition (FT) with Pre-Shared Key (PSK) and SHA-256. 97 */ 98 public static final int AKM_FT_PSK_SHA256 = 1 << 3; 99 /** 100 * Fast BSS Transition (FT) with Extensible Authentication Protocol (EAP) and SHA-384. 101 */ 102 public static final int AKM_FT_EAP_SHA384 = 1 << 4; 103 /** 104 * Fast BSS Transition (FT) with Pre-Shared Key (PSK) and SHA-384. 105 */ 106 public static final int AKM_FT_PSK_SHA384 = 1 << 5; 107 /** 108 * Fast Initial Link Setup (FILS) with Extensible Authentication Protocol (EAP) and SHA-256. 109 */ 110 public static final int AKM_FILS_EAP_SHA256 = 1 << 6; 111 /** 112 * Fast Initial Link Setup (FILS) with Extensible Authentication Protocol (EAP) and SHA-384. 113 */ 114 public static final int AKM_FILS_EAP_SHA384 = 1 << 7; 115 116 /** 117 * @hide 118 */ 119 private static final Map<String, Integer> sStringToAkm = new HashMap<>(); 120 121 static { 122 sStringToAkm.put("None", AKM_NONE); 123 sStringToAkm.put("PASN", AKM_PASN); 124 sStringToAkm.put("SAE", AKM_SAE); 125 sStringToAkm.put("EAP-FILS-SHA256", AKM_FILS_EAP_SHA256); 126 sStringToAkm.put("EAP-FILS-SHA384", AKM_FILS_EAP_SHA384); 127 sStringToAkm.put("FT/EAP", AKM_FT_EAP_SHA256); 128 sStringToAkm.put("FT/PSK", AKM_FT_PSK_SHA256); 129 sStringToAkm.put("EAP-FT-SHA384", AKM_FT_EAP_SHA384); 130 sStringToAkm.put("FT/PSK-SHA384", AKM_FT_PSK_SHA384); 131 } 132 133 /** 134 * Pairwise cipher used for encryption. 135 * 136 * @hide 137 */ 138 @IntDef(prefix = {"CIPHER_"}, flag = true, value = { 139 CIPHER_NONE, 140 CIPHER_CCMP_128, 141 CIPHER_CCMP_256, 142 CIPHER_GCMP_128, 143 CIPHER_GCMP_256}) 144 @Retention(RetentionPolicy.SOURCE) 145 public @interface Cipher { 146 } 147 148 /** 149 * No encryption. 150 */ 151 public static final int CIPHER_NONE = 0; 152 /** 153 * Counter Mode with Cipher Block Chaining Message Authentication Code Protocol (CCMP) with 154 * 128-bit key. 155 */ 156 public static final int CIPHER_CCMP_128 = 1 << 0; 157 /** 158 * Counter Mode with Cipher Block Chaining Message Authentication Code Protocol (CCMP) with 159 * 256-bit key. 160 */ 161 public static final int CIPHER_CCMP_256 = 1 << 1; 162 /** 163 * Galois/Counter Mode Protocol (GCMP) with 128-bit key. 164 */ 165 public static final int CIPHER_GCMP_128 = 1 << 2; 166 /** 167 * Galois/Counter Mode Protocol (GCMP) with 256-bit key. 168 */ 169 public static final int CIPHER_GCMP_256 = 1 << 3; 170 private static final Map<String, Integer> sStringToCipher = new HashMap<>(); 171 172 static { 173 sStringToCipher.put("None", CIPHER_NONE); 174 sStringToCipher.put("CCMP-128", CIPHER_CCMP_128); 175 sStringToCipher.put("CCMP-256", CIPHER_CCMP_256); 176 sStringToCipher.put("GCMP-128", CIPHER_GCMP_128); 177 sStringToCipher.put("GCMP-256", CIPHER_GCMP_256); 178 } 179 180 @AkmType 181 private final int mBaseAkms; 182 @Cipher 183 private final int mCiphers; 184 private String mPassword; 185 private final WifiSsid mWifiSsid; 186 private final byte[] mPasnComebackCookie; 187 188 /** 189 * Return base AKMs (Authentication and Key Management). 190 */ getBaseAkms()191 public @AkmType int getBaseAkms() { 192 return mBaseAkms; 193 } 194 195 /** 196 * Return pairwise ciphers. 197 */ getCiphers()198 public @Cipher int getCiphers() { 199 return mCiphers; 200 } 201 202 /** 203 * Get password used by base AKM. If null, password is retrieved from the saved network 204 * profile for the PASN authentication. See {@link #getWifiSsid()} on retrieving saved 205 * network profile. 206 */ 207 @Nullable getPassword()208 public String getPassword() { 209 return mPassword; 210 } 211 212 /** 213 * @hide 214 */ setPassword(String password)215 public void setPassword(String password) { 216 mPassword = password; 217 } 218 219 /** 220 * Get Wifi SSID which is used to retrieve saved network profile if {@link #getPassword()} 221 * is null. If Wifi SSID and password are not set and there is no saved profile corresponding to 222 * the responder, unauthenticated PASN will be used if {@link RangingRequest#getSecurityMode()} 223 * allows. See {@code SECURITY_MODE_*} for more details. 224 */ 225 @Nullable getWifiSsid()226 public WifiSsid getWifiSsid() { 227 return mWifiSsid; 228 } 229 230 /** 231 * Get PASN comeback cookie. See {@link Builder#setPasnComebackCookie(byte[])}. 232 **/ 233 @Nullable getPasnComebackCookie()234 public byte[] getPasnComebackCookie() { 235 return mPasnComebackCookie; 236 } 237 238 PasnConfig(@onNull Parcel in)239 private PasnConfig(@NonNull Parcel in) { 240 mBaseAkms = in.readInt(); 241 mCiphers = in.readInt(); 242 mPassword = in.readString(); 243 mWifiSsid = (SdkLevel.isAtLeastT()) ? in.readParcelable(WifiSsid.class.getClassLoader(), 244 WifiSsid.class) : in.readParcelable(WifiSsid.class.getClassLoader()); 245 mPasnComebackCookie = in.createByteArray(); 246 } 247 248 public static final @NonNull Creator<PasnConfig> CREATOR = new Creator<PasnConfig>() { 249 @Override 250 public PasnConfig createFromParcel(Parcel in) { 251 return new PasnConfig(in); 252 } 253 254 @Override 255 public PasnConfig[] newArray(int size) { 256 return new PasnConfig[size]; 257 } 258 }; 259 260 @Override describeContents()261 public int describeContents() { 262 return 0; 263 } 264 265 @Override writeToParcel(@ndroidx.annotation.NonNull Parcel dest, int flags)266 public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) { 267 dest.writeInt(mBaseAkms); 268 dest.writeInt(mCiphers); 269 dest.writeString(mPassword); 270 dest.writeParcelable(mWifiSsid, flags); 271 dest.writeByteArray(mPasnComebackCookie); 272 } 273 274 /** 275 * Convert capability string from {@link ScanResult} to a set of 276 * {@code AKM_*} supported by the PASN. 277 * 278 * @hide 279 */ getBaseAkmsFromCapabilities(String capabilities)280 public @AkmType static int getBaseAkmsFromCapabilities(String capabilities) { 281 @AkmType int akms = AKM_NONE; 282 if (capabilities == null) return akms; 283 for (String akm : sStringToAkm.keySet()) { 284 if (capabilities.contains(akm)) { 285 akms |= sStringToAkm.get(akm); 286 } 287 } 288 return akms; 289 } 290 291 /** 292 * Convert capability string from {@link ScanResult} to a set of 293 * {@code CIPHER_*}. 294 * 295 * @hide 296 */ getCiphersFromCapabilities(String capabilities)297 public @Cipher static int getCiphersFromCapabilities(String capabilities) { 298 @Cipher int ciphers = CIPHER_NONE; 299 if (capabilities == null) return ciphers; 300 for (String cipher : sStringToCipher.keySet()) { 301 if (capabilities.contains(cipher)) { 302 ciphers |= sStringToCipher.get(cipher); 303 } 304 } 305 return ciphers; 306 } 307 PasnConfig(Builder builder)308 private PasnConfig(Builder builder) { 309 mBaseAkms = builder.mBaseAkms; 310 mCiphers = builder.mCiphers; 311 mPassword = builder.mPassword; 312 mWifiSsid = builder.mWifiSsid; 313 mPasnComebackCookie = builder.mPasnComebackCookie; 314 } 315 316 /** 317 * @hide 318 */ isAkmRequiresPassword(int akms)319 public static boolean isAkmRequiresPassword(int akms) { 320 return (akms & AKM_SAE) != 0; 321 } 322 323 /** 324 * Builder for {@link PasnConfig} 325 */ 326 @FlaggedApi(Flags.FLAG_SECURE_RANGING) 327 public static final class Builder { 328 private final int mBaseAkms; 329 private final int mCiphers; 330 private String mPassword = null; 331 private WifiSsid mWifiSsid = null; 332 byte[] mPasnComebackCookie = null; 333 334 /** 335 * Builder 336 * 337 * @param baseAkms The AKMs that PASN is configured to use. PASN will use the most secure 338 * AKM in the configuration. 339 * @param ciphers The CIPHERs that PASN is configured to use. PASN will use the most 340 * secure CIPHER in the configuration which is applicable to the base AKM 341 */ Builder(@kmType int baseAkms, @Cipher int ciphers)342 public Builder(@AkmType int baseAkms, @Cipher int ciphers) { 343 mBaseAkms = baseAkms; 344 mCiphers = ciphers; 345 } 346 347 /** 348 * Sets the password if needed by the base AKM of the PASN. If not set, password is 349 * retrieved from the saved profile identified by the SSID. See 350 * {@link #setWifiSsid(WifiSsid)}. 351 * 352 * Note: If password and SSID is not set, secure ranging will use unauthenticated PASN. 353 * 354 * @param password password string 355 * @return a reference to this Builder 356 */ 357 @NonNull setPassword(@onNull String password)358 public Builder setPassword(@NonNull String password) { 359 Objects.requireNonNull(password, "Password must not be null"); 360 this.mPassword = password; 361 return this; 362 } 363 364 /** 365 * Sets the Wi-Fi Service Set Identifier (SSID). This is used to get the saved profile to 366 * retrieve password if password is not set using {@link #setPassword(String)}. 367 * 368 * Note: If password and SSID is not set, secure ranging will use unauthenticated PASN. 369 * 370 * @param wifiSsid Wi-Fi Service Set Identifier (SSID) 371 * @return a reference to this Builder 372 */ 373 @NonNull setWifiSsid(@onNull WifiSsid wifiSsid)374 public Builder setWifiSsid(@NonNull WifiSsid wifiSsid) { 375 Objects.requireNonNull(wifiSsid, "SSID must not be null"); 376 this.mWifiSsid = wifiSsid; 377 return this; 378 } 379 380 /** 381 * Set PASN comeback cookie. PASN authentication allows the station to provide comeback 382 * cookie which was indicated in the {@link RangingResult} by the AP with a deferral time. 383 * <p> 384 * When an AP receives a large volume of initial PASN Authentication frames, it can use 385 * the comeback after field in the PASN Parameters element to indicate a deferral time 386 * and optionally provide a comeback cookie which is an opaque sequence of octets. Upon 387 * receiving this response, the ranging initiator (STA) must wait for the specified time 388 * before retrying secure authentication, presenting the received cookie to the AP. See 389 * {@link RangingResult#getPasnComebackCookie()} and 390 * {@link RangingResult#getPasnComebackAfterMillis()}. 391 * 392 * @param pasnComebackCookie an opaque sequence of octets 393 * @return a reference to this Builder 394 */ 395 @NonNull setPasnComebackCookie(@onNull byte[] pasnComebackCookie)396 public Builder setPasnComebackCookie(@NonNull byte[] pasnComebackCookie) { 397 Objects.requireNonNull(pasnComebackCookie, "PASN comeback cookie must not be null"); 398 if (pasnComebackCookie.length > 255 || pasnComebackCookie.length == 0) { 399 throw new IllegalArgumentException("Cookie with invalid length " 400 + pasnComebackCookie.length); 401 } 402 mPasnComebackCookie = pasnComebackCookie; 403 return this; 404 } 405 406 /** 407 * Builds a {@link PasnConfig} object. 408 */ 409 @NonNull build()410 public PasnConfig build() { 411 return new PasnConfig(this); 412 } 413 } 414 415 @Override equals(Object o)416 public boolean equals(Object o) { 417 if (this == o) return true; 418 if (!(o instanceof PasnConfig that)) return false; 419 return mBaseAkms == that.mBaseAkms && mCiphers == that.mCiphers && Objects.equals( 420 mPassword, that.mPassword) && Objects.equals(mWifiSsid, that.mWifiSsid) 421 && Arrays.equals(mPasnComebackCookie, that.mPasnComebackCookie); 422 } 423 424 @Override hashCode()425 public int hashCode() { 426 int result = Objects.hash(mBaseAkms, mCiphers, mPassword, mWifiSsid); 427 result = 31 * result + Arrays.hashCode(mPasnComebackCookie); 428 return result; 429 } 430 431 @Override toString()432 public String toString() { 433 String password = (mPassword != null ? "*" : "null"); 434 return "PasnConfig{" + "mBaseAkms=" + mBaseAkms + ", mCiphers=" + mCiphers + ", mPassword='" 435 + password + '\'' + ", mWifiSsid=" + mWifiSsid + ", mPasnComebackCookie=" 436 + Arrays.toString(mPasnComebackCookie) + '}'; 437 } 438 } 439