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