summaryrefslogtreecommitdiff
path: root/java/src/com/google/polo/ssl/SslUtil.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/google/polo/ssl/SslUtil.java')
-rw-r--r--java/src/com/google/polo/ssl/SslUtil.java347
1 files changed, 347 insertions, 0 deletions
diff --git a/java/src/com/google/polo/ssl/SslUtil.java b/java/src/com/google/polo/ssl/SslUtil.java
new file mode 100644
index 0000000..cf0b3da
--- /dev/null
+++ b/java/src/com/google/polo/ssl/SslUtil.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.polo.ssl;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.x509.X509V1CertificateGenerator;
+import org.bouncycastle.x509.X509V3CertificateGenerator;
+import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
+import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Calendar;
+import java.util.Date;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * A collection of miscellaneous utility functions for use in Polo.
+ */
+public class SslUtil {
+
+ /**
+ * Generates a new RSA key pair.
+ *
+ * @return the new object
+ * @throws NoSuchAlgorithmException if the RSA generator could not be loaded
+ */
+ public static KeyPair generateRsaKeyPair() throws NoSuchAlgorithmException {
+ KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA");
+ KeyPair kp = kg.generateKeyPair();
+ return kp;
+ }
+
+ /**
+ * Creates a new, empty {@link KeyStore}
+ *
+ * @return the new KeyStore
+ * @throws GeneralSecurityException on error creating the keystore
+ * @throws IOException on error loading the keystore
+ */
+ public static KeyStore getEmptyKeyStore()
+ throws GeneralSecurityException, IOException {
+ KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+ ks.load(null, null);
+ return ks;
+ }
+
+ /**
+ * Generates a new, self-signed X509 V1 certificate for a KeyPair.
+ *
+ * @param pair the {@link KeyPair} to be used
+ * @param name X.500 distinguished name
+ * @return the new certificate
+ * @throws GeneralSecurityException on error generating the certificate
+ */
+ @SuppressWarnings("deprecation")
+ public static X509Certificate generateX509V1Certificate(KeyPair pair,
+ String name)
+ throws GeneralSecurityException {
+ java.security.Security.addProvider(
+ new org.bouncycastle.jce.provider.BouncyCastleProvider());
+
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(2009, 0, 1);
+ Date startDate = new Date(calendar.getTimeInMillis());
+ calendar.set(2029, 0, 1);
+ Date expiryDate = new Date(calendar.getTimeInMillis());
+
+ BigInteger serialNumber = BigInteger.valueOf(Math.abs(
+ System.currentTimeMillis()));
+
+ X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
+ X500Principal dnName = new X500Principal(name);
+ certGen.setSerialNumber(serialNumber);
+ certGen.setIssuerDN(dnName);
+ certGen.setNotBefore(startDate);
+ certGen.setNotAfter(expiryDate);
+ certGen.setSubjectDN(dnName); // note: same as issuer
+ certGen.setPublicKey(pair.getPublic());
+ certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
+
+ // This method is deprecated, but Android Eclair does not provide the
+ // generate() methods.
+ X509Certificate cert = certGen.generateX509Certificate(pair.getPrivate(), "BC");
+ return cert;
+ }
+
+ /**
+ * Generates a new, self-signed X509 V3 certificate for a KeyPair.
+ *
+ * @param pair the {@link KeyPair} to be used
+ * @param name X.500 distinguished name
+ * @param notBefore not valid before this date
+ * @param notAfter not valid after this date
+ * @param serialNumber serial number
+ * @return the new certificate
+ * @throws GeneralSecurityException on error generating the certificate
+ */
+ @SuppressWarnings("deprecation")
+ public static X509Certificate generateX509V3Certificate(KeyPair pair,
+ String name, Date notBefore, Date notAfter, BigInteger serialNumber)
+ throws GeneralSecurityException {
+ java.security.Security.addProvider(
+ new org.bouncycastle.jce.provider.BouncyCastleProvider());
+
+ X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
+ X509Name dnName = new X509Name(name);
+
+ certGen.setSerialNumber(serialNumber);
+ certGen.setIssuerDN(dnName);
+ certGen.setSubjectDN(dnName); // note: same as issuer
+ certGen.setNotBefore(notBefore);
+ certGen.setNotAfter(notAfter);
+ certGen.setPublicKey(pair.getPublic());
+ certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
+
+ // For self-signed certificates, OpenSSL 0.9.6 has specific requirements
+ // about certificate and extension content. Quoting the `man verify`:
+ //
+ // In OpenSSL 0.9.6 and later all certificates whose subject name matches
+ // the issuer name of the current certificate are subject to further
+ // tests. The relevant authority key identifier components of the current
+ // certificate (if present) must match the subject key identifier (if
+ // present) and issuer and serial number of the candidate issuer, in
+ // addition the keyUsage extension of the candidate issuer (if present)
+ // must permit certificate signing.
+ //
+ // In the code that follows,
+ // - the KeyUsage extension permits cert signing (KeyUsage.keyCertSign);
+ // - the Authority Key Identifier extension is added, matching the
+ // subject key identifier, and using the issuer, and serial number.
+
+ certGen.addExtension(X509Extensions.BasicConstraints, true,
+ new BasicConstraints(false));
+
+ certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature
+ | KeyUsage.keyEncipherment | KeyUsage.keyCertSign));
+ certGen.addExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(
+ KeyPurposeId.id_kp_serverAuth));
+
+ AuthorityKeyIdentifier authIdentifier = createAuthorityKeyIdentifier(
+ pair.getPublic(), dnName, serialNumber);
+
+ certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, true,
+ authIdentifier);
+ certGen.addExtension(X509Extensions.SubjectKeyIdentifier, true,
+ new SubjectKeyIdentifierStructure(pair.getPublic()));
+
+ certGen.addExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(
+ new GeneralName(GeneralName.rfc822Name, "googletv@test.test")));
+
+ // This method is deprecated, but Android Eclair does not provide the
+ // generate() methods.
+ X509Certificate cert = certGen.generateX509Certificate(pair.getPrivate(), "BC");
+ return cert;
+ }
+
+ /**
+ * Creates an AuthorityKeyIdentifier from a public key, name, and serial
+ * number.
+ * <p>
+ * {@link AuthorityKeyIdentifierStructure} is <i>almost</i> perfect for this,
+ * but sadly it does not have a constructor suitable for us:
+ * {@link AuthorityKeyIdentifierStructure#AuthorityKeyIdentifierStructure(PublicKey)}
+ * does not set the serial number or name (which is important to us), while
+ * {@link AuthorityKeyIdentifierStructure#AuthorityKeyIdentifierStructure(X509Certificate)}
+ * sets those fields but needs a completed certificate to do so.
+ * <p>
+ * This method addresses the gap in available {@link AuthorityKeyIdentifier}
+ * constructors provided by BouncyCastle; its implementation is derived from
+ * {@link AuthorityKeyIdentifierStructure#AuthorityKeyIdentifierStructure(X509Certificate)}.
+ *
+ * @param publicKey the public key
+ * @param name the name
+ * @param serialNumber the serial number
+ * @return a new {@link AuthorityKeyIdentifier}
+ */
+ private static AuthorityKeyIdentifier createAuthorityKeyIdentifier(
+ PublicKey publicKey, X509Name name, BigInteger serialNumber) {
+ GeneralName genName = new GeneralName(name);
+ SubjectPublicKeyInfo info;
+ try {
+ info = new SubjectPublicKeyInfo(
+ (ASN1Sequence)new ASN1InputStream(publicKey.getEncoded()).readObject());
+ } catch (IOException e) {
+ throw new RuntimeException("Error encoding public key");
+ }
+ return new AuthorityKeyIdentifier(info, new GeneralNames(genName), serialNumber);
+ }
+
+ /**
+ * Wrapper for {@link SslUtil#generateX509V3Certificate(KeyPair, String, Date, Date, BigInteger)}
+ * which uses a default validity period and serial number.
+ * <p>
+ * The validity period is Jan 1 2009 - Jan 1 2099. The serial number is the
+ * current system time.
+ */
+ public static X509Certificate generateX509V3Certificate(KeyPair pair,
+ String name) throws GeneralSecurityException {
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(2009, 0, 1);
+ Date notBefore = new Date(calendar.getTimeInMillis());
+ calendar.set(2099, 0, 1);
+ Date notAfter = new Date(calendar.getTimeInMillis());
+
+ BigInteger serialNumber = BigInteger.valueOf(Math.abs(
+ System.currentTimeMillis()));
+
+ return generateX509V3Certificate(pair, name, notBefore, notAfter,
+ serialNumber);
+ }
+
+ /**
+ * Wrapper for {@link SslUtil#generateX509V3Certificate(KeyPair, String, Date, Date, BigInteger)}
+ * which uses a default validity period.
+ * <p>
+ * The validity period is Jan 1 2009 - Jan 1 2099.
+ */
+ public static X509Certificate generateX509V3Certificate(KeyPair pair,
+ String name, BigInteger serialNumber) throws GeneralSecurityException {
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(2009, 0, 1);
+ Date notBefore = new Date(calendar.getTimeInMillis());
+ calendar.set(2099, 0, 1);
+ Date notAfter = new Date(calendar.getTimeInMillis());
+
+ return generateX509V3Certificate(pair, name, notBefore, notAfter,
+ serialNumber);
+ }
+
+ /**
+ * Generates a new {@code SSLContext} suitable for a test environment.
+ * <p>
+ * A new {@link KeyPair}, {@link X509Certificate},
+ * {@link DummyTrustManager}, and an empty
+ * {@link KeyStore} are created and used to initialize the context.
+ *
+ * @return the new context
+ * @throws GeneralSecurityException if an error occurred during
+ * initialization
+ * @throws IOException if an empty KeyStore could not be
+ * generated
+ */
+ public SSLContext generateTestSslContext()
+ throws GeneralSecurityException, IOException {
+ SSLContext sslcontext = SSLContext.getInstance("SSLv3");
+ KeyManager[] keyManagers = SslUtil.generateTestServerKeyManager("SunX509",
+ "test");
+ sslcontext.init(keyManagers,
+ new TrustManager[] { new DummyTrustManager()},
+ null);
+ return sslcontext;
+ }
+
+ /**
+ * Creates a new pain of {@link KeyManager}s, backed by a keystore file.
+ *
+ * @param keyManagerInstanceName name of the {@link KeyManagerFactory} to
+ * request
+ * @param fileName the name of the keystore to load
+ * @param password the password for the keystore
+ * @return the new object
+ * @throws GeneralSecurityException if an error occurred during
+ * initialization
+ * @throws IOException if the keystore could not be loaded
+ */
+ public static KeyManager[] getFileBackedKeyManagers(
+ String keyManagerInstanceName, String fileName, String password)
+ throws GeneralSecurityException, IOException {
+ KeyManagerFactory km = KeyManagerFactory.getInstance(
+ keyManagerInstanceName);
+ KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+ ks.load(new FileInputStream(fileName), password.toCharArray());
+ km.init(ks, password.toCharArray());
+ return km.getKeyManagers();
+ }
+
+ /**
+ * Creates a pair of {@link KeyManager}s suitable for use in testing.
+ * <p>
+ * A new {@link KeyPair} and {@link X509Certificate} are created and used to
+ * initialize the KeyManager.
+ *
+ * @param keyManagerInstanceName name of the {@link KeyManagerFactory}
+ * @param password password to apply to the new key store
+ * @return the new key managers
+ * @throws GeneralSecurityException if an error occurred during
+ * initialization
+ * @throws IOException if the keystore could not be generated
+ */
+ public static KeyManager[] generateTestServerKeyManager(
+ String keyManagerInstanceName, String password)
+ throws GeneralSecurityException, IOException {
+ KeyManagerFactory km = KeyManagerFactory.getInstance(
+ keyManagerInstanceName);
+ KeyPair pair = SslUtil.generateRsaKeyPair();
+ X509Certificate cert = SslUtil.generateX509V1Certificate(pair,
+ "CN=Test Server Cert");
+ Certificate[] chain = { cert };
+
+ KeyStore ks = SslUtil.getEmptyKeyStore();
+ ks.setKeyEntry("test-server", pair.getPrivate(),
+ password.toCharArray(), chain);
+ km.init(ks, password.toCharArray());
+ return km.getKeyManagers();
+ }
+
+}