MOManager.java revision d3fb9cbb12d013dd70e672ace5c41ab18a3679a0
1package com.android.server.wifi.hotspot2.omadm;
2
3import android.util.Log;
4
5import com.android.server.wifi.anqp.eap.EAP;
6import com.android.server.wifi.anqp.eap.EAPMethod;
7import com.android.server.wifi.anqp.eap.ExpandedEAPMethod;
8import com.android.server.wifi.anqp.eap.InnerAuthEAP;
9import com.android.server.wifi.anqp.eap.NonEAPInnerAuth;
10import com.android.server.wifi.hotspot2.Utils;
11import com.android.server.wifi.hotspot2.pps.Credential;
12import com.android.server.wifi.hotspot2.pps.HomeSP;
13
14import org.xml.sax.SAXException;
15
16import java.io.BufferedInputStream;
17import java.io.BufferedOutputStream;
18import java.io.File;
19import java.io.FileInputStream;
20import java.io.FileOutputStream;
21import java.io.IOException;
22import java.io.InputStream;
23import java.text.DateFormat;
24import java.text.ParseException;
25import java.text.SimpleDateFormat;
26import java.util.ArrayList;
27import java.util.Arrays;
28import java.util.Collection;
29import java.util.Date;
30import java.util.HashMap;
31import java.util.HashSet;
32import java.util.List;
33import java.util.Map;
34import java.util.Set;
35import java.util.TimeZone;
36
37/**
38 * Handles provisioning of PerProviderSubscription data.
39 */
40public class MOManager {
41    private final File mPpsFile;
42    private final Map<String, HomeSP> mSPs;
43
44    public MOManager(File ppsFile) throws IOException {
45        mPpsFile = ppsFile;
46        mSPs = new HashMap<>();
47    }
48
49    public File getPpsFile() {
50        return mPpsFile;
51    }
52
53    public Map<String, HomeSP> getLoadedSPs() {
54        return mSPs;
55    }
56
57    public List<HomeSP> loadAllSPs() throws IOException {
58        List<MOTree> trees = new ArrayList<MOTree>();
59        List<HomeSP> sps = new ArrayList<HomeSP>();
60
61        if (!mPpsFile.exists()) {
62            return sps;
63        }
64
65        BufferedInputStream in = null;
66        try {
67            in = new BufferedInputStream(new FileInputStream(mPpsFile));
68            while (in.available() > 0) {
69                MOTree tree = MOTree.unmarshal(in);
70                if (tree != null) {
71                    Log.d("PARSE-LOG", "adding tree no " + trees.size());
72                    trees.add(tree);
73                } else {
74                    break;
75                }
76            }
77        } finally {
78            if (in != null) {
79                try {
80                    in.close();
81                } catch (IOException ioe) {
82                    /**/
83                }
84            }
85        }
86
87        Log.d("PARSE-LOG", "number of trees " + trees.size());
88        for (MOTree moTree : trees) {
89            Log.d("PARSE-LOG", "pasring a moTree");
90            List<HomeSP> sp = buildSPs(moTree);
91            if (sp != null) {
92                Log.d("PARSE-LOG", "built " + sp.size() + " HomeSPs");
93                sps.addAll(sp);
94            } else {
95                Log.d("PARSE-LOG", "failed to build HomeSP");
96            }
97        }
98
99        Log.d("PARSE-LOG", "collected " + sps.size());
100        for (HomeSP sp : sps) {
101            Log.d("PARSE-LOG", "adding " + sp.getFQDN());
102            if (mSPs.put(sp.getFQDN(), sp) != null) {
103                Log.d("PARSE-LOG", "failed to add " + sp.getFQDN());
104                throw new OMAException("Multiple SPs for FQDN '" + sp.getFQDN() + "'");
105            } else {
106                Log.d("PARSE-LOG", "added " + sp.getFQDN() + " to list");
107            }
108        }
109
110        Log.d("PARSE-LOG", "found " + mSPs.size() + " configurations");
111        return sps;
112    }
113
114    public HomeSP addSP(InputStream xmlIn) throws IOException, SAXException {
115        OMAParser omaParser = new OMAParser();
116        MOTree tree = omaParser.parse(xmlIn, OMAConstants.LOC_PPS + ":1.0");
117        List<HomeSP> spList = buildSPs(tree);
118        if (spList.size() != 1) {
119            throw new OMAException("Expected exactly one HomeSP, got " + spList.size());
120        }
121        HomeSP sp = spList.iterator().next();
122        String fqdn = sp.getFQDN();
123        if (mSPs.put(fqdn, sp) != null) {
124            throw new OMAException("SP " + fqdn + " already exists");
125        }
126
127        BufferedOutputStream out = null;
128        try {
129            out = new BufferedOutputStream(new FileOutputStream(mPpsFile, true));
130            tree.marshal(out);
131            out.flush();
132        } finally {
133            if (out != null) {
134                try {
135                    out.close();
136                } catch (IOException ioe) {
137                    /**/
138                }
139            }
140        }
141
142        return sp;
143    }
144
145    public void saveAllSps(Collection<HomeSP> homeSPs) throws IOException {
146
147        OMAConstructed root = new OMAConstructed(null, "MgmtTree", "");
148
149        for (HomeSP homeSP : homeSPs) {
150            OMANode providerNode = root.addChild(TAG_PerProviderSubscription, null, null, null);
151            OMANode providerSubNode = providerNode.addChild("Node", null, null, null);
152
153            Log.d("PARSE-LOG", "creating node homeSP for " + homeSP.getFQDN());
154
155            if (mSPs.put(homeSP.getFQDN(), homeSP) != null) {
156                throw new OMAException("SP " + homeSP.getFQDN() + " already exists");
157            }
158            OMANode homeSpNode = providerSubNode.addChild(TAG_HomeSP, null, null, null);
159            homeSpNode.addChild(TAG_FQDN, null, homeSP.getFQDN(), null);
160            homeSpNode.addChild(TAG_FriendlyName, null, homeSP.getFriendlyName(), null);
161
162            OMANode credentialNode = providerSubNode.addChild(TAG_Credential, null, null, null);
163            Credential cred = homeSP.getCredential();
164            EAPMethod method = cred.getEAPMethod();
165
166            if (method == null) {
167                throw new OMAException("SP " + homeSP.getFQDN() + " already exists");
168            }
169
170            OMANode credRootNode;
171            if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_SIM
172                    || method.getEAPMethodID() == EAP.EAPMethodID.EAP_AKA
173                    || method.getEAPMethodID() == EAP.EAPMethodID.EAP_AKAPrim) {
174
175                Log.d("PARSE-LOG", "Saving SIM credential");
176                credRootNode = credentialNode.addChild(TAG_SIM, null, null, null);
177                credRootNode.addChild(TAG_IMSI, null, cred.getImsi(), null);
178
179            } else if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TTLS) {
180
181                Log.d("PARSE-LOG", "Saving TTLS Credential");
182                credRootNode = credentialNode.addChild(TAG_UsernamePassword, null, null, null);
183                credRootNode.addChild(TAG_Username, null, cred.getUserName(), null);
184
185            } else if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TLS) {
186
187                Log.d("PARSE-LOG", "Saving TLS Credential");
188                credRootNode = credentialNode.addChild(TAG_DigitalCertificate, null, null, null);
189
190            } else {
191                throw new OMAException("Invalid credential on " + homeSP.getFQDN());
192            }
193
194            credentialNode.addChild(TAG_Realm, null, homeSP.getCredential().getRealm(), null);
195            credentialNode.addChild(TAG_CheckAAAServerCertStatus, null, "true", null);
196            OMANode eapMethodNode = credRootNode.addChild(TAG_EAPMethod, null, null, null);
197            OMANode eapTypeNode = eapMethodNode.addChild(TAG_EAPType,
198                    null, EAP.mapEAPMethod(method.getEAPMethodID()).toString(), null);
199
200            if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TTLS) {
201                OMANode innerEAPType = eapMethodNode.addChild(TAG_InnerEAPType,
202                        null, EAP.mapEAPMethod(EAP.EAPMethodID.EAP_MSCHAPv2).toString(), null);
203            }
204
205            StringBuilder builder = new StringBuilder();
206            for (Long roamingConsortium : homeSP.getRoamingConsortiums()) {
207                builder.append(roamingConsortium.toString());
208            }
209            credentialNode.addChild(TAG_RoamingConsortiumOI, null, builder.toString(), null);
210        }
211
212        Log.d("PARSE-LOG", "Saving all SPs");
213
214        MOTree tree = new MOTree(OMAConstants.LOC_PPS + ":1.0", "1.2", root);
215        BufferedOutputStream out = null;
216        try {
217            out = new BufferedOutputStream(new FileOutputStream(mPpsFile, true));
218            tree.marshal(out);
219            out.flush();
220        } finally {
221            if (out != null) {
222                try {
223                    out.close();
224                } catch (IOException ioe) {
225                    /**/
226                }
227            }
228        }
229    }
230
231    private static final DateFormat DTFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
232
233    static {
234        DTFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
235    }
236
237    public static final String TAG_AAAServerTrustRoot = "AAAServerTrustRoot";
238    public static final String TAG_AbleToShare = "AbleToShare";
239    public static final String TAG_CertificateType = "CertificateType";
240    public static final String TAG_CertSHA256Fingerprint = "CertSHA256Fingerprint";
241    public static final String TAG_CertURL = "CertURL";
242    public static final String TAG_CheckAAAServerCertStatus = "CheckAAAServerCertStatus";
243    public static final String TAG_Country = "Country";
244    public static final String TAG_CreationDate = "CreationDate";
245    public static final String TAG_Credential = "Credential";
246    public static final String TAG_CredentialPriority = "CredentialPriority";
247    public static final String TAG_DataLimit = "DataLimit";
248    public static final String TAG_DigitalCertificate = "DigitalCertificate";
249    public static final String TAG_DLBandwidth = "DLBandwidth";
250    public static final String TAG_EAPMethod = "EAPMethod";
251    public static final String TAG_EAPType = "EAPType";
252    public static final String TAG_ExpirationDate = "ExpirationDate";
253    public static final String TAG_Extension = "Extension";
254    public static final String TAG_FQDN = "FQDN";
255    public static final String TAG_FQDN_Match = "FQDN_Match";
256    public static final String TAG_FriendlyName = "FriendlyName";
257    public static final String TAG_HESSID = "HESSID";
258    public static final String TAG_HomeOI = "HomeOI";
259    public static final String TAG_HomeOIList = "HomeOIList";
260    public static final String TAG_HomeOIRequired = "HomeOIRequired";
261    public static final String TAG_HomeSP = "HomeSP";
262    public static final String TAG_IconURL = "IconURL";
263    public static final String TAG_IMSI = "IMSI";
264    public static final String TAG_InnerEAPType = "InnerEAPType";
265    public static final String TAG_InnerMethod = "InnerMethod";
266    public static final String TAG_InnerVendorID = "InnerVendorID";
267    public static final String TAG_InnerVendorType = "InnerVendorType";
268    public static final String TAG_IPProtocol = "IPProtocol";
269    public static final String TAG_MachineManaged = "MachineManaged";
270    public static final String TAG_MaximumBSSLoadValue = "MaximumBSSLoadValue";
271    public static final String TAG_MinBackhaulThreshold = "MinBackhaulThreshold";
272    public static final String TAG_NetworkID = "NetworkID";
273    public static final String TAG_NetworkType = "NetworkType";
274    public static final String TAG_Other = "Other";
275    public static final String TAG_OtherHomePartners = "OtherHomePartners";
276    public static final String TAG_Password = "Password";
277    public static final String TAG_PerProviderSubscription = "PerProviderSubscription";
278    public static final String TAG_Policy = "Policy";
279    public static final String TAG_PolicyUpdate = "PolicyUpdate";
280    public static final String TAG_PortNumber = "PortNumber";
281    public static final String TAG_PreferredRoamingPartnerList = "PreferredRoamingPartnerList";
282    public static final String TAG_Priority = "Priority";
283    public static final String TAG_Realm = "Realm";
284    public static final String TAG_RequiredProtoPortTuple = "RequiredProtoPortTuple";
285    public static final String TAG_Restriction = "Restriction";
286    public static final String TAG_RoamingConsortiumOI = "RoamingConsortiumOI";
287    public static final String TAG_SIM = "SIM";
288    public static final String TAG_SoftTokenApp = "SoftTokenApp";
289    public static final String TAG_SPExclusionList = "SPExclusionList";
290    public static final String TAG_SSID = "SSID";
291    public static final String TAG_StartDate = "StartDate";
292    public static final String TAG_SubscriptionParameters = "SubscriptionParameters";
293    public static final String TAG_SubscriptionUpdate = "SubscriptionUpdate";
294    public static final String TAG_TimeLimit = "TimeLimit";
295    public static final String TAG_TrustRoot = "TrustRoot";
296    public static final String TAG_TypeOfSubscription = "TypeOfSubscription";
297    public static final String TAG_ULBandwidth = "ULBandwidth";
298    public static final String TAG_UpdateIdentifier = "UpdateIdentifier";
299    public static final String TAG_UpdateInterval = "UpdateInterval";
300    public static final String TAG_UpdateMethod = "UpdateMethod";
301    public static final String TAG_URI = "URI";
302    public static final String TAG_UsageLimits = "UsageLimits";
303    public static final String TAG_UsageTimePeriod = "UsageTimePeriod";
304    public static final String TAG_Username = "Username";
305    public static final String TAG_UsernamePassword = "UsernamePassword";
306    public static final String TAG_VendorId = "VendorId";
307    public static final String TAG_VendorType = "VendorType";
308
309    private static List<HomeSP> buildSPs(MOTree moTree) throws OMAException {
310        List<String> spPath = Arrays.asList(TAG_PerProviderSubscription);
311        OMAConstructed spList = moTree.getRoot().getListValue(spPath.iterator());
312
313        List<HomeSP> homeSPs = new ArrayList<HomeSP>();
314
315        Log.d("PARSE-LOG", " node-name = " + spList.getName());
316        if (spList == null) {
317            return homeSPs;
318        }
319
320        Log.d("PARSE-LOG", " num_children = " + spList.getChildren().size());
321        for (OMANode spRoot : spList.getChildren()) {
322            Log.d("PARSE-LOG", " node-name = " + spRoot.getName());
323            homeSPs.add(buildHomeSP(spRoot));
324        }
325
326        return homeSPs;
327    }
328
329    private static HomeSP buildHomeSP(OMANode ppsRoot) throws OMAException {
330        Log.d("PARSE-LOG", " node-name = " + ppsRoot.getName());
331        OMANode spRoot = ppsRoot.getChild(TAG_HomeSP);
332
333        String fqdn = spRoot.getScalarValue(Arrays.asList(TAG_FQDN).iterator());
334        String friendlyName = spRoot.getScalarValue(Arrays.asList(TAG_FriendlyName).iterator());
335        System.out.println("FQDN: " + fqdn + ", friendly: " + friendlyName);
336        String iconURL = spRoot.getScalarValue(Arrays.asList(TAG_IconURL).iterator());
337
338        Set<Long> roamingConsortiums = new HashSet<Long>();
339        String oiString = spRoot.getScalarValue(Arrays.asList(TAG_RoamingConsortiumOI).iterator());
340        if (oiString != null) {
341            for (String oi : oiString.split(",")) {
342                roamingConsortiums.add(Long.parseLong(oi.trim(), 16));
343            }
344        }
345
346        Map<String, Long> ssids = new HashMap<String, Long>();
347
348        OMANode ssidListNode = spRoot.getListValue(Arrays.asList(TAG_NetworkID).iterator());
349        if (ssidListNode != null) {
350            for (OMANode ssidRoot : ssidListNode.getChildren()) {
351                OMANode hessidNode = ssidRoot.getChild(TAG_HESSID);
352                ssids.put(ssidRoot.getChild(TAG_SSID).getValue(), getMac(hessidNode));
353            }
354        }
355
356        Set<Long> matchAnyOIs = new HashSet<Long>();
357        List<Long> matchAllOIs = new ArrayList<Long>();
358        OMANode homeOIListNode = spRoot.getListValue(Arrays.asList(TAG_HomeOIList).iterator());
359        if (homeOIListNode != null) {
360            for (OMANode homeOIRoot : homeOIListNode.getChildren()) {
361                String homeOI = homeOIRoot.getChild(TAG_HomeOI).getValue();
362                if (Boolean.parseBoolean(homeOIRoot.getChild(TAG_HomeOIRequired).getValue())) {
363                    matchAllOIs.add(Long.parseLong(homeOI, 16));
364                } else {
365                    matchAnyOIs.add(Long.parseLong(homeOI, 16));
366                }
367            }
368        }
369
370        Set<String> otherHomePartners = new HashSet<String>();
371        OMANode otherListNode =
372                spRoot.getListValue(Arrays.asList(TAG_OtherHomePartners).iterator());
373        if (otherListNode != null) {
374            for (OMANode fqdnNode : otherListNode.getChildren()) {
375                otherHomePartners.add(fqdnNode.getChild(TAG_FQDN).getValue());
376            }
377        }
378
379        Credential credential = buildCredential(ppsRoot.getChild(TAG_Credential));
380
381        Log.d("PARSE-LOG", " Building a new HomeSP for " + fqdn);
382        return new HomeSP(ssids, fqdn, roamingConsortiums, otherHomePartners,
383                matchAnyOIs, matchAllOIs, friendlyName, iconURL, credential);
384    }
385
386    private static Credential buildCredential(OMANode credNode) throws OMAException {
387        Log.d("PARSE-LOG", " Reading credential from " + credNode.getName());
388        long ctime = getTime(credNode.getChild(TAG_CreationDate));
389        long expTime = getTime(credNode.getChild(TAG_ExpirationDate));
390        String realm = getString(credNode.getChild(TAG_Realm));
391        boolean checkAAACert = getBoolean(credNode.getChild(TAG_CheckAAAServerCertStatus));
392
393        OMANode unNode = credNode.getChild(TAG_UsernamePassword);
394        OMANode certNode = credNode.getChild(TAG_DigitalCertificate);
395        OMANode simNode = credNode.getChild(TAG_SIM);
396
397        int alternatives = 0;
398        alternatives += unNode != null ? 1 : 0;
399        alternatives += certNode != null ? 1 : 0;
400        alternatives += simNode != null ? 1 : 0;
401        if (alternatives != 1) {
402            throw new OMAException("Expected exactly one credential type, got " + alternatives);
403        }
404
405        if (unNode != null) {
406            String userName = getString(unNode.getChild(TAG_Username));
407            String password = getString(unNode.getChild(TAG_Password));
408            boolean machineManaged = getBoolean(unNode.getChild(TAG_MachineManaged));
409            String softTokenApp = getString(unNode.getChild(TAG_SoftTokenApp));
410            boolean ableToShare = getBoolean(unNode.getChild(TAG_AbleToShare));
411
412            OMANode eapMethodNode = unNode.getChild(TAG_EAPMethod);
413            EAP.EAPMethodID eapMethodID =
414                    EAP.mapEAPMethod(getInteger(eapMethodNode.getChild(TAG_EAPType)));
415            if (eapMethodID == null) {
416                throw new OMAException("Unknown EAP method");
417            }
418
419            Long vid = getOptionalInteger(eapMethodNode.getChild(TAG_VendorId));
420            Long vtype = getOptionalInteger(eapMethodNode.getChild(TAG_VendorType));
421            Long innerEAPType = getOptionalInteger(eapMethodNode.getChild(TAG_InnerEAPType));
422            EAP.EAPMethodID innerEAPMethod = null;
423            if (innerEAPType != null) {
424                innerEAPMethod = EAP.mapEAPMethod(innerEAPType.intValue());
425                if (innerEAPMethod == null) {
426                    throw new OMAException("Bad inner EAP method: " + innerEAPType);
427                }
428            }
429
430            Long innerVid = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorID));
431            Long innerVtype = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorType));
432            String innerNonEAPMethod = getString(eapMethodNode.getChild(TAG_InnerMethod));
433
434            EAPMethod eapMethod;
435            if (innerEAPMethod != null) {
436                eapMethod = new EAPMethod(eapMethodID, new InnerAuthEAP(innerEAPMethod));
437            } else if (vid != null) {
438                eapMethod = new EAPMethod(eapMethodID,
439                        new ExpandedEAPMethod(EAP.AuthInfoID.ExpandedEAPMethod,
440                                vid.intValue(), vtype));
441            } else if (innerVid != null) {
442                eapMethod =
443                        new EAPMethod(eapMethodID, new ExpandedEAPMethod(EAP.AuthInfoID
444                                .ExpandedInnerEAPMethod, innerVid.intValue(), innerVtype));
445            } else if (innerNonEAPMethod != null) {
446                eapMethod = new EAPMethod(eapMethodID, new NonEAPInnerAuth(innerNonEAPMethod));
447            } else {
448                throw new OMAException("Incomplete set of EAP parameters");
449            }
450
451            return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, userName,
452                    password, machineManaged, softTokenApp, ableToShare);
453        }
454        if (certNode != null) {
455            try {
456                String certTypeString = getString(certNode.getChild(TAG_CertificateType));
457                byte[] fingerPrint = getOctets(certNode.getChild(TAG_CertSHA256Fingerprint));
458
459                EAPMethod eapMethod = new EAPMethod(EAP.EAPMethodID.EAP_TLS, null);
460
461                return new Credential(ctime, expTime, realm, checkAAACert, eapMethod,
462                        Credential.mapCertType(certTypeString), fingerPrint);
463            }
464            catch (NumberFormatException nfe) {
465                throw new OMAException("Bad hex string: " + nfe.toString());
466            }
467        }
468        if (simNode != null) {
469
470            String imsi = getString(simNode.getChild(TAG_IMSI));
471            EAPMethod eapMethod =
472                    new EAPMethod(EAP.mapEAPMethod(getInteger(simNode.getChild(TAG_EAPType))),
473                            null);
474
475            return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, imsi);
476        }
477        throw new OMAException("Missing credential parameters");
478    }
479
480    private static boolean getBoolean(OMANode boolNode) {
481        return boolNode != null && Boolean.parseBoolean(boolNode.getValue());
482    }
483
484    private static String getString(OMANode stringNode) {
485        return stringNode != null ? stringNode.getValue() : null;
486    }
487
488    private static int getInteger(OMANode intNode) throws OMAException {
489        if (intNode == null) {
490            throw new OMAException("Missing integer value");
491        }
492        try {
493            return Integer.parseInt(intNode.getValue());
494        } catch (NumberFormatException nfe) {
495            throw new OMAException("Invalid integer: " + intNode.getValue());
496        }
497    }
498
499    private static Long getMac(OMANode macNode) throws OMAException {
500        if (macNode == null) {
501            return null;
502        }
503        try {
504            return Long.parseLong(macNode.getValue(), 16);
505        } catch (NumberFormatException nfe) {
506            throw new OMAException("Invalid MAC: " + macNode.getValue());
507        }
508    }
509
510    private static Long getOptionalInteger(OMANode intNode) throws OMAException {
511        if (intNode == null) {
512            return null;
513        }
514        try {
515            return Long.parseLong(intNode.getValue());
516        } catch (NumberFormatException nfe) {
517            throw new OMAException("Invalid integer: " + intNode.getValue());
518        }
519    }
520
521    private static long getTime(OMANode timeNode) throws OMAException {
522        if (timeNode == null) {
523            return -1;
524        }
525        String timeText = timeNode.getValue();
526        try {
527            Date date = DTFormat.parse(timeText);
528            return date.getTime();
529        } catch (ParseException pe) {
530            throw new OMAException("Badly formatted time: " + timeText);
531        }
532    }
533
534    private static byte[] getOctets(OMANode octetNode) throws OMAException {
535        if (octetNode == null) {
536            // throw new OMAException("Missing byte value");
537            return null;
538        }
539        return Utils.hexToBytes(octetNode.getValue());
540    }
541}
542