1 package com.android.hotspot2.est; 2 3 import android.net.Network; 4 import android.util.Base64; 5 import android.util.Log; 6 7 import com.android.hotspot2.OMADMAdapter; 8 import com.android.hotspot2.asn1.Asn1Class; 9 import com.android.hotspot2.asn1.Asn1Constructed; 10 import com.android.hotspot2.asn1.Asn1Decoder; 11 import com.android.hotspot2.asn1.Asn1ID; 12 import com.android.hotspot2.asn1.Asn1Integer; 13 import com.android.hotspot2.asn1.Asn1Object; 14 import com.android.hotspot2.asn1.Asn1Oid; 15 import com.android.hotspot2.asn1.OidMappings; 16 import com.android.hotspot2.osu.HTTPHandler; 17 import com.android.hotspot2.osu.OSUFlowManager; 18 import com.android.hotspot2.osu.OSUSocketFactory; 19 import com.android.hotspot2.osu.commands.GetCertData; 20 import com.android.hotspot2.pps.HomeSP; 21 import com.android.hotspot2.utils.HTTPMessage; 22 import com.android.hotspot2.utils.HTTPResponse; 23 import com.android.org.bouncycastle.asn1.ASN1Encodable; 24 import com.android.org.bouncycastle.asn1.ASN1EncodableVector; 25 import com.android.org.bouncycastle.asn1.ASN1Set; 26 import com.android.org.bouncycastle.asn1.DERBitString; 27 import com.android.org.bouncycastle.asn1.DEREncodableVector; 28 import com.android.org.bouncycastle.asn1.DERIA5String; 29 import com.android.org.bouncycastle.asn1.DERObjectIdentifier; 30 import com.android.org.bouncycastle.asn1.DERPrintableString; 31 import com.android.org.bouncycastle.asn1.DERSet; 32 import com.android.org.bouncycastle.asn1.x509.Attribute; 33 import com.android.org.bouncycastle.jce.PKCS10CertificationRequest; 34 import com.android.org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec; 35 36 import java.io.ByteArrayInputStream; 37 import java.io.IOException; 38 import java.net.URL; 39 import java.nio.ByteBuffer; 40 import java.nio.charset.StandardCharsets; 41 import java.security.AlgorithmParameters; 42 import java.security.GeneralSecurityException; 43 import java.security.KeyPair; 44 import java.security.KeyPairGenerator; 45 import java.security.KeyStore; 46 import java.security.PrivateKey; 47 import java.security.cert.CertificateFactory; 48 import java.security.cert.X509Certificate; 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.Collection; 52 import java.util.HashMap; 53 import java.util.HashSet; 54 import java.util.Iterator; 55 import java.util.List; 56 import java.util.Map; 57 import java.util.Set; 58 59 import javax.net.ssl.KeyManager; 60 import javax.security.auth.x500.X500Principal; 61 62 //import com.android.org.bouncycastle.jce.provider.BouncyCastleProvider; 63 64 public class ESTHandler implements AutoCloseable { 65 private static final String TAG = "HS2EST"; 66 private static final int MinRSAKeySize = 2048; 67 68 private static final String CACERT_PATH = "/cacerts"; 69 private static final String CSR_PATH = "/csrattrs"; 70 private static final String SIMPLE_ENROLL_PATH = "/simpleenroll"; 71 private static final String SIMPLE_REENROLL_PATH = "/simplereenroll"; 72 73 private final URL mURL; 74 private final String mUser; 75 private final byte[] mPassword; 76 private final OSUSocketFactory mSocketFactory; 77 private final OMADMAdapter mOMADMAdapter; 78 79 private final List<X509Certificate> mCACerts = new ArrayList<>(); 80 private final List<X509Certificate> mClientCerts = new ArrayList<>(); 81 private PrivateKey mClientKey; 82 ESTHandler(GetCertData certData, Network network, OMADMAdapter omadmAdapter, KeyManager km, KeyStore ks, HomeSP homeSP, OSUFlowManager.FlowType flowType)83 public ESTHandler(GetCertData certData, Network network, OMADMAdapter omadmAdapter, 84 KeyManager km, KeyStore ks, HomeSP homeSP, OSUFlowManager.FlowType flowType) 85 throws IOException, GeneralSecurityException { 86 mURL = new URL(certData.getServer()); 87 mUser = certData.getUserName(); 88 mPassword = certData.getPassword(); 89 mSocketFactory = OSUSocketFactory.getSocketFactory(ks, homeSP, flowType, 90 network, mURL, km, true); 91 mOMADMAdapter = omadmAdapter; 92 } 93 94 @Override close()95 public void close() throws IOException { 96 } 97 getCACerts()98 public List<X509Certificate> getCACerts() { 99 return mCACerts; 100 } 101 getClientCerts()102 public List<X509Certificate> getClientCerts() { 103 return mClientCerts; 104 } 105 getClientKey()106 public PrivateKey getClientKey() { 107 return mClientKey; 108 } 109 indent(int amount)110 private static String indent(int amount) { 111 char[] indent = new char[amount * 2]; 112 Arrays.fill(indent, ' '); 113 return new String(indent); 114 } 115 execute(boolean reenroll)116 public void execute(boolean reenroll) throws IOException, GeneralSecurityException { 117 URL caURL = new URL(mURL.getProtocol(), mURL.getHost(), mURL.getPort(), 118 mURL.getFile() + CACERT_PATH); 119 120 HTTPResponse response; 121 try (HTTPHandler httpHandler = new HTTPHandler(StandardCharsets.ISO_8859_1, mSocketFactory, 122 mUser, mPassword)) { 123 response = httpHandler.doGetHTTP(caURL); 124 125 if (!"application/pkcs7-mime".equals(response.getHeaders(). 126 get(HTTPMessage.ContentTypeHeader))) { 127 throw new IOException("Unexpected Content-Type: " + 128 response.getHeaders().get(HTTPMessage.ContentTypeHeader)); 129 } 130 ByteBuffer octetBuffer = response.getBinaryPayload(); 131 Collection<Asn1Object> pkcs7Content1 = Asn1Decoder.decode(octetBuffer); 132 for (Asn1Object asn1Object : pkcs7Content1) { 133 Log.d(TAG, "---"); 134 Log.d(TAG, asn1Object.toString()); 135 } 136 Log.d(TAG, CACERT_PATH); 137 138 mCACerts.addAll(unpackPkcs7(octetBuffer)); 139 for (X509Certificate certificate : mCACerts) { 140 Log.d(TAG, "CA-Cert: " + certificate.getSubjectX500Principal()); 141 } 142 143 /* 144 byte[] octets = new byte[octetBuffer.remaining()]; 145 octetBuffer.duplicate().get(octets); 146 for (byte b : octets) { 147 System.out.printf("%02x ", b & 0xff); 148 } 149 Log.d(TAG, ); 150 */ 151 152 /* + BC 153 try { 154 byte[] octets = new byte[octetBuffer.remaining()]; 155 octetBuffer.duplicate().get(octets); 156 ASN1InputStream asnin = new ASN1InputStream(octets); 157 for (int n = 0; n < 100; n++) { 158 ASN1Primitive object = asnin.readObject(); 159 if (object == null) { 160 break; 161 } 162 parseObject(object, 0); 163 } 164 } 165 catch (Throwable t) { 166 t.printStackTrace(); 167 } 168 169 Collection<Asn1Object> pkcs7Content = Asn1Decoder.decode(octetBuffer); 170 for (Asn1Object asn1Object : pkcs7Content) { 171 Log.d(TAG, asn1Object); 172 } 173 174 if (pkcs7Content.size() != 1) { 175 throw new IOException("Unexpected pkcs 7 container: " + pkcs7Content.size()); 176 } 177 178 Asn1Constructed pkcs7Root = (Asn1Constructed) pkcs7Content.iterator().next(); 179 Iterator<Asn1ID> certPath = Arrays.asList(Pkcs7CertPath).iterator(); 180 Asn1Object certObject = pkcs7Root.findObject(certPath); 181 if (certObject == null || certPath.hasNext()) { 182 throw new IOException("Failed to find cert; returned object " + certObject + 183 ", path " + (certPath.hasNext() ? "short" : "exhausted")); 184 } 185 186 ByteBuffer certOctets = certObject.getPayload(); 187 if (certOctets == null) { 188 throw new IOException("No cert payload in: " + certObject); 189 } 190 191 byte[] certBytes = new byte[certOctets.remaining()]; 192 certOctets.get(certBytes); 193 194 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 195 Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(certBytes)); 196 Log.d(TAG, "EST Cert: " + cert); 197 */ 198 199 URL csrURL = new URL(mURL.getProtocol(), mURL.getHost(), mURL.getPort(), 200 mURL.getFile() + CSR_PATH); 201 response = httpHandler.doGetHTTP(csrURL); 202 203 octetBuffer = response.getBinaryPayload(); 204 byte[] csrData = buildCSR(octetBuffer, mOMADMAdapter, httpHandler); 205 206 /**/ 207 Collection<Asn1Object> o = Asn1Decoder.decode(ByteBuffer.wrap(csrData)); 208 Log.d(TAG, "CSR:"); 209 Log.d(TAG, o.iterator().next().toString()); 210 Log.d(TAG, "End CSR."); 211 /**/ 212 213 URL enrollURL = new URL(mURL.getProtocol(), mURL.getHost(), mURL.getPort(), 214 mURL.getFile() + (reenroll ? SIMPLE_REENROLL_PATH : SIMPLE_ENROLL_PATH)); 215 String data = Base64.encodeToString(csrData, Base64.DEFAULT); 216 octetBuffer = httpHandler.exchangeBinary(enrollURL, data, "application/pkcs10"); 217 218 Collection<Asn1Object> pkcs7Content2 = Asn1Decoder.decode(octetBuffer); 219 for (Asn1Object asn1Object : pkcs7Content2) { 220 Log.d(TAG, "---"); 221 Log.d(TAG, asn1Object.toString()); 222 } 223 mClientCerts.addAll(unpackPkcs7(octetBuffer)); 224 for (X509Certificate cert : mClientCerts) { 225 Log.d(TAG, cert.toString()); 226 } 227 } 228 } 229 230 private static final Asn1ID sSEQUENCE = new Asn1ID(Asn1Decoder.TAG_SEQ, Asn1Class.Universal); 231 private static final Asn1ID sCTXT0 = new Asn1ID(0, Asn1Class.Context); 232 private static final int PKCS7DataVersion = 1; 233 private static final int PKCS7SignedDataVersion = 3; 234 unpackPkcs7(ByteBuffer pkcs7)235 private static List<X509Certificate> unpackPkcs7(ByteBuffer pkcs7) 236 throws IOException, GeneralSecurityException { 237 Collection<Asn1Object> pkcs7Content = Asn1Decoder.decode(pkcs7); 238 239 if (pkcs7Content.size() != 1) { 240 throw new IOException("Unexpected pkcs 7 container: " + pkcs7Content.size()); 241 } 242 243 Asn1Object data = pkcs7Content.iterator().next(); 244 if (!data.isConstructed() || !data.matches(sSEQUENCE)) { 245 throw new IOException("Expected SEQ OF, got " + data.toSimpleString()); 246 } else if (data.getChildren().size() != 2) { 247 throw new IOException("Expected content info to have two children, got " + 248 data.getChildren().size()); 249 } 250 251 Iterator<Asn1Object> children = data.getChildren().iterator(); 252 Asn1Object contentType = children.next(); 253 if (!contentType.equals(Asn1Oid.PKCS7SignedData)) { 254 throw new IOException("Content not PKCS7 signed data"); 255 } 256 Asn1Object content = children.next(); 257 if (!content.isConstructed() || !content.matches(sCTXT0)) { 258 throw new IOException("Expected [CONTEXT 0] with one child, got " + 259 content.toSimpleString() + ", " + content.getChildren().size()); 260 } 261 262 Asn1Object signedData = content.getChildren().iterator().next(); 263 Map<Integer, Asn1Object> itemMap = new HashMap<>(); 264 for (Asn1Object item : signedData.getChildren()) { 265 if (itemMap.put(item.getTag(), item) != null && item.getTag() != Asn1Decoder.TAG_SET) { 266 throw new IOException("Duplicate item in SignedData: " + item.toSimpleString()); 267 } 268 } 269 270 Asn1Object versionObject = itemMap.get(Asn1Decoder.TAG_INTEGER); 271 if (versionObject == null || !(versionObject instanceof Asn1Integer)) { 272 throw new IOException("Bad or missing PKCS7 version: " + versionObject); 273 } 274 int pkcs7version = (int) ((Asn1Integer) versionObject).getValue(); 275 Asn1Object innerContentInfo = itemMap.get(Asn1Decoder.TAG_SEQ); 276 if (innerContentInfo == null || 277 !innerContentInfo.isConstructed() || 278 !innerContentInfo.matches(sSEQUENCE) || 279 innerContentInfo.getChildren().size() != 1) { 280 throw new IOException("Bad or missing PKCS7 contentInfo"); 281 } 282 Asn1Object contentID = innerContentInfo.getChildren().iterator().next(); 283 if (pkcs7version == PKCS7DataVersion && !contentID.equals(Asn1Oid.PKCS7Data) || 284 pkcs7version == PKCS7SignedDataVersion && !contentID.equals(Asn1Oid.PKCS7SignedData)) { 285 throw new IOException("Inner PKCS7 content (" + contentID + 286 ") not expected for version " + pkcs7version); 287 } 288 Asn1Object certWrapper = itemMap.get(0); 289 if (certWrapper == null || !certWrapper.isConstructed() || !certWrapper.matches(sCTXT0)) { 290 throw new IOException("Expected [CONTEXT 0], got: " + certWrapper); 291 } 292 293 List<X509Certificate> certList = new ArrayList<>(certWrapper.getChildren().size()); 294 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 295 for (Asn1Object certObject : certWrapper.getChildren()) { 296 ByteBuffer certOctets = ((Asn1Constructed) certObject).getEncoding(); 297 if (certOctets == null) { 298 throw new IOException("No cert payload in: " + certObject); 299 } 300 byte[] certBytes = new byte[certOctets.remaining()]; 301 certOctets.get(certBytes); 302 303 certList.add((X509Certificate) certFactory. 304 generateCertificate(new ByteArrayInputStream(certBytes))); 305 } 306 return certList; 307 } 308 buildCSR(ByteBuffer octetBuffer, OMADMAdapter omadmAdapter, HTTPHandler httpHandler)309 private byte[] buildCSR(ByteBuffer octetBuffer, OMADMAdapter omadmAdapter, 310 HTTPHandler httpHandler) throws IOException, GeneralSecurityException { 311 312 //Security.addProvider(new BouncyCastleProvider()); 313 314 Log.d(TAG, "/csrattrs:"); 315 /* 316 byte[] octets = new byte[octetBuffer.remaining()]; 317 octetBuffer.duplicate().get(octets); 318 for (byte b : octets) { 319 System.out.printf("%02x ", b & 0xff); 320 } 321 */ 322 Collection<Asn1Object> csrs = Asn1Decoder.decode(octetBuffer); 323 for (Asn1Object asn1Object : csrs) { 324 Log.d(TAG, asn1Object.toString()); 325 } 326 327 if (csrs.size() != 1) { 328 throw new IOException("Unexpected object count in CSR attributes response: " + 329 csrs.size()); 330 } 331 Asn1Object sequence = csrs.iterator().next(); 332 if (sequence.getClass() != Asn1Constructed.class) { 333 throw new IOException("Unexpected CSR attribute container: " + sequence); 334 } 335 336 String keyAlgo = null; 337 Asn1Oid keyAlgoOID = null; 338 String sigAlgo = null; 339 String curveName = null; 340 Asn1Oid pubCrypto = null; 341 int keySize = -1; 342 Map<Asn1Oid, ASN1Encodable> idAttributes = new HashMap<>(); 343 344 for (Asn1Object child : sequence.getChildren()) { 345 if (child.getTag() == Asn1Decoder.TAG_OID) { 346 Asn1Oid oid = (Asn1Oid) child; 347 OidMappings.SigEntry sigEntry = OidMappings.getSigEntry(oid); 348 if (sigEntry != null) { 349 sigAlgo = sigEntry.getSigAlgo(); 350 keyAlgoOID = sigEntry.getKeyAlgo(); 351 keyAlgo = OidMappings.getJCEName(keyAlgoOID); 352 } else if (oid.equals(OidMappings.sPkcs9AtChallengePassword)) { 353 byte[] tlsUnique = httpHandler.getTLSUnique(); 354 if (tlsUnique != null) { 355 idAttributes.put(oid, new DERPrintableString( 356 Base64.encodeToString(tlsUnique, Base64.DEFAULT))); 357 } else { 358 Log.w(TAG, "Cannot retrieve TLS unique channel binding"); 359 } 360 } 361 } else if (child.getTag() == Asn1Decoder.TAG_SEQ) { 362 Asn1Oid oid = null; 363 Set<Asn1Oid> oidValues = new HashSet<>(); 364 List<Asn1Object> values = new ArrayList<>(); 365 366 for (Asn1Object attributeSeq : child.getChildren()) { 367 if (attributeSeq.getTag() == Asn1Decoder.TAG_OID) { 368 oid = (Asn1Oid) attributeSeq; 369 } else if (attributeSeq.getTag() == Asn1Decoder.TAG_SET) { 370 for (Asn1Object value : attributeSeq.getChildren()) { 371 if (value.getTag() == Asn1Decoder.TAG_OID) { 372 oidValues.add((Asn1Oid) value); 373 } else { 374 values.add(value); 375 } 376 } 377 } 378 } 379 if (oid == null) { 380 throw new IOException("Invalid attribute, no OID"); 381 } 382 if (oid.equals(OidMappings.sExtensionRequest)) { 383 for (Asn1Oid subOid : oidValues) { 384 if (OidMappings.isIDAttribute(subOid)) { 385 if (subOid.equals(OidMappings.sMAC)) { 386 idAttributes.put(subOid, new DERIA5String(omadmAdapter.getMAC())); 387 } else if (subOid.equals(OidMappings.sIMEI)) { 388 idAttributes.put(subOid, new DERIA5String(omadmAdapter.getImei())); 389 } else if (subOid.equals(OidMappings.sMEID)) { 390 idAttributes.put(subOid, new DERBitString(omadmAdapter.getMeid())); 391 } else if (subOid.equals(OidMappings.sDevID)) { 392 idAttributes.put(subOid, 393 new DERPrintableString(omadmAdapter.getDevID())); 394 } 395 } 396 } 397 } else if (OidMappings.getCryptoID(oid) != null) { 398 pubCrypto = oid; 399 if (!values.isEmpty()) { 400 for (Asn1Object value : values) { 401 if (value.getTag() == Asn1Decoder.TAG_INTEGER) { 402 keySize = (int) ((Asn1Integer) value).getValue(); 403 } 404 } 405 } 406 if (oid.equals(OidMappings.sAlgo_EC)) { 407 if (oidValues.isEmpty()) { 408 throw new IOException("No ECC curve name provided"); 409 } 410 for (Asn1Oid value : oidValues) { 411 curveName = OidMappings.getJCEName(value); 412 if (curveName != null) { 413 break; 414 } 415 } 416 if (curveName == null) { 417 throw new IOException("Found no ECC curve for " + oidValues); 418 } 419 } 420 } 421 } 422 } 423 424 if (keyAlgoOID == null) { 425 throw new IOException("No public key algorithm specified"); 426 } 427 if (pubCrypto != null && !pubCrypto.equals(keyAlgoOID)) { 428 throw new IOException("Mismatching key algorithms"); 429 } 430 431 if (keyAlgoOID.equals(OidMappings.sAlgo_RSA)) { 432 if (keySize < MinRSAKeySize) { 433 if (keySize >= 0) { 434 Log.i(TAG, "Upgrading suggested RSA key size from " + 435 keySize + " to " + MinRSAKeySize); 436 } 437 keySize = MinRSAKeySize; 438 } 439 } 440 441 Log.d(TAG, String.format("pub key '%s', signature '%s', ECC curve '%s', id-atts %s", 442 keyAlgo, sigAlgo, curveName, idAttributes)); 443 444 /* 445 Ruckus: 446 SEQUENCE: 447 OID=1.2.840.113549.1.1.11 (algo_id_sha256WithRSAEncryption) 448 449 RFC-7030: 450 SEQUENCE: 451 OID=1.2.840.113549.1.9.7 (challengePassword) 452 SEQUENCE: 453 OID=1.2.840.10045.2.1 (algo_id_ecPublicKey) 454 SET: 455 OID=1.3.132.0.34 (secp384r1) 456 SEQUENCE: 457 OID=1.2.840.113549.1.9.14 (extensionRequest) 458 SET: 459 OID=1.3.6.1.1.1.1.22 (mac-address) 460 OID=1.2.840.10045.4.3.3 (eccdaWithSHA384) 461 462 1L, 3L, 6L, 1L, 1L, 1L, 1L, 22 463 */ 464 465 // ECC Does not appear to be supported currently 466 KeyPairGenerator kpg = KeyPairGenerator.getInstance(keyAlgo); 467 if (curveName != null) { 468 AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance(keyAlgo); 469 algorithmParameters.init(new ECNamedCurveGenParameterSpec(curveName)); 470 kpg.initialize(algorithmParameters 471 .getParameterSpec(ECNamedCurveGenParameterSpec.class)); 472 } else { 473 kpg.initialize(keySize); 474 } 475 KeyPair kp = kpg.generateKeyPair(); 476 477 X500Principal subject = new X500Principal("CN=Android, O=Google, C=US"); 478 479 mClientKey = kp.getPrivate(); 480 481 // !!! Map the idAttributes into an ASN1Set of values to pass to 482 // the PKCS10CertificationRequest - this code is using outdated BC classes and 483 // has *not* been tested. 484 ASN1Set attributes; 485 if (!idAttributes.isEmpty()) { 486 ASN1EncodableVector payload = new DEREncodableVector(); 487 for (Map.Entry<Asn1Oid, ASN1Encodable> entry : idAttributes.entrySet()) { 488 DERObjectIdentifier type = new DERObjectIdentifier(entry.getKey().toOIDString()); 489 ASN1Set values = new DERSet(entry.getValue()); 490 Attribute attribute = new Attribute(type, values); 491 payload.add(attribute); 492 } 493 attributes = new DERSet(payload); 494 } else { 495 attributes = null; 496 } 497 498 return new PKCS10CertificationRequest(sigAlgo, subject, kp.getPublic(), 499 attributes, mClientKey).getEncoded(); 500 } 501 } 502