Jerome Poichet | 7c99785 | 2014-05-20 10:50:05 -0700 | [diff] [blame] | 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 | |
Jerome Poichet | 7a318a6 | 2014-07-07 14:09:16 -0700 | [diff] [blame^] | 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.SubjectPublicKeyInfo; |
| 29 | import org.bouncycastle.asn1.x509.X509Extensions; |
| 30 | import org.bouncycastle.asn1.x509.X509Name; |
| 31 | import org.bouncycastle.x509.X509V1CertificateGenerator; |
| 32 | import org.bouncycastle.x509.X509V3CertificateGenerator; |
| 33 | import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure; |
| 34 | import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure; |
Jerome Poichet | 7c99785 | 2014-05-20 10:50:05 -0700 | [diff] [blame] | 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.NoSuchAlgorithmException; |
| 44 | import java.security.PublicKey; |
| 45 | import java.security.cert.Certificate; |
| 46 | import java.security.cert.X509Certificate; |
| 47 | import java.util.Calendar; |
| 48 | import java.util.Date; |
| 49 | |
| 50 | import javax.net.ssl.KeyManager; |
| 51 | import javax.net.ssl.KeyManagerFactory; |
| 52 | import javax.net.ssl.SSLContext; |
| 53 | import javax.net.ssl.TrustManager; |
| 54 | import javax.security.auth.x500.X500Principal; |
| 55 | |
| 56 | /** |
| 57 | * A collection of miscellaneous utility functions for use in Polo. |
| 58 | */ |
| 59 | public 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 Poichet | 7a318a6 | 2014-07-07 14:09:16 -0700 | [diff] [blame^] | 100 | new org.bouncycastle.jce.provider.BouncyCastleProvider()); |
Jerome Poichet | 7c99785 | 2014-05-20 10:50:05 -0700 | [diff] [blame] | 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 | // 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 Poichet | 7a318a6 | 2014-07-07 14:09:16 -0700 | [diff] [blame^] | 143 | new org.bouncycastle.jce.provider.BouncyCastleProvider()); |
Jerome Poichet | 7c99785 | 2014-05-20 10:50:05 -0700 | [diff] [blame] | 144 | |
| 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, |
| 186 | new SubjectKeyIdentifierStructure(pair.getPublic())); |
| 187 | |
| 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 | } |