Home | History | Annotate | Download | only in ssl
      1 /*
      2  * Copyright (C) 2009 Google Inc.  All rights reserved.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.polo.ssl;
     18 
     19 import org.bouncycastle.asn1.ASN1InputStream;
     20 import org.bouncycastle.asn1.ASN1Sequence;
     21 import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
     22 import org.bouncycastle.asn1.x509.BasicConstraints;
     23 import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
     24 import org.bouncycastle.asn1.x509.GeneralName;
     25 import org.bouncycastle.asn1.x509.GeneralNames;
     26 import org.bouncycastle.asn1.x509.KeyPurposeId;
     27 import org.bouncycastle.asn1.x509.KeyUsage;
     28 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
     29 import org.bouncycastle.asn1.x509.X509Extensions;
     30 import org.bouncycastle.asn1.x509.X509Name;
     31 import org.bouncycastle.x509.X509V1CertificateGenerator;
     32 import org.bouncycastle.x509.X509V3CertificateGenerator;
     33 import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
     34 import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
     35 
     36 import java.io.FileInputStream;
     37 import java.io.IOException;
     38 import java.math.BigInteger;
     39 import java.security.GeneralSecurityException;
     40 import java.security.KeyPair;
     41 import java.security.KeyPairGenerator;
     42 import java.security.KeyStore;
     43 import java.security.NoSuchAlgorithmException;
     44 import java.security.PublicKey;
     45 import java.security.cert.Certificate;
     46 import java.security.cert.X509Certificate;
     47 import java.util.Calendar;
     48 import java.util.Date;
     49 
     50 import javax.net.ssl.KeyManager;
     51 import javax.net.ssl.KeyManagerFactory;
     52 import javax.net.ssl.SSLContext;
     53 import javax.net.ssl.TrustManager;
     54 import javax.security.auth.x500.X500Principal;
     55 
     56 /**
     57  * A collection of miscellaneous utility functions for use in Polo.
     58  */
     59 public class SslUtil {
     60 
     61   /**
     62    * Generates a new RSA key pair.
     63    *
     64    * @return                           the new object
     65    * @throws NoSuchAlgorithmException  if the RSA generator could not be loaded
     66    */
     67   public static KeyPair generateRsaKeyPair() throws NoSuchAlgorithmException {
     68     KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA");
     69     KeyPair kp = kg.generateKeyPair();
     70     return kp;
     71   }
     72 
     73   /**
     74    * Creates a new, empty {@link KeyStore}
     75    *
     76    * @return                           the new KeyStore
     77    * @throws GeneralSecurityException  on error creating the keystore
     78    * @throws IOException               on error loading the keystore
     79    */
     80   public static KeyStore getEmptyKeyStore()
     81       throws GeneralSecurityException, IOException {
     82     KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
     83     ks.load(null, null);
     84     return ks;
     85   }
     86 
     87   /**
     88    * Generates a new, self-signed X509 V1 certificate for a KeyPair.
     89    *
     90    * @param  pair                      the {@link KeyPair} to be used
     91    * @param  name                      X.500 distinguished name
     92    * @return                           the new certificate
     93    * @throws GeneralSecurityException  on error generating the certificate
     94    */
     95   @SuppressWarnings("deprecation")
     96   @Deprecated
     97   public static X509Certificate generateX509V1Certificate(KeyPair pair,
     98       String name)
     99         throws GeneralSecurityException {
    100 
    101     Calendar calendar = Calendar.getInstance();
    102     calendar.set(2009, 0, 1);
    103     Date startDate = new Date(calendar.getTimeInMillis());
    104     calendar.set(2029, 0, 1);
    105     Date expiryDate = new Date(calendar.getTimeInMillis());
    106 
    107     BigInteger serialNumber = BigInteger.valueOf(Math.abs(
    108         System.currentTimeMillis()));
    109 
    110     X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
    111     X500Principal dnName = new X500Principal(name);
    112     certGen.setSerialNumber(serialNumber);
    113     certGen.setIssuerDN(dnName);
    114     certGen.setNotBefore(startDate);
    115     certGen.setNotAfter(expiryDate);
    116     certGen.setSubjectDN(dnName);   // note: same as issuer
    117     certGen.setPublicKey(pair.getPublic());
    118     certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
    119 
    120     X509Certificate cert = certGen.generate(pair.getPrivate());
    121     return cert;
    122   }
    123 
    124   /**
    125    * Generates a new, self-signed X509 V3 certificate for a KeyPair.
    126    *
    127    * @param  pair                      the {@link KeyPair} to be used
    128    * @param  name                      X.500 distinguished name
    129    * @param  notBefore                 not valid before this date
    130    * @param  notAfter                  not valid after this date
    131    * @param  serialNumber              serial number
    132    * @return                           the new certificate
    133    * @throws GeneralSecurityException  on error generating the certificate
    134    */
    135   @SuppressWarnings("deprecation")
    136   public static X509Certificate generateX509V3Certificate(KeyPair pair,
    137       String name, Date notBefore, Date notAfter, BigInteger serialNumber)
    138         throws GeneralSecurityException {
    139 
    140     X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
    141     X509Name dnName = new X509Name(name);
    142 
    143     certGen.setSerialNumber(serialNumber);
    144     certGen.setIssuerDN(dnName);
    145     certGen.setSubjectDN(dnName);   // note: same as issuer
    146     certGen.setNotBefore(notBefore);
    147     certGen.setNotAfter(notAfter);
    148     certGen.setPublicKey(pair.getPublic());
    149     certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
    150 
    151     // For self-signed certificates, OpenSSL 0.9.6 has specific requirements
    152     // about certificate and extension content.  Quoting the `man verify`:
    153     //
    154     //   In OpenSSL 0.9.6 and later all certificates whose subject name matches
    155     //   the issuer name of the current certificate are subject to further
    156     //   tests. The relevant authority key identifier components of the current
    157     //   certificate (if present) must match the subject key identifier (if
    158     //   present) and issuer and serial number of the candidate issuer, in
    159     //   addition the keyUsage extension of the candidate issuer (if present)
    160     //   must permit certificate signing.
    161     //
    162     // In the code that follows,
    163     //   - the KeyUsage extension permits cert signing (KeyUsage.keyCertSign);
    164     //   - the Authority Key Identifier extension is added, matching the
    165     //     subject key identifier, and using the issuer, and serial number.
    166 
    167     certGen.addExtension(X509Extensions.BasicConstraints, true,
    168         new BasicConstraints(false));
    169 
    170     certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature
    171         | KeyUsage.keyEncipherment | KeyUsage.keyCertSign));
    172     certGen.addExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(
    173         KeyPurposeId.id_kp_serverAuth));
    174 
    175     AuthorityKeyIdentifier authIdentifier = createAuthorityKeyIdentifier(
    176         pair.getPublic(), dnName, serialNumber);
    177 
    178     certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, true,
    179         authIdentifier);
    180     certGen.addExtension(X509Extensions.SubjectKeyIdentifier, true,
    181         new SubjectKeyIdentifierStructure(pair.getPublic()));
    182 
    183     certGen.addExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(
    184         new GeneralName(GeneralName.rfc822Name, "android-tv-remote-support (at) google.com")));
    185 
    186     X509Certificate cert = certGen.generate(pair.getPrivate());
    187     return cert;
    188   }
    189 
    190   /**
    191    * Creates an AuthorityKeyIdentifier from a public key, name, and serial
    192    * number.
    193    * <p>
    194    * {@link AuthorityKeyIdentifierStructure} is <i>almost</i> perfect for this,
    195    * but sadly it does not have a constructor suitable for us:
    196    * {@link AuthorityKeyIdentifierStructure#AuthorityKeyIdentifierStructure(PublicKey)}
    197    * does not set the serial number or name (which is important to us), while
    198    * {@link AuthorityKeyIdentifierStructure#AuthorityKeyIdentifierStructure(X509Certificate)}
    199    * sets those fields but needs a completed certificate to do so.
    200    * <p>
    201    * This method addresses the gap in available {@link AuthorityKeyIdentifier}
    202    * constructors provided by BouncyCastle; its implementation is derived from
    203    * {@link AuthorityKeyIdentifierStructure#AuthorityKeyIdentifierStructure(X509Certificate)}.
    204    *
    205    * @param publicKey  the public key
    206    * @param name  the name
    207    * @param serialNumber  the serial number
    208    * @return  a new {@link AuthorityKeyIdentifier}
    209    */
    210   static AuthorityKeyIdentifier createAuthorityKeyIdentifier(
    211       PublicKey publicKey, X509Name name, BigInteger serialNumber) {
    212     GeneralName genName = new GeneralName(name);
    213     SubjectPublicKeyInfo info;
    214     try {
    215       info = new SubjectPublicKeyInfo(
    216           (ASN1Sequence)new ASN1InputStream(publicKey.getEncoded()).readObject());
    217     } catch (IOException e) {
    218       throw new RuntimeException("Error encoding public key");
    219     }
    220     return new AuthorityKeyIdentifier(info, new GeneralNames(genName), serialNumber);
    221   }
    222 
    223   /**
    224    * Wrapper for {@link SslUtil#generateX509V3Certificate(KeyPair, String, Date, Date, BigInteger)}
    225    * which uses a default validity period and serial number.
    226    * <p>
    227    * The validity period is Jan 1 2009 - Jan 1 2099.  The serial number is the
    228    * current system time.
    229    */
    230   public static X509Certificate generateX509V3Certificate(KeyPair pair,
    231       String name) throws GeneralSecurityException {
    232     Calendar calendar = Calendar.getInstance();
    233     calendar.set(2009, 0, 1);
    234     Date notBefore  = new Date(calendar.getTimeInMillis());
    235     calendar.set(2099, 0, 1);
    236     Date notAfter = new Date(calendar.getTimeInMillis());
    237 
    238     BigInteger serialNumber = BigInteger.valueOf(Math.abs(
    239         System.currentTimeMillis()));
    240 
    241     return generateX509V3Certificate(pair, name, notBefore, notAfter,
    242         serialNumber);
    243   }
    244 
    245   /**
    246    * Wrapper for {@link SslUtil#generateX509V3Certificate(KeyPair, String, Date, Date, BigInteger)}
    247    * which uses a default validity period.
    248    * <p>
    249    * The validity period is Jan 1 2009 - Jan 1 2099.
    250    */
    251   public static X509Certificate generateX509V3Certificate(KeyPair pair,
    252       String name, BigInteger serialNumber) throws GeneralSecurityException {
    253     Calendar calendar = Calendar.getInstance();
    254     calendar.set(2009, 0, 1);
    255     Date notBefore  = new Date(calendar.getTimeInMillis());
    256     calendar.set(2099, 0, 1);
    257     Date notAfter = new Date(calendar.getTimeInMillis());
    258 
    259     return generateX509V3Certificate(pair, name, notBefore, notAfter,
    260         serialNumber);
    261   }
    262 
    263   /**
    264    * Generates a new {@code SSLContext} suitable for a test environment.
    265    * <p>
    266    * A new {@link KeyPair}, {@link X509Certificate},
    267    * {@link DummyTrustManager}, and an empty
    268    * {@link KeyStore} are created and used to initialize the context.
    269    *
    270    * @return                            the new context
    271    * @throws  GeneralSecurityException  if an error occurred during
    272    *                                    initialization
    273    * @throws  IOException               if an empty KeyStore could not be
    274    *                                    generated
    275    */
    276   public SSLContext generateTestSslContext()
    277       throws GeneralSecurityException, IOException {
    278     SSLContext sslcontext = SSLContext.getInstance("SSLv3");
    279     KeyManager[] keyManagers = SslUtil.generateTestServerKeyManager("SunX509",
    280         "test");
    281     sslcontext.init(keyManagers,
    282         new TrustManager[] { new DummyTrustManager()},
    283         null);
    284     return sslcontext;
    285   }
    286 
    287   /**
    288    * Creates a new pain of {@link KeyManager}s, backed by a keystore file.
    289    *
    290    * @param  keyManagerInstanceName    name of the {@link KeyManagerFactory} to
    291    *                                   request
    292    * @param  fileName                  the name of the keystore to load
    293    * @param  password                  the password for the keystore
    294    * @return                           the new object
    295    * @throws GeneralSecurityException  if an error occurred during
    296    *                                   initialization
    297    * @throws IOException               if the keystore could not be loaded
    298    */
    299   public static KeyManager[] getFileBackedKeyManagers(
    300       String keyManagerInstanceName, String fileName, String password)
    301       throws GeneralSecurityException, IOException {
    302     KeyManagerFactory km = KeyManagerFactory.getInstance(
    303         keyManagerInstanceName);
    304     KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
    305     ks.load(new FileInputStream(fileName), password.toCharArray());
    306     km.init(ks, password.toCharArray());
    307     return km.getKeyManagers();
    308   }
    309 
    310   /**
    311    * Creates a pair of {@link KeyManager}s suitable for use in testing.
    312    * <p>
    313    * A new {@link KeyPair} and {@link X509Certificate} are created and used to
    314    * initialize the KeyManager.
    315    *
    316    * @param  keyManagerInstanceName    name of the {@link KeyManagerFactory}
    317    * @param  password                  password to apply to the new key store
    318    * @return                           the new key managers
    319    * @throws GeneralSecurityException  if an error occurred during
    320    *                                   initialization
    321    * @throws IOException               if the keystore could not be generated
    322    */
    323   public static KeyManager[] generateTestServerKeyManager(
    324       String keyManagerInstanceName, String password)
    325       throws GeneralSecurityException, IOException {
    326     KeyManagerFactory km = KeyManagerFactory.getInstance(
    327         keyManagerInstanceName);
    328     KeyPair pair = SslUtil.generateRsaKeyPair();
    329     X509Certificate cert = SslUtil.generateX509V1Certificate(pair,
    330         "CN=Test Server Cert");
    331     Certificate[] chain = { cert };
    332 
    333     KeyStore ks = SslUtil.getEmptyKeyStore();
    334     ks.setKeyEntry("test-server", pair.getPrivate(),
    335         password.toCharArray(), chain);
    336     km.init(ks, password.toCharArray());
    337     return km.getKeyManagers();
    338   }
    339 
    340 }
    341