Home | History | Annotate | Download | only in omadm
      1 package com.android.server.wifi.hotspot2.omadm;
      2 
      3 import android.util.Base64;
      4 import android.util.Log;
      5 
      6 import com.android.server.wifi.IMSIParameter;
      7 import com.android.server.wifi.anqp.eap.EAP;
      8 import com.android.server.wifi.anqp.eap.EAPMethod;
      9 import com.android.server.wifi.anqp.eap.ExpandedEAPMethod;
     10 import com.android.server.wifi.anqp.eap.InnerAuthEAP;
     11 import com.android.server.wifi.anqp.eap.NonEAPInnerAuth;
     12 import com.android.server.wifi.hotspot2.Utils;
     13 import com.android.server.wifi.hotspot2.pps.Credential;
     14 import com.android.server.wifi.hotspot2.pps.HomeSP;
     15 
     16 import org.xml.sax.SAXException;
     17 
     18 import java.io.BufferedInputStream;
     19 import java.io.BufferedOutputStream;
     20 import java.io.File;
     21 import java.io.FileInputStream;
     22 import java.io.FileOutputStream;
     23 import java.io.IOException;
     24 import java.nio.charset.StandardCharsets;
     25 import java.text.DateFormat;
     26 import java.text.ParseException;
     27 import java.text.SimpleDateFormat;
     28 import java.util.ArrayList;
     29 import java.util.Arrays;
     30 import java.util.Collection;
     31 import java.util.Collections;
     32 import java.util.Date;
     33 import java.util.HashMap;
     34 import java.util.HashSet;
     35 import java.util.List;
     36 import java.util.Map;
     37 import java.util.Set;
     38 import java.util.TimeZone;
     39 
     40 /**
     41  * Handles provisioning of PerProviderSubscription data.
     42  */
     43 public 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