com.oath.auth.Utils.java Source code

Java tutorial

Introduction

Here is the source code for com.oath.auth.Utils.java

Source

/**
 * Copyright 2017 Yahoo Holdings, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.oath.auth;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;

import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.io.Resources;

public class Utils {

    private static final Logger LOG = LoggerFactory.getLogger(Utils.class);

    private static final String SSLCONTEXT_ALGORITHM = "TLSv1.2";
    private static final String PROP_KEY_WAIT_TIME = "athenz.cert_refresher.key_wait_time";

    private static final char[] KEYSTORE_PASSWORD = "secret".toCharArray();

    // how long to wait for keys - default 10 mins

    private static final long KEY_WAIT_TIME_MILLIS = TimeUnit.MINUTES
            .toMillis(Integer.parseInt(System.getProperty(PROP_KEY_WAIT_TIME, "10")));

    public static KeyStore getKeyStore(final String jksFilePath) throws Exception {
        return getKeyStore(jksFilePath, KEYSTORE_PASSWORD);
    }

    public static KeyStore getKeyStore(final String jksFilePath, final char[] password) throws Exception {
        final KeyStore keyStore = KeyStore.getInstance("JKS");
        ///CLOVER:OFF
        if (Paths.get(jksFilePath).isAbsolute()) {
            // Can not cover this branch in unit test. Can not refer any files by absolute paths
            try (InputStream jksFileInputStream = new FileInputStream(jksFilePath)) {
                keyStore.load(jksFileInputStream, password);
                return keyStore;
            }
        }
        ///CLOVER:ON

        try (InputStream jksFileInputStream = Resources.getResource(jksFilePath).openStream()) {
            keyStore.load(jksFileInputStream, password);
            return keyStore;
        }
    }

    public static KeyManager[] getKeyManagers(final String athenzPublicCert, final String athenzPrivateKey)
            throws Exception {
        final KeyStore keystore = createKeyStore(athenzPublicCert, athenzPrivateKey);
        final KeyManagerFactory keyManagerFactory = KeyManagerFactory
                .getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(keystore, KEYSTORE_PASSWORD);
        return keyManagerFactory.getKeyManagers();
    }

    /**
     * Generate the KeyRefresher object first as the server will need access to
     * it (to turn it off and on as needed). It requires that the proxies are
     * created which are then stored in the KeyRefresher. This method requires
     * the paths to the private key and certificate files along with the
     * trust-store path which has been created already and just needs to be
     * monitored for changes. Using default password of "secret" for both stores.
     * @param trustStorePath path to the trust-store
     * @param certPath path to the certificate file
     * @param keyPath path to the private key file
     * @return KeyRefresher object
     */
    public static KeyRefresher generateKeyRefresher(final String trustStorePath, final String athenzPublicCert,
            final String athenzPrivateKey) throws Exception {
        return generateKeyRefresher(trustStorePath, KEYSTORE_PASSWORD, athenzPublicCert, athenzPrivateKey);
    }

    /**
     * Generate the KeyRefresher object first as the server will need access to
     * it (to turn it off and on as needed). It requires that the proxies are
     * created which are then stored in the KeyRefresher. This method requires
     * the paths to the private key and certificate files along with the
     * trust-store path which has been created already and just needs to be
     * monitored for changes.
     * @param trustStorePath path to the trust-store
     * @param trustStorePassword trust store password
     * @param certPath path to the certificate file
     * @param keyPath path to the private key file
     * @return KeyRefresher object
     */
    public static KeyRefresher generateKeyRefresher(final String trustStorePath, final String trustStorePassword,
            final String athenzPublicCert, final String athenzPrivateKey) throws Exception {
        return generateKeyRefresher(trustStorePath, trustStorePassword.toCharArray(), athenzPublicCert,
                athenzPrivateKey);
    }

    /**
     * Generate the KeyRefresher object first as the server will need access to
     * it (to turn it off and on as needed). It requires that the proxies are
     * created which are then stored in the KeyRefresher. This method requires
     * the paths to the private key and certificate files along with the
     * trust-store path which has been created already and just needs to be
     * monitored for changes.
     * @param trustStorePath path to the trust-store
     * @param trustStorePassword trust store password
     * @param certPath path to the certificate file
     * @param keyPath path to the private key file
     * @return KeyRefresher object
     */
    public static KeyRefresher generateKeyRefresher(final String trustStorePath, final char[] trustStorePassword,
            final String athenzPublicCert, final String athenzPrivateKey) throws Exception {
        TrustStore trustStore = new TrustStore(trustStorePath,
                new JavaKeyStoreProvider(trustStorePath, trustStorePassword));
        return getKeyRefresher(athenzPublicCert, athenzPrivateKey, trustStore);
    }

    /**
     * Generate the KeyRefresher object first as the server will need access to
     * it (to turn it off and on as needed). It requires that the proxies are
     * created which are then stored in the KeyRefresher. This method requires
     * the paths to the private key and certificate files along with the
     * trust-store path which has been created already and just needs to be
     * monitored for changes. Using default password of "secret" for both stores.
     * @param trustStorePath path to the trust-store
     * @param certPath path to the certificate file
     * @param keyPath path to the private key file
     * @return KeyRefresher object
     */
    public static KeyRefresher generateKeyRefresherFromCaCert(final String caCertPath,
            final String athenzPublicCert, final String athenzPrivateKey) throws Exception {
        TrustStore trustStore = new TrustStore(caCertPath, new CaCertKeyStoreProvider(caCertPath));
        return getKeyRefresher(athenzPublicCert, athenzPrivateKey, trustStore);
    }

    static KeyRefresher getKeyRefresher(String athenzPublicCert, String athenzPrivateKey, TrustStore trustStore)
            throws Exception {
        KeyManagerProxy keyManagerProxy = new KeyManagerProxy(getKeyManagers(athenzPublicCert, athenzPrivateKey));
        TrustManagerProxy trustManagerProxy = new TrustManagerProxy(trustStore.getTrustManagers());
        return new KeyRefresher(athenzPublicCert, athenzPrivateKey, trustStore, keyManagerProxy, trustManagerProxy);
    }

    /**
     * this method will create a new SSLContext object that can be updated on the fly should the
     * public/private keys / trustStore change.
     * @param keyManagerProxy uses standard KeyManager interface except also allows
     *        for the updating of KeyManager on the fly
     * @param trustManagerProxy uses standard TrustManager interface except also allows
     *        for the updating of TrustManager on the fly
     * @return a valid SSLContext object using the passed in key/trust managers
     * @throws Exception sslContext.init can throw exceptions
     */
    public static SSLContext buildSSLContext(KeyManagerProxy keyManagerProxy, TrustManagerProxy trustManagerProxy)
            throws Exception {
        final SSLContext sslContext = SSLContext.getInstance(SSLCONTEXT_ALGORITHM);
        sslContext.init(new KeyManager[] { keyManagerProxy }, new TrustManager[] { trustManagerProxy }, null);
        return sslContext;
    }

    /**
     * @param athenzPublicCert the location on the public certificate file
     * @param athenzPrivateKey the location of the private key file
     * @return a KeyStore with loaded key and certificate
     * @throws Exception KeyStore generation can throw Exception for many reasons
     */
    public static KeyStore createKeyStore(final String athenzPublicCert, final String athenzPrivateKey)
            throws Exception {

        X509Certificate certificate;
        PrivateKey privateKey = null;
        KeyStore keyStore = null;
        File certFile = null;
        File keyFile = null;

        try {
            if (Paths.get(athenzPublicCert).isAbsolute() && Paths.get(athenzPrivateKey).isAbsolute()) {
                certFile = new File(athenzPublicCert);
                keyFile = new File(athenzPrivateKey);
                long startTime = System.currentTimeMillis();
                while (!certFile.exists() || !keyFile.exists()) {
                    long durationInMillis = System.currentTimeMillis() - startTime;
                    if (durationInMillis > KEY_WAIT_TIME_MILLIS) {
                        throw new RuntimeException("Keyfresher waited " + durationInMillis
                                + " ms for valid public or private key files. Giving up.");
                    }
                    LOG.error("Missing Athenz public certificate or private key files. Waiting {} ms",
                            durationInMillis);
                    Thread.sleep(1000);
                }
            } else {
                certFile = new File(Resources.getResource(athenzPublicCert).getFile());
                keyFile = new File(Resources.getResource(athenzPrivateKey).getFile());
            }
        } catch (Throwable t) {
            throw new IllegalArgumentException(t);
        }

        try (InputStream publicCertStream = new FileInputStream(certFile);
                InputStream privateKeyStream = new FileInputStream(keyFile);
                PEMParser pemParser = new PEMParser(new InputStreamReader(privateKeyStream))) {

            final CertificateFactory cf = CertificateFactory.getInstance("X.509");
            final JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter();
            Object key = pemParser.readObject();

            if (key instanceof PEMKeyPair) {
                PrivateKeyInfo pKeyInfo = ((PEMKeyPair) key).getPrivateKeyInfo();
                privateKey = pemConverter.getPrivateKey(pKeyInfo);
            } else if (key instanceof PrivateKeyInfo) {
                privateKey = pemConverter.getPrivateKey((PrivateKeyInfo) key);
            } else {
                throw new IllegalStateException("Unknown object type: " + key.getClass().getName());
            }

            certificate = (X509Certificate) cf.generateCertificate(publicCertStream);
            keyStore = KeyStore.getInstance("JKS");
            String alias = certificate.getSubjectX500Principal().getName();
            keyStore.load(null);
            keyStore.setKeyEntry(alias, privateKey, KEYSTORE_PASSWORD, new X509Certificate[] { certificate });

        } catch (IOException e) {
            throw new IllegalArgumentException(e);
        }

        return keyStore;
    }
}