package com.puppetlabs.puppetdb.javaclient.ssl;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.spec.KeySpec;
import javax.net.ssl.TrustManagerFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.puppetlabs.puppetdb.javaclient.APIPreferences;
/**
* Abstract provider for a {@link SSLSocketFactory}.
*/
public abstract class AbstractSSLSocketFactoryProvider implements Provider<SSLSocketFactory> {
private static final String PASSWORD = "puppet";
@Inject
private APIPreferences preferences;
/**
* Creates a new SSL socket factory
*
* @return The created factory
*/
@Override
public SSLSocketFactory get() {
try {
// We need a factory that can generate a X.509 certificate using BouncyCastleProvideer
CertificateFactory factory = CertificateFactory.getInstance("X.509");
KeyStore trustStore = getTrustStore(factory);
TrustStrategy trustStrategy = trustStore == null
? new TrustSelfSignedStrategy()
: null;
return new SSLSocketFactory(
SSLSocketFactory.TLS, getKeyStore(factory, PASSWORD), PASSWORD, trustStore, null, trustStrategy, getHostnameVerifier());
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new ProvisionException("Unable to create SSLSocketFactory", e);
}
}
/**
* Returns the Certificate for the Certificate Authority or <code>null</code> if no such certificate exists. Returning <code>null</code>
* will
* cause the created SSL factory to accept self signed certificates.
*
* @param factory
* The certificate factory to use when generating the certificate
* @return The certificate or <code>null</code>.
* @throws IOException
* @throws GeneralSecurityException
*/
protected abstract Certificate getCACertificate(CertificateFactory factory) throws IOException, GeneralSecurityException;
/**
* Returns the mandatory Host Certificate.
*
* @param factory
* The certificate factory to use when generating the certificate
* @return The certificate. This method must never return <code>null</code>.
* @throws IOException
* @throws GeneralSecurityException
*/
protected abstract Certificate getHostCertificate(CertificateFactory factory) throws IOException, GeneralSecurityException;
/**
* Returns a hostname verifier that either allows all hostnames or a
* browser compatible hostname verifier depending on the setting of the {@link APIPreferences#isAllowAllHosts()}.
*
* @return A hostname verifier
* @see AllowAllHostnameVerifier
* @see BrowserCompatHostnameVerifier
*/
protected X509HostnameVerifier getHostnameVerifier() {
return preferences.isAllowAllHosts()
? SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
: SSLSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER;
}
/**
* Creates a new <code>JKS</code> key store and initializes it with the host private key and host certificate.
*
* @param factory
* The factory used when generating the certificate
* @param password
* A password used to protect the key
* @return The new key store.
* @throws ProvisionException
* @throws IOException
* @throws GeneralSecurityException
*/
protected KeyStore getKeyStore(CertificateFactory factory, String password) throws ProvisionException, IOException,
GeneralSecurityException {
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null);
keyStore.setKeyEntry(
"key-alias", getPrivateKey(getPrivateKeySpec()), password.toCharArray(), new Certificate[] { getHostCertificate(factory) });
return keyStore;
}
/**
* Returns the injected preferences.
*
* @return The preferences.
*/
protected APIPreferences getPreferences() {
return preferences;
}
/**
* Extracts the private key from an RSA key specification.
*
* @param privateKey
* The DER encoded private key to extract from
* @return The private key of the keypair
* @throws IOException
* @throws GeneralSecurityException
*/
protected PrivateKey getPrivateKey(KeySpec privateKey) throws IOException, GeneralSecurityException {
return KeyFactory.getInstance("RSA").generatePrivate(privateKey);
}
/**
* Returns a RSA specification for the mandatory private key.
*
* @return The private key spec. This method must never return <code>null</code>.
* @throws KeyException
* @throws IOException
*/
protected abstract KeySpec getPrivateKeySpec() throws KeyException, IOException;
/**
* Creates a new <code>JKS</code> trust store and initializes it with the CA certificate returned by
* {@link #getCACertificate(CertificateFactory)} .
* If that method returns <code>null</code> then this method will also return <code>null</code>.
*
* @param factory
* The factory used when generating the certificate
* @return The new trust store.
* @throws ProvisionException
* @throws IOException
* @throws GeneralSecurityException
* @see {@link #getTrustStrategy()}
*/
protected KeyStore getTrustStore(CertificateFactory factory) throws ProvisionException, IOException, GeneralSecurityException {
// Set up a trustStore so that we can verify the server certificate
Certificate caCert = getCACertificate(factory);
if(caCert == null)
return null;
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(null);
// initialize trust manager factory with the read truststore
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
trustStore.setCertificateEntry("ca-cert-alias", caCert);
return trustStore;
}
}