All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.azure.sdk.iot.deps.auth.IotHubSSLContext Maven / Gradle / Ivy

/*
 *  Copyright (c) Microsoft. All rights reserved.
 *  Licensed under the MIT license. See LICENSE file in the project root for full license information.
 */

package com.microsoft.azure.sdk.iot.deps.auth;

import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
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.UUID;

public class IotHubSSLContext
{
    private SSLContext sslContext = null;

    private static final String SSL_CONTEXT_INSTANCE = "TLSv1.2";

    private static final String CERTIFICATE_ALIAS = "cert-alias";
    private static final String PRIVATE_KEY_ALIAS = "key-alias";

    private static final String TRUSTED_IOT_HUB_CERT_PREFIX = "trustedIotHubCert-";

    /**
     * Creates a SSLContext for the IotHub.
     *
     * @throws KeyStoreException  if no Provider supports a KeyStoreSpi implementation for the specified type or
     *                            if the keystore has not been initialized,
     *                            or the given alias already exists and does not identify an entry containing a trusted certificate,
     *                            or this operation fails for some other reason.
     * @throws KeyManagementException As per https://docs.oracle.com/javase/7/docs/api/java/security/KeyManagementException.html
     * @throws IOException If the certificate provided was null or invalid
     * @throws CertificateException As per https://docs.oracle.com/javase/7/docs/api/java/security/cert/CertificateException.html
     * @throws NoSuchAlgorithmException if the default SSL Context cannot be created
     */
    public IotHubSSLContext()
            throws KeyStoreException, KeyManagementException, IOException, CertificateException, NoSuchAlgorithmException
    {
        //Codes_SRS_IOTHUBSSLCONTEXT_25_001: [**The constructor shall create a default certificate to be used with IotHub.**]**
        IotHubCertificateManager defaultCert = new IotHubCertificateManager();
        generateDefaultSSLContext(defaultCert);
    }

    /**
     * Constructor that takes and saves an SSLContext object
     * @param sslContext the ssl context to save
     */
    public IotHubSSLContext(SSLContext sslContext)
    {
        if (sslContext == null)
        {
            //Codes_SRS_IOTHUBSSLCONTEXT_34_028: [If the provided sslContext is null, this function shall throw an IllegalArgumentException.]
            throw new IllegalArgumentException("sslContext cannot be null");
        }

        //Codes_SRS_IOTHUBSSLCONTEXT_34_027: [This constructor shall save the provided ssl context.]
        this.sslContext = sslContext;
    }

    /**
     * Creates a default SSLContext for the IotHub with the specified certificate.
     *
     * @param trustedCert the certificate to be trusted
     * @param isPath if the trustedCert is a path to the trusted cert, or if it is the certificate itself
     *
     * @throws KeyStoreException  if no Provider supports a KeyStoreSpi implementation for the specified type or
     *                            if the keystore has not been initialized,
     *                            or the given alias already exists and does not identify an entry containing a trusted certificate,
     *                            or this operation fails for some other reason.
     * @throws KeyManagementException As per https://docs.oracle.com/javase/7/docs/api/java/security/KeyManagementException.html
     * @throws IOException If the certificate provided was null or invalid
     * @throws CertificateException As per https://docs.oracle.com/javase/7/docs/api/java/security/cert/CertificateException.html
     * @throws NoSuchAlgorithmException if the default SSL Context cannot be created
     */
    public IotHubSSLContext(String trustedCert, boolean isPath)
            throws KeyStoreException, KeyManagementException, IOException, CertificateException, NoSuchAlgorithmException
    {
        IotHubCertificateManager defaultCert = new IotHubCertificateManager();

        if (isPath)
        {
            //Codes_SRS_IOTHUBSSLCONTEXT_34_025: [If the provided cert is a path, this function shall set the path of the default cert to the provided cert path.]
            defaultCert.setCertificatesPath(trustedCert);
        }
        else
        {
            //Codes_SRS_IOTHUBSSLCONTEXT_34_026: [If the provided cert is not a path, this function shall set the default cert to the provided cert.]
            defaultCert.setCertificates(trustedCert);
        }

        generateDefaultSSLContext(defaultCert);
    }

