diff options
Diffstat (limited to 'java/src/com/google/polo/ssl/SslUtil.java')
-rw-r--r-- | java/src/com/google/polo/ssl/SslUtil.java | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/java/src/com/google/polo/ssl/SslUtil.java b/java/src/com/google/polo/ssl/SslUtil.java new file mode 100644 index 0000000..cf0b3da --- /dev/null +++ b/java/src/com/google/polo/ssl/SslUtil.java @@ -0,0 +1,347 @@ +/* + * 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.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 org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure; + +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.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") + public static X509Certificate generateX509V1Certificate(KeyPair pair, + String name) + throws GeneralSecurityException { + java.security.Security.addProvider( + new org.bouncycastle.jce.provider.BouncyCastleProvider()); + + 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"); + + // This method is deprecated, but Android Eclair does not provide the + // generate() methods. + X509Certificate cert = certGen.generateX509Certificate(pair.getPrivate(), "BC"); + 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 { + java.security.Security.addProvider( + new org.bouncycastle.jce.provider.BouncyCastleProvider()); + + 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, + new SubjectKeyIdentifierStructure(pair.getPublic())); + + certGen.addExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames( + new GeneralName(GeneralName.rfc822Name, "googletv@test.test"))); + + // This method is deprecated, but Android Eclair does not provide the + // generate() methods. + X509Certificate cert = certGen.generateX509Certificate(pair.getPrivate(), "BC"); + 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} + */ + private 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); + } + + /** + * 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(); + } + +} |