blob: 3d092b35c65c22d4adc9c5835c4d6950a1fb1f79 [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;
28import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
29import org.bouncycastle.asn1.x509.X509Extensions;
30import org.bouncycastle.asn1.x509.X509Name;
31import org.bouncycastle.x509.X509V1CertificateGenerator;
32import org.bouncycastle.x509.X509V3CertificateGenerator;
33import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
34import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
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")
Jerome Poichet8e471582014-07-31 21:19:03 -070096 @Deprecated
Jerome Poichet7c997852014-05-20 10:50:05 -070097 public static X509Certificate generateX509V1Certificate(KeyPair pair,
98 String name)
99 throws GeneralSecurityException {
Jerome Poichet7c997852014-05-20 10:50:05 -0700100
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
Jerome Poichet8e471582014-07-31 21:19:03 -0700120 X509Certificate cert = certGen.generate(pair.getPrivate());
Jerome Poichet7c997852014-05-20 10:50:05 -0700121 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 {
Jerome Poichet7c997852014-05-20 10:50:05 -0700139
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(
Jerome Poichet8e471582014-07-31 21:19:03 -0700184 new GeneralName(GeneralName.rfc822Name, "android-tv-remote-support@google.com")));
Jerome Poichet7c997852014-05-20 10:50:05 -0700185
Jerome Poichet8e471582014-07-31 21:19:03 -0700186 X509Certificate cert = certGen.generate(pair.getPrivate());
Jerome Poichet7c997852014-05-20 10:50:05 -0700187 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 */
Jerome Poichet8e471582014-07-31 21:19:03 -0700210 static AuthorityKeyIdentifier createAuthorityKeyIdentifier(
Jerome Poichet7c997852014-05-20 10:50:05 -0700211 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}