1 /*
2  * Copyright (C) 2016 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 com.android.server.wifi.hotspot2;
18 
19 import static com.android.server.wifi.hotspot2.Utils.isCarrierEapMethod;
20 
21 import com.android.server.wifi.IMSIParameter;
22 import com.android.server.wifi.hotspot2.anqp.CellularNetwork;
23 import com.android.server.wifi.hotspot2.anqp.DomainNameElement;
24 import com.android.server.wifi.hotspot2.anqp.NAIRealmData;
25 import com.android.server.wifi.hotspot2.anqp.NAIRealmElement;
26 import com.android.server.wifi.hotspot2.anqp.RoamingConsortiumElement;
27 import com.android.server.wifi.hotspot2.anqp.ThreeGPPNetworkElement;
28 import com.android.server.wifi.hotspot2.anqp.eap.AuthParam;
29 import com.android.server.wifi.hotspot2.anqp.eap.EAPMethod;
30 
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34 
35 /**
36  * Utility class for providing matching functions against ANQP elements.
37  */
38 public class ANQPMatcher {
39     /**
40      * Match the domain names in the ANQP element against the provider's FQDN and SIM credential.
41      * The Domain Name ANQP element might contain domains for 3GPP network (e.g.
42      * wlan.mnc*.mcc*.3gppnetwork.org), so we should match that against the provider's SIM
43      * credential if one is provided.
44      *
45      * @param element The Domain Name ANQP element
46      * @param fqdn The FQDN to compare against
47      * @param imsiParam The IMSI parameter of the provider
48      * @param simImsiList The list of IMSI from the installed SIM cards that matched provider's
49      *                    IMSI parameter
50      * @return true if a match is found
51      */
matchDomainName(DomainNameElement element, String fqdn, IMSIParameter imsiParam, List<String> simImsiList)52     public static boolean matchDomainName(DomainNameElement element, String fqdn,
53             IMSIParameter imsiParam, List<String> simImsiList) {
54         if (element == null) {
55             return false;
56         }
57 
58         for (String domain : element.getDomains()) {
59             if (DomainMatcher.arg2SubdomainOfArg1(fqdn, domain)) {
60                 return true;
61             }
62 
63             // Try to retrieve the MCC-MNC string from the domain (for 3GPP network domain) and
64             // match against the provider's SIM credential.
65             if (matchMccMnc(Utils.getMccMnc(Utils.splitDomain(domain)), imsiParam, simImsiList)) {
66                 return true;
67             }
68         }
69         return false;
70     }
71 
72     /**
73      * Match the roaming consortium OIs in the ANQP element against the roaming consortium OIs
74      * of a provider.
75      *
76      * @param element The Roaming Consortium ANQP element
77      * @param providerOIs The roaming consortium OIs of the provider
78      * @return true if a match is found
79      */
matchRoamingConsortium(RoamingConsortiumElement element, long[] providerOIs)80     public static boolean matchRoamingConsortium(RoamingConsortiumElement element,
81             long[] providerOIs) {
82         if (element == null) {
83             return false;
84         }
85         if (providerOIs == null) {
86             return false;
87         }
88         List<Long> rcOIs = element.getOIs();
89         for (long oi : providerOIs) {
90             if (rcOIs.contains(oi)) {
91                 return true;
92             }
93         }
94         return false;
95     }
96 
97     /**
98      * Match the NAI realm in the ANQP element against the realm and authentication method of
99      * a provider.
100      *
101      * @param element The NAI Realm ANQP element
102      * @param realm The realm of the provider's credential
103      * @param eapMethodID The EAP Method ID of the provider's credential
104      * @param authParam The authentication parameter of the provider's credential
105      * @return an integer indicating the match status
106      */
matchNAIRealm(NAIRealmElement element, String realm, int eapMethodID, AuthParam authParam)107     public static int matchNAIRealm(NAIRealmElement element, String realm, int eapMethodID,
108             AuthParam authParam) {
109         if (element == null || element.getRealmDataList().isEmpty()) {
110             return AuthMatch.INDETERMINATE;
111         }
112 
113         int bestMatch = AuthMatch.NONE;
114         for (NAIRealmData realmData : element.getRealmDataList()) {
115             int match = matchNAIRealmData(realmData, realm, eapMethodID, authParam);
116             if (match > bestMatch) {
117                 bestMatch = match;
118                 if (bestMatch == AuthMatch.EXACT) {
119                     break;
120                 }
121             }
122         }
123         return bestMatch;
124     }
125 
126     /**
127      * Get a EAP-Method from a corresponding NAI realm that has one of them (EAP-SIM/AKA/AKA)'.
128      *
129      * @param realm a realm of the provider's credential.
130      * @param element The NAI Realm ANQP element
131      * @return a EAP Method (EAP-SIM/AKA/AKA') from matching NAI realm, {@code -1} otherwise.
132      */
getCarrierEapMethodFromMatchingNAIRealm(String realm, NAIRealmElement element)133     public static int getCarrierEapMethodFromMatchingNAIRealm(String realm,
134             NAIRealmElement element) {
135         if (element == null || element.getRealmDataList().isEmpty()) {
136             return -1;
137         }
138 
139         for (NAIRealmData realmData : element.getRealmDataList()) {
140             int eapMethodID = getEapMethodForNAIRealmWithCarrier(realm, realmData);
141             if (eapMethodID != -1) {
142                 return eapMethodID;
143             }
144         }
145         return -1;
146     }
147 
148     /**
149      * Match the 3GPP Network in the ANQP element against the SIM credential of a provider.
150      *
151      * @param element 3GPP Network ANQP element
152      * @param imsiParam The IMSI parameter of the provider's SIM credential
153      * @param simImsiList The list of IMSI from the installed SIM cards that matched provider's
154      *                    IMSI parameter
155      * @return true if a matched is found
156      */
matchThreeGPPNetwork(ThreeGPPNetworkElement element, IMSIParameter imsiParam, List<String> simImsiList)157     public static  boolean matchThreeGPPNetwork(ThreeGPPNetworkElement element,
158             IMSIParameter imsiParam, List<String> simImsiList) {
159         if (element == null) {
160             return false;
161         }
162         for (CellularNetwork network : element.getNetworks()) {
163             if (matchCellularNetwork(network, imsiParam, simImsiList)) {
164                 return true;
165             }
166         }
167         return false;
168     }
169 
170     /**
171      * Match the given NAI Realm data against the realm and authentication method of a provider.
172      *
173      * @param realmData The NAI Realm data
174      * @param realm The realm of the provider's credential
175      * @param eapMethodID The EAP Method ID of the provider's credential
176      * @param authParam The authentication parameter of the provider's credential
177      * @return an integer indicating the match status
178      */
matchNAIRealmData(NAIRealmData realmData, String realm, int eapMethodID, AuthParam authParam)179     private static int matchNAIRealmData(NAIRealmData realmData, String realm, int eapMethodID,
180             AuthParam authParam) {
181         // Check for realm domain name match.
182         int realmMatch = AuthMatch.NONE;
183         for (String realmStr : realmData.getRealms()) {
184             if (DomainMatcher.arg2SubdomainOfArg1(realm, realmStr)) {
185                 realmMatch = AuthMatch.REALM;
186                 break;
187             }
188         }
189 
190         if (realmData.getEAPMethods().isEmpty()) {
191             return realmMatch;
192         }
193 
194         // Check for EAP method match.
195         int eapMethodMatch = AuthMatch.NONE;
196         for (EAPMethod eapMethod : realmData.getEAPMethods()) {
197             eapMethodMatch = matchEAPMethod(eapMethod, eapMethodID, authParam);
198             if (eapMethodMatch != AuthMatch.NONE) {
199                 break;
200             }
201         }
202 
203         if (eapMethodMatch == AuthMatch.NONE) {
204             return AuthMatch.NONE;
205         }
206 
207         if (realmMatch == AuthMatch.NONE) {
208             return eapMethodMatch;
209         }
210         return realmMatch | eapMethodMatch;
211     }
212 
getEapMethodForNAIRealmWithCarrier(String realm, NAIRealmData realmData)213     private static int getEapMethodForNAIRealmWithCarrier(String realm,
214             NAIRealmData realmData) {
215         int realmMatch = AuthMatch.NONE;
216 
217         for (String realmStr : realmData.getRealms()) {
218             if (DomainMatcher.arg2SubdomainOfArg1(realm, realmStr)) {
219                 realmMatch = AuthMatch.REALM;
220                 break;
221             }
222         }
223 
224         if (realmMatch == AuthMatch.NONE) {
225             return -1;
226         }
227 
228         for (EAPMethod eapMethod : realmData.getEAPMethods()) {
229             if (isCarrierEapMethod(eapMethod.getEAPMethodID())) {
230                 return eapMethod.getEAPMethodID();
231             }
232         }
233         return -1;
234     }
235 
236     /**
237      * Match the given EAPMethod against the authentication method of a provider.
238      *
239      * @param method The EAP Method
240      * @param eapMethodID The EAP Method ID of the provider's credential
241      * @param authParam The authentication parameter of the provider's credential
242      * @return an integer indicating the match status
243      */
matchEAPMethod(EAPMethod method, int eapMethodID, AuthParam authParam)244     private static int matchEAPMethod(EAPMethod method, int eapMethodID, AuthParam authParam) {
245         if (method.getEAPMethodID() != eapMethodID) {
246             return AuthMatch.NONE;
247         }
248         // Check for authentication parameter match.
249         if (authParam != null) {
250             Map<Integer, Set<AuthParam>> authParams = method.getAuthParams();
251             if (authParams.isEmpty()) {
252                 // no auth methods to match
253                 return AuthMatch.METHOD;
254             }
255             Set<AuthParam> paramSet = authParams.get(authParam.getAuthTypeID());
256             if (paramSet == null || !paramSet.contains(authParam)) {
257                 return AuthMatch.NONE;
258             }
259             return AuthMatch.METHOD_PARAM;
260         }
261         return AuthMatch.METHOD;
262     }
263 
264     /**
265      * Match a cellular network information in the 3GPP Network ANQP element against the SIM
266      * credential of a provider.
267      *
268      * @param network The cellular network that contained list of PLMNs
269      * @param imsiParam IMSI parameter of the provider
270      * @param simImsiList The list of IMSI from the installed SIM cards that matched provider's
271      *                    IMSI parameter
272      * @return true if a match is found
273      */
matchCellularNetwork(CellularNetwork network, IMSIParameter imsiParam, List<String> simImsiList)274     private static boolean matchCellularNetwork(CellularNetwork network, IMSIParameter imsiParam,
275             List<String> simImsiList) {
276         for (String plmn : network.getPlmns()) {
277             if (matchMccMnc(plmn, imsiParam, simImsiList)) {
278                 return true;
279             }
280         }
281         return false;
282     }
283 
284     /**
285      * Match a MCC-MNC against the SIM credential of a provider.
286      *
287      * @param mccMnc The string containing MCC-MNC
288      * @param imsiParam The IMSI parameter of the provider
289      * @param simImsiList The list of IMSI from the installed SIM cards that matched provider's
290      *                    IMSI parameter
291      * @return true if a match is found
292      */
matchMccMnc(String mccMnc, IMSIParameter imsiParam, List<String> simImsiList)293     private static boolean matchMccMnc(String mccMnc, IMSIParameter imsiParam,
294             List<String> simImsiList) {
295         if (imsiParam == null || simImsiList == null) {
296             return false;
297         }
298         // Match against the IMSI parameter in the provider.
299         if (!imsiParam.matchesMccMnc(mccMnc)) {
300             return false;
301         }
302         // Additional check for verifying the match with IMSIs from the SIM cards, since the IMSI
303         // parameter might not contain the full 6-digit MCC MNC (e.g. IMSI parameter is an IMSI
304         // prefix that contained less than 6-digit of numbers "12345*").
305         for (String imsi : simImsiList) {
306             if (imsi.startsWith(mccMnc)) {
307                 return true;
308             }
309         }
310         return false;
311     }
312 }
313