MOManager.java revision 07f11f6f2ee7ec17cb08180035dfb5002aaaf5df
1package com.android.server.wifi.hotspot2.omadm;
2
3import android.util.Base64;
4import android.util.Log;
5
6import com.android.server.wifi.IMSIParameter;
7import com.android.server.wifi.anqp.eap.EAP;
8import com.android.server.wifi.anqp.eap.EAPMethod;
9import com.android.server.wifi.anqp.eap.ExpandedEAPMethod;
10import com.android.server.wifi.anqp.eap.InnerAuthEAP;
11import com.android.server.wifi.anqp.eap.NonEAPInnerAuth;
12import com.android.server.wifi.hotspot2.Utils;
13import com.android.server.wifi.hotspot2.pps.Credential;
14import com.android.server.wifi.hotspot2.pps.HomeSP;
15
16import org.xml.sax.SAXException;
17
18import java.io.BufferedInputStream;
19import java.io.BufferedOutputStream;
20import java.io.File;
21import java.io.FileInputStream;
22import java.io.FileOutputStream;
23import java.io.IOException;
24import java.nio.charset.StandardCharsets;
25import java.text.DateFormat;
26import java.text.ParseException;
27import java.text.SimpleDateFormat;
28import java.util.ArrayList;
29import java.util.Arrays;
30import java.util.Collection;
31import java.util.Collections;
32import java.util.Date;
33import java.util.HashMap;
34import java.util.HashSet;
35import java.util.List;
36import java.util.Map;
37import java.util.Set;
38import java.util.TimeZone;
39
40/**
41 * Handles provisioning of PerProviderSubscription data.
42 */
43public class MOManager {
44
45    public static final String TAG_AAAServerTrustRoot = "AAAServerTrustRoot";
46    public static final String TAG_AbleToShare = "AbleToShare";
47    public static final String TAG_CertificateType = "CertificateType";
48    public static final String TAG_CertSHA256Fingerprint = "CertSHA256Fingerprint";
49    public static final String TAG_CertURL = "CertURL";
50    public static final String TAG_CheckAAAServerCertStatus = "CheckAAAServerCertStatus";
51    public static final String TAG_Country = "Country";
52    public static final String TAG_CreationDate = "CreationDate";
53    public static final String TAG_Credential = "Credential";
54    public static final String TAG_CredentialPriority = "CredentialPriority";
55    public static final String TAG_DataLimit = "DataLimit";
56    public static final String TAG_DigitalCertificate = "DigitalCertificate";
57    public static final String TAG_DLBandwidth = "DLBandwidth";
58    public static final String TAG_EAPMethod = "EAPMethod";
59    public static final String TAG_EAPType = "EAPType";
60    public static final String TAG_ExpirationDate = "ExpirationDate";
61    public static final String TAG_Extension = "Extension";
62    public static final String TAG_FQDN = "FQDN";
63    public static final String TAG_FQDN_Match = "FQDN_Match";
64    public static final String TAG_FriendlyName = "FriendlyName";
65    public static final String TAG_HESSID = "HESSID";
66    public static final String TAG_HomeOI = "HomeOI";
67    public static final String TAG_HomeOIList = "HomeOIList";
68    public static final String TAG_HomeOIRequired = "HomeOIRequired";
69    public static final String TAG_HomeSP = "HomeSP";
70    public static final String TAG_IconURL = "IconURL";
71    public static final String TAG_IMSI = "IMSI";
72    public static final String TAG_InnerEAPType = "InnerEAPType";
73    public static final String TAG_InnerMethod = "InnerMethod";
74    public static final String TAG_InnerVendorID = "InnerVendorID";
75    public static final String TAG_InnerVendorType = "InnerVendorType";
76    public static final String TAG_IPProtocol = "IPProtocol";
77    public static final String TAG_MachineManaged = "MachineManaged";
78    public static final String TAG_MaximumBSSLoadValue = "MaximumBSSLoadValue";
79    public static final String TAG_MinBackhaulThreshold = "MinBackhaulThreshold";
80    public static final String TAG_NetworkID = "NetworkID";
81    public static final String TAG_NetworkType = "NetworkType";
82    public static final String TAG_Other = "Other";
83    public static final String TAG_OtherHomePartners = "OtherHomePartners";
84    public static final String TAG_Password = "Password";
85    public static final String TAG_PerProviderSubscription = "PerProviderSubscription";
86    public static final String TAG_Policy = "Policy";
87    public static final String TAG_PolicyUpdate = "PolicyUpdate";
88    public static final String TAG_PortNumber = "PortNumber";
89    public static final String TAG_PreferredRoamingPartnerList = "PreferredRoamingPartnerList";
90    public static final String TAG_Priority = "Priority";
91    public static final String TAG_Realm = "Realm";
92    public static final String TAG_RequiredProtoPortTuple = "RequiredProtoPortTuple";
93    public static final String TAG_Restriction = "Restriction";
94    public static final String TAG_RoamingConsortiumOI = "RoamingConsortiumOI";
95    public static final String TAG_SIM = "SIM";
96    public static final String TAG_SoftTokenApp = "SoftTokenApp";
97    public static final String TAG_SPExclusionList = "SPExclusionList";
98    public static final String TAG_SSID = "SSID";
99    public static final String TAG_StartDate = "StartDate";
100    public static final String TAG_SubscriptionParameters = "SubscriptionParameters";
101    public static final String TAG_SubscriptionUpdate = "SubscriptionUpdate";
102    public static final String TAG_TimeLimit = "TimeLimit";
103    public static final String TAG_TrustRoot = "TrustRoot";
104    public static final String TAG_TypeOfSubscription = "TypeOfSubscription";
105    public static final String TAG_ULBandwidth = "ULBandwidth";
106    public static final String TAG_UpdateIdentifier = "UpdateIdentifier";
107    public static final String TAG_UpdateInterval = "UpdateInterval";
108    public static final String TAG_UpdateMethod = "UpdateMethod";
109    public static final String TAG_URI = "URI";
110    public static final String TAG_UsageLimits = "UsageLimits";
111    public static final String TAG_UsageTimePeriod = "UsageTimePeriod";
112    public static final String TAG_Username = "Username";
113    public static final String TAG_UsernamePassword = "UsernamePassword";
114    public static final String TAG_VendorId = "VendorId";
115    public static final String TAG_VendorType = "VendorType";
116
117    private static final DateFormat DTFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
118
119    static {
120        DTFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
121    }
122
123    private final File mPpsFile;
124    private final boolean mEnabled;
125    private final Map<String, HomeSP> mSPs;
126
127    public MOManager(File ppsFile, boolean hs2enabled) {
128        mPpsFile = ppsFile;
129        mEnabled = hs2enabled;
130        mSPs = new HashMap<>();
131    }
132
133    public File getPpsFile() {
134        return mPpsFile;
135    }
136
137    public boolean isEnabled() {
138        return mEnabled;
139    }
140
141    public boolean isConfigured() {
142        return mEnabled && !mSPs.isEmpty();
143    }
144
145    public Map<String, HomeSP> getLoadedSPs() {
146        return Collections.unmodifiableMap(mSPs);
147    }
148
149    public List<HomeSP> loadAllSPs() throws IOException {
150
151        if (!mEnabled || !mPpsFile.exists()) {
152            return Collections.emptyList();
153        }
154
155        try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
156            MOTree moTree = MOTree.unmarshal(in);
157            mSPs.clear();
158            if (moTree == null) {
159                return Collections.emptyList();     // Empty file
160            }
161
162            List<HomeSP> sps = buildSPs(moTree);
163            if (sps != null) {
164                for (HomeSP sp : sps) {
165                    if (mSPs.put(sp.getFQDN(), sp) != null) {
166                        throw new OMAException("Multiple SPs for FQDN '" + sp.getFQDN() + "'");
167                    } else {
168                        Log.d(Utils.hs2LogTag(getClass()), "retrieved " + sp.getFQDN() + " from PPS");
169                    }
170                }
171                return sps;
172
173            } else {
174                throw new OMAException("Failed to build HomeSP");
175            }
176        }
177    }
178
179    public static HomeSP buildSP(String xml) throws IOException, SAXException {
180        OMAParser omaParser = new OMAParser();
181        MOTree tree = omaParser.parse(xml, OMAConstants.LOC_PPS + ":1.0");
182        List<HomeSP> spList = buildSPs(tree);
183        if (spList.size() != 1) {
184            throw new OMAException("Expected exactly one HomeSP, got " + spList.size());
185        }
186        return spList.iterator().next();
187    }
188
189    public HomeSP addSP(String xml) throws IOException, SAXException {
190        OMAParser omaParser = new OMAParser();
191        MOTree tree = omaParser.parse(xml, OMAConstants.LOC_PPS + ":1.0");
192        List<HomeSP> spList = buildSPs(tree);
193        if (spList.size() != 1) {
194            throw new OMAException("Expected exactly one HomeSP, got " + spList.size());
195        }
196        HomeSP sp = spList.iterator().next();
197        String fqdn = sp.getFQDN();
198        if (mSPs.put(fqdn, sp) != null) {
199            throw new OMAException("SP " + fqdn + " already exists");
200        }
201
202        BufferedOutputStream out = null;
203        try {
204            out = new BufferedOutputStream(new FileOutputStream(mPpsFile, true));
205            tree.marshal(out);
206            out.flush();
207        } finally {
208            if (out != null) {
209                try {
210                    out.close();
211                } catch (IOException ioe) {
212                    /**/
213                }
214            }
215        }
216
217        return sp;
218    }
219
220    public HomeSP getHomeSP(String fqdn) {
221        return mSPs.get(fqdn);
222    }
223
224    public void addSP(HomeSP homeSP) throws IOException {
225        if (!mEnabled) {
226            throw new IOException("HS2.0 not enabled on this device");
227        }
228        if (mSPs.containsKey(homeSP.getFQDN())) {
229            Log.d(Utils.hs2LogTag(getClass()), "HS20 profile for " +
230                    homeSP.getFQDN() + " already exists");
231            return;
232        }
233        Log.d(Utils.hs2LogTag(getClass()), "Adding new HS20 profile for " + homeSP.getFQDN());
234        mSPs.put(homeSP.getFQDN(), homeSP);
235        writeMO(mSPs.values(), mPpsFile);
236    }
237
238    public void removeSP(String fqdn) throws IOException {
239        if (mSPs.remove(fqdn) == null) {
240            Log.d(Utils.hs2LogTag(getClass()), "No HS20 profile to delete for " + fqdn);
241            return;
242        }
243        Log.d(Utils.hs2LogTag(getClass()), "Deleting HS20 profile for " + fqdn);
244        writeMO(mSPs.values(), mPpsFile);
245    }
246
247    public void updateAndSaveAllSps(Collection<HomeSP> homeSPs) throws IOException {
248
249        boolean dirty = false;
250        List<HomeSP> newSet = new ArrayList<>(homeSPs.size());
251
252        Map<String, HomeSP> spClone = new HashMap<>(mSPs);
253        for (HomeSP homeSP : homeSPs) {
254            Log.d(Utils.hs2LogTag(getClass()), "Passed HomeSP: " + homeSP);
255            HomeSP existing = spClone.remove(homeSP.getFQDN());
256            if (existing == null) {
257                dirty = true;
258                newSet.add(homeSP);
259                Log.d(Utils.hs2LogTag(getClass()), "New HomeSP");
260            }
261            else if (!homeSP.deepEquals(existing)) {
262                dirty = true;
263                newSet.add(homeSP.getClone(existing.getCredential().getPassword()));
264                Log.d(Utils.hs2LogTag(getClass()), "Non-equal HomeSP: " + existing);
265            }
266            else {
267                newSet.add(existing);
268                Log.d(Utils.hs2LogTag(getClass()), "Keeping HomeSP: " + existing);
269            }
270        }
271
272        Log.d(Utils.hs2LogTag(getClass()),
273                String.format("Saving all SPs (%s): current %s (%d), new %s (%d)",
274                dirty ? "dirty" : "clean",
275                fqdnList(mSPs.values()), mSPs.size(),
276                fqdnList(newSet), newSet.size()));
277
278        if (!dirty && spClone.isEmpty()) {
279            Log.d(Utils.hs2LogTag(getClass()), "Not persisting");
280            return;
281        }
282
283        rewriteMO(newSet, mSPs, mPpsFile);
284    }
285
286    private static void rewriteMO(Collection<HomeSP> homeSPs, Map<String, HomeSP> current, File f)
287            throws IOException {
288
289        current.clear();
290
291        OMAConstructed ppsNode = new OMAConstructed(null, TAG_PerProviderSubscription, null);
292        int instance = 0;
293        for (HomeSP homeSP : homeSPs) {
294            buildHomeSPTree(homeSP, ppsNode, instance++);
295            current.put(homeSP.getFQDN(), homeSP);
296        }
297
298        MOTree tree = new MOTree(OMAConstants.LOC_PPS + ":1.0", "1.2", ppsNode);
299        try (BufferedOutputStream out =
300                     new BufferedOutputStream(new FileOutputStream(f, false))) {
301            tree.marshal(out);
302            out.flush();
303        }
304    }
305
306    private static void writeMO(Collection<HomeSP> homeSPs, File f) throws IOException {
307
308        OMAConstructed ppsNode = new OMAConstructed(null, TAG_PerProviderSubscription, null);
309        int instance = 0;
310        for (HomeSP homeSP : homeSPs) {
311            buildHomeSPTree(homeSP, ppsNode, instance++);
312        }
313
314        MOTree tree = new MOTree(OMAConstants.LOC_PPS + ":1.0", "1.2", ppsNode);
315        try (BufferedOutputStream out =
316                     new BufferedOutputStream(new FileOutputStream(f, false))) {
317            tree.marshal(out);
318            out.flush();
319        }
320    }
321
322    private static String fqdnList(Collection<HomeSP> sps) {
323        StringBuilder sb = new StringBuilder();
324        boolean first = true;
325        for (HomeSP sp : sps) {
326            if (first) {
327                first = false;
328            }
329            else {
330                sb.append(", ");
331            }
332            sb.append(sp.getFQDN());
333        }
334        return sb.toString();
335    }
336
337    private static void buildHomeSPTree(HomeSP homeSP, OMAConstructed root, int spInstance)
338            throws IOException {
339        OMANode providerSubNode = root.addChild(getInstanceString(spInstance), null, null, null);
340
341        // The HomeSP:
342        OMANode homeSpNode = providerSubNode.addChild(TAG_HomeSP, null, null, null);
343        if (!homeSP.getSSIDs().isEmpty()) {
344            OMAConstructed nwkIDNode =
345                    (OMAConstructed) homeSpNode.addChild(TAG_NetworkID, null, null, null);
346            int instance = 0;
347            for (Map.Entry<String, Long> entry : homeSP.getSSIDs().entrySet()) {
348                OMAConstructed inode =
349                        (OMAConstructed) nwkIDNode.addChild(getInstanceString(instance++), null, null, null);
350                inode.addChild(TAG_SSID, null, entry.getKey(), null);
351                if (entry.getValue() != null) {
352                    inode.addChild(TAG_HESSID, null, String.format("%012x", entry.getValue()), null);
353                }
354            }
355        }
356
357        homeSpNode.addChild(TAG_FriendlyName, null, homeSP.getFriendlyName(), null);
358
359        if (homeSP.getIconURL() != null) {
360            homeSpNode.addChild(TAG_IconURL, null, homeSP.getIconURL(), null);
361        }
362
363        homeSpNode.addChild(TAG_FQDN, null, homeSP.getFQDN(), null);
364
365        if (!homeSP.getMatchAllOIs().isEmpty() || !homeSP.getMatchAnyOIs().isEmpty()) {
366            OMAConstructed homeOIList =
367                    (OMAConstructed) homeSpNode.addChild(TAG_HomeOIList, null, null, null);
368
369            int instance = 0;
370            for (Long oi : homeSP.getMatchAllOIs()) {
371                OMAConstructed inode =
372                        (OMAConstructed) homeOIList.addChild(getInstanceString(instance++),
373                                null, null, null);
374                inode.addChild(TAG_HomeOI, null, String.format("%x", oi), null);
375                inode.addChild(TAG_HomeOIRequired, null, "TRUE", null);
376            }
377            for (Long oi : homeSP.getMatchAnyOIs()) {
378                OMAConstructed inode =
379                        (OMAConstructed) homeOIList.addChild(getInstanceString(instance++),
380                                null, null, null);
381                inode.addChild(TAG_HomeOI, null, String.format("%x", oi), null);
382                inode.addChild(TAG_HomeOIRequired, null, "FALSE", null);
383            }
384        }
385
386        if (!homeSP.getOtherHomePartners().isEmpty()) {
387            OMAConstructed otherPartners =
388                    (OMAConstructed) homeSpNode.addChild(TAG_OtherHomePartners, null, null, null);
389            int instance = 0;
390            for (String fqdn : homeSP.getOtherHomePartners()) {
391                OMAConstructed inode =
392                        (OMAConstructed) otherPartners.addChild(getInstanceString(instance++),
393                                null, null, null);
394                inode.addChild(TAG_FQDN, null, fqdn, null);
395            }
396        }
397
398        if (!homeSP.getRoamingConsortiums().isEmpty()) {
399            homeSpNode.addChild(TAG_RoamingConsortiumOI, null, getRCList(homeSP.getRoamingConsortiums()), null);
400        }
401
402        // The Credential:
403        OMANode credentialNode = providerSubNode.addChild(TAG_Credential, null, null, null);
404        Credential cred = homeSP.getCredential();
405        EAPMethod method = cred.getEAPMethod();
406
407        if (cred.getCtime() > 0) {
408            credentialNode.addChild(TAG_CreationDate,
409                    null, DTFormat.format(new Date(cred.getCtime())), null);
410        }
411        if (cred.getExpTime() > 0) {
412            credentialNode.addChild(TAG_ExpirationDate,
413                    null, DTFormat.format(new Date(cred.getExpTime())), null);
414        }
415
416        if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_SIM
417                || method.getEAPMethodID() == EAP.EAPMethodID.EAP_AKA
418                || method.getEAPMethodID() == EAP.EAPMethodID.EAP_AKAPrim) {
419
420            OMANode simNode = credentialNode.addChild(TAG_SIM, null, null, null);
421            simNode.addChild(TAG_IMSI, null, cred.getImsi().toString(), null);
422            simNode.addChild(TAG_EAPType, null,
423                    Integer.toString(EAP.mapEAPMethod(method.getEAPMethodID())), null);
424
425        } else if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TTLS) {
426
427            OMANode unpNode = credentialNode.addChild(TAG_UsernamePassword, null, null, null);
428            unpNode.addChild(TAG_Username, null, cred.getUserName(), null);
429            unpNode.addChild(TAG_Password, null,
430                    Base64.encodeToString(cred.getPassword().getBytes(StandardCharsets.UTF_8),
431                            Base64.DEFAULT), null);
432            OMANode eapNode = unpNode.addChild(TAG_EAPMethod, null, null, null);
433            eapNode.addChild(TAG_EAPType, null,
434                    Integer.toString(EAP.mapEAPMethod(method.getEAPMethodID())), null);
435            eapNode.addChild(TAG_InnerMethod, null,
436                    ((NonEAPInnerAuth) method.getAuthParam()).getOMAtype(), null);
437
438        } else if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TLS) {
439
440            OMANode certNode = credentialNode.addChild(TAG_DigitalCertificate, null, null, null);
441            certNode.addChild(TAG_CertificateType, null, Credential.CertTypeX509, null);
442            certNode.addChild(TAG_CertSHA256Fingerprint, null,
443                    Utils.toHex(cred.getFingerPrint()), null);
444
445        } else {
446            throw new OMAException("Invalid credential on " + homeSP.getFQDN());
447        }
448
449        credentialNode.addChild(TAG_Realm, null, cred.getRealm(), null);
450
451        // !!! Note: This node defines CRL checking through OSCP, I suspect we won't be able
452        // to do that so it is commented out:
453        //credentialNode.addChild(TAG_CheckAAAServerCertStatus, null, "TRUE", null);
454    }
455
456    private static String getInstanceString(int instance) {
457        return "i" + instance;
458    }
459
460    private static String getRCList(Collection<Long> rcs) {
461        StringBuilder builder = new StringBuilder();
462        boolean first = true;
463        for (Long roamingConsortium : rcs) {
464            if (first) {
465                first = false;
466            }
467            else {
468                builder.append(',');
469            }
470            builder.append(String.format("%x", roamingConsortium));
471        }
472        return builder.toString();
473    }
474
475    private static List<HomeSP> buildSPs(MOTree moTree) throws OMAException {
476        OMAConstructed spList;
477        if (moTree.getRoot().getName().equals(TAG_PerProviderSubscription)) {
478            // The PPS file is rooted at PPS instead of MgmtTree to conserve space
479            spList = moTree.getRoot();
480        }
481        else {
482            List<String> spPath = Arrays.asList(TAG_PerProviderSubscription);
483            spList = moTree.getRoot().getListValue(spPath.iterator());
484        }
485
486        List<HomeSP> homeSPs = new ArrayList<>();
487
488        if (spList == null) {
489            return homeSPs;
490        }
491        for (OMANode spRoot : spList.getChildren()) {
492            homeSPs.add(buildHomeSP(spRoot));
493        }
494
495        return homeSPs;
496    }
497
498    private static HomeSP buildHomeSP(OMANode ppsRoot) throws OMAException {
499        OMANode spRoot = ppsRoot.getChild(TAG_HomeSP);
500
501        String fqdn = spRoot.getScalarValue(Arrays.asList(TAG_FQDN).iterator());
502        String friendlyName = spRoot.getScalarValue(Arrays.asList(TAG_FriendlyName).iterator());
503        String iconURL = spRoot.getScalarValue(Arrays.asList(TAG_IconURL).iterator());
504
505        HashSet<Long> roamingConsortiums = new HashSet<>();
506        String oiString = spRoot.getScalarValue(Arrays.asList(TAG_RoamingConsortiumOI).iterator());
507        if (oiString != null) {
508            for (String oi : oiString.split(",")) {
509                roamingConsortiums.add(Long.parseLong(oi.trim(), 16));
510            }
511        }
512
513        Map<String, Long> ssids = new HashMap<>();
514
515        OMANode ssidListNode = spRoot.getListValue(Arrays.asList(TAG_NetworkID).iterator());
516        if (ssidListNode != null) {
517            for (OMANode ssidRoot : ssidListNode.getChildren()) {
518                OMANode hessidNode = ssidRoot.getChild(TAG_HESSID);
519                ssids.put(ssidRoot.getChild(TAG_SSID).getValue(), getMac(hessidNode));
520            }
521        }
522
523        Set<Long> matchAnyOIs = new HashSet<>();
524        List<Long> matchAllOIs = new ArrayList<>();
525        OMANode homeOIListNode = spRoot.getListValue(Arrays.asList(TAG_HomeOIList).iterator());
526        if (homeOIListNode != null) {
527            for (OMANode homeOIRoot : homeOIListNode.getChildren()) {
528                String homeOI = homeOIRoot.getChild(TAG_HomeOI).getValue();
529                if (Boolean.parseBoolean(homeOIRoot.getChild(TAG_HomeOIRequired).getValue())) {
530                    matchAllOIs.add(Long.parseLong(homeOI, 16));
531                } else {
532                    matchAnyOIs.add(Long.parseLong(homeOI, 16));
533                }
534            }
535        }
536
537        Set<String> otherHomePartners = new HashSet<>();
538        OMANode otherListNode =
539                spRoot.getListValue(Arrays.asList(TAG_OtherHomePartners).iterator());
540        if (otherListNode != null) {
541            for (OMANode fqdnNode : otherListNode.getChildren()) {
542                otherHomePartners.add(fqdnNode.getChild(TAG_FQDN).getValue());
543            }
544        }
545
546        Credential credential = buildCredential(ppsRoot.getChild(TAG_Credential));
547
548        return new HomeSP(ssids, fqdn, roamingConsortiums, otherHomePartners,
549                matchAnyOIs, matchAllOIs, friendlyName, iconURL, credential);
550    }
551
552    private static Credential buildCredential(OMANode credNode) throws OMAException {
553        long ctime = getTime(credNode.getChild(TAG_CreationDate));
554        long expTime = getTime(credNode.getChild(TAG_ExpirationDate));
555        String realm = getString(credNode.getChild(TAG_Realm));
556        boolean checkAAACert = getBoolean(credNode.getChild(TAG_CheckAAAServerCertStatus));
557
558        OMANode unNode = credNode.getChild(TAG_UsernamePassword);
559        OMANode certNode = credNode.getChild(TAG_DigitalCertificate);
560        OMANode simNode = credNode.getChild(TAG_SIM);
561
562        int alternatives = 0;
563        alternatives += unNode != null ? 1 : 0;
564        alternatives += certNode != null ? 1 : 0;
565        alternatives += simNode != null ? 1 : 0;
566        if (alternatives != 1) {
567            throw new OMAException("Expected exactly one credential type, got " + alternatives);
568        }
569
570        if (unNode != null) {
571            String userName = getString(unNode.getChild(TAG_Username));
572            String password = getString(unNode.getChild(TAG_Password));
573            boolean machineManaged = getBoolean(unNode.getChild(TAG_MachineManaged));
574            String softTokenApp = getString(unNode.getChild(TAG_SoftTokenApp));
575            boolean ableToShare = getBoolean(unNode.getChild(TAG_AbleToShare));
576
577            OMANode eapMethodNode = unNode.getChild(TAG_EAPMethod);
578            int eapID = getInteger(eapMethodNode.getChild(TAG_EAPType));
579
580            EAP.EAPMethodID eapMethodID = EAP.mapEAPMethod(eapID);
581            if (eapMethodID == null) {
582                throw new OMAException("Unknown EAP method: " + eapID);
583            }
584
585            Long vid = getOptionalInteger(eapMethodNode.getChild(TAG_VendorId));
586            Long vtype = getOptionalInteger(eapMethodNode.getChild(TAG_VendorType));
587            Long innerEAPType = getOptionalInteger(eapMethodNode.getChild(TAG_InnerEAPType));
588            EAP.EAPMethodID innerEAPMethod = null;
589            if (innerEAPType != null) {
590                innerEAPMethod = EAP.mapEAPMethod(innerEAPType.intValue());
591                if (innerEAPMethod == null) {
592                    throw new OMAException("Bad inner EAP method: " + innerEAPType);
593                }
594            }
595
596            Long innerVid = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorID));
597            Long innerVtype = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorType));
598            String innerNonEAPMethod = getString(eapMethodNode.getChild(TAG_InnerMethod));
599
600            EAPMethod eapMethod;
601            if (innerEAPMethod != null) {
602                eapMethod = new EAPMethod(eapMethodID, new InnerAuthEAP(innerEAPMethod));
603            } else if (vid != null) {
604                eapMethod = new EAPMethod(eapMethodID,
605                        new ExpandedEAPMethod(EAP.AuthInfoID.ExpandedEAPMethod,
606                                vid.intValue(), vtype));
607            } else if (innerVid != null) {
608                eapMethod =
609                        new EAPMethod(eapMethodID, new ExpandedEAPMethod(EAP.AuthInfoID
610                                .ExpandedInnerEAPMethod, innerVid.intValue(), innerVtype));
611            } else if (innerNonEAPMethod != null) {
612                eapMethod = new EAPMethod(eapMethodID, new NonEAPInnerAuth(innerNonEAPMethod));
613            } else {
614                throw new OMAException("Incomplete set of EAP parameters");
615            }
616
617            return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, userName,
618                    password, machineManaged, softTokenApp, ableToShare);
619        }
620        if (certNode != null) {
621            try {
622                String certTypeString = getString(certNode.getChild(TAG_CertificateType));
623                byte[] fingerPrint = getOctets(certNode.getChild(TAG_CertSHA256Fingerprint));
624
625                EAPMethod eapMethod = new EAPMethod(EAP.EAPMethodID.EAP_TLS, null);
626
627                return new Credential(ctime, expTime, realm, checkAAACert, eapMethod,
628                        Credential.mapCertType(certTypeString), fingerPrint);
629            }
630            catch (NumberFormatException nfe) {
631                throw new OMAException("Bad hex string: " + nfe.toString());
632            }
633        }
634        if (simNode != null) {
635            try {
636                IMSIParameter imsi = new IMSIParameter(getString(simNode.getChild(TAG_IMSI)));
637
638                EAPMethod eapMethod =
639                        new EAPMethod(EAP.mapEAPMethod(getInteger(simNode.getChild(TAG_EAPType))),
640                                null);
641
642                return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, imsi);
643            }
644            catch (IOException ioe) {
645                throw new OMAException("Failed to parse IMSI: " + ioe);
646            }
647        }
648        throw new OMAException("Missing credential parameters");
649    }
650
651    private static boolean getBoolean(OMANode boolNode) {
652        return boolNode != null && Boolean.parseBoolean(boolNode.getValue());
653    }
654
655    private static String getString(OMANode stringNode) {
656        return stringNode != null ? stringNode.getValue() : null;
657    }
658
659    private static int getInteger(OMANode intNode) throws OMAException {
660        if (intNode == null) {
661            throw new OMAException("Missing integer value");
662        }
663        try {
664            return Integer.parseInt(intNode.getValue());
665        } catch (NumberFormatException nfe) {
666            throw new OMAException("Invalid integer: " + intNode.getValue());
667        }
668    }
669
670    private static Long getMac(OMANode macNode) throws OMAException {
671        if (macNode == null) {
672            return null;
673        }
674        try {
675            return Long.parseLong(macNode.getValue(), 16);
676        } catch (NumberFormatException nfe) {
677            throw new OMAException("Invalid MAC: " + macNode.getValue());
678        }
679    }
680
681    private static Long getOptionalInteger(OMANode intNode) throws OMAException {
682        if (intNode == null) {
683            return null;
684        }
685        try {
686            return Long.parseLong(intNode.getValue());
687        } catch (NumberFormatException nfe) {
688            throw new OMAException("Invalid integer: " + intNode.getValue());
689        }
690    }
691
692    private static long getTime(OMANode timeNode) throws OMAException {
693        if (timeNode == null) {
694            return Utils.UNSET_TIME;
695        }
696        String timeText = timeNode.getValue();
697        try {
698            Date date = DTFormat.parse(timeText);
699            return date.getTime();
700        } catch (ParseException pe) {
701            throw new OMAException("Badly formatted time: " + timeText);
702        }
703    }
704
705    private static byte[] getOctets(OMANode octetNode) throws OMAException {
706        if (octetNode == null) {
707            throw new OMAException("Missing byte value");
708        }
709        return Utils.hexToBytes(octetNode.getValue());
710    }
711}
712