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 135 setSelections(TAG_FQDN_Match, 136 "exactmatch", Boolean.FALSE, 137 "includesubdomains", Boolean.TRUE); 138 setSelections(TAG_UpdateMethod, 139 "oma-dm-clientinitiated", Boolean.FALSE, 140 "spp-clientinitiated", Boolean.TRUE); 141 setSelections(TAG_Restriction, 142 "homesp", UpdateInfo.UpdateRestriction.HomeSP, 143 "roamingpartner", UpdateInfo.UpdateRestriction.RoamingPartner, 144 "unrestricted", UpdateInfo.UpdateRestriction.Unrestricted); 145 } 146 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 159 public MOManager(File ppsFile, boolean hs2enabled) { 160 mPpsFile = ppsFile; 161 mEnabled = hs2enabled; 162 mSPs = new HashMap<>(); 163 } 164 165 public File getPpsFile() { 166 return mPpsFile; 167 } 168 169 public boolean isEnabled() { 170 return mEnabled; 171 } 172 173 public boolean isConfigured() { 174 return mEnabled && !mSPs.isEmpty(); 175 } 176 177 public Map<String, HomeSP> getLoadedSPs() { 178 return Collections.unmodifiableMap(mSPs); 179 } 180 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 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 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 */ 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 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 */ 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 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 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 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 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 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 488 public HomeSP getHomeSP(String fqdn) { 489 return mSPs.get(fqdn); 490 } 491 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 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 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 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 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 673 private static String getInstanceString(int instance) { 674 return "r1i" + instance; 675 } 676 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 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 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 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 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 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 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 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 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 970 private static boolean getBoolean(OMANode boolNode) { 971 return boolNode != null && Boolean.parseBoolean(boolNode.getValue()); 972 } 973 974 public static String getString(OMANode stringNode) { 975 return stringNode != null ? stringNode.getValue() : null; 976 } 977 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 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 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 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 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 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