    /**
     * Creates a default SSLContext for the IotHub with the specified certificate.
     * @param publicKeyCertificateString the public key for x509 authentication
     * @param privateKeyString the private key for x509 authentication
     * @param cert the trusted certificate
     * @param isPath If the provided cert is a path, or the actual certificate itself
     *
     * @throws KeyStoreException  if no Provider supports a KeyStoreSpi implementation for the specified type or
     *                            if the keystore has not been initialized,
     *                            or the given alias already exists and does not identify an entry containing a trusted certificate,
     *                            or this operation fails for some other reason.
     * @throws KeyManagementException As per https://docs.oracle.com/javase/7/docs/api/java/security/KeyManagementException.html
     * @throws IOException If the certificate provided was null or invalid
     * @throws CertificateException As per https://docs.oracle.com/javase/7/docs/api/java/security/cert/CertificateException.html
     * @throws NoSuchAlgorithmException if the default SSL Context cannot be created
     */
    public IotHubSSLContext(String publicKeyCertificateString, String privateKeyString, String cert, boolean isPath)
            throws KeyStoreException, KeyManagementException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException
    {
        IotHubCertificateManager defaultCert = new IotHubCertificateManager();

        if (isPath)
        {
            //Codes_SRS_IOTHUBSSLCONTEXT_34_040: [If the provided cert is a path, this function shall set the path of the default cert to the provided cert path.]
            defaultCert.setCertificatesPath(cert);
        }
        else
        {
            //Codes_SRS_IOTHUBSSLCONTEXT_34_041: [If the provided cert is not a path, this function shall set the default cert to the provided cert.]
            defaultCert.setCertificates(cert);
        }

        // Codes_SRS_IOTHUBSSLCONTEXT_34_042: [This constructor shall generate a temporary password to protect the created keystore holding the private key.]
        // Codes_SRS_IOTHUBSSLCONTEXT_34_043: [The constructor shall create default SSL context for TLSv1.2.]
        // Codes_SRS_IOTHUBSSLCONTEXT_34_044: [The constructor shall create a keystore containing the public key certificate and the private key.]
        // Codes_SRS_IOTHUBSSLCONTEXT_34_045: [The constructor shall initialize a default trust manager factory that accepts communications from Iot Hub.]
        // Codes_SRS_IOTHUBSSLCONTEXT_34_046: [The constructor shall initialize SSL context with its initialized keystore, its initialized TrustManagerFactory and a new secure random.]
        generateSSLContextWithKeys(publicKeyCertificateString, privateKeyString, defaultCert);
    }

    /**
     * Constructor that takes a public key certificate and private key pair.
     *
     * @param publicKeyCertificateString The PEM formatted public key certificate string
     * @param privateKeyString The PEM formatted private key string
     * @throws KeyManagementException If the SSLContext could not be initialized
     * @throws IOException If an IO exception occurs
     * @throws CertificateException If a certificate cannot be loaded
     * @throws KeyStoreException If the provided certificates cannot be loaded into the JVM keystore
     * @throws UnrecoverableKeyException if accessing the passphrase protected keystore fails due to the key
     * @throws NoSuchAlgorithmException if the default SSLContext cannot be generated
     */
    public IotHubSSLContext(String publicKeyCertificateString, String privateKeyString)
            throws KeyManagementException, IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException
    {
        generateSSLContextWithKeys(publicKeyCertificateString, privateKeyString, new IotHubCertificateManager());
    }

    /**
     * Getter for the IotHubSSLContext
     * @return SSLContext defined for the IotHub.
     */
    public SSLContext getSSLContext()
    {
        //Codes_SRS_IOTHUBSSLCONTEXT_25_017: [*This method shall return the value of sslContext.**]**
        return this.sslContext;
    }

