• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.hotspot2.omadm;
2 
3 import android.util.Base64;
4 import android.util.Log;
5 
6 import com.android.anqp.eap.EAP;
7 import com.android.anqp.eap.EAPMethod;
8 import com.android.anqp.eap.ExpandedEAPMethod;
9 import com.android.anqp.eap.InnerAuthEAP;
10 import com.android.anqp.eap.NonEAPInnerAuth;
11 import com.android.hotspot2.IMSIParameter;
12 import com.android.hotspot2.Utils;
13 import com.android.hotspot2.osu.OSUManager;
14 import com.android.hotspot2.osu.commands.MOData;
15 import com.android.hotspot2.pps.Credential;
16 import com.android.hotspot2.pps.HomeSP;
17 import com.android.hotspot2.pps.Policy;
18 import com.android.hotspot2.pps.SubscriptionParameters;
19 import com.android.hotspot2.pps.UpdateInfo;
20 
21 import org.xml.sax.SAXException;
22 
23 import java.io.BufferedInputStream;
24 import java.io.BufferedOutputStream;
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.FileNotFoundException;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.nio.charset.StandardCharsets;
31 import java.text.DateFormat;
32 import java.text.ParseException;
33 import java.text.SimpleDateFormat;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Collection;
37 import java.util.Collections;
38 import java.util.Date;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.LinkedList;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.TimeZone;
46 
47 /**
48  * Handles provisioning of PerProviderSubscription data.
49  */
50 public class MOManager {
51 
52     public static final String TAG_AAAServerTrustRoot = "AAAServerTrustRoot";
53     public static final String TAG_AbleToShare = "AbleToShare";
54     public static final String TAG_CertificateType = "CertificateType";
55     public static final String TAG_CertSHA256Fingerprint = "CertSHA256Fingerprint";
56     public static final String TAG_CertURL = "CertURL";
57     public static final String TAG_CheckAAAServerCertStatus = "CheckAAAServerCertStatus";
58     public static final String TAG_Country = "Country";
59     public static final String TAG_CreationDate = "CreationDate";
60     public static final String TAG_Credential = "Credential";
61     public static final String TAG_CredentialPriority = "CredentialPriority";
62     public static final String TAG_DataLimit = "DataLimit";
63     public static final String TAG_DigitalCertificate = "DigitalCertificate";
64     public static final String TAG_DLBandwidth = "DLBandwidth";
65     public static final String TAG_EAPMethod = "EAPMethod";
66     public static final String TAG_EAPType = "EAPType";
67     public static final String TAG_ExpirationDate = "ExpirationDate";
68     public static final String TAG_Extension = "Extension";
69     public static final String TAG_FQDN = "FQDN";
70     public static final String TAG_FQDN_Match = "FQDN_Match";
71     public static final String TAG_FriendlyName = "FriendlyName";
72     public static final String TAG_HESSID = "HESSID";
73     public static final String TAG_HomeOI = "HomeOI";
74     public static final String TAG_HomeOIList = "HomeOIList";
75     public static final String TAG_HomeOIRequired = "HomeOIRequired";
76     public static final String TAG_HomeSP = "HomeSP";
77     public static final String TAG_IconURL = "IconURL";
78     public static final String TAG_IMSI = "IMSI";
79     public static final String TAG_InnerEAPType = "InnerEAPType";
80     public static final String TAG_InnerMethod = "InnerMethod";
81     public static final String TAG_InnerVendorID = "InnerVendorID";
82     public static final String TAG_InnerVendorType = "InnerVendorType";
83     public static final String TAG_IPProtocol = "IPProtocol";
84     public static final String TAG_MachineManaged = "MachineManaged";
85     public static final String TAG_MaximumBSSLoadValue = "MaximumBSSLoadValue";
86     public static final String TAG_MinBackhaulThreshold = "MinBackhaulThreshold";
87     public static final String TAG_NetworkID = "NetworkID";
88     public static final String TAG_NetworkType = "NetworkType";
89     public static final String TAG_Other = "Other";
90     public static final String TAG_OtherHomePartners = "OtherHomePartners";
91     public static final String TAG_Password = "Password";
92     public static final String TAG_PerProviderSubscription = "PerProviderSubscription";
93     public static final String TAG_Policy = "Policy";
94     public static final String TAG_PolicyUpdate = "PolicyUpdate";
95     public static final String TAG_PortNumber = "PortNumber";
96     public static final String TAG_PreferredRoamingPartnerList = "PreferredRoamingPartnerList";
97     public static final String TAG_Priority = "Priority";
98     public static final String TAG_Realm = "Realm";
99     public static final String TAG_RequiredProtoPortTuple = "RequiredProtoPortTuple";
100     public static final String TAG_Restriction = "Restriction";
101     public static final String TAG_RoamingConsortiumOI = "RoamingConsortiumOI";
102     public static final String TAG_SIM = "SIM";
103     public static final String TAG_SoftTokenApp = "SoftTokenApp";
104     public static final String TAG_SPExclusionList = "SPExclusionList";
105     public static final String TAG_SSID = "SSID";
106     public static final String TAG_StartDate = "StartDate";
107     public static final String TAG_SubscriptionParameters = "SubscriptionParameters";
108     public static final String TAG_SubscriptionUpdate = "SubscriptionUpdate";
109     public static final String TAG_TimeLimit = "TimeLimit";
110     public static final String TAG_TrustRoot = "TrustRoot";
111     public static final String TAG_TypeOfSubscription = "TypeOfSubscription";
112     public static final String TAG_ULBandwidth = "ULBandwidth";
113     public static final String TAG_UpdateIdentifier = "UpdateIdentifier";
114     public static final String TAG_UpdateInterval = "UpdateInterval";
115     public static final String TAG_UpdateMethod = "UpdateMethod";
116     public static final String TAG_URI = "URI";
117     public static final String TAG_UsageLimits = "UsageLimits";
118     public static final String TAG_UsageTimePeriod = "UsageTimePeriod";
119     public static final String TAG_Username = "Username";
120     public static final String TAG_UsernamePassword = "UsernamePassword";
121     public static final String TAG_VendorId = "VendorId";
122     public static final String TAG_VendorType = "VendorType";
123 
124     public static final long IntervalFactor = 60000L;  // All MO intervals are in minutes
125 
126     private static final DateFormat DTFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
127 
128     private static final Map<String, Map<String, Object>> sSelectionMap;
129 
130     static {
131         DTFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
132 
133         sSelectionMap = new HashMap<>();
134 
setSelections(TAG_FQDN_Match, "exactmatch", Boolean.FALSE, "includesubdomains", Boolean.TRUE)135         setSelections(TAG_FQDN_Match,
136                 "exactmatch", Boolean.FALSE,
137                 "includesubdomains", Boolean.TRUE);
setSelections(TAG_UpdateMethod, "oma-dm-clientinitiated", Boolean.FALSE, "spp-clientinitiated", Boolean.TRUE)138         setSelections(TAG_UpdateMethod,
139                 "oma-dm-clientinitiated", Boolean.FALSE,
140                 "spp-clientinitiated", Boolean.TRUE);
setSelections(TAG_Restriction, "homesp", UpdateInfo.UpdateRestriction.HomeSP, "roamingpartner", UpdateInfo.UpdateRestriction.RoamingPartner, "unrestricted", UpdateInfo.UpdateRestriction.Unrestricted)141         setSelections(TAG_Restriction,
142                 "homesp", UpdateInfo.UpdateRestriction.HomeSP,
143                 "roamingpartner", UpdateInfo.UpdateRestriction.RoamingPartner,
144                 "unrestricted", UpdateInfo.UpdateRestriction.Unrestricted);
145     }
146 
setSelections(String key, Object... pairs)147     private static void setSelections(String key, Object... pairs) {
148         Map<String, Object> kvp = new HashMap<>();
149         sSelectionMap.put(key, kvp);
150         for (int n = 0; n < pairs.length; n += 2) {
151             kvp.put(pairs[n].toString(), pairs[n + 1]);
152         }
153     }
154 
155     private final File mPpsFile;
156     private final boolean mEnabled;
157     private final Map<String, HomeSP> mSPs;
158 
MOManager(File ppsFile, boolean hs2enabled)159     public MOManager(File ppsFile, boolean hs2enabled) {
160         mPpsFile = ppsFile;
161         mEnabled = hs2enabled;
162         mSPs = new HashMap<>();
163     }
164 
getPpsFile()165     public File getPpsFile() {
166         return mPpsFile;
167     }
168 
isEnabled()169     public boolean isEnabled() {
170         return mEnabled;
171     }
172 
isConfigured()173     public boolean isConfigured() {
174         return mEnabled && !mSPs.isEmpty();
175     }
176 
getLoadedSPs()177     public Map<String, HomeSP> getLoadedSPs() {
178         return Collections.unmodifiableMap(mSPs);
179     }
180 
loadAllSPs()181     public List<HomeSP> loadAllSPs() throws IOException {
182 
183         if (!mEnabled || !mPpsFile.exists()) {
184             return Collections.emptyList();
185         }
186 
187         try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
188             MOTree moTree = MOTree.unmarshal(in);
189             mSPs.clear();
190             if (moTree == null) {
191                 return Collections.emptyList();     // Empty file
192             }
193 
194             List<HomeSP> sps = buildSPs(moTree);
195             if (sps != null) {
196                 for (HomeSP sp : sps) {
197                     if (mSPs.put(sp.getFQDN(), sp) != null) {
198                         throw new OMAException("Multiple SPs for FQDN '" + sp.getFQDN() + "'");
199                     } else {
200                         Log.d(OSUManager.TAG, "retrieved " + sp.getFQDN() + " from PPS");
201                     }
202                 }
203                 return sps;
204 
205             } else {
206                 throw new OMAException("Failed to build HomeSP");
207             }
208         }
209     }
210 
buildSP(String xml)211     public static HomeSP buildSP(String xml) throws IOException, SAXException {
212         OMAParser omaParser = new OMAParser();
213         MOTree tree = omaParser.parse(xml, OMAConstants.PPS_URN);
214         List<HomeSP> spList = buildSPs(tree);
215         if (spList.size() != 1) {
216             throw new OMAException("Expected exactly one HomeSP, got " + spList.size());
217         }
218         return spList.iterator().next();
219     }
220 
addSP(String xml, OSUManager osuManager)221     public HomeSP addSP(String xml, OSUManager osuManager) throws IOException, SAXException {
222         OMAParser omaParser = new OMAParser();
223         return addSP(omaParser.parse(xml, OMAConstants.PPS_URN), osuManager);
224     }
225 
226     private static final List<String> FQDNPath = Arrays.asList(TAG_HomeSP, TAG_FQDN);
227 
228     /**
229      * R1 *only* addSP method.
230      *
231      * @param homeSP
232      * @throws IOException
233      */
addSP(HomeSP homeSP, OSUManager osuManager)234     public void addSP(HomeSP homeSP, OSUManager osuManager) throws IOException {
235         if (!mEnabled) {
236             throw new IOException("HS2.0 not enabled on this device");
237         }
238         if (mSPs.containsKey(homeSP.getFQDN())) {
239             Log.d(OSUManager.TAG, "HS20 profile for " +
240                     homeSP.getFQDN() + " already exists");
241             return;
242         }
243         Log.d(OSUManager.TAG, "Adding new HS20 profile for " + homeSP.getFQDN());
244 
245         OMAConstructed dummyRoot = new OMAConstructed(null, TAG_PerProviderSubscription, null);
246         buildHomeSPTree(homeSP, dummyRoot, mSPs.size() + 1);
247         try {
248             addSP(dummyRoot, osuManager);
249         } catch (FileNotFoundException fnfe) {
250             MOTree tree =
251                     MOTree.buildMgmtTree(OMAConstants.PPS_URN, OMAConstants.OMAVersion, dummyRoot);
252             // No file to load a pre-build MO tree from, create a new one and save it.
253             //MOTree tree = new MOTree(OMAConstants.PPS_URN, OMAConstants.OMAVersion, dummyRoot);
254             writeMO(tree, mPpsFile, osuManager);
255         }
256         mSPs.put(homeSP.getFQDN(), homeSP);
257     }
258 
addSP(MOTree instanceTree, OSUManager osuManager)259     public HomeSP addSP(MOTree instanceTree, OSUManager osuManager) throws IOException {
260         List<HomeSP> spList = buildSPs(instanceTree);
261         if (spList.size() != 1) {
262             throw new OMAException("Expected exactly one HomeSP, got " + spList.size());
263         }
264 
265         HomeSP sp = spList.iterator().next();
266         String fqdn = sp.getFQDN();
267         if (mSPs.put(fqdn, sp) != null) {
268             throw new OMAException("SP " + fqdn + " already exists");
269         }
270 
271         OMAConstructed pps = (OMAConstructed) instanceTree.getRoot().
272                 getChild(TAG_PerProviderSubscription);
273 
274         try {
275             addSP(pps, osuManager);
276         } catch (FileNotFoundException fnfe) {
277             MOTree tree = new MOTree(instanceTree.getUrn(), instanceTree.getDtdRev(),
278                     instanceTree.getRoot());
279             writeMO(tree, mPpsFile, osuManager);
280         }
281 
282         return sp;
283     }
284 
285     /**
286      * Add an SP sub-tree. mo must be PPS with an immediate instance child (e.g. Cred01) and an
287      * optional UpdateIdentifier,
288      *
289      * @param mo The new MO
290      * @throws IOException
291      */
addSP(OMANode mo, OSUManager osuManager)292     private void addSP(OMANode mo, OSUManager osuManager) throws IOException {
293         MOTree moTree;
294         try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
295             moTree = MOTree.unmarshal(in);
296             moTree.getRoot().addChild(mo);
297 
298                 /*
299             OMAConstructed ppsRoot = (OMAConstructed)
300                     moTree.getRoot().addChild(TAG_PerProviderSubscription, "", null, null);
301             for (OMANode child : mo.getChildren()) {
302                 ppsRoot.addChild(child);
303                 if (!child.isLeaf()) {
304                     moTree.getRoot().addChild(child);
305                 }
306                 else if (child.getName().equals(TAG_UpdateIdentifier)) {
307                     OMANode currentUD = moTree.getRoot().getChild(TAG_UpdateIdentifier);
308                     if (currentUD != null) {
309                         moTree.getRoot().replaceNode(currentUD, child);
310                     }
311                     else {
312                         moTree.getRoot().addChild(child);
313                     }
314                 }
315             }
316                 */
317         }
318         writeMO(moTree, mPpsFile, osuManager);
319     }
320 
findTargetTree(MOTree moTree, String fqdn)321     private static OMAConstructed findTargetTree(MOTree moTree, String fqdn) throws OMAException {
322         OMANode pps = moTree.getRoot();
323         for (OMANode node : pps.getChildren()) {
324             OMANode instance = null;
325             if (node.getName().equals(TAG_PerProviderSubscription)) {
326                 instance = getInstanceNode((OMAConstructed) node);
327             } else if (!node.isLeaf()) {
328                 instance = node;
329             }
330             if (instance != null) {
331                 String nodeFqdn = getString(instance.getListValue(FQDNPath.iterator()));
332                 if (fqdn.equalsIgnoreCase(nodeFqdn)) {
333                     return (OMAConstructed) node;
334                     // targetTree is rooted at the PPS
335                 }
336             }
337         }
338         return null;
339     }
340 
getInstanceNode(OMAConstructed root)341     private static OMAConstructed getInstanceNode(OMAConstructed root) throws OMAException {
342         for (OMANode child : root.getChildren()) {
343             if (!child.isLeaf()) {
344                 return (OMAConstructed) child;
345             }
346         }
347         throw new OMAException("Cannot find instance node");
348     }
349 
modifySP(HomeSP homeSP, Collection<MOData> mods, OSUManager osuManager)350     public HomeSP modifySP(HomeSP homeSP, Collection<MOData> mods, OSUManager osuManager)
351             throws IOException {
352 
353         Log.d(OSUManager.TAG, "modifying SP: " + mods);
354         MOTree moTree;
355         int ppsMods = 0;
356         int updateIdentifier = 0;
357         try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
358             moTree = MOTree.unmarshal(in);
359             // moTree is PPS/?/provider-data
360 
361             OMAConstructed targetTree = findTargetTree(moTree, homeSP.getFQDN());
362             if (targetTree == null) {
363                 throw new IOException("Failed to find PPS tree for " + homeSP.getFQDN());
364             }
365             OMAConstructed instance = getInstanceNode(targetTree);
366 
367             for (MOData mod : mods) {
368                 LinkedList<String> tailPath =
369                         getTailPath(mod.getBaseURI(), TAG_PerProviderSubscription);
370                 OMAConstructed modRoot = mod.getMOTree().getRoot();
371                 // modRoot is the MgmtTree with the actual object as a direct child
372                 // (e.g. Credential)
373 
374                 if (tailPath.getFirst().equals(TAG_UpdateIdentifier)) {
375                     updateIdentifier = getInteger(modRoot.getChildren().iterator().next());
376                     OMANode oldUdi = targetTree.getChild(TAG_UpdateIdentifier);
377                     if (getInteger(oldUdi) != updateIdentifier) {
378                         ppsMods++;
379                     }
380                     if (oldUdi != null) {
381                         targetTree.replaceNode(oldUdi, modRoot.getChild(TAG_UpdateIdentifier));
382                     } else {
383                         targetTree.addChild(modRoot.getChild(TAG_UpdateIdentifier));
384                     }
385                 } else {
386                     tailPath.removeFirst();     // Drop the instance
387                     OMANode current = instance.getListValue(tailPath.iterator());
388                     if (current == null) {
389                         throw new IOException("No previous node for " + tailPath + " in " +
390                                 homeSP.getFQDN());
391                     }
392                     for (OMANode newNode : modRoot.getChildren()) {
393                         // newNode is something like Credential
394                         // current is the same existing node
395                         OMANode old = current.getParent().replaceNode(current, newNode);
396                         ppsMods++;
397                     }
398                 }
399             }
400         }
401         writeMO(moTree, mPpsFile, osuManager);
402 
403         if (ppsMods == 0) {
404             return null;    // HomeSP not modified.
405         }
406 
407         // Return a new rebuilt HomeSP
408         List<HomeSP> sps = buildSPs(moTree);
409         if (sps != null) {
410             for (HomeSP sp : sps) {
411                 if (sp.getFQDN().equals(homeSP.getFQDN())) {
412                     return sp;
413                 }
414             }
415         } else {
416             throw new OMAException("Failed to build HomeSP");
417         }
418         return null;
419     }
420 
getTailPath(String pathString, String rootName)421     private static LinkedList<String> getTailPath(String pathString, String rootName)
422             throws IOException {
423         String[] path = pathString.split("/");
424         int pathIndex;
425         for (pathIndex = 0; pathIndex < path.length; pathIndex++) {
426             if (path[pathIndex].equalsIgnoreCase(rootName)) {
427                 pathIndex++;
428                 break;
429             }
430         }
431         if (pathIndex >= path.length) {
432             throw new IOException("Bad node-path: " + pathString);
433         }
434         LinkedList<String> tailPath = new LinkedList<>();
435         while (pathIndex < path.length) {
436             tailPath.add(path[pathIndex]);
437             pathIndex++;
438         }
439         return tailPath;
440     }
441 
getHomeSP(String fqdn)442     public HomeSP getHomeSP(String fqdn) {
443         return mSPs.get(fqdn);
444     }
445 
removeSP(String fqdn, OSUManager osuManager)446     public void removeSP(String fqdn, OSUManager osuManager) throws IOException {
447         if (mSPs.remove(fqdn) == null) {
448             Log.d(OSUManager.TAG, "No HS20 profile to delete for " + fqdn);
449             return;
450         }
451 
452         Log.d(OSUManager.TAG, "Deleting HS20 profile for " + fqdn);
453 
454         MOTree moTree;
455         try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
456             moTree = MOTree.unmarshal(in);
457             OMAConstructed tbd = findTargetTree(moTree, fqdn);
458             if (tbd == null) {
459                 throw new IOException("Node " + fqdn + " doesn't exist in MO tree");
460             }
461             OMAConstructed pps = moTree.getRoot();
462             OMANode removed = pps.removeNode("?", tbd);
463             if (removed == null) {
464                 throw new IOException("Failed to remove " + fqdn + " out of MO tree");
465             }
466         }
467         writeMO(moTree, mPpsFile, osuManager);
468         osuManager.spDeleted(fqdn);
469     }
470 
getMOTree(HomeSP homeSP)471     public MOTree getMOTree(HomeSP homeSP) throws IOException {
472         try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
473             MOTree moTree = MOTree.unmarshal(in);
474             OMAConstructed target = findTargetTree(moTree, homeSP.getFQDN());
475             if (target == null) {
476                 throw new IOException("Can't find " + homeSP.getFQDN() + " in MO tree");
477             }
478             return MOTree.buildMgmtTree(OMAConstants.PPS_URN, OMAConstants.OMAVersion, target);
479         }
480     }
481 
writeMO(MOTree moTree, File f, OSUManager osuManager)482     private static void writeMO(MOTree moTree, File f, OSUManager osuManager) throws IOException {
483         try (BufferedOutputStream out =
484                      new BufferedOutputStream(new FileOutputStream(f, false))) {
485             moTree.marshal(out);
486             out.flush();
487         }
488     }
489 
fqdnList(Collection<HomeSP> sps)490     private static String fqdnList(Collection<HomeSP> sps) {
491         StringBuilder sb = new StringBuilder();
492         boolean first = true;
493         for (HomeSP sp : sps) {
494             if (first) {
495                 first = false;
496             } else {
497                 sb.append(", ");
498             }
499             sb.append(sp.getFQDN());
500         }
501         return sb.toString();
502     }
503 
buildHomeSPTree(HomeSP homeSP, OMAConstructed root, int instanceID)504     private static OMANode buildHomeSPTree(HomeSP homeSP, OMAConstructed root, int instanceID)
505             throws IOException {
506         OMANode providerSubNode = root.addChild(getInstanceString(instanceID),
507                 null, null, null);
508 
509         // The HomeSP:
510         OMANode homeSpNode = providerSubNode.addChild(TAG_HomeSP, null, null, null);
511         if (!homeSP.getSSIDs().isEmpty()) {
512             OMAConstructed nwkIDNode =
513                     (OMAConstructed) homeSpNode.addChild(TAG_NetworkID, null, null, null);
514             int instance = 0;
515             for (Map.Entry<String, Long> entry : homeSP.getSSIDs().entrySet()) {
516                 OMAConstructed inode =
517                         (OMAConstructed) nwkIDNode
518                                 .addChild(getInstanceString(instance++), null, null, null);
519                 inode.addChild(TAG_SSID, null, entry.getKey(), null);
520                 if (entry.getValue() != null) {
521                     inode.addChild(TAG_HESSID, null,
522                             String.format("%012x", entry.getValue()), null);
523                 }
524             }
525         }
526 
527         homeSpNode.addChild(TAG_FriendlyName, null, homeSP.getFriendlyName(), null);
528 
529         if (homeSP.getIconURL() != null) {
530             homeSpNode.addChild(TAG_IconURL, null, homeSP.getIconURL(), null);
531         }
532 
533         homeSpNode.addChild(TAG_FQDN, null, homeSP.getFQDN(), null);
534 
535         if (!homeSP.getMatchAllOIs().isEmpty() || !homeSP.getMatchAnyOIs().isEmpty()) {
536             OMAConstructed homeOIList =
537                     (OMAConstructed) homeSpNode.addChild(TAG_HomeOIList, null, null, null);
538 
539             int instance = 0;
540             for (Long oi : homeSP.getMatchAllOIs()) {
541                 OMAConstructed inode =
542                         (OMAConstructed) homeOIList.addChild(getInstanceString(instance++),
543                                 null, null, null);
544                 inode.addChild(TAG_HomeOI, null, String.format("%x", oi), null);
545                 inode.addChild(TAG_HomeOIRequired, null, "TRUE", null);
546             }
547             for (Long oi : homeSP.getMatchAnyOIs()) {
548                 OMAConstructed inode =
549                         (OMAConstructed) homeOIList.addChild(getInstanceString(instance++),
550                                 null, null, null);
551                 inode.addChild(TAG_HomeOI, null, String.format("%x", oi), null);
552                 inode.addChild(TAG_HomeOIRequired, null, "FALSE", null);
553             }
554         }
555 
556         if (!homeSP.getOtherHomePartners().isEmpty()) {
557             OMAConstructed otherPartners =
558                     (OMAConstructed) homeSpNode.addChild(TAG_OtherHomePartners, null, null, null);
559             int instance = 0;
560             for (String fqdn : homeSP.getOtherHomePartners()) {
561                 OMAConstructed inode =
562                         (OMAConstructed) otherPartners.addChild(getInstanceString(instance++),
563                                 null, null, null);
564                 inode.addChild(TAG_FQDN, null, fqdn, null);
565             }
566         }
567 
568         if (!homeSP.getRoamingConsortiums().isEmpty()) {
569             homeSpNode.addChild(TAG_RoamingConsortiumOI, null,
570                     getRCList(homeSP.getRoamingConsortiums()), null);
571         }
572 
573         // The Credential:
574         OMANode credentialNode = providerSubNode.addChild(TAG_Credential, null, null, null);
575         Credential cred = homeSP.getCredential();
576         EAPMethod method = cred.getEAPMethod();
577 
578         if (cred.getCtime() > 0) {
579             credentialNode.addChild(TAG_CreationDate,
580                     null, DTFormat.format(new Date(cred.getCtime())), null);
581         }
582         if (cred.getExpTime() > 0) {
583             credentialNode.addChild(TAG_ExpirationDate,
584                     null, DTFormat.format(new Date(cred.getExpTime())), null);
585         }
586 
587         if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_SIM
588                 || method.getEAPMethodID() == EAP.EAPMethodID.EAP_AKA
589                 || method.getEAPMethodID() == EAP.EAPMethodID.EAP_AKAPrim) {
590 
591             OMANode simNode = credentialNode.addChild(TAG_SIM, null, null, null);
592             simNode.addChild(TAG_IMSI, null, cred.getImsi().toString(), null);
593             simNode.addChild(TAG_EAPType, null,
594                     Integer.toString(EAP.mapEAPMethod(method.getEAPMethodID())), null);
595 
596         } else if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TTLS) {
597 
598             OMANode unpNode = credentialNode.addChild(TAG_UsernamePassword, null, null, null);
599             unpNode.addChild(TAG_Username, null, cred.getUserName(), null);
600             unpNode.addChild(TAG_Password, null,
601                     Base64.encodeToString(cred.getPassword().getBytes(StandardCharsets.UTF_8),
602                             Base64.DEFAULT), null);
603             OMANode eapNode = unpNode.addChild(TAG_EAPMethod, null, null, null);
604             eapNode.addChild(TAG_EAPType, null,
605                     Integer.toString(EAP.mapEAPMethod(method.getEAPMethodID())), null);
606             eapNode.addChild(TAG_InnerMethod, null,
607                     ((NonEAPInnerAuth) method.getAuthParam()).getOMAtype(), null);
608 
609         } else if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TLS) {
610 
611             OMANode certNode = credentialNode.addChild(TAG_DigitalCertificate, null, null, null);
612             certNode.addChild(TAG_CertificateType, null, Credential.CertTypeX509, null);
613             certNode.addChild(TAG_CertSHA256Fingerprint, null,
614                     Utils.toHex(cred.getFingerPrint()), null);
615 
616         } else {
617             throw new OMAException("Invalid credential on " + homeSP.getFQDN());
618         }
619 
620         credentialNode.addChild(TAG_Realm, null, cred.getRealm(), null);
621 
622         // !!! Note: This node defines CRL checking through OSCP, I suspect we won't be able
623         // to do that so it is commented out:
624         //credentialNode.addChild(TAG_CheckAAAServerCertStatus, null, "TRUE", null);
625         return providerSubNode;
626     }
627 
getInstanceString(int instance)628     private static String getInstanceString(int instance) {
629         return "r1i" + instance;
630     }
631 
getRCList(Collection<Long> rcs)632     private static String getRCList(Collection<Long> rcs) {
633         StringBuilder builder = new StringBuilder();
634         boolean first = true;
635         for (Long roamingConsortium : rcs) {
636             if (first) {
637                 first = false;
638             } else {
639                 builder.append(',');
640             }
641             builder.append(String.format("%x", roamingConsortium));
642         }
643         return builder.toString();
644     }
645 
buildSPs(MOTree moTree)646     public static List<HomeSP> buildSPs(MOTree moTree) throws OMAException {
647         OMAConstructed spList;
648         List<HomeSP> homeSPs = new ArrayList<>();
649         if (moTree.getRoot().getName().equals(TAG_PerProviderSubscription)) {
650             // The old PPS file was rooted at PPS instead of MgmtTree to conserve space
651             spList = moTree.getRoot();
652 
653             if (spList == null) {
654                 return homeSPs;
655             }
656 
657             for (OMANode node : spList.getChildren()) {
658                 if (!node.isLeaf()) {
659                     homeSPs.add(buildHomeSP(node, 0));
660                 }
661             }
662         } else {
663             for (OMANode ppsRoot : moTree.getRoot().getChildren()) {
664                 if (ppsRoot.getName().equals(TAG_PerProviderSubscription)) {
665                     Integer updateIdentifier = null;
666                     OMANode instance = null;
667                     for (OMANode child : ppsRoot.getChildren()) {
668                         if (child.getName().equals(TAG_UpdateIdentifier)) {
669                             updateIdentifier = getInteger(child);
670                         } else if (!child.isLeaf()) {
671                             instance = child;
672                         }
673                     }
674                     if (instance == null) {
675                         throw new OMAException("PPS node missing instance node");
676                     }
677                     homeSPs.add(buildHomeSP(instance,
678                             updateIdentifier != null ? updateIdentifier : 0));
679                 }
680             }
681         }
682 
683         return homeSPs;
684     }
685 
buildHomeSP(OMANode ppsRoot, int updateIdentifier)686     private static HomeSP buildHomeSP(OMANode ppsRoot, int updateIdentifier) throws OMAException {
687         OMANode spRoot = ppsRoot.getChild(TAG_HomeSP);
688 
689         String fqdn = spRoot.getScalarValue(Arrays.asList(TAG_FQDN).iterator());
690         String friendlyName = spRoot.getScalarValue(Arrays.asList(TAG_FriendlyName).iterator());
691         String iconURL = spRoot.getScalarValue(Arrays.asList(TAG_IconURL).iterator());
692 
693         HashSet<Long> roamingConsortiums = new HashSet<>();
694         String oiString = spRoot.getScalarValue(Arrays.asList(TAG_RoamingConsortiumOI).iterator());
695         if (oiString != null) {
696             for (String oi : oiString.split(",")) {
697                 roamingConsortiums.add(Long.parseLong(oi.trim(), 16));
698             }
699         }
700 
701         Map<String, Long> ssids = new HashMap<>();
702 
703         OMANode ssidListNode = spRoot.getListValue(Arrays.asList(TAG_NetworkID).iterator());
704         if (ssidListNode != null) {
705             for (OMANode ssidRoot : ssidListNode.getChildren()) {
706                 OMANode hessidNode = ssidRoot.getChild(TAG_HESSID);
707                 ssids.put(ssidRoot.getChild(TAG_SSID).getValue(), getMac(hessidNode));
708             }
709         }
710 
711         Set<Long> matchAnyOIs = new HashSet<>();
712         List<Long> matchAllOIs = new ArrayList<>();
713         OMANode homeOIListNode = spRoot.getListValue(Arrays.asList(TAG_HomeOIList).iterator());
714         if (homeOIListNode != null) {
715             for (OMANode homeOIRoot : homeOIListNode.getChildren()) {
716                 String homeOI = homeOIRoot.getChild(TAG_HomeOI).getValue();
717                 if (Boolean.parseBoolean(homeOIRoot.getChild(TAG_HomeOIRequired).getValue())) {
718                     matchAllOIs.add(Long.parseLong(homeOI, 16));
719                 } else {
720                     matchAnyOIs.add(Long.parseLong(homeOI, 16));
721                 }
722             }
723         }
724 
725         Set<String> otherHomePartners = new HashSet<>();
726         OMANode otherListNode =
727                 spRoot.getListValue(Arrays.asList(TAG_OtherHomePartners).iterator());
728         if (otherListNode != null) {
729             for (OMANode fqdnNode : otherListNode.getChildren()) {
730                 otherHomePartners.add(fqdnNode.getChild(TAG_FQDN).getValue());
731             }
732         }
733 
734         Credential credential = buildCredential(ppsRoot.getChild(TAG_Credential));
735 
736         OMANode policyNode = ppsRoot.getChild(TAG_Policy);
737         Policy policy = policyNode != null ? new Policy(policyNode) : null;
738 
739         Map<String, String> aaaTrustRoots;
740         OMANode aaaRootNode = ppsRoot.getChild(TAG_AAAServerTrustRoot);
741         if (aaaRootNode == null) {
742             aaaTrustRoots = null;
743         } else {
744             aaaTrustRoots = new HashMap<>(aaaRootNode.getChildren().size());
745             for (OMANode child : aaaRootNode.getChildren()) {
746                 aaaTrustRoots.put(getString(child, TAG_CertURL),
747                         getString(child, TAG_CertSHA256Fingerprint));
748             }
749         }
750 
751         OMANode updateNode = ppsRoot.getChild(TAG_SubscriptionUpdate);
752         UpdateInfo subscriptionUpdate = updateNode != null ? new UpdateInfo(updateNode) : null;
753         OMANode subNode = ppsRoot.getChild(TAG_SubscriptionParameters);
754         SubscriptionParameters subscriptionParameters = subNode != null ?
755                 new SubscriptionParameters(subNode) : null;
756 
757         return new HomeSP(ssids, fqdn, roamingConsortiums, otherHomePartners,
758                 matchAnyOIs, matchAllOIs, friendlyName, iconURL, credential,
759                 policy, getInteger(ppsRoot.getChild(TAG_CredentialPriority), 0),
760                 aaaTrustRoots, subscriptionUpdate, subscriptionParameters, updateIdentifier);
761     }
762 
buildCredential(OMANode credNode)763     private static Credential buildCredential(OMANode credNode) throws OMAException {
764         long ctime = getTime(credNode.getChild(TAG_CreationDate));
765         long expTime = getTime(credNode.getChild(TAG_ExpirationDate));
766         String realm = getString(credNode.getChild(TAG_Realm));
767         boolean checkAAACert = getBoolean(credNode.getChild(TAG_CheckAAAServerCertStatus));
768 
769         OMANode unNode = credNode.getChild(TAG_UsernamePassword);
770         OMANode certNode = credNode.getChild(TAG_DigitalCertificate);
771         OMANode simNode = credNode.getChild(TAG_SIM);
772 
773         int alternatives = 0;
774         alternatives += unNode != null ? 1 : 0;
775         alternatives += certNode != null ? 1 : 0;
776         alternatives += simNode != null ? 1 : 0;
777         if (alternatives != 1) {
778             throw new OMAException("Expected exactly one credential type, got " + alternatives);
779         }
780 
781         if (unNode != null) {
782             String userName = getString(unNode.getChild(TAG_Username));
783             String password = getString(unNode.getChild(TAG_Password));
784             boolean machineManaged = getBoolean(unNode.getChild(TAG_MachineManaged));
785             String softTokenApp = getString(unNode.getChild(TAG_SoftTokenApp));
786             boolean ableToShare = getBoolean(unNode.getChild(TAG_AbleToShare));
787 
788             OMANode eapMethodNode = unNode.getChild(TAG_EAPMethod);
789             int eapID = getInteger(eapMethodNode.getChild(TAG_EAPType));
790 
791             EAP.EAPMethodID eapMethodID = EAP.mapEAPMethod(eapID);
792             if (eapMethodID == null) {
793                 throw new OMAException("Unknown EAP method: " + eapID);
794             }
795 
796             Long vid = getOptionalInteger(eapMethodNode.getChild(TAG_VendorId));
797             Long vtype = getOptionalInteger(eapMethodNode.getChild(TAG_VendorType));
798             Long innerEAPType = getOptionalInteger(eapMethodNode.getChild(TAG_InnerEAPType));
799             EAP.EAPMethodID innerEAPMethod = null;
800             if (innerEAPType != null) {
801                 innerEAPMethod = EAP.mapEAPMethod(innerEAPType.intValue());
802                 if (innerEAPMethod == null) {
803                     throw new OMAException("Bad inner EAP method: " + innerEAPType);
804                 }
805             }
806 
807             Long innerVid = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorID));
808             Long innerVtype = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorType));
809             String innerNonEAPMethod = getString(eapMethodNode.getChild(TAG_InnerMethod));
810 
811             EAPMethod eapMethod;
812             if (innerEAPMethod != null) {
813                 eapMethod = new EAPMethod(eapMethodID, new InnerAuthEAP(innerEAPMethod));
814             } else if (vid != null) {
815                 eapMethod = new EAPMethod(eapMethodID,
816                         new ExpandedEAPMethod(EAP.AuthInfoID.ExpandedEAPMethod,
817                                 vid.intValue(), vtype));
818             } else if (innerVid != null) {
819                 eapMethod =
820                         new EAPMethod(eapMethodID, new ExpandedEAPMethod(EAP.AuthInfoID
821                                 .ExpandedInnerEAPMethod, innerVid.intValue(), innerVtype));
822             } else if (innerNonEAPMethod != null) {
823                 eapMethod = new EAPMethod(eapMethodID, new NonEAPInnerAuth(innerNonEAPMethod));
824             } else {
825                 throw new OMAException("Incomplete set of EAP parameters");
826             }
827 
828             return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, userName,
829                     password, machineManaged, softTokenApp, ableToShare);
830         }
831         if (certNode != null) {
832             try {
833                 String certTypeString = getString(certNode.getChild(TAG_CertificateType));
834                 byte[] fingerPrint = getOctets(certNode.getChild(TAG_CertSHA256Fingerprint));
835 
836                 EAPMethod eapMethod = new EAPMethod(EAP.EAPMethodID.EAP_TLS, null);
837 
838                 return new Credential(ctime, expTime, realm, checkAAACert, eapMethod,
839                         Credential.mapCertType(certTypeString), fingerPrint);
840             } catch (NumberFormatException nfe) {
841                 throw new OMAException("Bad hex string: " + nfe.toString());
842             }
843         }
844         if (simNode != null) {
845             try {
846                 IMSIParameter imsi = new IMSIParameter(getString(simNode.getChild(TAG_IMSI)));
847 
848                 EAPMethod eapMethod =
849                         new EAPMethod(EAP.mapEAPMethod(getInteger(simNode.getChild(TAG_EAPType))),
850                                 null);
851 
852                 return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, imsi);
853             } catch (IOException ioe) {
854                 throw new OMAException("Failed to parse IMSI: " + ioe);
855             }
856         }
857         throw new OMAException("Missing credential parameters");
858     }
859 
getChild(OMANode node, String key)860     public static OMANode getChild(OMANode node, String key) throws OMAException {
861         OMANode child = node.getChild(key);
862         if (child == null) {
863             throw new OMAException("No such node: " + key);
864         }
865         return child;
866     }
867 
getString(OMANode node, String key)868     public static String getString(OMANode node, String key) throws OMAException {
869         OMANode child = node.getChild(key);
870         if (child == null) {
871             throw new OMAException("Missing value for " + key);
872         } else if (!child.isLeaf()) {
873             throw new OMAException(key + " is not a leaf node");
874         }
875         return child.getValue();
876     }
877 
getLong(OMANode node, String key, Long dflt)878     public static long getLong(OMANode node, String key, Long dflt) throws OMAException {
879         OMANode child = node.getChild(key);
880         if (child == null) {
881             if (dflt != null) {
882                 return dflt;
883             } else {
884                 throw new OMAException("Missing value for " + key);
885             }
886         } else {
887             if (!child.isLeaf()) {
888                 throw new OMAException(key + " is not a leaf node");
889             }
890             String value = child.getValue();
891             try {
892                 long result = Long.parseLong(value);
893                 if (result < 0) {
894                     throw new OMAException("Negative value for " + key);
895                 }
896                 return result;
897             } catch (NumberFormatException nfe) {
898                 throw new OMAException("Value for " + key + " is non-numeric: " + value);
899             }
900         }
901     }
902 
getSelection(OMANode node, String key)903     public static <T> T getSelection(OMANode node, String key) throws OMAException {
904         OMANode child = node.getChild(key);
905         if (child == null) {
906             throw new OMAException("Missing value for " + key);
907         } else if (!child.isLeaf()) {
908             throw new OMAException(key + " is not a leaf node");
909         }
910         return getSelection(key, child.getValue());
911     }
912 
getSelection(String key, String value)913     public static <T> T getSelection(String key, String value) throws OMAException {
914         if (value == null) {
915             throw new OMAException("No value for " + key);
916         }
917         Map<String, Object> kvp = sSelectionMap.get(key);
918         T result = (T) kvp.get(value.toLowerCase());
919         if (result == null) {
920             throw new OMAException("Invalid value '" + value + "' for " + key);
921         }
922         return result;
923     }
924 
getBoolean(OMANode boolNode)925     private static boolean getBoolean(OMANode boolNode) {
926         return boolNode != null && Boolean.parseBoolean(boolNode.getValue());
927     }
928 
getString(OMANode stringNode)929     public static String getString(OMANode stringNode) {
930         return stringNode != null ? stringNode.getValue() : null;
931     }
932 
getInteger(OMANode intNode, int dflt)933     private static int getInteger(OMANode intNode, int dflt) throws OMAException {
934         if (intNode == null) {
935             return dflt;
936         }
937         return getInteger(intNode);
938     }
939 
getInteger(OMANode intNode)940     private static int getInteger(OMANode intNode) throws OMAException {
941         if (intNode == null) {
942             throw new OMAException("Missing integer value");
943         }
944         try {
945             return Integer.parseInt(intNode.getValue());
946         } catch (NumberFormatException nfe) {
947             throw new OMAException("Invalid integer: " + intNode.getValue());
948         }
949     }
950 
getMac(OMANode macNode)951     private static Long getMac(OMANode macNode) throws OMAException {
952         if (macNode == null) {
953             return null;
954         }
955         try {
956             return Long.parseLong(macNode.getValue(), 16);
957         } catch (NumberFormatException nfe) {
958             throw new OMAException("Invalid MAC: " + macNode.getValue());
959         }
960     }
961 
getOptionalInteger(OMANode intNode)962     private static Long getOptionalInteger(OMANode intNode) throws OMAException {
963         if (intNode == null) {
964             return null;
965         }
966         try {
967             return Long.parseLong(intNode.getValue());
968         } catch (NumberFormatException nfe) {
969             throw new OMAException("Invalid integer: " + intNode.getValue());
970         }
971     }
972 
getTime(OMANode timeNode)973     public static long getTime(OMANode timeNode) throws OMAException {
974         if (timeNode == null) {
975             return Utils.UNSET_TIME;
976         }
977         String timeText = timeNode.getValue();
978         try {
979             Date date = DTFormat.parse(timeText);
980             return date.getTime();
981         } catch (ParseException pe) {
982             throw new OMAException("Badly formatted time: " + timeText);
983         }
984     }
985 
getOctets(OMANode octetNode)986     private static byte[] getOctets(OMANode octetNode) throws OMAException {
987         if (octetNode == null) {
988             throw new OMAException("Missing byte value");
989         }
990         return Utils.hexToBytes(octetNode.getValue());
991     }
992 }
993