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 17package com.google.polo.ssl; 18 19import 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.SubjectKeyIdentifier; 29import 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; 35 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.MessageDigest; 44import java.security.NoSuchAlgorithmException; 45import java.security.PublicKey; 46import java.security.cert.Certificate; 47import java.security.cert.X509Certificate; 48import java.util.Calendar; 49import java.util.Date; 50 51import javax.net.ssl.KeyManager; 52import javax.net.ssl.KeyManagerFactory; 53import javax.net.ssl.SSLContext; 54import javax.net.ssl.TrustManager; 55import javax.security.auth.x500.X500Principal; 56 57/** 58 * A collection of miscellaneous utility functions for use in Polo. 59 */ 60public 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 */ 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 */ 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 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") 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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