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