MOManager.java revision 0047ccf563baa288777e06c6fe95d3681fcf5ccd
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 if (spList == null) { 316 return homeSPs; 317 } 318 Log.d("PARSE-LOG", " node-name = " + spList.getName()); 319 Log.d("PARSE-LOG", " num_children = " + spList.getChildren().size()); 320 for (OMANode spRoot : spList.getChildren()) { 321 Log.d("PARSE-LOG", " node-name = " + spRoot.getName()); 322 homeSPs.add(buildHomeSP(spRoot)); 323 } 324 325 return homeSPs; 326 } 327 328 private static HomeSP buildHomeSP(OMANode ppsRoot) throws OMAException { 329 Log.d("PARSE-LOG", " node-name = " + ppsRoot.getName()); 330 OMANode spRoot = ppsRoot.getChild(TAG_HomeSP); 331 332 String fqdn = spRoot.getScalarValue(Arrays.asList(TAG_FQDN).iterator()); 333 String friendlyName = spRoot.getScalarValue(Arrays.asList(TAG_FriendlyName).iterator()); 334 System.out.println("FQDN: " + fqdn + ", friendly: " + friendlyName); 335 String iconURL = spRoot.getScalarValue(Arrays.asList(TAG_IconURL).iterator()); 336 337 Set<Long> roamingConsortiums = new HashSet<Long>(); 338 String oiString = spRoot.getScalarValue(Arrays.asList(TAG_RoamingConsortiumOI).iterator()); 339 if (oiString != null) { 340 for (String oi : oiString.split(",")) { 341 roamingConsortiums.add(Long.parseLong(oi.trim(), 16)); 342 } 343 } 344 345 Map<String, Long> ssids = new HashMap<String, Long>(); 346 347 OMANode ssidListNode = spRoot.getListValue(Arrays.asList(TAG_NetworkID).iterator()); 348 if (ssidListNode != null) { 349 for (OMANode ssidRoot : ssidListNode.getChildren()) { 350 OMANode hessidNode = ssidRoot.getChild(TAG_HESSID); 351 ssids.put(ssidRoot.getChild(TAG_SSID).getValue(), getMac(hessidNode)); 352 } 353 } 354 355 Set<Long> matchAnyOIs = new HashSet<Long>(); 356 List<Long> matchAllOIs = new ArrayList<Long>(); 357 OMANode homeOIListNode = spRoot.getListValue(Arrays.asList(TAG_HomeOIList).iterator()); 358 if (homeOIListNode != null) { 359 for (OMANode homeOIRoot : homeOIListNode.getChildren()) { 360 String homeOI = homeOIRoot.getChild(TAG_HomeOI).getValue(); 361 if (Boolean.parseBoolean(homeOIRoot.getChild(TAG_HomeOIRequired).getValue())) { 362 matchAllOIs.add(Long.parseLong(homeOI, 16)); 363 } else { 364 matchAnyOIs.add(Long.parseLong(homeOI, 16)); 365 } 366 } 367 } 368 369 Set<String> otherHomePartners = new HashSet<String>(); 370 OMANode otherListNode = 371 spRoot.getListValue(Arrays.asList(TAG_OtherHomePartners).iterator()); 372 if (otherListNode != null) { 373 for (OMANode fqdnNode : otherListNode.getChildren()) { 374 otherHomePartners.add(fqdnNode.getChild(TAG_FQDN).getValue()); 375 } 376 } 377 378 Credential credential = buildCredential(ppsRoot.getChild(TAG_Credential)); 379 380 Log.d("PARSE-LOG", " Building a new HomeSP for " + fqdn); 381 return new HomeSP(ssids, fqdn, roamingConsortiums, otherHomePartners, 382 matchAnyOIs, matchAllOIs, friendlyName, iconURL, credential); 383 } 384 385 private static Credential buildCredential(OMANode credNode) throws OMAException { 386 Log.d("PARSE-LOG", " Reading credential from " + credNode.getName()); 387 long ctime = getTime(credNode.getChild(TAG_CreationDate)); 388 long expTime = getTime(credNode.getChild(TAG_ExpirationDate)); 389 String realm = getString(credNode.getChild(TAG_Realm)); 390 boolean checkAAACert = getBoolean(credNode.getChild(TAG_CheckAAAServerCertStatus)); 391 392 OMANode unNode = credNode.getChild(TAG_UsernamePassword); 393 OMANode certNode = credNode.getChild(TAG_DigitalCertificate); 394 OMANode simNode = credNode.getChild(TAG_SIM); 395 396 int alternatives = 0; 397 alternatives += unNode != null ? 1 : 0; 398 alternatives += certNode != null ? 1 : 0; 399 alternatives += simNode != null ? 1 : 0; 400 if (alternatives != 1) { 401 throw new OMAException("Expected exactly one credential type, got " + alternatives); 402 } 403 404 if (unNode != null) { 405 String userName = getString(unNode.getChild(TAG_Username)); 406 String password = getString(unNode.getChild(TAG_Password)); 407 boolean machineManaged = getBoolean(unNode.getChild(TAG_MachineManaged)); 408 String softTokenApp = getString(unNode.getChild(TAG_SoftTokenApp)); 409 boolean ableToShare = getBoolean(unNode.getChild(TAG_AbleToShare)); 410 411 OMANode eapMethodNode = unNode.getChild(TAG_EAPMethod); 412 EAP.EAPMethodID eapMethodID = 413 EAP.mapEAPMethod(getInteger(eapMethodNode.getChild(TAG_EAPType))); 414 if (eapMethodID == null) { 415 throw new OMAException("Unknown EAP method"); 416 } 417 418 Long vid = getOptionalInteger(eapMethodNode.getChild(TAG_VendorId)); 419 Long vtype = getOptionalInteger(eapMethodNode.getChild(TAG_VendorType)); 420 Long innerEAPType = getOptionalInteger(eapMethodNode.getChild(TAG_InnerEAPType)); 421 EAP.EAPMethodID innerEAPMethod = null; 422 if (innerEAPType != null) { 423 innerEAPMethod = EAP.mapEAPMethod(innerEAPType.intValue()); 424 if (innerEAPMethod == null) { 425 throw new OMAException("Bad inner EAP method: " + innerEAPType); 426 } 427 } 428 429 Long innerVid = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorID)); 430 Long innerVtype = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorType)); 431 String innerNonEAPMethod = getString(eapMethodNode.getChild(TAG_InnerMethod)); 432 433 EAPMethod eapMethod; 434 if (innerEAPMethod != null) { 435 eapMethod = new EAPMethod(eapMethodID, new InnerAuthEAP(innerEAPMethod)); 436 } else if (vid != null) { 437 eapMethod = new EAPMethod(eapMethodID, 438 new ExpandedEAPMethod(EAP.AuthInfoID.ExpandedEAPMethod, 439 vid.intValue(), vtype)); 440 } else if (innerVid != null) { 441 eapMethod = 442 new EAPMethod(eapMethodID, new ExpandedEAPMethod(EAP.AuthInfoID 443 .ExpandedInnerEAPMethod, innerVid.intValue(), innerVtype)); 444 } else if (innerNonEAPMethod != null) { 445 eapMethod = new EAPMethod(eapMethodID, new NonEAPInnerAuth(innerNonEAPMethod)); 446 } else { 447 throw new OMAException("Incomplete set of EAP parameters"); 448 } 449 450 return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, userName, 451 password, machineManaged, softTokenApp, ableToShare); 452 } 453 if (certNode != null) { 454 try { 455 String certTypeString = getString(certNode.getChild(TAG_CertificateType)); 456 byte[] fingerPrint = getOctets(certNode.getChild(TAG_CertSHA256Fingerprint)); 457 458 EAPMethod eapMethod = new EAPMethod(EAP.EAPMethodID.EAP_TLS, null); 459 460 return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, 461 Credential.mapCertType(certTypeString), fingerPrint); 462 } 463 catch (NumberFormatException nfe) { 464 throw new OMAException("Bad hex string: " + nfe.toString()); 465 } 466 } 467 if (simNode != null) { 468 469 String imsi = getString(simNode.getChild(TAG_IMSI)); 470 EAPMethod eapMethod = 471 new EAPMethod(EAP.mapEAPMethod(getInteger(simNode.getChild(TAG_EAPType))), 472 null); 473 474 return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, imsi); 475 } 476 throw new OMAException("Missing credential parameters"); 477 } 478 479 private static boolean getBoolean(OMANode boolNode) { 480 return boolNode != null && Boolean.parseBoolean(boolNode.getValue()); 481 } 482 483 private static String getString(OMANode stringNode) { 484 return stringNode != null ? stringNode.getValue() : null; 485 } 486 487 private static int getInteger(OMANode intNode) throws OMAException { 488 if (intNode == null) { 489 throw new OMAException("Missing integer value"); 490 } 491 try { 492 return Integer.parseInt(intNode.getValue()); 493 } catch (NumberFormatException nfe) { 494 throw new OMAException("Invalid integer: " + intNode.getValue()); 495 } 496 } 497 498 private static Long getMac(OMANode macNode) throws OMAException { 499 if (macNode == null) { 500 return null; 501 } 502 try { 503 return Long.parseLong(macNode.getValue(), 16); 504 } catch (NumberFormatException nfe) { 505 throw new OMAException("Invalid MAC: " + macNode.getValue()); 506 } 507 } 508 509 private static Long getOptionalInteger(OMANode intNode) throws OMAException { 510 if (intNode == null) { 511 return null; 512 } 513 try { 514 return Long.parseLong(intNode.getValue()); 515 } catch (NumberFormatException nfe) { 516 throw new OMAException("Invalid integer: " + intNode.getValue()); 517 } 518 } 519 520 private static long getTime(OMANode timeNode) throws OMAException { 521 if (timeNode == null) { 522 return -1; 523 } 524 String timeText = timeNode.getValue(); 525 try { 526 Date date = DTFormat.parse(timeText); 527 return date.getTime(); 528 } catch (ParseException pe) { 529 throw new OMAException("Badly formatted time: " + timeText); 530 } 531 } 532 533 private static byte[] getOctets(OMANode octetNode) throws OMAException { 534 if (octetNode == null) { 535 // throw new OMAException("Missing byte value"); 536 return null; 537 } 538 return Utils.hexToBytes(octetNode.getValue()); 539 } 540} 541