• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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