package com.android.hotspot2.est; import android.net.Network; import android.util.Base64; import android.util.Log; import com.android.hotspot2.OMADMAdapter; import com.android.hotspot2.asn1.Asn1Class; import com.android.hotspot2.asn1.Asn1Constructed; import com.android.hotspot2.asn1.Asn1Decoder; import com.android.hotspot2.asn1.Asn1ID; import com.android.hotspot2.asn1.Asn1Integer; import com.android.hotspot2.asn1.Asn1Object; import com.android.hotspot2.asn1.Asn1Oid; import com.android.hotspot2.asn1.OidMappings; import com.android.hotspot2.osu.HTTPHandler; import com.android.hotspot2.osu.OSUFlowManager; import com.android.hotspot2.osu.OSUSocketFactory; import com.android.hotspot2.osu.commands.GetCertData; import com.android.hotspot2.pps.HomeSP; import com.android.hotspot2.utils.HTTPMessage; import com.android.hotspot2.utils.HTTPResponse; import com.android.org.bouncycastle.asn1.ASN1Encodable; import com.android.org.bouncycastle.asn1.ASN1EncodableVector; import com.android.org.bouncycastle.asn1.ASN1Set; import com.android.org.bouncycastle.asn1.DERBitString; import com.android.org.bouncycastle.asn1.DEREncodableVector; import com.android.org.bouncycastle.asn1.DERIA5String; import com.android.org.bouncycastle.asn1.DERObjectIdentifier; import com.android.org.bouncycastle.asn1.DERPrintableString; import com.android.org.bouncycastle.asn1.DERSet; import com.android.org.bouncycastle.asn1.x509.Attribute; import com.android.org.bouncycastle.jce.PKCS10CertificationRequest; import com.android.org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URL; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.net.ssl.KeyManager; import javax.security.auth.x500.X500Principal; //import com.android.org.bouncycastle.jce.provider.BouncyCastleProvider; public class ESTHandler implements AutoCloseable { private static final String TAG = "HS2EST"; private static final int MinRSAKeySize = 2048; private static final String CACERT_PATH = "/cacerts"; private static final String CSR_PATH = "/csrattrs"; private static final String SIMPLE_ENROLL_PATH = "/simpleenroll"; private static final String SIMPLE_REENROLL_PATH = "/simplereenroll"; private final URL mURL; private final String mUser; private final byte[] mPassword; private final OSUSocketFactory mSocketFactory; private final OMADMAdapter mOMADMAdapter; private final List mCACerts = new ArrayList<>(); private final List mClientCerts = new ArrayList<>(); private PrivateKey mClientKey; public ESTHandler(GetCertData certData, Network network, OMADMAdapter omadmAdapter, KeyManager km, KeyStore ks, HomeSP homeSP, OSUFlowManager.FlowType flowType) throws IOException, GeneralSecurityException { mURL = new URL(certData.getServer()); mUser = certData.getUserName(); mPassword = certData.getPassword(); mSocketFactory = OSUSocketFactory.getSocketFactory(ks, homeSP, flowType, network, mURL, km, true); mOMADMAdapter = omadmAdapter; } @Override public void close() throws IOException { } public List getCACerts() { return mCACerts; } public List getClientCerts() { return mClientCerts; } public PrivateKey getClientKey() { return mClientKey; } private static String indent(int amount) { char[] indent = new char[amount * 2]; Arrays.fill(indent, ' '); return new String(indent); } public void execute(boolean reenroll) throws IOException, GeneralSecurityException { URL caURL = new URL(mURL.getProtocol(), mURL.getHost(), mURL.getPort(), mURL.getFile() + CACERT_PATH); HTTPResponse response; try (HTTPHandler httpHandler = new HTTPHandler(StandardCharsets.ISO_8859_1, mSocketFactory, mUser, mPassword)) { response = httpHandler.doGetHTTP(caURL); if (!"application/pkcs7-mime".equals(response.getHeaders(). get(HTTPMessage.ContentTypeHeader))) { throw new IOException("Unexpected Content-Type: " + response.getHeaders().get(HTTPMessage.ContentTypeHeader)); } ByteBuffer octetBuffer = response.getBinaryPayload(); Collection pkcs7Content1 = Asn1Decoder.decode(octetBuffer); for (Asn1Object asn1Object : pkcs7Content1) { Log.d(TAG, "---"); Log.d(TAG, asn1Object.toString()); } Log.d(TAG, CACERT_PATH); mCACerts.addAll(unpackPkcs7(octetBuffer)); for (X509Certificate certificate : mCACerts) { Log.d(TAG, "CA-Cert: " + certificate.getSubjectX500Principal()); } /* byte[] octets = new byte[octetBuffer.remaining()]; octetBuffer.duplicate().get(octets); for (byte b : octets) { System.out.printf("%02x ", b & 0xff); } Log.d(TAG, ); */ /* + BC try { byte[] octets = new byte[octetBuffer.remaining()]; octetBuffer.duplicate().get(octets); ASN1InputStream asnin = new ASN1InputStream(octets); for (int n = 0; n < 100; n++) { ASN1Primitive object = asnin.readObject(); if (object == null) { break; } parseObject(object, 0); } } catch (Throwable t) { t.printStackTrace(); } Collection pkcs7Content = Asn1Decoder.decode(octetBuffer); for (Asn1Object asn1Object : pkcs7Content) { Log.d(TAG, asn1Object); } if (pkcs7Content.size() != 1) { throw new IOException("Unexpected pkcs 7 container: " + pkcs7Content.size()); } Asn1Constructed pkcs7Root = (Asn1Constructed) pkcs7Content.iterator().next(); Iterator certPath = Arrays.asList(Pkcs7CertPath).iterator(); Asn1Object certObject = pkcs7Root.findObject(certPath); if (certObject == null || certPath.hasNext()) { throw new IOException("Failed to find cert; returned object " + certObject + ", path " + (certPath.hasNext() ? "short" : "exhausted")); } ByteBuffer certOctets = certObject.getPayload(); if (certOctets == null) { throw new IOException("No cert payload in: " + certObject); } byte[] certBytes = new byte[certOctets.remaining()]; certOctets.get(certBytes); CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(certBytes)); Log.d(TAG, "EST Cert: " + cert); */ URL csrURL = new URL(mURL.getProtocol(), mURL.getHost(), mURL.getPort(), mURL.getFile() + CSR_PATH); response = httpHandler.doGetHTTP(csrURL); octetBuffer = response.getBinaryPayload(); byte[] csrData = buildCSR(octetBuffer, mOMADMAdapter, httpHandler); /**/ Collection o = Asn1Decoder.decode(ByteBuffer.wrap(csrData)); Log.d(TAG, "CSR:"); Log.d(TAG, o.iterator().next().toString()); Log.d(TAG, "End CSR."); /**/ URL enrollURL = new URL(mURL.getProtocol(), mURL.getHost(), mURL.getPort(), mURL.getFile() + (reenroll ? SIMPLE_REENROLL_PATH : SIMPLE_ENROLL_PATH)); String data = Base64.encodeToString(csrData, Base64.DEFAULT); octetBuffer = httpHandler.exchangeBinary(enrollURL, data, "application/pkcs10"); Collection pkcs7Content2 = Asn1Decoder.decode(octetBuffer); for (Asn1Object asn1Object : pkcs7Content2) { Log.d(TAG, "---"); Log.d(TAG, asn1Object.toString()); } mClientCerts.addAll(unpackPkcs7(octetBuffer)); for (X509Certificate cert : mClientCerts) { Log.d(TAG, cert.toString()); } } } private static final Asn1ID sSEQUENCE = new Asn1ID(Asn1Decoder.TAG_SEQ, Asn1Class.Universal); private static final Asn1ID sCTXT0 = new Asn1ID(0, Asn1Class.Context); private static final int PKCS7DataVersion = 1; private static final int PKCS7SignedDataVersion = 3; private static List unpackPkcs7(ByteBuffer pkcs7) throws IOException, GeneralSecurityException { Collection pkcs7Content = Asn1Decoder.decode(pkcs7); if (pkcs7Content.size() != 1) { throw new IOException("Unexpected pkcs 7 container: " + pkcs7Content.size()); } Asn1Object data = pkcs7Content.iterator().next(); if (!data.isConstructed() || !data.matches(sSEQUENCE)) { throw new IOException("Expected SEQ OF, got " + data.toSimpleString()); } else if (data.getChildren().size() != 2) { throw new IOException("Expected content info to have two children, got " + data.getChildren().size()); } Iterator children = data.getChildren().iterator(); Asn1Object contentType = children.next(); if (!contentType.equals(Asn1Oid.PKCS7SignedData)) { throw new IOException("Content not PKCS7 signed data"); } Asn1Object content = children.next(); if (!content.isConstructed() || !content.matches(sCTXT0)) { throw new IOException("Expected [CONTEXT 0] with one child, got " + content.toSimpleString() + ", " + content.getChildren().size()); } Asn1Object signedData = content.getChildren().iterator().next(); Map itemMap = new HashMap<>(); for (Asn1Object item : signedData.getChildren()) { if (itemMap.put(item.getTag(), item) != null && item.getTag() != Asn1Decoder.TAG_SET) { throw new IOException("Duplicate item in SignedData: " + item.toSimpleString()); } } Asn1Object versionObject = itemMap.get(Asn1Decoder.TAG_INTEGER); if (versionObject == null || !(versionObject instanceof Asn1Integer)) { throw new IOException("Bad or missing PKCS7 version: " + versionObject); } int pkcs7version = (int) ((Asn1Integer) versionObject).getValue(); Asn1Object innerContentInfo = itemMap.get(Asn1Decoder.TAG_SEQ); if (innerContentInfo == null || !innerContentInfo.isConstructed() || !innerContentInfo.matches(sSEQUENCE) || innerContentInfo.getChildren().size() != 1) { throw new IOException("Bad or missing PKCS7 contentInfo"); } Asn1Object contentID = innerContentInfo.getChildren().iterator().next(); if (pkcs7version == PKCS7DataVersion && !contentID.equals(Asn1Oid.PKCS7Data) || pkcs7version == PKCS7SignedDataVersion && !contentID.equals(Asn1Oid.PKCS7SignedData)) { throw new IOException("Inner PKCS7 content (" + contentID + ") not expected for version " + pkcs7version); } Asn1Object certWrapper = itemMap.get(0); if (certWrapper == null || !certWrapper.isConstructed() || !certWrapper.matches(sCTXT0)) { throw new IOException("Expected [CONTEXT 0], got: " + certWrapper); } List certList = new ArrayList<>(certWrapper.getChildren().size()); CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); for (Asn1Object certObject : certWrapper.getChildren()) { ByteBuffer certOctets = ((Asn1Constructed) certObject).getEncoding(); if (certOctets == null) { throw new IOException("No cert payload in: " + certObject); } byte[] certBytes = new byte[certOctets.remaining()]; certOctets.get(certBytes); certList.add((X509Certificate) certFactory. generateCertificate(new ByteArrayInputStream(certBytes))); } return certList; } private byte[] buildCSR(ByteBuffer octetBuffer, OMADMAdapter omadmAdapter, HTTPHandler httpHandler) throws IOException, GeneralSecurityException { //Security.addProvider(new BouncyCastleProvider()); Log.d(TAG, "/csrattrs:"); /* byte[] octets = new byte[octetBuffer.remaining()]; octetBuffer.duplicate().get(octets); for (byte b : octets) { System.out.printf("%02x ", b & 0xff); } */ Collection csrs = Asn1Decoder.decode(octetBuffer); for (Asn1Object asn1Object : csrs) { Log.d(TAG, asn1Object.toString()); } if (csrs.size() != 1) { throw new IOException("Unexpected object count in CSR attributes response: " + csrs.size()); } Asn1Object sequence = csrs.iterator().next(); if (sequence.getClass() != Asn1Constructed.class) { throw new IOException("Unexpected CSR attribute container: " + sequence); } String keyAlgo = null; Asn1Oid keyAlgoOID = null; String sigAlgo = null; String curveName = null; Asn1Oid pubCrypto = null; int keySize = -1; Map idAttributes = new HashMap<>(); for (Asn1Object child : sequence.getChildren()) { if (child.getTag() == Asn1Decoder.TAG_OID) { Asn1Oid oid = (Asn1Oid) child; OidMappings.SigEntry sigEntry = OidMappings.getSigEntry(oid); if (sigEntry != null) { sigAlgo = sigEntry.getSigAlgo(); keyAlgoOID = sigEntry.getKeyAlgo(); keyAlgo = OidMappings.getJCEName(keyAlgoOID); } else if (oid.equals(OidMappings.sPkcs9AtChallengePassword)) { byte[] tlsUnique = httpHandler.getTLSUnique(); if (tlsUnique != null) { idAttributes.put(oid, new DERPrintableString( Base64.encodeToString(tlsUnique, Base64.DEFAULT))); } else { Log.w(TAG, "Cannot retrieve TLS unique channel binding"); } } } else if (child.getTag() == Asn1Decoder.TAG_SEQ) { Asn1Oid oid = null; Set oidValues = new HashSet<>(); List values = new ArrayList<>(); for (Asn1Object attributeSeq : child.getChildren()) { if (attributeSeq.getTag() == Asn1Decoder.TAG_OID) { oid = (Asn1Oid) attributeSeq; } else if (attributeSeq.getTag() == Asn1Decoder.TAG_SET) { for (Asn1Object value : attributeSeq.getChildren()) { if (value.getTag() == Asn1Decoder.TAG_OID) { oidValues.add((Asn1Oid) value); } else { values.add(value); } } } } if (oid == null) { throw new IOException("Invalid attribute, no OID"); } if (oid.equals(OidMappings.sExtensionRequest)) { for (Asn1Oid subOid : oidValues) { if (OidMappings.isIDAttribute(subOid)) { if (subOid.equals(OidMappings.sMAC)) { idAttributes.put(subOid, new DERIA5String(omadmAdapter.getMAC())); } else if (subOid.equals(OidMappings.sIMEI)) { idAttributes.put(subOid, new DERIA5String(omadmAdapter.getImei())); } else if (subOid.equals(OidMappings.sMEID)) { idAttributes.put(subOid, new DERBitString(omadmAdapter.getMeid())); } else if (subOid.equals(OidMappings.sDevID)) { idAttributes.put(subOid, new DERPrintableString(omadmAdapter.getDevID())); } } } } else if (OidMappings.getCryptoID(oid) != null) { pubCrypto = oid; if (!values.isEmpty()) { for (Asn1Object value : values) { if (value.getTag() == Asn1Decoder.TAG_INTEGER) { keySize = (int) ((Asn1Integer) value).getValue(); } } } if (oid.equals(OidMappings.sAlgo_EC)) { if (oidValues.isEmpty()) { throw new IOException("No ECC curve name provided"); } for (Asn1Oid value : oidValues) { curveName = OidMappings.getJCEName(value); if (curveName != null) { break; } } if (curveName == null) { throw new IOException("Found no ECC curve for " + oidValues); } } } } } if (keyAlgoOID == null) { throw new IOException("No public key algorithm specified"); } if (pubCrypto != null && !pubCrypto.equals(keyAlgoOID)) { throw new IOException("Mismatching key algorithms"); } if (keyAlgoOID.equals(OidMappings.sAlgo_RSA)) { if (keySize < MinRSAKeySize) { if (keySize >= 0) { Log.i(TAG, "Upgrading suggested RSA key size from " + keySize + " to " + MinRSAKeySize); } keySize = MinRSAKeySize; } } Log.d(TAG, String.format("pub key '%s', signature '%s', ECC curve '%s', id-atts %s", keyAlgo, sigAlgo, curveName, idAttributes)); /* Ruckus: SEQUENCE: OID=1.2.840.113549.1.1.11 (algo_id_sha256WithRSAEncryption) RFC-7030: SEQUENCE: OID=1.2.840.113549.1.9.7 (challengePassword) SEQUENCE: OID=1.2.840.10045.2.1 (algo_id_ecPublicKey) SET: OID=1.3.132.0.34 (secp384r1) SEQUENCE: OID=1.2.840.113549.1.9.14 (extensionRequest) SET: OID=1.3.6.1.1.1.1.22 (mac-address) OID=1.2.840.10045.4.3.3 (eccdaWithSHA384) 1L, 3L, 6L, 1L, 1L, 1L, 1L, 22 */ // ECC Does not appear to be supported currently KeyPairGenerator kpg = KeyPairGenerator.getInstance(keyAlgo); if (curveName != null) { AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance(keyAlgo); algorithmParameters.init(new ECNamedCurveGenParameterSpec(curveName)); kpg.initialize(algorithmParameters .getParameterSpec(ECNamedCurveGenParameterSpec.class)); } else { kpg.initialize(keySize); } KeyPair kp = kpg.generateKeyPair(); X500Principal subject = new X500Principal("CN=Android, O=Google, C=US"); mClientKey = kp.getPrivate(); // !!! Map the idAttributes into an ASN1Set of values to pass to // the PKCS10CertificationRequest - this code is using outdated BC classes and // has *not* been tested. ASN1Set attributes; if (!idAttributes.isEmpty()) { ASN1EncodableVector payload = new DEREncodableVector(); for (Map.Entry entry : idAttributes.entrySet()) { DERObjectIdentifier type = new DERObjectIdentifier(entry.getKey().toOIDString()); ASN1Set values = new DERSet(entry.getValue()); Attribute attribute = new Attribute(type, values); payload.add(attribute); } attributes = new DERSet(payload); } else { attributes = null; } return new PKCS10CertificationRequest(sigAlgo, subject, kp.getPublic(), attributes, mClientKey).getEncoded(); } }