/* * 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(); } }