MOManager.java revision d3fb9cbb12d013dd70e672ace5c41ab18a3679a0
1package com.android.server.wifi.hotspot2.omadm; 2 3import android.util.Log; 4 5import com.android.server.wifi.anqp.eap.EAP; 6import com.android.server.wifi.anqp.eap.EAPMethod; 7import com.android.server.wifi.anqp.eap.ExpandedEAPMethod; 8import com.android.server.wifi.anqp.eap.InnerAuthEAP; 9import com.android.server.wifi.anqp.eap.NonEAPInnerAuth; 10import com.android.server.wifi.hotspot2.Utils; 11import com.android.server.wifi.hotspot2.pps.Credential; 12import com.android.server.wifi.hotspot2.pps.HomeSP; 13 14import org.xml.sax.SAXException; 15 16import java.io.BufferedInputStream; 17import java.io.BufferedOutputStream; 18import java.io.File; 19import java.io.FileInputStream; 20import java.io.FileOutputStream; 21import java.io.IOException; 22import java.io.InputStream; 23import java.text.DateFormat; 24import java.text.ParseException; 25import java.text.SimpleDateFormat; 26import java.util.ArrayList; 27import java.util.Arrays; 28import java.util.Collection; 29import java.util.Date; 30import java.util.HashMap; 31import java.util.HashSet; 32import java.util.List; 33import java.util.Map; 34import java.util.Set; 35import java.util.TimeZone; 36 37/** 38 * Handles provisioning of PerProviderSubscription data. 39 */ 40public class MOManager { 41 private final File mPpsFile; 42 private final Map<String, HomeSP> mSPs; 43 44 public MOManager(File ppsFile) throws IOException { 45 mPpsFile = ppsFile; 46 mSPs = new HashMap<>(); 47 } 48 49 public File getPpsFile() { 50 return mPpsFile; 51 } 52 53 public Map<String, HomeSP> getLoadedSPs() { 54 return mSPs; 55 } 56 57 public List<HomeSP> loadAllSPs() throws IOException { 58 List<MOTree> trees = new ArrayList<MOTree>(); 59 List<HomeSP> sps = new ArrayList<HomeSP>(); 60 61 if (!mPpsFile.exists()) { 62 return sps; 63 } 64 65 BufferedInputStream in = null; 66 try { 67 in = new BufferedInputStream(new FileInputStream(mPpsFile)); 68 while (in.available() > 0) { 69 MOTree tree = MOTree.unmarshal(in); 70 if (tree != null) { 71 Log.d("PARSE-LOG", "adding tree no " + trees.size()); 72 trees.add(tree); 73 } else { 74 break; 75 } 76 } 77 } finally { 78 if (in != null) { 79 try { 80 in.close(); 81 } catch (IOException ioe) { 82 /**/ 83 } 84 } 85 } 86 87 Log.d("PARSE-LOG", "number of trees " + trees.size()); 88 for (MOTree moTree : trees) { 89 Log.d("PARSE-LOG", "pasring a moTree"); 90 List<HomeSP> sp = buildSPs(moTree); 91 if (sp != null) { 92 Log.d("PARSE-LOG", "built " + sp.size() + " HomeSPs"); 93 sps.addAll(sp); 94 } else { 95 Log.d("PARSE-LOG", "failed to build HomeSP"); 96 } 97 } 98 99 Log.d("PARSE-LOG", "collected " + sps.size()); 100 for (HomeSP sp : sps) { 101 Log.d("PARSE-LOG", "adding " + sp.getFQDN()); 102 if (mSPs.put(sp.getFQDN(), sp) != null) { 103 Log.d("PARSE-LOG", "failed to add " + sp.getFQDN()); 104 throw new OMAException("Multiple SPs for FQDN '" + sp.getFQDN() + "'"); 105 } else { 106 Log.d("PARSE-LOG", "added " + sp.getFQDN() + " to list"); 107 } 108 } 109 110 Log.d("PARSE-LOG", "found " + mSPs.size() + " configurations"); 111 return sps; 112 } 113 114 public HomeSP addSP(InputStream xmlIn) throws IOException, SAXException { 115 OMAParser omaParser = new OMAParser(); 116 MOTree tree = omaParser.parse(xmlIn, OMAConstants.LOC_PPS + ":1.0"); 117 List<HomeSP> spList = buildSPs(tree); 118 if (spList.size() != 1) { 119 throw new OMAException("Expected exactly one HomeSP, got " + spList.size()); 120 } 121 HomeSP sp = spList.iterator().next(); 122 String fqdn = sp.getFQDN(); 123 if (mSPs.put(fqdn, sp) != null) { 124 throw new OMAException("SP " + fqdn + " already exists"); 125 } 126 127 BufferedOutputStream out = null; 128 try { 129 out = new BufferedOutputStream(new FileOutputStream(mPpsFile, true)); 130 tree.marshal(out); 131 out.flush(); 132 } finally { 133 if (out != null) { 134 try { 135 out.close(); 136 } catch (IOException ioe) { 137 /**/ 138 } 139 } 140 } 141 142 return sp; 143 } 144 145 public void saveAllSps(Collection<HomeSP> homeSPs) throws IOException { 146 147 OMAConstructed root = new OMAConstructed(null, "MgmtTree", ""); 148 149 for (HomeSP homeSP : homeSPs) { 150 OMANode providerNode = root.addChild(TAG_PerProviderSubscription, null, null, null); 151 OMANode providerSubNode = providerNode.addChild("Node", null, null, null); 152 153 Log.d("PARSE-LOG", "creating node homeSP for " + homeSP.getFQDN()); 154 155 if (mSPs.put(homeSP.getFQDN(), homeSP) != null) { 156 throw new OMAException("SP " + homeSP.getFQDN() + " already exists"); 157 } 158 OMANode homeSpNode = providerSubNode.addChild(TAG_HomeSP, null, null, null); 159 homeSpNode.addChild(TAG_FQDN, null, homeSP.getFQDN(), null); 160 homeSpNode.addChild(TAG_FriendlyName, null, homeSP.getFriendlyName(), null); 161 162 OMANode credentialNode = providerSubNode.addChild(TAG_Credential, null, null, null); 163 Credential cred = homeSP.getCredential(); 164 EAPMethod method = cred.getEAPMethod(); 165 166 if (method == null) { 167 throw new OMAException("SP " + homeSP.getFQDN() + " already exists"); 168 } 169 170 OMANode credRootNode; 171 if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_SIM 172 || method.getEAPMethodID() == EAP.EAPMethodID.EAP_AKA 173 || method.getEAPMethodID() == EAP.EAPMethodID.EAP_AKAPrim) { 174 175 Log.d("PARSE-LOG", "Saving SIM credential"); 176 credRootNode = credentialNode.addChild(TAG_SIM, null, null, null); 177 credRootNode.addChild(TAG_IMSI, null, cred.getImsi(), null); 178 179 } else if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TTLS) { 180 181 Log.d("PARSE-LOG", "Saving TTLS Credential"); 182 credRootNode = credentialNode.addChild(TAG_UsernamePassword, null, null, null); 183 credRootNode.addChild(TAG_Username, null, cred.getUserName(), null); 184 185 } else if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TLS) { 186 187 Log.d("PARSE-LOG", "Saving TLS Credential"); 188 credRootNode = credentialNode.addChild(TAG_DigitalCertificate, null, null, null); 189 190 } else { 191 throw new OMAException("Invalid credential on " + homeSP.getFQDN()); 192 } 193 194 credentialNode.addChild(TAG_Realm, null, homeSP.getCredential().getRealm(), null); 195 credentialNode.addChild(TAG_CheckAAAServerCertStatus, null, "true", null); 196 OMANode eapMethodNode = credRootNode.addChild(TAG_EAPMethod, null, null, null); 197 OMANode eapTypeNode = eapMethodNode.addChild(TAG_EAPType, 198 null, EAP.mapEAPMethod(method.getEAPMethodID()).toString(), null); 199 200 if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TTLS) { 201 OMANode innerEAPType = eapMethodNode.addChild(TAG_InnerEAPType, 202 null, EAP.mapEAPMethod(EAP.EAPMethodID.EAP_MSCHAPv2).toString(), null); 203 } 204 205 StringBuilder builder = new StringBuilder(); 206 for (Long roamingConsortium : homeSP.getRoamingConsortiums()) { 207 builder.append(roamingConsortium.toString()); 208 } 209 credentialNode.addChild(TAG_RoamingConsortiumOI, null, builder.toString(), null); 210 } 211 212 Log.d("PARSE-LOG", "Saving all SPs"); 213 214 MOTree tree = new MOTree(OMAConstants.LOC_PPS + ":1.0", "1.2", root); 215 BufferedOutputStream out = null; 216 try { 217 out = new BufferedOutputStream(new FileOutputStream(mPpsFile, true)); 218 tree.marshal(out); 219 out.flush(); 220 } finally { 221 if (out != null) { 222 try { 223 out.close(); 224 } catch (IOException ioe) { 225 /**/ 226 } 227 } 228 } 229 } 230 231 private static final DateFormat DTFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); 232 233 static { 234 DTFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 235 } 236 237 public static final String TAG_AAAServerTrustRoot = "AAAServerTrustRoot"; 238 public static final String TAG_AbleToShare = "AbleToShare"; 239 public static final String TAG_CertificateType = "CertificateType"; 240 public static final String TAG_CertSHA256Fingerprint = "CertSHA256Fingerprint"; 241 public static final String TAG_CertURL = "CertURL"; 242 public static final String TAG_CheckAAAServerCertStatus = "CheckAAAServerCertStatus"; 243 public static final String TAG_Country = "Country"; 244 public static final String TAG_CreationDate = "CreationDate"; 245 public static final String TAG_Credential = "Credential"; 246 public static final String TAG_CredentialPriority = "CredentialPriority"; 247 public static final String TAG_DataLimit = "DataLimit"; 248 public static final String TAG_DigitalCertificate = "DigitalCertificate"; 249 public static final String TAG_DLBandwidth = "DLBandwidth"; 250 public static final String TAG_EAPMethod = "EAPMethod"; 251 public static final String TAG_EAPType = "EAPType"; 252 public static final String TAG_ExpirationDate = "ExpirationDate"; 253 public static final String TAG_Extension = "Extension"; 254 public static final String TAG_FQDN = "FQDN"; 255 public static final String TAG_FQDN_Match = "FQDN_Match"; 256 public static final String TAG_FriendlyName = "FriendlyName"; 257 public static final String TAG_HESSID = "HESSID"; 258 public static final String TAG_HomeOI = "HomeOI"; 259 public static final String TAG_HomeOIList = "HomeOIList"; 260 public static final String TAG_HomeOIRequired = "HomeOIRequired"; 261 public static final String TAG_HomeSP = "HomeSP"; 262 public static final String TAG_IconURL = "IconURL"; 263 public static final String TAG_IMSI = "IMSI"; 264 public static final String TAG_InnerEAPType = "InnerEAPType"; 265 public static final String TAG_InnerMethod = "InnerMethod"; 266 public static final String TAG_InnerVendorID = "InnerVendorID"; 267 public static final String TAG_InnerVendorType = "InnerVendorType"; 268 public static final String TAG_IPProtocol = "IPProtocol"; 269 public static final String TAG_MachineManaged = "MachineManaged"; 270 public static final String TAG_MaximumBSSLoadValue = "MaximumBSSLoadValue"; 271 public static final String TAG_MinBackhaulThreshold = "MinBackhaulThreshold"; 272 public static final String TAG_NetworkID = "NetworkID"; 273 public static final String TAG_NetworkType = "NetworkType"; 274 public static final String TAG_Other = "Other"; 275 public static final String TAG_OtherHomePartners = "OtherHomePartners"; 276 public static final String TAG_Password = "Password"; 277 public static final String TAG_PerProviderSubscription = "PerProviderSubscription"; 278 public static final String TAG_Policy = "Policy"; 279 public static final String TAG_PolicyUpdate = "PolicyUpdate"; 280 public static final String TAG_PortNumber = "PortNumber"; 281 public static final String TAG_PreferredRoamingPartnerList = "PreferredRoamingPartnerList"; 282 public static final String TAG_Priority = "Priority"; 283 public static final String TAG_Realm = "Realm"; 284 public static final String TAG_RequiredProtoPortTuple = "RequiredProtoPortTuple"; 285 public static final String TAG_Restriction = "Restriction"; 286 public static final String TAG_RoamingConsortiumOI = "RoamingConsortiumOI"; 287 public static final String TAG_SIM = "SIM"; 288 public static final String TAG_SoftTokenApp = "SoftTokenApp"; 289 public static final String TAG_SPExclusionList = "SPExclusionList"; 290 public static final String TAG_SSID = "SSID"; 291 public static final String TAG_StartDate = "StartDate"; 292 public static final String TAG_SubscriptionParameters = "SubscriptionParameters"; 293 public static final String TAG_SubscriptionUpdate = "SubscriptionUpdate"; 294 public static final String TAG_TimeLimit = "TimeLimit"; 295 public static final String TAG_TrustRoot = "TrustRoot"; 296 public static final String TAG_TypeOfSubscription = "TypeOfSubscription"; 297 public static final String TAG_ULBandwidth = "ULBandwidth"; 298 public static final String TAG_UpdateIdentifier = "UpdateIdentifier"; 299 public static final String TAG_UpdateInterval = "UpdateInterval"; 300 public static final String TAG_UpdateMethod = "UpdateMethod"; 301 public static final String TAG_URI = "URI"; 302 public static final String TAG_UsageLimits = "UsageLimits"; 303 public static final String TAG_UsageTimePeriod = "UsageTimePeriod"; 304 public static final String TAG_Username = "Username"; 305 public static final String TAG_UsernamePassword = "UsernamePassword"; 306 public static final String TAG_VendorId = "VendorId"; 307 public static final String TAG_VendorType = "VendorType"; 308 309 private static List<HomeSP> buildSPs(MOTree moTree) throws OMAException { 310 List<String> spPath = Arrays.asList(TAG_PerProviderSubscription); 311 OMAConstructed spList = moTree.getRoot().getListValue(spPath.iterator()); 312 313 List<HomeSP> homeSPs = new ArrayList<HomeSP>(); 314 315 Log.d("PARSE-LOG", " node-name = " + spList.getName()); 316 if (spList == null) { 317 return homeSPs; 318 } 319 320 Log.d("PARSE-LOG", " num_children = " + spList.getChildren().size()); 321 for (OMANode spRoot : spList.getChildren()) { 322 Log.d("PARSE-LOG", " node-name = " + spRoot.getName()); 323 homeSPs.add(buildHomeSP(spRoot)); 324 } 325 326 return homeSPs; 327 } 328 329 private static HomeSP buildHomeSP(OMANode ppsRoot) throws OMAException { 330 Log.d("PARSE-LOG", " node-name = " + ppsRoot.getName()); 331 OMANode spRoot = ppsRoot.getChild(TAG_HomeSP); 332 333 String fqdn = spRoot.getScalarValue(Arrays.asList(TAG_FQDN).iterator()); 334 String friendlyName = spRoot.getScalarValue(Arrays.asList(TAG_FriendlyName).iterator()); 335 System.out.println("FQDN: " + fqdn + ", friendly: " + friendlyName); 336 String iconURL = spRoot.getScalarValue(Arrays.asList(TAG_IconURL).iterator()); 337 338 Set<Long> roamingConsortiums = new HashSet<Long>(); 339 String oiString = spRoot.getScalarValue(Arrays.asList(TAG_RoamingConsortiumOI).iterator()); 340 if (oiString != null) { 341 for (String oi : oiString.split(",")) { 342 roamingConsortiums.add(Long.parseLong(oi.trim(), 16)); 343 } 344 } 345 346 Map<String, Long> ssids = new HashMap<String, Long>(); 347 348 OMANode ssidListNode = spRoot.getListValue(Arrays.asList(TAG_NetworkID).iterator()); 349 if (ssidListNode != null) { 350 for (OMANode ssidRoot : ssidListNode.getChildren()) { 351 OMANode hessidNode = ssidRoot.getChild(TAG_HESSID); 352 ssids.put(ssidRoot.getChild(TAG_SSID).getValue(), getMac(hessidNode)); 353 } 354 } 355 356 Set<Long> matchAnyOIs = new HashSet<Long>(); 357 List<Long> matchAllOIs = new ArrayList<Long>(); 358 OMANode homeOIListNode = spRoot.getListValue(Arrays.asList(TAG_HomeOIList).iterator()); 359 if (homeOIListNode != null) { 360 for (OMANode homeOIRoot : homeOIListNode.getChildren()) { 361 String homeOI = homeOIRoot.getChild(TAG_HomeOI).getValue(); 362 if (Boolean.parseBoolean(homeOIRoot.getChild(TAG_HomeOIRequired).getValue())) { 363 matchAllOIs.add(Long.parseLong(homeOI, 16)); 364 } else { 365 matchAnyOIs.add(Long.parseLong(homeOI, 16)); 366 } 367 } 368 } 369 370 Set<String> otherHomePartners = new HashSet<String>(); 371 OMANode otherListNode = 372 spRoot.getListValue(Arrays.asList(TAG_OtherHomePartners).iterator()); 373 if (otherListNode != null) { 374 for (OMANode fqdnNode : otherListNode.getChildren()) { 375 otherHomePartners.add(fqdnNode.getChild(TAG_FQDN).getValue()); 376 } 377 } 378 379 Credential credential = buildCredential(ppsRoot.getChild(TAG_Credential)); 380 381 Log.d("PARSE-LOG", " Building a new HomeSP for " + fqdn); 382 return new HomeSP(ssids, fqdn, roamingConsortiums, otherHomePartners, 383 matchAnyOIs, matchAllOIs, friendlyName, iconURL, credential); 384 } 385 386 private static Credential buildCredential(OMANode credNode) throws OMAException { 387 Log.d("PARSE-LOG", " Reading credential from " + credNode.getName()); 388 long ctime = getTime(credNode.getChild(TAG_CreationDate)); 389 long expTime = getTime(credNode.getChild(TAG_ExpirationDate)); 390 String realm = getString(credNode.getChild(TAG_Realm)); 391 boolean checkAAACert = getBoolean(credNode.getChild(TAG_CheckAAAServerCertStatus)); 392 393 OMANode unNode = credNode.getChild(TAG_UsernamePassword); 394 OMANode certNode = credNode.getChild(TAG_DigitalCertificate); 395 OMANode simNode = credNode.getChild(TAG_SIM); 396 397 int alternatives = 0; 398 alternatives += unNode != null ? 1 : 0; 399 alternatives += certNode != null ? 1 : 0; 400 alternatives += simNode != null ? 1 : 0; 401 if (alternatives != 1) { 402 throw new OMAException("Expected exactly one credential type, got " + alternatives); 403 } 404 405 if (unNode != null) { 406 String userName = getString(unNode.getChild(TAG_Username)); 407 String password = getString(unNode.getChild(TAG_Password)); 408 boolean machineManaged = getBoolean(unNode.getChild(TAG_MachineManaged)); 409 String softTokenApp = getString(unNode.getChild(TAG_SoftTokenApp)); 410 boolean ableToShare = getBoolean(unNode.getChild(TAG_AbleToShare)); 411 412 OMANode eapMethodNode = unNode.getChild(TAG_EAPMethod); 413 EAP.EAPMethodID eapMethodID = 414 EAP.mapEAPMethod(getInteger(eapMethodNode.getChild(TAG_EAPType))); 415 if (eapMethodID == null) { 416 throw new OMAException("Unknown EAP method"); 417 } 418 419 Long vid = getOptionalInteger(eapMethodNode.getChild(TAG_VendorId)); 420 Long vtype = getOptionalInteger(eapMethodNode.getChild(TAG_VendorType)); 421 Long innerEAPType = getOptionalInteger(eapMethodNode.getChild(TAG_InnerEAPType)); 422 EAP.EAPMethodID innerEAPMethod = null; 423 if (innerEAPType != null) { 424 innerEAPMethod = EAP.mapEAPMethod(innerEAPType.intValue()); 425 if (innerEAPMethod == null) { 426 throw new OMAException("Bad inner EAP method: " + innerEAPType); 427 } 428 } 429 430 Long innerVid = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorID)); 431 Long innerVtype = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorType)); 432 String innerNonEAPMethod = getString(eapMethodNode.getChild(TAG_InnerMethod)); 433 434 EAPMethod eapMethod; 435 if (innerEAPMethod != null) { 436 eapMethod = new EAPMethod(eapMethodID, new InnerAuthEAP(innerEAPMethod)); 437 } else if (vid != null) { 438 eapMethod = new EAPMethod(eapMethodID, 439 new ExpandedEAPMethod(EAP.AuthInfoID.ExpandedEAPMethod, 440 vid.intValue(), vtype)); 441 } else if (innerVid != null) { 442 eapMethod = 443 new EAPMethod(eapMethodID, new ExpandedEAPMethod(EAP.AuthInfoID 444 .ExpandedInnerEAPMethod, innerVid.intValue(), innerVtype)); 445 } else if (innerNonEAPMethod != null) { 446 eapMethod = new EAPMethod(eapMethodID, new NonEAPInnerAuth(innerNonEAPMethod)); 447 } else { 448 throw new OMAException("Incomplete set of EAP parameters"); 449 } 450 451 return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, userName, 452 password, machineManaged, softTokenApp, ableToShare); 453 } 454 if (certNode != null) { 455 try { 456 String certTypeString = getString(certNode.getChild(TAG_CertificateType)); 457 byte[] fingerPrint = getOctets(certNode.getChild(TAG_CertSHA256Fingerprint)); 458 459 EAPMethod eapMethod = new EAPMethod(EAP.EAPMethodID.EAP_TLS, null); 460 461 return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, 462 Credential.mapCertType(certTypeString), fingerPrint); 463 } 464 catch (NumberFormatException nfe) { 465 throw new OMAException("Bad hex string: " + nfe.toString()); 466 } 467 } 468 if (simNode != null) { 469 470 String imsi = getString(simNode.getChild(TAG_IMSI)); 471 EAPMethod eapMethod = 472 new EAPMethod(EAP.mapEAPMethod(getInteger(simNode.getChild(TAG_EAPType))), 473 null); 474 475 return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, imsi); 476 } 477 throw new OMAException("Missing credential parameters"); 478 } 479 480 private static boolean getBoolean(OMANode boolNode) { 481 return boolNode != null && Boolean.parseBoolean(boolNode.getValue()); 482 } 483 484 private static String getString(OMANode stringNode) { 485 return stringNode != null ? stringNode.getValue() : null; 486 } 487 488 private static int getInteger(OMANode intNode) throws OMAException { 489 if (intNode == null) { 490 throw new OMAException("Missing integer value"); 491 } 492 try { 493 return Integer.parseInt(intNode.getValue()); 494 } catch (NumberFormatException nfe) { 495 throw new OMAException("Invalid integer: " + intNode.getValue()); 496 } 497 } 498 499 private static Long getMac(OMANode macNode) throws OMAException { 500 if (macNode == null) { 501 return null; 502 } 503 try { 504 return Long.parseLong(macNode.getValue(), 16); 505 } catch (NumberFormatException nfe) { 506 throw new OMAException("Invalid MAC: " + macNode.getValue()); 507 } 508 } 509 510 private static Long getOptionalInteger(OMANode intNode) throws OMAException { 511 if (intNode == null) { 512 return null; 513 } 514 try { 515 return Long.parseLong(intNode.getValue()); 516 } catch (NumberFormatException nfe) { 517 throw new OMAException("Invalid integer: " + intNode.getValue()); 518 } 519 } 520 521 private static long getTime(OMANode timeNode) throws OMAException { 522 if (timeNode == null) { 523 return -1; 524 } 525 String timeText = timeNode.getValue(); 526 try { 527 Date date = DTFormat.parse(timeText); 528 return date.getTime(); 529 } catch (ParseException pe) { 530 throw new OMAException("Badly formatted time: " + timeText); 531 } 532 } 533 534 private static byte[] getOctets(OMANode octetNode) throws OMAException { 535 if (octetNode == null) { 536 // throw new OMAException("Missing byte value"); 537 return null; 538 } 539 return Utils.hexToBytes(octetNode.getValue()); 540 } 541} 542