• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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    */
generateRsaKeyPair()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    */
getEmptyKeyStore()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
generateX509V1Certificate(KeyPair pair, String name)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")
generateX509V3Certificate(KeyPair pair, String name, Date notBefore, Date notAfter, BigInteger serialNumber)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    */
createAuthorityKeyIdentifier( PublicKey publicKey, X509Name name, BigInteger serialNumber)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    */
createSubjectKeyIdentifier(PublicKey publicKey)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    */
generateX509V3Certificate(KeyPair pair, String name)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    */
generateX509V3Certificate(KeyPair pair, String name, BigInteger serialNumber)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    */
generateTestSslContext()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    */
getFileBackedKeyManagers( String keyManagerInstanceName, String fileName, String password)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    */
generateTestServerKeyManager( String keyManagerInstanceName, String password)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