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