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