| /* |
| * 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") |
| @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, |
| new SubjectKeyIdentifierStructure(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); |
| } |
| |
| /** |
| * 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(); |
| } |
| |
| } |