• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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