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