org.deviceconnect.android.ssl.EndPointKeyStoreManager.java Source code

Java tutorial

Introduction

Here is the source code for org.deviceconnect.android.ssl.EndPointKeyStoreManager.java

Source

/*
 EndPointKeyStoreManager.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.ComponentName;
import android.content.Context;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.jce.PKCS10CertificationRequest;
import org.deviceconnect.android.BuildConfig;

import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.security.auth.x500.X500Principal;

/**
 * ????.
 *
 * <p>
 * ?????????????
 * Device Connect Manager?????????????.
 *
 * ??????????????.
 *
 * NOTE: ???????1?.
 * </p>
 *
 * @author NTT DOCOMO, INC.
 */
public class EndPointKeyStoreManager extends AbstractKeyStoreManager implements KeyStoreManager {

    /**
     * ??.
     */
    private static final ComponentName DEFAULT_ROOT_CA = new ComponentName("org.deviceconnect.android.manager",
            "org.deviceconnect.android.manager.ssl.DConnectCertificateAuthorityService");

    /**
     * ?.
     */
    private final String mAlias;

    /**
     * ?????.
     */
    private final ComponentName mRootCA;

    /**
     * .
     */
    private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();

    /**
     * .
     */
    private final Logger mLogger = Logger.getLogger("LocalCA");

    /**
     * ?SANs????????.
     */
    private final List<SAN> mSANs = new ArrayList<>();

    /**
     * .
     *
     * @param context 
     * @param keyStorePath ??
     */
    public EndPointKeyStoreManager(final Context context, final String keyStorePath) {
        this(context, keyStorePath, context.getPackageName());
    }

    /**
     * .
     *
     * @param context 
     * @param keyStorePath ??
     */
    public EndPointKeyStoreManager(final Context context, final String keyStorePath, final String alias) {
        this(context, keyStorePath, alias, DEFAULT_ROOT_CA);
    }

    /**
     * .
     *
     * @param context 
     * @param keyStorePath ??
     * @param rootCA ???
     */
    EndPointKeyStoreManager(final Context context, final String keyStorePath, final String alias,
            final ComponentName rootCA) {
        super(context, keyStorePath);
        mRootCA = rootCA;
        mAlias = alias;
        restoreIPAddress(alias);
    }

    /**
     * ??????IP??.
     *
     * @param alias ?
     */
    private void restoreIPAddress(final String alias) {
        if (BuildConfig.DEBUG) {
            mLogger.log(Level.INFO, "Checking IP Addresses...: alias = " + alias);
        }
        try {
            Certificate certificate = mKeyStore.getCertificate(alias);
            if (certificate == null) {
                if (BuildConfig.DEBUG) {
                    mLogger.info("Certificate is not stored yet: alias = " + alias);
                }
                return;
            }
            if (BuildConfig.DEBUG) {
                mLogger.log(Level.INFO, "Restoring IP Addresses...: alias = " + alias);
            }
            if (certificate instanceof X509Certificate) {
                X509Certificate x509 = (X509Certificate) certificate;
                Collection<List<?>> names = x509.getSubjectAlternativeNames();
                if (names != null) {
                    if (BuildConfig.DEBUG) {
                        mLogger.log(Level.INFO, "SANs: size = " + names.size());
                    }
                    for (Iterator<List<?>> it = names.iterator(); it.hasNext();) {
                        List<?> list = it.next();
                        if (list.size() == 2) {
                            Object tagNo = list.get(0);
                            Object value = list.get(1);

                            if (BuildConfig.DEBUG) {
                                mLogger.info("SAN: tagNo = " + tagNo + ", value = " + value);
                            }

                            if (tagNo instanceof Integer && value instanceof String) {
                                mSANs.add(new SAN((Integer) tagNo, (String) value));
                            }
                        }
                    }
                } else {
                    if (BuildConfig.DEBUG) {
                        mLogger.log(Level.INFO, "No SANs is defined in certificate.");
                    }
                }
            } else {
                mLogger.log(Level.SEVERE, "Certificate format is not X.509: class = " + certificate.getClass());
            }
        } catch (CertificateParsingException e) {
            mLogger.log(Level.WARNING, "Failed to parse IP Addresses of certificate.", e);
        } catch (KeyStoreException e) {
            mLogger.log(Level.WARNING, "Failed to restore IP Addresses.", e);
        }
    }