    /**
     * Creates an SSLContext from a public key certificate, private key, and certificate manager.
     *
     * @param publicKeyCertificateString The PEM formatted public key certificate string
     * @param privateKeyString The PEM formatted private key string
     * @throws KeyManagementException If the SSLContext could not be initialized
     * @throws IOException If an IO exception occurs
     * @throws CertificateException If a certificate cannot be loaded
     * @throws KeyStoreException If the provided certificates cannot be loaded into the JVM keystore
     * @throws UnrecoverableKeyException if accessing the passphrase protected keystore fails due to the key
     * @throws NoSuchAlgorithmException if the default SSLContext cannot be generated
     */
    private void generateSSLContextWithKeys(String publicKeyCertificateString, String privateKeyString, IotHubCertificateManager certificateManager)
            throws KeyManagementException, IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException
    {
        Key privateKey = IotHubSSLContext.parsePrivateKey(privateKeyString);
        Collection certChain = IotHubSSLContext.parsePublicKeyCertificate(publicKeyCertificateString);

        X509Certificate[] certs = certChain.toArray(new X509Certificate[certChain.size()]);

        //Codes_SRS_IOTHUBSSLCONTEXT_34_018: [This constructor shall generate a temporary password to protect the created keystore holding the private key.]
        char[] temporaryPassword = generateTemporaryPassword();

        //Codes_SRS_IOTHUBSSLCONTEXT_34_020: [The constructor shall create a keystore containing the public key certificate and the private key.]
        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
        keystore.load(null);
        keystore.setCertificateEntry(CERTIFICATE_ALIAS, certs[0]);
        keystore.setKeyEntry(PRIVATE_KEY_ALIAS, privateKey, temporaryPassword, certs);

        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(keystore, temporaryPassword);

        //wipe password from stack memory after done using it
        Arrays.fill(temporaryPassword, 0, temporaryPassword.length, '0');

        //Codes_SRS_IOTHUBSSLCONTEXT_34_021: [The constructor shall initialize a default trust manager factory that accepts communications from Iot Hub.]
        TrustManagerFactory trustManagerFactory = generateTrustManagerFactory(certificateManager, keystore);

        //Codes_SRS_IOTHUBSSLCONTEXT_34_019: [The constructor shall create default SSL context for TLSv1.2.]
        this.sslContext = SSLContext.getInstance(SSL_CONTEXT_INSTANCE);

        //Codes_SRS_IOTHUBSSLCONTEXT_34_024: [The constructor shall initialize SSL context with its initialized keystore, its initialized TrustManagerFactory and a new secure random.]
        this.sslContext.init(kmf.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
    }

    /**
     * Generates the default SSL Context and saves it to this object's SSLContext object
     *
     * @throws KeyStoreException If the provided certificateManager's certificates cannot be loaded into the trust manager used in creating the SSLContext
     * @throws IOException If a valid certificate could not be retrieved from the provided certificateManager
     * @throws CertificateException If the provided certificateManager cannot retrieve any certificates for any of a variety of reasons
     * @throws KeyManagementException If the generated SSLContext cannot be initialized given the provided certificateManager's certificates
     * @throws NoSuchAlgorithmException if default ssl context cannot be created or the trust manager cannot be created
     */
    private void generateDefaultSSLContext(IotHubCertificateManager certificateManager)
            throws KeyStoreException, IOException, CertificateException, KeyManagementException, NoSuchAlgorithmException
    {
        //Codes_SRS_IOTHUBSSLCONTEXT_25_002: [The constructor shall create default SSL context for TLSv1.2.]
        this.sslContext = SSLContext.getInstance(SSL_CONTEXT_INSTANCE);

        //Codes_SRS_IOTHUBSSLCONTEXT_25_003: [The constructor shall create default TrustManagerFactory with the default algorithm.]
        //Codes_SRS_IOTHUBSSLCONTEXT_25_004: [The constructor shall create default KeyStore instance with the default type and initialize it.]
        //Codes_SRS_IOTHUBSSLCONTEXT_25_005: [The constructor shall set the above created certificateManager into a keystore.]
        //Codes_SRS_IOTHUBSSLCONTEXT_25_006: [The constructor shall initialize TrustManagerFactory with the above initialized keystore.]
        //Codes_SRS_IOTHUBSSLCONTEXT_25_007: [The constructor shall initialize SSL context with the above initialized TrustManagerFactory and a new secure random.]
        TrustManagerFactory trustManagerFactory = generateTrustManagerFactory(certificateManager, null);

        this.sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
    }

    /**
     * Generate a trust key store that has the public key needed to trust all messages from Iot Hub
     * @param certificateManager the certificate manager to build the trust manager factory from
     * @param trustKeyStore the trust key store to load. If this is null, a default trust key store shall be generated
     * @return The default trust manager factory
     * @throws NoSuchAlgorithmException if the default sslcontext cannot be created
     * @throws KeyStoreException if the created key store cannot be created
     * @throws IOException If a valid certificate could not be defined.
     * @throws CertificateException If a certificate cannot be created by a certificate factory
     */
    private TrustManagerFactory generateTrustManagerFactory(IotHubCertificateManager certificateManager, KeyStore trustKeyStore)
            throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException
    {
        if (trustKeyStore == null)
        {
            trustKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustKeyStore.load(null);
        }

        for (Certificate c : certificateManager.getCertificateCollection())
        {
            trustKeyStore.setCertificateEntry(TRUSTED_IOT_HUB_CERT_PREFIX + UUID.randomUUID(), c);
        }

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(trustKeyStore);

        return trustManagerFactory;
    }

    private char[] generateTemporaryPassword()
    {
        byte[] randomBytes = new byte[256];
        char[] randomChars = new char[256];
        new SecureRandom().nextBytes(randomBytes);

        for (int i = 0; i < 256; i++)
        {
            randomChars[i] = (char) randomBytes[i];
        }

        return randomChars;
    }

    private static Key parsePrivateKey(String privateKeyString) throws CertificateException
    {
        try
        {
            // Codes_SRS_IOTHUBSSLCONTEXT_34_031: [This function shall return a Private Key instance created by the provided PEM formatted privateKeyString.]
            Security.addProvider(new BouncyCastleProvider());
            PEMParser privateKeyParser = new PEMParser(new StringReader(privateKeyString));
            Object possiblePrivateKey = privateKeyParser.readObject();
            return IotHubSSLContext.getPrivateKey(possiblePrivateKey);
        }
        catch (Exception e)
        {
            // Codes_SRS_IOTHUBSSLCONTEXT_34_032: [If any exception is encountered while attempting to create the private key instance, this function shall throw a CertificateException.]
            throw new CertificateException(e);
        }
    }

    private static Collection parsePublicKeyCertificate(String publicKeyCertificateString) throws CertificateException
    {
        try
        {
            Collection certChain = new ArrayList<>();

            // Codes_SRS_IOTHUBSSLCONTEXT_34_033: [This function shall return the X509Certificate cert chain specified by the PEM formatted publicKeyCertificateString.]
            Security.addProvider(new BouncyCastleProvider());

            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
            final PemReader publicKeyCertificateReader = new PemReader(new StringReader(publicKeyCertificateString));

            try
            {
                PemObject possiblePublicKeyCertificate;
                while (((possiblePublicKeyCertificate = publicKeyCertificateReader.readPemObject()) != null))
                {
                    byte[] content = possiblePublicKeyCertificate.getContent();
                    if (content.length > 0)
                    {
                        final ByteArrayInputStream bais = new ByteArrayInputStream(content);

                        while (bais.available() > 0)
                        {
                            final Certificate cert = certFactory.generateCertificate(bais);
                            if (cert instanceof X509Certificate)
                            {
                                certChain.add((X509Certificate) cert);
                            }
                        }
                    }
                    else
                    {
                        break;
                    }
                }
            }
            finally
            {
                publicKeyCertificateReader.close();
            }

            return certChain;
        }
        catch (Exception e)
        {
            // Codes_SRS_IOTHUBSSLCONTEXT_34_034: [If any exception is encountered while attempting to create the public key certificate instance, this function shall throw a CertificateException.]
            throw new CertificateException(e);
        }
    }

    private static Key getPrivateKey(Object possiblePrivateKey) throws IOException
    {
        if (possiblePrivateKey instanceof PEMKeyPair)
        {
            return new JcaPEMKeyConverter().getKeyPair((PEMKeyPair) possiblePrivateKey)
                    .getPrivate();
        }
        else if (possiblePrivateKey instanceof PrivateKeyInfo)
        {
            return new JcaPEMKeyConverter().getPrivateKey((PrivateKeyInfo) possiblePrivateKey);
        }
        else
        {
            throw new IOException("Unable to parse private key, type unknown");
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy