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.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; 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 @Deprecated 97 public static X509Certificate generateX509V1Certificate(KeyPair pair, 98 String name) 99 throws GeneralSecurityException { 100 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 120 X509Certificate cert = certGen.generate(pair.getPrivate()); 121 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 { 139 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( 184 new GeneralName(GeneralName.rfc822Name, "android-tv-remote-support (at) google.com"))); 185 186 X509Certificate cert = certGen.generate(pair.getPrivate()); 187 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 */ 210 static AuthorityKeyIdentifier createAuthorityKeyIdentifier( 211 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 } 341