/*
* Copyright (C) 2009 Google Inc. All rights reserved.
*
* 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.google.polo.ssl;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
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.asn1.x509.X509Extensions;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.x509.X509V1CertificateGenerator;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.Date;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.security.auth.x500.X500Principal;
/**
* A collection of miscellaneous utility functions for use in Polo.
*/
public class SslUtil {
/**
* Generates a new RSA key pair.
*
* @return the new object
* @throws NoSuchAlgorithmException if the RSA generator could not be loaded
*/
public static KeyPair generateRsaKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA");
KeyPair kp = kg.generateKeyPair();
return kp;
}
/**
* Creates a new, empty {@link KeyStore}
*
* @return the new KeyStore
* @throws GeneralSecurityException on error creating the keystore
* @throws IOException on error loading the keystore
*/
public static KeyStore getEmptyKeyStore()
throws GeneralSecurityException, IOException {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
return ks;
}
/**
* Generates a new, self-signed X509 V1 certificate for a KeyPair.
*
* @param pair the {@link KeyPair} to be used
* @param name X.500 distinguished name
* @return the new certificate
* @throws GeneralSecurityException on error generating the certificate
*/
@SuppressWarnings("deprecation")
@Deprecated
public static X509Certificate generateX509V1Certificate(KeyPair pair,
String name)
throws GeneralSecurityException {
Calendar calendar = Calendar.getInstance();
calendar.set(2009, 0, 1);
Date startDate = new Date(calendar.getTimeInMillis());
calendar.set(2029, 0, 1);
Date expiryDate = new Date(calendar.getTimeInMillis());
BigInteger serialNumber = BigInteger.valueOf(Math.abs(
System.currentTimeMillis()));
X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
X500Principal dnName = new X500Principal(name);
certGen.setSerialNumber(serialNumber);
certGen.setIssuerDN(dnName);
certGen.setNotBefore(startDate);
certGen.setNotAfter(expiryDate);
certGen.setSubjectDN(dnName); // note: same as issuer
certGen.setPublicKey(pair.getPublic());
certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
X509Certificate cert = certGen.generate(pair.getPrivate());
return cert;
}
/**
* Generates a new, self-signed X509 V3 certificate for a KeyPair.
*
* @param pair the {@link KeyPair} to be used
* @param name X.500 distinguished name
* @param notBefore not valid before this date
* @param notAfter not valid after this date
* @param serialNumber serial number
* @return the new certificate
* @throws GeneralSecurityException on error generating the certificate
*/
@SuppressWarnings("deprecation")
public static X509Certificate generateX509V3Certificate(KeyPair pair,
String name, Date notBefore, Date notAfter, BigInteger serialNumber)
throws GeneralSecurityException {
X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
X509Name dnName = new X509Name(name);
certGen.setSerialNumber(serialNumber);
certGen.setIssuerDN(dnName);
certGen.setSubjectDN(dnName); // note: same as issuer
certGen.setNotBefore(notBefore);
certGen.setNotAfter(notAfter);
certGen.setPublicKey(pair.getPublic());
certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
// For self-signed certificates, OpenSSL 0.9.6 has specific requirements
// about certificate and extension content. Quoting the `man verify`:
//
// In OpenSSL 0.9.6 and later all certificates whose subject name matches
// the issuer name of the current certificate are subject to further
// tests. The relevant authority key identifier components of the current
// certificate (if present) must match the subject key identifier (if
// present) and issuer and serial number of the candidate issuer, in
// addition the keyUsage extension of the candidate issuer (if present)
// must permit certificate signing.
//
// In the code that follows,
// - the KeyUsage extension permits cert signing (KeyUsage.keyCertSign);
// - the Authority Key Identifier extension is added, matching the
// subject key identifier, and using the issuer, and serial number.
certGen.addExtension(X509Extensions.BasicConstraints, true,
new BasicConstraints(false));
certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature
| KeyUsage.keyEncipherment | KeyUsage.keyCertSign));
certGen.addExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(
KeyPurposeId.id_kp_serverAuth));
AuthorityKeyIdentifier authIdentifier = createAuthorityKeyIdentifier(
pair.getPublic(), dnName, serialNumber);
certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, true,
authIdentifier);
certGen.addExtension(X509Extensions.SubjectKeyIdentifier, true,
createSubjectKeyIdentifier(pair.getPublic()));
certGen.addExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(
new GeneralName(GeneralName.rfc822Name, "android-tv-remote-support@google.com")));
X509Certificate cert = certGen.generate(pair.getPrivate());
return cert;
}
/**
* Creates an AuthorityKeyIdentifier from a public key, name, and serial
* number.
* <p>
* {@link AuthorityKeyIdentifierStructure} is <i>almost</i> perfect for this,
* but sadly it does not have a constructor suitable for us:
* {@link AuthorityKeyIdentifierStructure#AuthorityKeyIdentifierStructure(PublicKey)}
* does not set the serial number or name (which is important to us), while
* {@link AuthorityKeyIdentifierStructure#AuthorityKeyIdentifierStructure(X509Certificate)}
* sets those fields but needs a completed certificate to do so.
* <p>
* This method addresses the gap in available {@link AuthorityKeyIdentifier}
* constructors provided by BouncyCastle; its implementation is derived from
* {@link AuthorityKeyIdentifierStructure#AuthorityKeyIdentifierStructure(X509Certificate)}.
*
* @param publicKey the public key
* @param name the name
* @param serialNumber the serial number
* @return a new {@link AuthorityKeyIdentifier}
*/
static AuthorityKeyIdentifier createAuthorityKeyIdentifier(
PublicKey publicKey, X509Name name, BigInteger serialNumber) {
GeneralName genName = new GeneralName(name);
SubjectPublicKeyInfo info;
try {
info = new SubjectPublicKeyInfo(
(ASN1Sequence)new ASN1InputStream(publicKey.getEncoded()).readObject());
} catch (IOException e) {
throw new RuntimeException("Error encoding public key");
}
return new AuthorityKeyIdentifier(info, new GeneralNames(genName), serialNumber);
}
/**
* Creates a SubjectKeyIdentifier from a public key.
* <p>
* @param publicKey the public key
* @return a new {@link SubjectKeyIdentifier}
*/
static SubjectKeyIdentifier createSubjectKeyIdentifier(PublicKey publicKey) {
SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
MessageDigest digester;
try {
digester = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Could not get SHA-1 digest instance");
}
return new SubjectKeyIdentifier(digester.digest(info.getPublicKeyData().getBytes()));
}
/**
* Wrapper for {@link SslUtil#generateX509V3Certificate(KeyPair, String, Date, Date, BigInteger)}
* which uses a default validity period and serial number.
* <p>
* The validity period is Jan 1 2009 - Jan 1 2099. The serial number is the
* current system time.
*/
public static X509Certificate generateX509V3Certificate(KeyPair pair,
String name) throws GeneralSecurityException {
Calendar calendar = Calendar.getInstance();
calendar.set(2009, 0, 1);
Date notBefore = new Date(calendar.getTimeInMillis());
calendar.set(2099, 0, 1);
Date notAfter = new Date(calendar.getTimeInMillis());
BigInteger serialNumber = BigInteger.valueOf(Math.abs(
System.currentTimeMillis()));
return generateX509V3Certificate(pair, name, notBefore, notAfter,
serialNumber);
}
/**
* Wrapper for {@link SslUtil#generateX509V3Certificate(KeyPair, String, Date, Date, BigInteger)}
* which uses a default validity period.
* <p>
* The validity period is Jan 1 2009 - Jan 1 2099.
*/
public static X509Certificate generateX509V3Certificate(KeyPair pair,
String name, BigInteger serialNumber) throws GeneralSecurityException {
Calendar calendar = Calendar.getInstance();
calendar.set(2009, 0, 1);
Date notBefore = new Date(calendar.getTimeInMillis());
calendar.set(2099, 0, 1);
Date notAfter = new Date(calendar.getTimeInMillis());
return generateX509V3Certificate(pair, name, notBefore, notAfter,
serialNumber);
}
/**
* Generates a new {@code SSLContext} suitable for a test environment.
* <p>
* A new {@link KeyPair}, {@link X509Certificate},
* {@link DummyTrustManager}, and an empty
* {@link KeyStore} are created and used to initialize the context.
*
* @return the new context
* @throws GeneralSecurityException if an error occurred during
* initialization
* @throws IOException if an empty KeyStore could not be
* generated
*/
public SSLContext generateTestSslContext()
throws GeneralSecurityException, IOException {
SSLContext sslcontext = SSLContext.getInstance("SSLv3");
KeyManager[] keyManagers = SslUtil.generateTestServerKeyManager("SunX509",
"test");
sslcontext.init(keyManagers,
new TrustManager[] { new DummyTrustManager()},
null);
return sslcontext;
}
/**
* Creates a new pain of {@link KeyManager}s, backed by a keystore file.
*
* @param keyManagerInstanceName name of the {@link KeyManagerFactory} to
* request
* @param fileName the name of the keystore to load
* @param password the password for the keystore
* @return the new object
* @throws GeneralSecurityException if an error occurred during
* initialization
* @throws IOException if the keystore could not be loaded
*/
public static KeyManager[] getFileBackedKeyManagers(
String keyManagerInstanceName, String fileName, String password)
throws GeneralSecurityException, IOException {
KeyManagerFactory km = KeyManagerFactory.getInstance(
keyManagerInstanceName);
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(new FileInputStream(fileName), password.toCharArray());
km.init(ks, password.toCharArray());
return km.getKeyManagers();
}
/**
* Creates a pair of {@link KeyManager}s suitable for use in testing.
* <p>
* A new {@link KeyPair} and {@link X509Certificate} are created and used to
* initialize the KeyManager.
*
* @param keyManagerInstanceName name of the {@link KeyManagerFactory}
* @param password password to apply to the new key store
* @return the new key managers
* @throws GeneralSecurityException if an error occurred during
* initialization
* @throws IOException if the keystore could not be generated
*/
public static KeyManager[] generateTestServerKeyManager(
String keyManagerInstanceName, String password)
throws GeneralSecurityException, IOException {
KeyManagerFactory km = KeyManagerFactory.getInstance(
keyManagerInstanceName);
KeyPair pair = SslUtil.generateRsaKeyPair();
X509Certificate cert = SslUtil.generateX509V1Certificate(pair,
"CN=Test Server Cert");
Certificate[] chain = { cert };
KeyStore ks = SslUtil.getEmptyKeyStore();
ks.setKeyEntry("test-server", pair.getPrivate(),
password.toCharArray(), chain);
km.init(ks, password.toCharArray());
return km.getKeyManagers();
}
}