• 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));
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)234     public void addSP(HomeSP homeSP) 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);
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);
255         }
256         mSPs.put(homeSP.getFQDN(), homeSP);
257     }
258 
addSP(MOTree instanceTree)259     public HomeSP addSP(MOTree instanceTree) 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);
276         } catch (FileNotFoundException fnfe) {
277             MOTree tree = new MOTree(instanceTree.getUrn(), instanceTree.getDtdRev(),
278                     instanceTree.getRoot());
279             writeMO(tree, mPpsFile);
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)292     private void addSP(OMANode mo) 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);
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, MOTree moTree, Collection<MOData> mods)350     public static HomeSP modifySP(HomeSP homeSP, MOTree moTree, Collection<MOData> mods)
351             throws OMAException {
352 
353         OMAConstructed ppsTree =
354                 (OMAConstructed) moTree.getRoot().getChildren().iterator().next();
355         OMAConstructed instance = getInstanceNode(ppsTree);
356 
357         int ppsMods = 0;
358         int updateIdentifier = homeSP.getUpdateIdentifier();
359         for (MOData mod : mods) {
360             LinkedList<String> tailPath =
361                     getTailPath(mod.getBaseURI(), TAG_PerProviderSubscription);
362             OMAConstructed modRoot = mod.getMOTree().getRoot();
363             // modRoot is the MgmtTree with the actual object as a direct child
364             // (e.g. Credential)
365 
366             if (tailPath.getFirst().equals(TAG_UpdateIdentifier)) {
367                 updateIdentifier = getInteger(modRoot.getChildren().iterator().next());
368                 OMANode oldUdi = ppsTree.getChild(TAG_UpdateIdentifier);
369                 if (getInteger(oldUdi) != updateIdentifier) {
370                     ppsMods++;
371                 }
372                 if (oldUdi != null) {
373                     ppsTree.replaceNode(oldUdi, modRoot.getChild(TAG_UpdateIdentifier));
374                 } else {
375                     ppsTree.addChild(modRoot.getChild(TAG_UpdateIdentifier));
376                 }
377             } else {
378                 tailPath.removeFirst();     // Drop the instance
379                 OMANode current = instance.getListValue(tailPath.iterator());
380                 if (current == null) {
381                     throw new OMAException("No previous node for " + tailPath + " in "
382                             + homeSP.getFQDN());
383                 }
384                 for (OMANode newNode : modRoot.getChildren()) {
385                     // newNode is something like Credential
386                     // current is the same existing node
387                     OMANode old = current.getParent().replaceNode(current, newNode);
388                     ppsMods++;
389                 }
390             }
391         }
392 
393         return ppsMods > 0 ? buildHomeSP(instance, updateIdentifier) : null;
394     }
395 
modifySP(HomeSP homeSP, Collection<MOData> mods)396     public HomeSP modifySP(HomeSP homeSP, Collection<MOData> mods)
397             throws IOException {
398 
399         Log.d(OSUManager.TAG, "modifying SP: " + mods);
400         MOTree moTree;
401         int ppsMods = 0;
402         int updateIdentifier = 0;
403         try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
404             moTree = MOTree.unmarshal(in);
405             // moTree is PPS/?/provider-data
406 
407             OMAConstructed targetTree = findTargetTree(moTree, homeSP.getFQDN());
408             if (targetTree == null) {
409                 throw new IOException("Failed to find PPS tree for " + homeSP.getFQDN());
410             }
411             OMAConstructed instance = getInstanceNode(targetTree);
412 
413             for (MOData mod : mods) {
414                 LinkedList<String> tailPath =
415                         getTailPath(mod.getBaseURI(), TAG_PerProviderSubscription);
416                 OMAConstructed modRoot = mod.getMOTree().getRoot();
417                 // modRoot is the MgmtTree with the actual object as a direct child
418                 // (e.g. Credential)
419 
420                 if (tailPath.getFirst().equals(TAG_UpdateIdentifier)) {
421                     updateIdentifier = getInteger(modRoot.getChildren().iterator().next());
422                     OMANode oldUdi = targetTree.getChild(TAG_UpdateIdentifier);
423                     if (getInteger(oldUdi) != updateIdentifier) {
424                         ppsMods++;
425                     }
426                     if (oldUdi != null) {
427                         targetTree.replaceNode(oldUdi, modRoot.getChild(TAG_UpdateIdentifier));
428                     } else {
429                         targetTree.addChild(modRoot.getChild(TAG_UpdateIdentifier));
430                     }
431                 } else {
432                     tailPath.removeFirst();     // Drop the instance
433                     OMANode current = instance.getListValue(tailPath.iterator());
434                     if (current == null) {
435                         throw new IOException("No previous node for " + tailPath + " in " +
436                                 homeSP.getFQDN());
437                     }
438                     for (OMANode newNode : modRoot.getChildren()) {
439                         // newNode is something like Credential
440                         // current is the same existing node
441                         OMANode old = current.getParent().replaceNode(current, newNode);
442                         ppsMods++;
443                     }
444                 }
445             }
446         }
447         writeMO(moTree, mPpsFile);
448 
449         if (ppsMods == 0) {
450             return null;    // HomeSP not modified.
451         }
452 
453         // Return a new rebuilt HomeSP
454         List<HomeSP> sps = buildSPs(moTree);
455         if (sps != null) {
456             for (HomeSP sp : sps) {
457                 if (sp.getFQDN().equals(homeSP.getFQDN())) {
458                     return sp;
459                 }
460             }
461         } else {
462             throw new OMAException("Failed to build HomeSP");
463         }
464         return null;
465     }
466 
getTailPath(String pathString, String rootName)467     private static LinkedList<String> getTailPath(String pathString, String rootName)
468             throws OMAException {
469         String[] path = pathString.split("/");
470         int pathIndex;
471         for (pathIndex = 0; pathIndex < path.length; pathIndex++) {
472             if (path[pathIndex].equalsIgnoreCase(rootName)) {
473                 pathIndex++;
474                 break;
475             }
476         }
477         if (pathIndex >= path.length) {
478             throw new OMAException("Bad node-path: " + pathString);
479         }
480         LinkedList<String> tailPath = new LinkedList<>();
481         while (pathIndex < path.length) {
482             tailPath.add(path[pathIndex]);
483             pathIndex++;
484         }
485         return tailPath;
486     }
487 
getHomeSP(String fqdn)488     public HomeSP getHomeSP(String fqdn) {
489         return mSPs.get(fqdn);
490     }
491 
removeSP(String fqdn)492     public void removeSP(String fqdn) throws IOException {
493         if (mSPs.remove(fqdn) == null) {
494             Log.d(OSUManager.TAG, "No HS20 profile to delete for " + fqdn);
495             return;
496         }
497 
498         Log.d(OSUManager.TAG, "Deleting HS20 profile for " + fqdn);
499 
500         MOTree moTree;
501         try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
502             moTree = MOTree.unmarshal(in);
503             OMAConstructed tbd = findTargetTree(moTree, fqdn);
504             if (tbd == null) {
505                 throw new IOException("Node " + fqdn + " doesn't exist in MO tree");
506             }
507             OMAConstructed pps = moTree.getRoot();
508             OMANode removed = pps.removeNode("?", tbd);
509             if (removed == null) {
510                 throw new IOException("Failed to remove " + fqdn + " out of MO tree");
511             }
512         }
513         writeMO(moTree, mPpsFile);
514     }
515 
getMOTree(HomeSP homeSP)516     public MOTree getMOTree(HomeSP homeSP) throws IOException {
517         try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
518             MOTree moTree = MOTree.unmarshal(in);
519             OMAConstructed target = findTargetTree(moTree, homeSP.getFQDN());
520             if (target == null) {
521                 throw new IOException("Can't find " + homeSP.getFQDN() + " in MO tree");
522             }
523             return MOTree.buildMgmtTree(OMAConstants.PPS_URN, OMAConstants.OMAVersion, target);
524         }
525     }
526 
writeMO(MOTree moTree, File f)527     private static void writeMO(MOTree moTree, File f) throws IOException {
528         try (BufferedOutputStream out =
529                      new BufferedOutputStream(new FileOutputStream(f, false))) {
530             moTree.marshal(out);
531             out.flush();
532         }
533     }
534 
fqdnList(Collection<HomeSP> sps)535     private static String fqdnList(Collection<HomeSP> sps) {
536         StringBuilder sb = new StringBuilder();
537         boolean first = true;
538         for (HomeSP sp : sps) {
539             if (first) {
540                 first = false;
541             } else {
542                 sb.append(", ");
543             }
544             sb.append(sp.getFQDN());
545         }
546         return sb.toString();
547     }
548 
buildHomeSPTree(HomeSP homeSP, OMAConstructed root, int instanceID)549     private static OMANode buildHomeSPTree(HomeSP homeSP, OMAConstructed root, int instanceID)
550             throws IOException {
551         OMANode providerSubNode = root.addChild(getInstanceString(instanceID),
552                 null, null, null);
553 
554         // The HomeSP:
555         OMANode homeSpNode = providerSubNode.addChild(TAG_HomeSP, null, null, null);
556         if (!homeSP.getSSIDs().isEmpty()) {
557             OMAConstructed nwkIDNode =
558                     (OMAConstructed) homeSpNode.addChild(TAG_NetworkID, null, null, null);
559             int instance = 0;
560             for (Map.Entry<String, Long> entry : homeSP.getSSIDs().entrySet()) {
561                 OMAConstructed inode =
562                         (OMAConstructed) nwkIDNode
563                                 .addChild(getInstanceString(instance++), null, null, null);
564                 inode.addChild(TAG_SSID, null, entry.getKey(), null);
565                 if (entry.getValue() != null) {
566                     inode.addChild(TAG_HESSID, null,
567                             String.format("%012x", entry.getValue()), null);
568                 }
569             }
570         }
571 
572         homeSpNode.addChild(TAG_FriendlyName, null, homeSP.getFriendlyName(), null);
573 
574         if (homeSP.getIconURL() != null) {
575             homeSpNode.addChild(TAG_IconURL, null, homeSP.getIconURL(), null);
576         }
577 
578         homeSpNode.addChild(TAG_FQDN, null, homeSP.getFQDN(), null);
579 
580         if (!homeSP.getMatchAllOIs().isEmpty() || !homeSP.getMatchAnyOIs().isEmpty()) {
581             OMAConstructed homeOIList =
582                     (OMAConstructed) homeSpNode.addChild(TAG_HomeOIList, null, null, null);
583 
584             int instance = 0;
585             for (Long oi : homeSP.getMatchAllOIs()) {
586                 OMAConstructed inode =
587                         (OMAConstructed) homeOIList.addChild(getInstanceString(instance++),
588                                 null, null, null);
589                 inode.addChild(TAG_HomeOI, null, String.format("%x", oi), null);
590                 inode.addChild(TAG_HomeOIRequired, null, "TRUE", null);
591             }
592             for (Long oi : homeSP.getMatchAnyOIs()) {
593                 OMAConstructed inode =
594                         (OMAConstructed) homeOIList.addChild(getInstanceString(instance++),
595                                 null, null, null);
596                 inode.addChild(TAG_HomeOI, null, String.format("%x", oi), null);
597                 inode.addChild(TAG_HomeOIRequired, null, "FALSE", null);
598             }
599         }
600 
601         if (!homeSP.getOtherHomePartners().isEmpty()) {
602             OMAConstructed otherPartners =
603                     (OMAConstructed) homeSpNode.addChild(TAG_OtherHomePartners, null, null, null);
604             int instance = 0;
605             for (String fqdn : homeSP.getOtherHomePartners()) {
606                 OMAConstructed inode =
607                         (OMAConstructed) otherPartners.addChild(getInstanceString(instance++),
608                                 null, null, null);
609                 inode.addChild(TAG_FQDN, null, fqdn, null);
610             }
611         }
612 
613         if (!homeSP.getRoamingConsortiums().isEmpty()) {
614             homeSpNode.addChild(TAG_RoamingConsortiumOI, null,
615                     getRCList(homeSP.getRoamingConsortiums()), null);
616         }
617 
618         // The Credential:
619         OMANode credentialNode = providerSubNode.addChild(TAG_Credential, null, null, null);
620         Credential cred = homeSP.getCredential();
621         EAPMethod method = cred.getEAPMethod();
622 
623         if (cred.getCtime() > 0) {
624             credentialNode.addChild(TAG_CreationDate,
625                     null, DTFormat.format(new Date(cred.getCtime())), null);
626         }
627         if (cred.getExpTime() > 0) {
628             credentialNode.addChild(TAG_ExpirationDate,
629                     null, DTFormat.format(new Date(cred.getExpTime())), null);
630         }
631 
632         if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_SIM
633                 || method.getEAPMethodID() == EAP.EAPMethodID.EAP_AKA
634                 || method.getEAPMethodID() == EAP.EAPMethodID.EAP_AKAPrim) {
635 
636             OMANode simNode = credentialNode.addChild(TAG_SIM, null, null, null);
637             simNode.addChild(TAG_IMSI, null, cred.getImsi().toString(), null);
638             simNode.addChild(TAG_EAPType, null,
639                     Integer.toString(EAP.mapEAPMethod(method.getEAPMethodID())), null);
640 
641         } else if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TTLS) {
642 
643             OMANode unpNode = credentialNode.addChild(TAG_UsernamePassword, null, null, null);
644             unpNode.addChild(TAG_Username, null, cred.getUserName(), null);
645             unpNode.addChild(TAG_Password, null,
646                     Base64.encodeToString(cred.getPassword().getBytes(StandardCharsets.UTF_8),
647                             Base64.DEFAULT), null);
648             OMANode eapNode = unpNode.addChild(TAG_EAPMethod, null, null, null);
649             eapNode.addChild(TAG_EAPType, null,
650                     Integer.toString(EAP.mapEAPMethod(method.getEAPMethodID())), null);
651             eapNode.addChild(TAG_InnerMethod, null,
652                     ((NonEAPInnerAuth) method.getAuthParam()).getOMAtype(), null);
653 
654         } else if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TLS) {
655 
656             OMANode certNode = credentialNode.addChild(TAG_DigitalCertificate, null, null, null);
657             certNode.addChild(TAG_CertificateType, null, Credential.CertTypeX509, null);
658             certNode.addChild(TAG_CertSHA256Fingerprint, null,
659                     Utils.toHex(cred.getFingerPrint()), null);
660 
661         } else {
662             throw new OMAException("Invalid credential on " + homeSP.getFQDN());
663         }
664 
665         credentialNode.addChild(TAG_Realm, null, cred.getRealm(), null);
666 
667         // !!! Note: This node defines CRL checking through OSCP, I suspect we won't be able
668         // to do that so it is commented out:
669         //credentialNode.addChild(TAG_CheckAAAServerCertStatus, null, "TRUE", null);
670         return providerSubNode;
671     }
672 
getInstanceString(int instance)673     private static String getInstanceString(int instance) {
674         return "r1i" + instance;
675     }
676 
getRCList(Collection<Long> rcs)677     private static String getRCList(Collection<Long> rcs) {
678         StringBuilder builder = new StringBuilder();
679         boolean first = true;
680         for (Long roamingConsortium : rcs) {
681             if (first) {
682                 first = false;
683             } else {
684                 builder.append(',');
685             }
686             builder.append(String.format("%x", roamingConsortium));
687         }
688         return builder.toString();
689     }
690 
buildSPs(MOTree moTree)691     public static List<HomeSP> buildSPs(MOTree moTree) throws OMAException {
692         OMAConstructed spList;
693         List<HomeSP> homeSPs = new ArrayList<>();
694         if (moTree.getRoot().getName().equals(TAG_PerProviderSubscription)) {
695             // The old PPS file was rooted at PPS instead of MgmtTree to conserve space
696             spList = moTree.getRoot();
697 
698             if (spList == null) {
699                 return homeSPs;
700             }
701 
702             for (OMANode node : spList.getChildren()) {
703                 if (!node.isLeaf()) {
704                     homeSPs.add(buildHomeSP(node, 0));
705                 }
706             }
707         } else {
708             for (OMANode ppsRoot : moTree.getRoot().getChildren()) {
709                 if (ppsRoot.getName().equals(TAG_PerProviderSubscription)) {
710                     Integer updateIdentifier = null;
711                     OMANode instance = null;
712                     for (OMANode child : ppsRoot.getChildren()) {
713                         if (child.getName().equals(TAG_UpdateIdentifier)) {
714                             updateIdentifier = getInteger(child);
715                         } else if (!child.isLeaf()) {
716                             instance = child;
717                         }
718                     }
719                     if (instance == null) {
720                         throw new OMAException("PPS node missing instance node");
721                     }
722                     homeSPs.add(buildHomeSP(instance,
723                             updateIdentifier != null ? updateIdentifier : 0));
724                 }
725             }
726         }
727 
728         return homeSPs;
729     }
730 
buildHomeSP(OMANode ppsRoot, int updateIdentifier)731     private static HomeSP buildHomeSP(OMANode ppsRoot, int updateIdentifier) throws OMAException {
732         OMANode spRoot = ppsRoot.getChild(TAG_HomeSP);
733 
734         String fqdn = spRoot.getScalarValue(Arrays.asList(TAG_FQDN).iterator());
735         String friendlyName = spRoot.getScalarValue(Arrays.asList(TAG_FriendlyName).iterator());
736         String iconURL = spRoot.getScalarValue(Arrays.asList(TAG_IconURL).iterator());
737 
738         HashSet<Long> roamingConsortiums = new HashSet<>();
739         String oiString = spRoot.getScalarValue(Arrays.asList(TAG_RoamingConsortiumOI).iterator());
740         if (oiString != null) {
741             for (String oi : oiString.split(",")) {
742                 roamingConsortiums.add(Long.parseLong(oi.trim(), 16));
743             }
744         }
745 
746         Map<String, Long> ssids = new HashMap<>();
747 
748         OMANode ssidListNode = spRoot.getListValue(Arrays.asList(TAG_NetworkID).iterator());
749         if (ssidListNode != null) {
750             for (OMANode ssidRoot : ssidListNode.getChildren()) {
751                 OMANode hessidNode = ssidRoot.getChild(TAG_HESSID);
752                 ssids.put(ssidRoot.getChild(TAG_SSID).getValue(), getMac(hessidNode));
753             }
754         }
755 
756         Set<Long> matchAnyOIs = new HashSet<>();
757         List<Long> matchAllOIs = new ArrayList<>();
758         OMANode homeOIListNode = spRoot.getListValue(Arrays.asList(TAG_HomeOIList).iterator());
759         if (homeOIListNode != null) {
760             for (OMANode homeOIRoot : homeOIListNode.getChildren()) {
761                 String homeOI = homeOIRoot.getChild(TAG_HomeOI).getValue();
762                 if (Boolean.parseBoolean(homeOIRoot.getChild(TAG_HomeOIRequired).getValue())) {
763                     matchAllOIs.add(Long.parseLong(homeOI, 16));
764                 } else {
765                     matchAnyOIs.add(Long.parseLong(homeOI, 16));
766                 }
767             }
768         }
769 
770         Set<String> otherHomePartners = new HashSet<>();
771         OMANode otherListNode =
772                 spRoot.getListValue(Arrays.asList(TAG_OtherHomePartners).iterator());
773         if (otherListNode != null) {
774             for (OMANode fqdnNode : otherListNode.getChildren()) {
775                 otherHomePartners.add(fqdnNode.getChild(TAG_FQDN).getValue());
776             }
777         }
778 
779         Credential credential = buildCredential(ppsRoot.getChild(TAG_Credential));
780 
781         OMANode policyNode = ppsRoot.getChild(TAG_Policy);
782         Policy policy = policyNode != null ? new Policy(policyNode) : null;
783 
784         Map<String, String> aaaTrustRoots;
785         OMANode aaaRootNode = ppsRoot.getChild(TAG_AAAServerTrustRoot);
786         if (aaaRootNode == null) {
787             aaaTrustRoots = null;
788         } else {
789             aaaTrustRoots = new HashMap<>(aaaRootNode.getChildren().size());
790             for (OMANode child : aaaRootNode.getChildren()) {
791                 aaaTrustRoots.put(getString(child, TAG_CertURL),
792                         getString(child, TAG_CertSHA256Fingerprint));
793             }
794         }
795 
796         OMANode updateNode = ppsRoot.getChild(TAG_SubscriptionUpdate);
797         UpdateInfo subscriptionUpdate = updateNode != null ? new UpdateInfo(updateNode) : null;
798         OMANode subNode = ppsRoot.getChild(TAG_SubscriptionParameters);
799         SubscriptionParameters subscriptionParameters = subNode != null ?
800                 new SubscriptionParameters(subNode) : null;
801 
802         return new HomeSP(ssids, fqdn, roamingConsortiums, otherHomePartners,
803                 matchAnyOIs, matchAllOIs, friendlyName, iconURL, credential,
804                 policy, getInteger(ppsRoot.getChild(TAG_CredentialPriority), 0),
805                 aaaTrustRoots, subscriptionUpdate, subscriptionParameters, updateIdentifier);
806     }
807 
buildCredential(OMANode credNode)808     private static Credential buildCredential(OMANode credNode) throws OMAException {
809         long ctime = getTime(credNode.getChild(TAG_CreationDate));
810         long expTime = getTime(credNode.getChild(TAG_ExpirationDate));
811         String realm = getString(credNode.getChild(TAG_Realm));
812         boolean checkAAACert = getBoolean(credNode.getChild(TAG_CheckAAAServerCertStatus));
813 
814         OMANode unNode = credNode.getChild(TAG_UsernamePassword);
815         OMANode certNode = credNode.getChild(TAG_DigitalCertificate);
816         OMANode simNode = credNode.getChild(TAG_SIM);
817 
818         int alternatives = 0;
819         alternatives += unNode != null ? 1 : 0;
820         alternatives += certNode != null ? 1 : 0;
821         alternatives += simNode != null ? 1 : 0;
822         if (alternatives != 1) {
823             throw new OMAException("Expected exactly one credential type, got " + alternatives);
824         }
825 
826         if (unNode != null) {
827             String userName = getString(unNode.getChild(TAG_Username));
828             String password = getString(unNode.getChild(TAG_Password));
829             boolean machineManaged = getBoolean(unNode.getChild(TAG_MachineManaged));
830             String softTokenApp = getString(unNode.getChild(TAG_SoftTokenApp));
831             boolean ableToShare = getBoolean(unNode.getChild(TAG_AbleToShare));
832 
833             OMANode eapMethodNode = unNode.getChild(TAG_EAPMethod);
834             int eapID = getInteger(eapMethodNode.getChild(TAG_EAPType));
835 
836             EAP.EAPMethodID eapMethodID = EAP.mapEAPMethod(eapID);
837             if (eapMethodID == null) {
838                 throw new OMAException("Unknown EAP method: " + eapID);
839             }
840 
841             Long vid = getOptionalInteger(eapMethodNode.getChild(TAG_VendorId));
842             Long vtype = getOptionalInteger(eapMethodNode.getChild(TAG_VendorType));
843             Long innerEAPType = getOptionalInteger(eapMethodNode.getChild(TAG_InnerEAPType));
844             EAP.EAPMethodID innerEAPMethod = null;
845             if (innerEAPType != null) {
846                 innerEAPMethod = EAP.mapEAPMethod(innerEAPType.intValue());
847                 if (innerEAPMethod == null) {
848                     throw new OMAException("Bad inner EAP method: " + innerEAPType);
849                 }
850             }
851 
852             Long innerVid = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorID));
853             Long innerVtype = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorType));
854             String innerNonEAPMethod = getString(eapMethodNode.getChild(TAG_InnerMethod));
855 
856             EAPMethod eapMethod;
857             if (innerEAPMethod != null) {
858                 eapMethod = new EAPMethod(eapMethodID, new InnerAuthEAP(innerEAPMethod));
859             } else if (vid != null) {
860                 eapMethod = new EAPMethod(eapMethodID,
861                         new ExpandedEAPMethod(EAP.AuthInfoID.ExpandedEAPMethod,
862                                 vid.intValue(), vtype));
863             } else if (innerVid != null) {
864                 eapMethod =
865                         new EAPMethod(eapMethodID, new ExpandedEAPMethod(EAP.AuthInfoID
866                                 .ExpandedInnerEAPMethod, innerVid.intValue(), innerVtype));
867             } else if (innerNonEAPMethod != null) {
868                 eapMethod = new EAPMethod(eapMethodID, new NonEAPInnerAuth(innerNonEAPMethod));
869             } else {
870                 throw new OMAException("Incomplete set of EAP parameters");
871             }
872 
873             return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, userName,
874                     password, machineManaged, softTokenApp, ableToShare);
875         }
876         if (certNode != null) {
877             try {
878                 String certTypeString = getString(certNode.getChild(TAG_CertificateType));
879                 byte[] fingerPrint = getOctets(certNode.getChild(TAG_CertSHA256Fingerprint));
880 
881                 EAPMethod eapMethod = new EAPMethod(EAP.EAPMethodID.EAP_TLS, null);
882 
883                 return new Credential(ctime, expTime, realm, checkAAACert, eapMethod,
884                         Credential.mapCertType(certTypeString), fingerPrint);
885             } catch (NumberFormatException nfe) {
886                 throw new OMAException("Bad hex string: " + nfe.toString());
887             }
888         }
889         if (simNode != null) {
890             try {
891                 IMSIParameter imsi = new IMSIParameter(getString(simNode.getChild(TAG_IMSI)));
892 
893                 EAPMethod eapMethod =
894                         new EAPMethod(EAP.mapEAPMethod(getInteger(simNode.getChild(TAG_EAPType))),
895                                 null);
896 
897                 return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, imsi);
898             } catch (IOException ioe) {
899                 throw new OMAException("Failed to parse IMSI: " + ioe);
900             }
901         }
902         throw new OMAException("Missing credential parameters");
903     }
904 
getChild(OMANode node, String key)905     public static OMANode getChild(OMANode node, String key) throws OMAException {
906         OMANode child = node.getChild(key);
907         if (child == null) {
908             throw new OMAException("No such node: " + key);
909         }
910         return child;
911     }
912 
getString(OMANode node, String key)913     public static String getString(OMANode node, String key) throws OMAException {
914         OMANode child = node.getChild(key);
915         if (child == null) {
916             throw new OMAException("Missing value for " + key);
917         } else if (!child.isLeaf()) {
918             throw new OMAException(key + " is not a leaf node");
919         }
920         return child.getValue();
921     }
922 
getLong(OMANode node, String key, Long dflt)923     public static long getLong(OMANode node, String key, Long dflt) throws OMAException {
924         OMANode child = node.getChild(key);
925         if (child == null) {
926             if (dflt != null) {
927                 return dflt;
928             } else {
929                 throw new OMAException("Missing value for " + key);
930             }
931         } else {
932             if (!child.isLeaf()) {
933                 throw new OMAException(key + " is not a leaf node");
934             }
935             String value = child.getValue();
936             try {
937                 long result = Long.parseLong(value);
938                 if (result < 0) {
939                     throw new OMAException("Negative value for " + key);
940                 }
941                 return result;
942             } catch (NumberFormatException nfe) {
943                 throw new OMAException("Value for " + key + " is non-numeric: " + value);
944             }
945         }
946     }
947 
getSelection(OMANode node, String key)948     public static <T> T getSelection(OMANode node, String key) throws OMAException {
949         OMANode child = node.getChild(key);
950         if (child == null) {
951             throw new OMAException("Missing value for " + key);
952         } else if (!child.isLeaf()) {
953             throw new OMAException(key + " is not a leaf node");
954         }
955         return getSelection(key, child.getValue());
956     }
957 
getSelection(String key, String value)958     public static <T> T getSelection(String key, String value) throws OMAException {
959         if (value == null) {
960             throw new OMAException("No value for " + key);
961         }
962         Map<String, Object> kvp = sSelectionMap.get(key);
963         T result = (T) kvp.get(value.toLowerCase());
964         if (result == null) {
965             throw new OMAException("Invalid value '" + value + "' for " + key);
966         }
967         return result;
968     }
969 
getBoolean(OMANode boolNode)970     private static boolean getBoolean(OMANode boolNode) {
971         return boolNode != null && Boolean.parseBoolean(boolNode.getValue());
972     }
973 
getString(OMANode stringNode)974     public static String getString(OMANode stringNode) {
975         return stringNode != null ? stringNode.getValue() : null;
976     }
977 
getInteger(OMANode intNode, int dflt)978     private static int getInteger(OMANode intNode, int dflt) throws OMAException {
979         if (intNode == null) {
980             return dflt;
981         }
982         return getInteger(intNode);
983     }
984 
getInteger(OMANode intNode)985     private static int getInteger(OMANode intNode) throws OMAException {
986         if (intNode == null) {
987             throw new OMAException("Missing integer value");
988         }
989         try {
990             return Integer.parseInt(intNode.getValue());
991         } catch (NumberFormatException nfe) {
992             throw new OMAException("Invalid integer: " + intNode.getValue());
993         }
994     }
995 
getMac(OMANode macNode)996     private static Long getMac(OMANode macNode) throws OMAException {
997         if (macNode == null) {
998             return null;
999         }
1000         try {
1001             return Long.parseLong(macNode.getValue(), 16);
1002         } catch (NumberFormatException nfe) {
1003             throw new OMAException("Invalid MAC: " + macNode.getValue());
1004         }
1005     }
1006 
getOptionalInteger(OMANode intNode)1007     private static Long getOptionalInteger(OMANode intNode) throws OMAException {
1008         if (intNode == null) {
1009             return null;
1010         }
1011         try {
1012             return Long.parseLong(intNode.getValue());
1013         } catch (NumberFormatException nfe) {
1014             throw new OMAException("Invalid integer: " + intNode.getValue());
1015         }
1016     }
1017 
getTime(OMANode timeNode)1018     public static long getTime(OMANode timeNode) throws OMAException {
1019         if (timeNode == null) {
1020             return Utils.UNSET_TIME;
1021         }
1022         String timeText = timeNode.getValue();
1023         try {
1024             Date date = DTFormat.parse(timeText);
1025             return date.getTime();
1026         } catch (ParseException pe) {
1027             throw new OMAException("Badly formatted time: " + timeText);
1028         }
1029     }
1030 
getOctets(OMANode octetNode)1031     private static byte[] getOctets(OMANode octetNode) throws OMAException {
1032         if (octetNode == null) {
1033             throw new OMAException("Missing byte value");
1034         }
1035         return Utils.hexToBytes(octetNode.getValue());
1036     }
1037 }
1038