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