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
17package com.google.polo.ssl;
18
19import org.bouncycastle.asn1.ASN1InputStream;
20import org.bouncycastle.asn1.ASN1Sequence;
21import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
22import org.bouncycastle.asn1.x509.BasicConstraints;
23import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
24import org.bouncycastle.asn1.x509.GeneralName;
25import org.bouncycastle.asn1.x509.GeneralNames;
26import org.bouncycastle.asn1.x509.KeyPurposeId;
27import org.bouncycastle.asn1.x509.KeyUsage;
28import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
29import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
30import org.bouncycastle.asn1.x509.X509Extensions;
31import org.bouncycastle.asn1.x509.X509Name;
32import org.bouncycastle.x509.X509V1CertificateGenerator;
33import org.bouncycastle.x509.X509V3CertificateGenerator;
34import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
35
36import java.io.FileInputStream;
37import java.io.IOException;
38import java.math.BigInteger;
39import java.security.GeneralSecurityException;
40import java.security.KeyPair;
41import java.security.KeyPairGenerator;
42import java.security.KeyStore;
43import java.security.MessageDigest;
44import java.security.NoSuchAlgorithmException;
45import java.security.PublicKey;
46import java.security.cert.Certificate;
47import java.security.cert.X509Certificate;
48import java.util.Calendar;
49import java.util.Date;
50
51import javax.net.ssl.KeyManager;
52import javax.net.ssl.KeyManagerFactory;
53import javax.net.ssl.SSLContext;
54import javax.net.ssl.TrustManager;
55import javax.security.auth.x500.X500Principal;
56
57/**
58 * A collection of miscellaneous utility functions for use in Polo.
59 */
60public 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@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