Java tutorial
/* CertificateAuthority.java Copyright (c) 2018 NTT DOCOMO,INC. Released under the MIT license http://opensource.org/licenses/mit-license.php */ package org.deviceconnect.android.ssl; import android.content.Context; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1Set; import org.bouncycastle.asn1.ASN1StreamParser; import org.bouncycastle.asn1.DEREncodable; import org.bouncycastle.asn1.DERIA5String; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.DERSet; import org.bouncycastle.asn1.DERTaggedObject; import org.bouncycastle.asn1.pkcs.CertificationRequestInfo; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.jce.PKCS10CertificationRequest; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.Certificate; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javax.security.auth.x500.X500Principal; /** * ?. * * ?????????. * ? {@link #getRootCertificate()} ?????. * * ??????????????????????. * ?????{@link #requestCertificate(byte[])} ????. * * NOTE: * ??????????????? * Subject Alternative Names (SANs)???????????. * * @author NTT DOCOMO, INC. */ class CertificateAuthority { /** * ?. */ private final RootKeyStoreManager mRootKeyStoreMgr; /** * . */ private final Logger mLogger = Logger.getLogger("LocalCA"); /** * ???. */ private final String mIssuerName; /** * . * * @param context * @param issuerName ??? * @param keyStoreFileName ??? */ CertificateAuthority(final Context context, final String issuerName, final String keyStoreFileName) { mRootKeyStoreMgr = new RootKeyStoreManager(context, issuerName, keyStoreFileName); mIssuerName = issuerName; } /** * ??. * * <p> * ??????????????????. * 10?. * </p> * * @return */ byte[] getRootCertificate() { final KeyStore[] keyStore = new KeyStore[1]; final KeyStoreError[] errors = new KeyStoreError[1]; final CountDownLatch lock = new CountDownLatch(1); // ? mRootKeyStoreMgr.requestKeyStore(mIssuerName, new KeyStoreCallback() { @Override public void onSuccess(final KeyStore result, final Certificate cert, final Certificate rootCert) { mLogger.severe("Got Root CA keystore: subject = issuer = " + mIssuerName); keyStore[0] = result; lock.countDown(); } @Override public void onError(final KeyStoreError error) { mLogger.severe("Failed to get keystore: " + error); errors[0] = error; lock.countDown(); } }); try { lock.await(10, TimeUnit.SECONDS); if (keyStore[0] == null && errors[0] == null) { mLogger.log(Level.SEVERE, "Timeout was occurred for keystore generation."); return null; } if (keyStore[0] != null) { Certificate rootCert = keyStore[0].getCertificate(mIssuerName); if (rootCert != null) { return rootCert.getEncoded(); } else { throw new IllegalStateException("Fix bug."); } } } catch (InterruptedException e) { mLogger.log(Level.SEVERE, "Failed to encode certificate to byte array.", e); } catch (GeneralSecurityException e) { mLogger.log(Level.SEVERE, "Failed to get certificate from keystore.", e); } return null; } /** * ??????. * * @param pkcs10 PKCS#10?????. * @return ???????. ?????null */ byte[] requestCertificate(final byte[] pkcs10) { try { if (getRootCertificate() == null) { return null; } // ?? PKCS10CertificationRequest request = new PKCS10CertificationRequest(pkcs10); PrivateKey signingKey = mRootKeyStoreMgr.getPrivateKey(mIssuerName); KeyPair keyPair = new KeyPair(request.getPublicKey(), signingKey); X500Principal subject = new X500Principal("CN=localhost"); X500Principal issuer = new X500Principal("CN=" + mIssuerName); GeneralNames generalNames = parseSANs(request); // Certificate certificate = mRootKeyStoreMgr.generateX509V3Certificate(keyPair, subject, issuer, generalNames, false); return certificate.getEncoded(); } catch (GeneralSecurityException e) { mLogger.log(Level.SEVERE, "Failed to generate certificate to byte array.", e); } catch (IOException e) { mLogger.log(Level.SEVERE, "Failed to parse SANs in certificate.", e); } return null; } /** * ???? Subject Alternative Names (SANs) ??. * * @param request ??? * @return SubjectAlternativeNames? {@link GeneralNames} * @throws IOException ????? */ private GeneralNames parseSANs(final PKCS10CertificationRequest request) throws IOException { List<ASN1Encodable> generalNames = new ArrayList<>(); CertificationRequestInfo info = request.getCertificationRequestInfo(); ASN1Set attributes = info.getAttributes(); for (int i = 0; i < attributes.size(); i++) { DEREncodable extensionRequestObj = attributes.getObjectAt(i); if (!(extensionRequestObj instanceof DERSequence)) { continue; } DERSequence extensionRequest = (DERSequence) extensionRequestObj; if (extensionRequest.size() != 2) { continue; } DEREncodable idObj = extensionRequest.getObjectAt(0); DEREncodable contentObj = extensionRequest.getObjectAt(1); if (!(idObj instanceof ASN1ObjectIdentifier && contentObj instanceof DERSet)) { continue; } ASN1ObjectIdentifier id = (ASN1ObjectIdentifier) idObj; DERSet content = (DERSet) contentObj; if (!id.getId().equals("1.2.840.113549.1.9.14")) { continue; } if (content.size() < 1) { continue; } DEREncodable extensionsObj = content.getObjectAt(0); if (!(extensionsObj instanceof DERSequence)) { continue; } DERSequence extensions = (DERSequence) extensionsObj; for (int k = 0; k < extensions.size(); k++) { DEREncodable extensionObj = extensions.getObjectAt(k); if (!(extensionObj instanceof DERSequence)) { continue; } DERSequence extension = (DERSequence) extensionObj; if (extension.size() != 2) { continue; } DEREncodable extensionIdObj = extension.getObjectAt(0); DEREncodable extensionContentObj = extension.getObjectAt(1); if (!(extensionIdObj instanceof ASN1ObjectIdentifier)) { continue; } ASN1ObjectIdentifier extensionId = (ASN1ObjectIdentifier) extensionIdObj; if (extensionId.getId().equals("2.5.29.17")) { DEROctetString san = (DEROctetString) extensionContentObj; ASN1StreamParser sanParser = new ASN1StreamParser(san.parser().getOctetStream()); DEREncodable namesObj = sanParser.readObject().getDERObject(); if (namesObj instanceof DERSequence) { DERSequence names = (DERSequence) namesObj; for (int m = 0; m < names.size(); m++) { DEREncodable nameObj = names.getObjectAt(m); if (nameObj instanceof DERTaggedObject) { DERTaggedObject name = (DERTaggedObject) nameObj; switch (name.getTagNo()) { case GeneralName.dNSName: generalNames.add(new GeneralName(GeneralName.dNSName, DERIA5String.getInstance(name, false))); break; case GeneralName.iPAddress: generalNames.add(new GeneralName(GeneralName.iPAddress, DEROctetString.getInstance(name, true))); break; } } } } } } } if (generalNames.size() > 0) { return new GeneralNames(new DERSequence(generalNames.toArray(new ASN1Encodable[generalNames.size()]))); } return null; } }