blob: e27d4f7684ed3680c8d65badd324c53a9c441126 [file] [log] [blame]
Jerome Poichet7c997852014-05-20 10:50:05 -07001/*
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
Jerome Poichet7a318a62014-07-07 14:09:16 -070019import 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;
Jerome Poichet53040902014-08-11 17:08:54 -070028import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
Jerome Poichet7a318a62014-07-07 14:09:16 -070029import 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;
Jerome Poichet7c997852014-05-20 10:50:05 -070035
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.NoSuchAlgorithmException;
44import java.security.PublicKey;
45import java.security.cert.Certificate;
46import java.security.cert.X509Certificate;
47import java.util.Calendar;
48import java.util.Date;
49
50import javax.net.ssl.KeyManager;
51import javax.net.ssl.KeyManagerFactory;
52import javax.net.ssl.SSLContext;
53import javax.net.ssl.TrustManager;
54import javax.security.auth.x500.X500Principal;
55
56/**
57 * A collection of miscellaneous utility functions for use in Polo.
58 */
59public 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 public static X509Certificate generateX509V1Certificate(KeyPair pair,
97 String name)
98 throws GeneralSecurityException {
99 java.security.Security.addProvider(
Jerome Poichet7a318a62014-07-07 14:09:16 -0700100 new org.bouncycastle.jce.provider.BouncyCastleProvider());
Jerome Poichet7c997852014-05-20 10:50:05 -0700101
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 // This method is deprecated, but Android Eclair does not provide the
122 // generate() methods.
123 X509Certificate cert = certGen.generateX509Certificate(pair.getPrivate(), "BC");
124 return cert;
125 }
126
127 /**
128 * Generates a new, self-signed X509 V3 certificate for a KeyPair.
129 *
130 * @param pair the {@link KeyPair} to be used
131 * @param name X.500 distinguished name
132 * @param notBefore not valid before this date
133 * @param notAfter not valid after this date
134 * @param serialNumber serial number
135 * @return the new certificate
136 * @throws GeneralSecurityException on error generating the certificate
137 */
138 @SuppressWarnings("deprecation")
139 public static X509Certificate generateX509V3Certificate(KeyPair pair,
140 String name, Date notBefore, Date notAfter, BigInteger serialNumber)
141 throws GeneralSecurityException {
142 java.security.Security.addProvider(
Jerome Poichet7a318a62014-07-07 14:09:16 -0700143 new org.bouncycastle.jce.provider.BouncyCastleProvider());
Jerome Poichet7c997852014-05-20 10:50:05 -0700144
145 X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
146 X509Name dnName = new X509Name(name);
147
148 certGen.setSerialNumber(serialNumber);
149 certGen.setIssuerDN(dnName);
150 certGen.setSubjectDN(dnName); // note: same as issuer
151 certGen.setNotBefore(notBefore);
152 certGen.setNotAfter(notAfter);
153 certGen.setPublicKey(pair.getPublic());
154 certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
155
156 // For self-signed certificates, OpenSSL 0.9.6 has specific requirements
157 // about certificate and extension content. Quoting the `man verify`:
158 //
159 // In OpenSSL 0.9.6 and later all certificates whose subject name matches
160 // the issuer name of the current certificate are subject to further
161 // tests. The relevant authority key identifier components of the current
162 // certificate (if present) must match the subject key identifier (if
163 // present) and issuer and serial number of the candidate issuer, in
164 // addition the keyUsage extension of the candidate issuer (if present)
165 // must permit certificate signing.
166 //
167 // In the code that follows,
168 // - the KeyUsage extension permits cert signing (KeyUsage.keyCertSign);
169 // - the Authority Key Identifier extension is added, matching the
170 // subject key identifier, and using the issuer, and serial number.
171
172 certGen.addExtension(X509Extensions.BasicConstraints, true,
173 new BasicConstraints(false));
174
175 certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature
176 | KeyUsage.keyEncipherment | KeyUsage.keyCertSign));
177 certGen.addExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(
178 KeyPurposeId.id_kp_serverAuth));
179
180 AuthorityKeyIdentifier authIdentifier = createAuthorityKeyIdentifier(
181 pair.getPublic(), dnName, serialNumber);
182
183 certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, true,
184 authIdentifier);
185 certGen.addExtension(X509Extensions.SubjectKeyIdentifier, true,
Jerome Poichet53040902014-08-11 17:08:54 -0700186 SubjectKeyIdentifier.getInstance(pair.getPublic().getEncoded()));
Jerome Poichet7c997852014-05-20 10:50:05 -0700187
188 certGen.addExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(
189 new GeneralName(GeneralName.rfc822Name, "googletv@test.test")));
190
191 // This method is deprecated, but Android Eclair does not provide the
192 // generate() methods.
193 X509Certificate cert = certGen.generateX509Certificate(pair.getPrivate(), "BC");
194 return cert;
195 }
196
197 /**
198 * Creates an AuthorityKeyIdentifier from a public key, name, and serial
199 * number.
200 * <p>
201 * {@link AuthorityKeyIdentifierStructure} is <i>almost</i> perfect for this,
202 * but sadly it does not have a constructor suitable for us:
203 * {@link AuthorityKeyIdentifierStructure#AuthorityKeyIdentifierStructure(PublicKey)}
204 * does not set the serial number or name (which is important to us), while
205 * {@link AuthorityKeyIdentifierStructure#AuthorityKeyIdentifierStructure(X509Certificate)}
206 * sets those fields but needs a completed certificate to do so.
207 * <p>
208 * This method addresses the gap in available {@link AuthorityKeyIdentifier}
209 * constructors provided by BouncyCastle; its implementation is derived from
210 * {@link AuthorityKeyIdentifierStructure#AuthorityKeyIdentifierStructure(X509Certificate)}.
211 *
212 * @param publicKey the public key
213 * @param name the name
214 * @param serialNumber the serial number
215 * @return a new {@link AuthorityKeyIdentifier}
216 */
217 private static AuthorityKeyIdentifier createAuthorityKeyIdentifier(
218 PublicKey publicKey, X509Name name, BigInteger serialNumber) {
219 GeneralName genName = new GeneralName(name);
220 SubjectPublicKeyInfo info;
221 try {
222 info = new SubjectPublicKeyInfo(
223 (ASN1Sequence)new ASN1InputStream(publicKey.getEncoded()).readObject());
224 } catch (IOException e) {
225 throw new RuntimeException("Error encoding public key");
226 }
227 return new AuthorityKeyIdentifier(info, new GeneralNames(genName), serialNumber);
228 }
229
230 /**
231 * Wrapper for {@link SslUtil#generateX509V3Certificate(KeyPair, String, Date, Date, BigInteger)}
232 * which uses a default validity period and serial number.
233 * <p>
234 * The validity period is Jan 1 2009 - Jan 1 2099. The serial number is the
235 * current system time.
236 */
237 public static X509Certificate generateX509V3Certificate(KeyPair pair,
238 String name) throws GeneralSecurityException {
239 Calendar calendar = Calendar.getInstance();
240 calendar.set(2009, 0, 1);
241 Date notBefore = new Date(calendar.getTimeInMillis());
242 calendar.set(2099, 0, 1);
243 Date notAfter = new Date(calendar.getTimeInMillis());
244
245 BigInteger serialNumber = BigInteger.valueOf(Math.abs(
246 System.currentTimeMillis()));
247
248 return generateX509V3Certificate(pair, name, notBefore, notAfter,
249 serialNumber);
250 }
251
252 /**
253 * Wrapper for {@link SslUtil#generateX509V3Certificate(KeyPair, String, Date, Date, BigInteger)}
254 * which uses a default validity period.
255 * <p>
256 * The validity period is Jan 1 2009 - Jan 1 2099.
257 */
258 public static X509Certificate generateX509V3Certificate(KeyPair pair,
259 String name, BigInteger serialNumber) throws GeneralSecurityException {
260 Calendar calendar = Calendar.getInstance();
261 calendar.set(2009, 0, 1);
262 Date notBefore = new Date(calendar.getTimeInMillis());
263 calendar.set(2099, 0, 1);
264 Date notAfter = new Date(calendar.getTimeInMillis());
265
266 return generateX509V3Certificate(pair, name, notBefore, notAfter,
267 serialNumber);
268 }
269
270 /**
271 * Generates a new {@code SSLContext} suitable for a test environment.
272 * <p>
273 * A new {@link KeyPair}, {@link X509Certificate},
274 * {@link DummyTrustManager}, and an empty
275 * {@link KeyStore} are created and used to initialize the context.
276 *
277 * @return the new context
278 * @throws GeneralSecurityException if an error occurred during
279 * initialization
280 * @throws IOException if an empty KeyStore could not be
281 * generated
282 */
283 public SSLContext generateTestSslContext()
284 throws GeneralSecurityException, IOException {
285 SSLContext sslcontext = SSLContext.getInstance("SSLv3");
286 KeyManager[] keyManagers = SslUtil.generateTestServerKeyManager("SunX509",
287 "test");
288 sslcontext.init(keyManagers,
289 new TrustManager[] { new DummyTrustManager()},
290 null);
291 return sslcontext;
292 }
293
294 /**
295 * Creates a new pain of {@link KeyManager}s, backed by a keystore file.
296 *
297 * @param keyManagerInstanceName name of the {@link KeyManagerFactory} to
298 * request
299 * @param fileName the name of the keystore to load
300 * @param password the password for the keystore
301 * @return the new object
302 * @throws GeneralSecurityException if an error occurred during
303 * initialization
304 * @throws IOException if the keystore could not be loaded
305 */
306 public static KeyManager[] getFileBackedKeyManagers(
307 String keyManagerInstanceName, String fileName, String password)
308 throws GeneralSecurityException, IOException {
309 KeyManagerFactory km = KeyManagerFactory.getInstance(
310 keyManagerInstanceName);
311 KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
312 ks.load(new FileInputStream(fileName), password.toCharArray());
313 km.init(ks, password.toCharArray());
314 return km.getKeyManagers();
315 }
316
317 /**
318 * Creates a pair of {@link KeyManager}s suitable for use in testing.
319 * <p>
320 * A new {@link KeyPair} and {@link X509Certificate} are created and used to
321 * initialize the KeyManager.
322 *
323 * @param keyManagerInstanceName name of the {@link KeyManagerFactory}
324 * @param password password to apply to the new key store
325 * @return the new key managers
326 * @throws GeneralSecurityException if an error occurred during
327 * initialization
328 * @throws IOException if the keystore could not be generated
329 */
330 public static KeyManager[] generateTestServerKeyManager(
331 String keyManagerInstanceName, String password)
332 throws GeneralSecurityException, IOException {
333 KeyManagerFactory km = KeyManagerFactory.getInstance(
334 keyManagerInstanceName);
335 KeyPair pair = SslUtil.generateRsaKeyPair();
336 X509Certificate cert = SslUtil.generateX509V1Certificate(pair,
337 "CN=Test Server Cert");
338 Certificate[] chain = { cert };
339
340 KeyStore ks = SslUtil.getEmptyKeyStore();
341 ks.setKeyEntry("test-server", pair.getPrivate(),
342 password.toCharArray(), chain);
343 km.init(ks, password.toCharArray());
344 return km.getKeyManagers();
345 }
346
347}