    /**
     * ??IP??????????????.
     *
     * @param ipAddress IP
     * @return ????<code>true</code>?????????<code>false</code>
     */
    private boolean hasIPAddress(final String ipAddress) {
        for (SAN address : mSANs) {
            if (address.mName.equals(ipAddress)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void requestKeyStore(final String ipAddress, final KeyStoreCallback callback) {
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                if (BuildConfig.DEBUG) {
                    mLogger.info("Requested keystore: alias = " + getAlias() + ", IP Address = " + ipAddress);
                }
                try {
                    String alias = getAlias();
                    if (hasIPAddress(ipAddress)) {
                        if (BuildConfig.DEBUG) {
                            mLogger.info("Certificate is cached for alias: " + alias);
                        }
                        Certificate[] chain = mKeyStore.getCertificateChain(getAlias());
                        callback.onSuccess(mKeyStore, chain[0], chain[1]);
                    } else {
                        if (BuildConfig.DEBUG) {
                            mLogger.info("Generating key pair...");
                        }
                        final KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
                        final KeyPair keyPair = keyGenerator.generateKeyPair();

                        if (BuildConfig.DEBUG) {
                            mLogger.info("Generated key pair.");
                            mLogger.info("Executing certificate request...");
                        }

                        final CertificateAuthorityClient localCA = new CertificateAuthorityClient(mContext,
                                mRootCA);

                        final List<ASN1Encodable> names = new ArrayList<>();
                        names.add(new GeneralName(GeneralName.iPAddress, ipAddress));
                        for (SAN cache : mSANs) {
                            if (!cache.mName.equals(ipAddress)) {
                                names.add(new GeneralName(cache.mTagNo, cache.mName));
                            }
                        }
                        names.add(new GeneralName(GeneralName.iPAddress, "0.0.0.0"));
                        names.add(new GeneralName(GeneralName.iPAddress, "127.0.0.1"));
                        names.add(new GeneralName(GeneralName.dNSName, "localhost"));
                        GeneralNames generalNames = new GeneralNames(
                                new DERSequence(names.toArray(new ASN1Encodable[names.size()])));

                        localCA.executeCertificateRequest(createCSR(keyPair, "localhost", generalNames),
                                new CertificateRequestCallback() {
                                    @Override
                                    public void onCreate(final Certificate cert, final Certificate rootCert) {
                                        if (BuildConfig.DEBUG) {
                                            mLogger.info("Generated server certificate");
                                        }

                                        try {
                                            Certificate[] chain = { cert, rootCert };
                                            setCertificate(chain, keyPair.getPrivate());
                                            saveKeyStore();
                                            if (BuildConfig.DEBUG) {
                                                mLogger.info("Saved server certificate");
                                            }
                                            mSANs.add(new SAN(GeneralName.iPAddress, ipAddress));
                                            callback.onSuccess(mKeyStore, cert, rootCert);
                                        } catch (Exception e) {
                                            mLogger.log(Level.SEVERE, "Failed to save server certificate", e);
                                            callback.onError(KeyStoreError.FAILED_BACKUP_KEYSTORE);
                                        } finally {
                                            localCA.dispose();
                                        }
                                    }

                                    @Override
                                    public void onError() {
                                        mLogger.severe("Failed to generate server certificate");

                                        localCA.dispose();
                                        callback.onError(KeyStoreError.FAILED_BACKUP_KEYSTORE);
                                    }
                                });
                    }
                } catch (KeyStoreException e) {
                    callback.onError(KeyStoreError.BROKEN_KEYSTORE);
                } catch (GeneralSecurityException e) {
                    callback.onError(KeyStoreError.UNSUPPORTED_CERTIFICATE_FORMAT);
                }
            }
        });
    }

    /**
     * ??.
     *
     * @return 
     */
    private String getAlias() {
        return mAlias;
    }

    /**
     * ??????.
     *
     * @param certChain ?
     * @param privateKey 
     * @throws KeyStoreException ?????
     */
    private void setCertificate(final Certificate[] certChain, final PrivateKey privateKey)
            throws KeyStoreException {
        mKeyStore.setKeyEntry(getAlias(), privateKey, null, certChain);
    }

    /**
     * ??????.
     *
     * @param keyPair 
     * @param commonName ?
     * @param generalNames SANs
     * @return ????
     * @throws GeneralSecurityException ?????
     */
    private static PKCS10CertificationRequest createCSR(final KeyPair keyPair, final String commonName,
            final GeneralNames generalNames) throws GeneralSecurityException {
        final String signatureAlgorithm = "SHA256WithRSAEncryption";
        final X500Principal principal = new X500Principal(
                "CN=" + commonName + ", O=Device Connect Project, L=N/A, ST=N/A, C=JP");
        DERSequence sanExtension = new DERSequence(
                new ASN1Encodable[] { X509Extensions.SubjectAlternativeName, new DEROctetString(generalNames) });
        DERSet extensions = new DERSet(new DERSequence(sanExtension));
        DERSequence extensionRequest = new DERSequence(
                new ASN1Encodable[] { PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extensions });
        DERSet attributes = new DERSet(extensionRequest);
        return new PKCS10CertificationRequest(signatureAlgorithm, principal, keyPair.getPublic(), attributes,
                keyPair.getPrivate());
    }

    private static class SAN {
        final int mTagNo;
        final String mName;

        SAN(final int tagNo, final String name) {
            mTagNo = tagNo;
            mName = name;
        }
    }
}