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