
org.littleshoot.proxy.mitm.CertificateHelper Maven / Gradle / Ivy
Show all versions of littleproxy-mitm Show documentation
package org.littleshoot.proxy.mitm;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyManagementException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Random;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.bc.BcX509ExtensionUtils;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class CertificateHelper {
private static final Logger log = LoggerFactory.getLogger(CertificateHelper.class);
public static final String PROVIDER_NAME = BouncyCastleProvider.PROVIDER_NAME;
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final String KEYGEN_ALGORITHM = "RSA";
private static final String SECURE_RANDOM_ALGORITHM = "SHA1PRNG";
/**
* The signature algorithm starting with the message digest to use when
* signing certificates. On 64-bit systems this should be set to SHA512, on
* 32-bit systems this is SHA256. On 64-bit systems, SHA512 generally
* performs better than SHA256; see this question for details:
* http://crypto.stackexchange.com/questions/26336/sha512-faster-than-sha256
*/
private static final String SIGNATURE_ALGORITHM = is32BitJvm() ? "SHA256" : "SHA512" + "WithRSAEncryption";
/**
* Uses the non-portable system property sun.arch.data.model to help
* determine if we are running on a 32-bit JVM. Since the majority of modern
* systems are 64 bits, this method "assumes" 64 bits and only returns true
* if sun.arch.data.model explicitly indicates a 32-bit JVM.
*
* @return true if we can determine definitively that this is a 32-bit JVM,
* otherwise false
*/
private static boolean is32BitJvm() {
Integer bits = Integer.getInteger("sun.arch.data.model");
return bits != null && bits == 32;
}
private static final int ROOT_KEYSIZE = 2048;
private static final int FAKE_KEYSIZE = 1024;
/**
* Current time minus 1 year, just in case software clock goes back due to
* time synchronization
*/
private static final Date NOT_BEFORE = new Date(System.currentTimeMillis() - 86400000L * 365);
/**
* The maximum possible value in X.509 specification: 9999-12-31 23:59:59,
* new Date(253402300799000L), but Apple iOS 8 fails with a certificate
* expiration date grater than Mon, 24 Jan 6084 02:07:59 GMT (issue #6).
*
* Hundred years in the future from starting the proxy should be enough.
*/
private static final Date NOT_AFTER = new Date(
System.currentTimeMillis() + 86400000L * 365 * 100);
/**
* Enforce TLS 1.2 if available, since it's not default up to Java 8.
*
* Java 7 disables TLS 1.1 and 1.2 for clients. From Java Cryptography Architecture Oracle Providers Documentation:
* Although SunJSSE in the Java SE 7 release supports TLS 1.1 and TLS 1.2,
* neither version is enabled by default for client connections. Some
* servers do not implement forward compatibility correctly and refuse to
* talk to TLS 1.1 or TLS 1.2 clients. For interoperability, SunJSSE does
* not enable TLS 1.1 or TLS 1.2 by default for client connections.
*/
private static final String SSL_CONTEXT_PROTOCOL = "TLSv1.2";
/**
* {@link SSLContext}: Every implementation of the Java platform is required
* to support the following standard SSLContext protocol: TLSv1
*/
private static final String SSL_CONTEXT_FALLBACK_PROTOCOL = "TLSv1";
public static KeyPair generateKeyPair(int keySize)
throws NoSuchAlgorithmException, NoSuchProviderException {
KeyPairGenerator generator = KeyPairGenerator
.getInstance(KEYGEN_ALGORITHM/* , PROVIDER_NAME */);
SecureRandom secureRandom = SecureRandom
.getInstance(SECURE_RANDOM_ALGORITHM/* , PROVIDER_NAME */);
generator.initialize(keySize, secureRandom);
return generator.generateKeyPair();
}
public static KeyStore createRootCertificate(Authority authority,
String keyStoreType) throws NoSuchAlgorithmException,
NoSuchProviderException, CertIOException, IOException,
OperatorCreationException, CertificateException, KeyStoreException {
KeyPair keyPair = generateKeyPair(ROOT_KEYSIZE);
X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
nameBuilder.addRDN(BCStyle.CN, authority.commonName());
nameBuilder.addRDN(BCStyle.O, authority.organization());
nameBuilder.addRDN(BCStyle.OU, authority.organizationalUnitName());
X500Name issuer = nameBuilder.build();
BigInteger serial = BigInteger.valueOf(initRandomSerial());
X500Name subject = issuer;
PublicKey pubKey = keyPair.getPublic();
X509v3CertificateBuilder generator = new JcaX509v3CertificateBuilder(
issuer, serial, NOT_BEFORE, NOT_AFTER, subject, pubKey);
generator.addExtension(Extension.subjectKeyIdentifier, false,
createSubjectKeyIdentifier(pubKey));
generator.addExtension(Extension.basicConstraints, true,
new BasicConstraints(true));
KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign
| KeyUsage.digitalSignature | KeyUsage.keyEncipherment
| KeyUsage.dataEncipherment | KeyUsage.cRLSign);
generator.addExtension(Extension.keyUsage, false, usage);
ASN1EncodableVector purposes = new ASN1EncodableVector();
purposes.add(KeyPurposeId.id_kp_serverAuth);
purposes.add(KeyPurposeId.id_kp_clientAuth);
purposes.add(KeyPurposeId.anyExtendedKeyUsage);
generator.addExtension(Extension.extendedKeyUsage, false,
new DERSequence(purposes));
X509Certificate cert = signCertificate(generator, keyPair.getPrivate());
KeyStore result = KeyStore
.getInstance(keyStoreType/* , PROVIDER_NAME */);
result.load(null, null);
result.setKeyEntry(authority.alias(), keyPair.getPrivate(),
authority.password(), new Certificate[] { cert });
return result;
}
private static SubjectKeyIdentifier createSubjectKeyIdentifier(Key key)
throws IOException {
ByteArrayInputStream bIn = new ByteArrayInputStream(key.getEncoded());
ASN1InputStream is = null;
try {
is = new ASN1InputStream(bIn);
ASN1Sequence seq = (ASN1Sequence) is.readObject();
SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(seq);
return new BcX509ExtensionUtils().createSubjectKeyIdentifier(info);
} finally {
IOUtils.closeQuietly(is);
}
}
public static KeyStore createServerCertificate(String commonName,
SubjectAlternativeNameHolder subjectAlternativeNames,
Authority authority, Certificate caCert, PrivateKey caPrivKey)
throws NoSuchAlgorithmException, NoSuchProviderException,
IOException, OperatorCreationException, CertificateException,
InvalidKeyException, SignatureException, KeyStoreException {
KeyPair keyPair = generateKeyPair(FAKE_KEYSIZE);
X500Name issuer = new X509CertificateHolder(caCert.getEncoded())
.getSubject();
BigInteger serial = BigInteger.valueOf(initRandomSerial());
X500NameBuilder name = new X500NameBuilder(BCStyle.INSTANCE);
name.addRDN(BCStyle.CN, commonName);
name.addRDN(BCStyle.O, authority.certOrganisation());
name.addRDN(BCStyle.OU, authority.certOrganizationalUnitName());
X500Name subject = name.build();
X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(
issuer, serial, NOT_BEFORE, NOT_AFTER, subject,
keyPair.getPublic());
builder.addExtension(Extension.subjectKeyIdentifier, false,
createSubjectKeyIdentifier(keyPair.getPublic()));
builder.addExtension(Extension.basicConstraints, false,
new BasicConstraints(false));
subjectAlternativeNames.fillInto(builder);
X509Certificate cert = signCertificate(builder, caPrivKey);
cert.checkValidity(new Date());
cert.verify(caCert.getPublicKey());
KeyStore result = KeyStore.getInstance(KeyStore.getDefaultType()
/* , PROVIDER_NAME */);
result.load(null, null);
Certificate[] chain = { cert, caCert };
result.setKeyEntry(authority.alias(), keyPair.getPrivate(),
authority.password(), chain);
return result;
}
private static X509Certificate signCertificate(
X509v3CertificateBuilder certificateBuilder,
PrivateKey signedWithPrivateKey) throws OperatorCreationException,
CertificateException {
ContentSigner signer = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM)
.setProvider(PROVIDER_NAME).build(signedWithPrivateKey);
X509Certificate cert = new JcaX509CertificateConverter().setProvider(
PROVIDER_NAME).getCertificate(certificateBuilder.build(signer));
return cert;
}
public static TrustManager[] getTrustManagers(KeyStore keyStore)
throws KeyStoreException, NoSuchAlgorithmException,
NoSuchProviderException {
String trustManAlg = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(trustManAlg
/* , PROVIDER_NAME */);
tmf.init(keyStore);
return tmf.getTrustManagers();
}
public static KeyManager[] getKeyManagers(KeyStore keyStore,
Authority authority) throws NoSuchAlgorithmException,
NoSuchProviderException, UnrecoverableKeyException,
KeyStoreException {
String keyManAlg = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManAlg
/* , PROVIDER_NAME */);
kmf.init(keyStore, authority.password());
return kmf.getKeyManagers();
}
public static SSLContext newClientContext(KeyManager[] keyManagers,
TrustManager[] trustManagers) throws NoSuchAlgorithmException,
KeyManagementException, NoSuchProviderException {
SSLContext result = newSSLContext();
result.init(keyManagers, trustManagers, null);
return result;
}
public static SSLContext newServerContext(KeyManager[] keyManagers)
throws NoSuchAlgorithmException, NoSuchProviderException,
KeyManagementException {
SSLContext result = newSSLContext();
SecureRandom random = new SecureRandom();
random.setSeed(System.currentTimeMillis());
result.init(keyManagers, null, random);
return result;
}
private static SSLContext newSSLContext() throws NoSuchAlgorithmException {
try {
log.debug("Using protocol {}", SSL_CONTEXT_PROTOCOL);
return SSLContext.getInstance(SSL_CONTEXT_PROTOCOL
/* , PROVIDER_NAME */);
} catch (NoSuchAlgorithmException e) {
log.warn("Protocol {} not available, falling back to {}", SSL_CONTEXT_PROTOCOL,
SSL_CONTEXT_FALLBACK_PROTOCOL);
return SSLContext.getInstance(SSL_CONTEXT_FALLBACK_PROTOCOL
/* , PROVIDER_NAME */);
}
}
public static long initRandomSerial() {
final Random rnd = new Random();
rnd.setSeed(System.currentTimeMillis());
// prevent browser certificate caches, cause of doubled serial numbers
// using 48bit random number
long sl = ((long) rnd.nextInt()) << 32 | (rnd.nextInt() & 0xFFFFFFFFL);
// let reserve of 16 bit for increasing, serials have to be positive
sl = sl & 0x0000FFFFFFFFFFFFL;
return sl;
}
}