diff options
author | Kenny Root <kroot@google.com> | 2012-08-20 10:48:46 -0700 |
---|---|---|
committer | Kenny Root <kroot@google.com> | 2012-08-22 08:52:55 -0700 |
commit | db026710ec0adcf7f72dfb24c65d38a882ee26d8 (patch) | |
tree | cff080fbecd17c5d6e6a60a7bc2adccbd6761b20 /keystore | |
parent | e29df16cb57b69995df597e8a6d95d986c1c43fc (diff) | |
download | frameworks_base-db026710ec0adcf7f72dfb24c65d38a882ee26d8.zip frameworks_base-db026710ec0adcf7f72dfb24c65d38a882ee26d8.tar.gz frameworks_base-db026710ec0adcf7f72dfb24c65d38a882ee26d8.tar.bz2 |
Add KeyPairGenerator for Android keystore
This allows end-users to generate keys in the keystore without the
private part of the key ever needing to leave the device. The generation
process also generates a self-signed certificate.
Change-Id: I114ffb8e0cbe3b1edaae7e69e8aa578cb835efc9
Diffstat (limited to 'keystore')
8 files changed, 638 insertions, 20 deletions
diff --git a/keystore/java/android/security/AndroidKeyPairGenerator.java b/keystore/java/android/security/AndroidKeyPairGenerator.java new file mode 100644 index 0000000..c42001b --- /dev/null +++ b/keystore/java/android/security/AndroidKeyPairGenerator.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * 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 android.security; + +import com.android.org.bouncycastle.x509.X509V3CertificateGenerator; + +import org.apache.harmony.xnet.provider.jsse.OpenSSLEngine; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyPairGeneratorSpi; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; + +/** + * Provides a way to create instances of a KeyPair which will be placed in the + * Android keystore service usable only by the application that called it. This + * can be used in conjunction with + * {@link java.security.KeyStore#getInstance(String)} using the + * {@code "AndroidKeyStore"} type. + * <p> + * This class can not be directly instantiated and must instead be used via the + * {@link KeyPairGenerator#getInstance(String) + * KeyPairGenerator.getInstance("AndroidKeyPairGenerator")} API. + * + * {@hide} + */ +@SuppressWarnings("deprecation") +public class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { + public static final String NAME = "AndroidKeyPairGenerator"; + + private android.security.KeyStore mKeyStore; + + private AndroidKeyPairGeneratorSpec mSpec; + + /** + * Generate a KeyPair which is backed by the Android keystore service. You + * must call {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)} + * with an {@link AndroidKeyPairGeneratorSpec} as the {@code params} + * argument before calling this otherwise an {@code IllegalStateException} + * will be thrown. + * <p> + * This will create an entry in the Android keystore service with a + * self-signed certificate using the {@code params} specified in the + * {@code initialize(params)} call. + * + * @throws IllegalStateException when called before calling + * {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)} + * @see java.security.KeyPairGeneratorSpi#generateKeyPair() + */ + @Override + public KeyPair generateKeyPair() { + if (mKeyStore == null || mSpec == null) { + throw new IllegalStateException( + "Must call initialize with an AndroidKeyPairGeneratorSpec first"); + } + + final String alias = mSpec.getKeystoreAlias(); + + Credentials.deleteAllTypesForAlias(mKeyStore, alias); + + final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; + mKeyStore.generate(privateKeyAlias); + + final PrivateKey privKey; + final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore"); + try { + privKey = engine.getPrivateKeyById(privateKeyAlias); + } catch (InvalidKeyException e) { + throw new RuntimeException("Can't get key", e); + } + + final byte[] pubKeyBytes = mKeyStore.getPubkey(privateKeyAlias); + + final PublicKey pubKey; + try { + final KeyFactory keyFact = KeyFactory.getInstance("RSA"); + pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes)); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("Can't instantiate RSA key generator", e); + } catch (InvalidKeySpecException e) { + throw new IllegalStateException("keystore returned invalid key encoding", e); + } + + final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); + certGen.setPublicKey(pubKey); + certGen.setSerialNumber(mSpec.getSerialNumber()); + certGen.setSubjectDN(mSpec.getSubjectDN()); + certGen.setIssuerDN(mSpec.getSubjectDN()); + certGen.setNotBefore(mSpec.getStartDate()); + certGen.setNotAfter(mSpec.getEndDate()); + certGen.setSignatureAlgorithm("sha1WithRSA"); + + final X509Certificate cert; + try { + cert = certGen.generate(privKey); + } catch (Exception e) { + Credentials.deleteAllTypesForAlias(mKeyStore, alias); + throw new IllegalStateException("Can't generate certificate", e); + } + + byte[] certBytes; + try { + certBytes = cert.getEncoded(); + } catch (CertificateEncodingException e) { + Credentials.deleteAllTypesForAlias(mKeyStore, alias); + throw new IllegalStateException("Can't get encoding of certificate", e); + } + + if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, certBytes)) { + Credentials.deleteAllTypesForAlias(mKeyStore, alias); + throw new IllegalStateException("Can't store certificate in AndroidKeyStore"); + } + + return new KeyPair(pubKey, privKey); + } + + @Override + public void initialize(int keysize, SecureRandom random) { + throw new IllegalArgumentException("cannot specify keysize with AndroidKeyPairGenerator"); + } + + @Override + public void initialize(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + if (params == null) { + throw new InvalidAlgorithmParameterException( + "must supply params of type AndroidKeyPairGenericSpec"); + } else if (!(params instanceof AndroidKeyPairGeneratorSpec)) { + throw new InvalidAlgorithmParameterException( + "params must be of type AndroidKeyPairGeneratorSpec"); + } + + AndroidKeyPairGeneratorSpec spec = (AndroidKeyPairGeneratorSpec) params; + + mSpec = spec; + mKeyStore = android.security.KeyStore.getInstance(); + } +} diff --git a/keystore/java/android/security/AndroidKeyPairGeneratorSpec.java b/keystore/java/android/security/AndroidKeyPairGeneratorSpec.java new file mode 100644 index 0000000..311359c --- /dev/null +++ b/keystore/java/android/security/AndroidKeyPairGeneratorSpec.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * 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 android.security; + +import android.content.Context; +import android.text.TextUtils; + +import java.math.BigInteger; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Date; + +import javax.security.auth.x500.X500Principal; + +/** + * This provides the required parameters needed for initializing the KeyPair + * generator that works with + * <a href="{@docRoot}guide/topics/security/keystore.html">Android KeyStore + * facility</a>. + */ +public class AndroidKeyPairGeneratorSpec implements AlgorithmParameterSpec { + private final String mKeystoreAlias; + + private final Context mContext; + + private final X500Principal mSubjectDN; + + private final BigInteger mSerialNumber; + + private final Date mStartDate; + + private final Date mEndDate; + + /** + * Parameter specification for the "{@code AndroidKeyPairGenerator}" + * instance of the {@link java.security.KeyPairGenerator} API. The + * {@code context} passed in may be used to pop up some UI to ask the user + * to unlock or initialize the Android keystore facility. + * <p> + * After generation, the {@code keyStoreAlias} is used with the + * {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter)} + * interface to retrieve the {@link PrivateKey} and its associated + * {@link Certificate} chain. + * <p> + * The KeyPair generator will create a self-signed certificate with the + * properties of {@code subjectDN} as its X.509v3 Subject Distinguished Name + * and as its X.509v3 Issuer Distinguished Name, using the specified + * {@code serialNumber}, and the validity date starting at {@code startDate} + * and ending at {@code endDate}. + * + * @param context Android context for the activity + * @param keyStoreAlias name to use for the generated key in the Android + * keystore + * @param subjectDN X.509 v3 Subject Distinguished Name + * @param serialNumber X509 v3 certificate serial number + * @param startDate the start of the self-signed certificate validity period + * @param endDate the end date of the self-signed certificate validity + * period + * @throws IllegalArgumentException when any argument is {@code null} or + * {@code endDate} is before {@code startDate}. + */ + public AndroidKeyPairGeneratorSpec(Context context, String keyStoreAlias, + X500Principal subjectDN, BigInteger serialNumber, Date startDate, Date endDate) { + if (context == null) { + throw new IllegalArgumentException("context == null"); + } else if (TextUtils.isEmpty(keyStoreAlias)) { + throw new IllegalArgumentException("keyStoreAlias must not be empty"); + } else if (subjectDN == null) { + throw new IllegalArgumentException("subjectDN == null"); + } else if (serialNumber == null) { + throw new IllegalArgumentException("serialNumber == null"); + } else if (startDate == null) { + throw new IllegalArgumentException("startDate == null"); + } else if (endDate == null) { + throw new IllegalArgumentException("endDate == null"); + } else if (endDate.before(startDate)) { + throw new IllegalArgumentException("endDate < startDate"); + } + + mContext = context; + mKeystoreAlias = keyStoreAlias; + mSubjectDN = subjectDN; + mSerialNumber = serialNumber; + mStartDate = startDate; + mEndDate = endDate; + } + + /** + * @hide + */ + String getKeystoreAlias() { + return mKeystoreAlias; + } + + /** + * @hide + */ + Context getContext() { + return mContext; + } + + /** + * @hide + */ + X500Principal getSubjectDN() { + return mSubjectDN; + } + + /** + * @hide + */ + BigInteger getSerialNumber() { + return mSerialNumber; + } + + /** + * @hide + */ + Date getStartDate() { + return mStartDate; + } + + /** + * @hide + */ + Date getEndDate() { + return mEndDate; + } +} diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java index a629f8d..e19217f 100644 --- a/keystore/java/android/security/AndroidKeyStore.java +++ b/keystore/java/android/security/AndroidKeyStore.java @@ -46,9 +46,8 @@ import java.util.Iterator; import java.util.Set; /** - * A java.security.KeyStore interface for the Android KeyStore. This class is - * hidden from the Android API, but an instance of it can be created via the - * {@link java.security.KeyStore#getInstance(String) + * A java.security.KeyStore interface for the Android KeyStore. An instance of + * it can be created via the {@link java.security.KeyStore#getInstance(String) * KeyStore.getInstance("AndroidKeyStore")} interface. This returns a * java.security.KeyStore backed by this "AndroidKeyStore" implementation. * <p> @@ -277,7 +276,7 @@ public class AndroidKeyStore extends KeyStoreSpi { * Make sure we clear out all the types we know about before trying to * write. */ - deleteAllTypesForAlias(alias); + Credentials.deleteAllTypesForAlias(mKeyStore, alias); if (!mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes)) { throw new KeyStoreException("Couldn't put private key in keystore"); @@ -315,26 +314,11 @@ public class AndroidKeyStore extends KeyStoreSpi { @Override public void engineDeleteEntry(String alias) throws KeyStoreException { - if (!deleteAllTypesForAlias(alias)) { + if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) { throw new KeyStoreException("No such entry " + alias); } } - /** - * Delete all types (private key, certificate, CA certificate) for a - * particular {@code alias}. All three can exist for any given alias. - * Returns {@code true} if there was at least one of those types. - */ - private boolean deleteAllTypesForAlias(String alias) { - /* - * Make sure every type is deleted. There can be all three types, so - * don't use a conditional here. - */ - return mKeyStore.delKey(Credentials.USER_PRIVATE_KEY + alias) - | mKeyStore.delete(Credentials.USER_CERTIFICATE + alias) - | mKeyStore.delete(Credentials.CA_CERTIFICATE + alias); - } - private Set<String> getUniqueAliases() { final String[] rawAliases = mKeyStore.saw(""); if (rawAliases == null) { diff --git a/keystore/java/android/security/AndroidKeyStoreProvider.java b/keystore/java/android/security/AndroidKeyStoreProvider.java index df22f58..40d7e1a 100644 --- a/keystore/java/android/security/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/AndroidKeyStoreProvider.java @@ -29,6 +29,11 @@ public class AndroidKeyStoreProvider extends Provider { public AndroidKeyStoreProvider() { super(PROVIDER_NAME, 1.0, "Android KeyStore security provider"); + // java.security.KeyStore put("KeyStore." + AndroidKeyStore.NAME, AndroidKeyStore.class.getName()); + + // java.security.KeyPairGenerator + put("KeyPairGenerator." + AndroidKeyPairGenerator.NAME, + AndroidKeyPairGenerator.class.getName()); } } diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java index 68ba2b1..72332eb 100644 --- a/keystore/java/android/security/Credentials.java +++ b/keystore/java/android/security/Credentials.java @@ -185,4 +185,19 @@ public class Credentials { Log.w(LOGTAG, e.toString()); } } + + /** + * Delete all types (private key, certificate, CA certificate) for a + * particular {@code alias}. All three can exist for any given alias. + * Returns {@code true} if there was at least one of those types. + */ + static boolean deleteAllTypesForAlias(KeyStore keystore, String alias) { + /* + * Make sure every type is deleted. There can be all three types, so + * don't use a conditional here. + */ + return keystore.delKey(Credentials.USER_PRIVATE_KEY + alias) + | keystore.delete(Credentials.USER_CERTIFICATE + alias) + | keystore.delete(Credentials.CA_CERTIFICATE + alias); + } } diff --git a/keystore/java/android/security/package.html b/keystore/java/android/security/package.html new file mode 100644 index 0000000..610cbf0 --- /dev/null +++ b/keystore/java/android/security/package.html @@ -0,0 +1,9 @@ +<HTML> +<BODY> + <p>Provides access to a few facilities of the Android security + subsystems.</p> + <p>For information on how to use this facility, see the <a + href="{@docRoot}guide/topics/security/keystore.html">Android + KeyStore facility</a> guide.</p> +</BODY> +</HTML>
\ No newline at end of file diff --git a/keystore/tests/src/android/security/AndroidKeyPairGeneratorSpecTest.java b/keystore/tests/src/android/security/AndroidKeyPairGeneratorSpecTest.java new file mode 100644 index 0000000..e6a3750 --- /dev/null +++ b/keystore/tests/src/android/security/AndroidKeyPairGeneratorSpecTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * 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 android.security; + +import android.test.AndroidTestCase; + +import java.math.BigInteger; +import java.util.Date; + +import javax.security.auth.x500.X500Principal; + +public class AndroidKeyPairGeneratorSpecTest extends AndroidTestCase { + private static final String TEST_ALIAS_1 = "test1"; + + private static final X500Principal TEST_DN_1 = new X500Principal("CN=test1"); + + private static final long NOW_MILLIS = System.currentTimeMillis(); + + private static final BigInteger SERIAL_1 = BigInteger.ONE; + + /* We have to round this off because X509v3 doesn't store milliseconds. */ + private static final Date NOW = new Date(NOW_MILLIS - (NOW_MILLIS % 1000L)); + + @SuppressWarnings("deprecation") + private static final Date NOW_PLUS_10_YEARS = new Date(NOW.getYear() + 10, 0, 1); + + public void testConstructor_Success() throws Exception { + AndroidKeyPairGeneratorSpec spec = new AndroidKeyPairGeneratorSpec(getContext(), + TEST_ALIAS_1, TEST_DN_1, SERIAL_1, NOW, NOW_PLUS_10_YEARS); + + assertEquals("Context should be the one specified", getContext(), spec.getContext()); + + assertEquals("Alias should be the one specified", TEST_ALIAS_1, spec.getKeystoreAlias()); + + assertEquals("subjectDN should be the one specified", TEST_DN_1, spec.getSubjectDN()); + + assertEquals("startDate should be the one specified", NOW, spec.getStartDate()); + + assertEquals("endDate should be the one specified", NOW_PLUS_10_YEARS, spec.getEndDate()); + } + + public void testConstructor_NullContext_Failure() throws Exception { + try { + new AndroidKeyPairGeneratorSpec(null, TEST_ALIAS_1, TEST_DN_1, SERIAL_1, NOW, + NOW_PLUS_10_YEARS); + fail("Should throw IllegalArgumentException when context is null"); + } catch (IllegalArgumentException success) { + } + } + + public void testConstructor_NullKeystoreAlias_Failure() throws Exception { + try { + new AndroidKeyPairGeneratorSpec(getContext(), null, TEST_DN_1, SERIAL_1, NOW, + NOW_PLUS_10_YEARS); + fail("Should throw IllegalArgumentException when keystoreAlias is null"); + } catch (IllegalArgumentException success) { + } + } + + public void testConstructor_NullSubjectDN_Failure() throws Exception { + try { + new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, null, SERIAL_1, NOW, + NOW_PLUS_10_YEARS); + fail("Should throw IllegalArgumentException when subjectDN is null"); + } catch (IllegalArgumentException success) { + } + } + + public void testConstructor_NullSerial_Failure() throws Exception { + try { + new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, null, NOW, + NOW_PLUS_10_YEARS); + fail("Should throw IllegalArgumentException when startDate is null"); + } catch (IllegalArgumentException success) { + } + } + + public void testConstructor_NullStartDate_Failure() throws Exception { + try { + new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1, null, + NOW_PLUS_10_YEARS); + fail("Should throw IllegalArgumentException when startDate is null"); + } catch (IllegalArgumentException success) { + } + } + + public void testConstructor_NullEndDate_Failure() throws Exception { + try { + new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1, NOW, + null); + fail("Should throw IllegalArgumentException when keystoreAlias is null"); + } catch (IllegalArgumentException success) { + } + } + + public void testConstructor_EndBeforeStart_Failure() throws Exception { + try { + new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1, + NOW_PLUS_10_YEARS, NOW); + fail("Should throw IllegalArgumentException when end is before start"); + } catch (IllegalArgumentException success) { + } + } +} diff --git a/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java b/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java new file mode 100644 index 0000000..d108caa --- /dev/null +++ b/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * 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 android.security; + +import android.test.AndroidTestCase; + +import java.io.ByteArrayInputStream; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Date; + +import javax.security.auth.x500.X500Principal; + +public class AndroidKeyPairGeneratorTest extends AndroidTestCase { + private android.security.KeyStore mAndroidKeyStore; + + private java.security.KeyPairGenerator mGenerator; + + private static final String TEST_ALIAS_1 = "test1"; + + private static final String TEST_ALIAS_2 = "test2"; + + private static final X500Principal TEST_DN_1 = new X500Principal("CN=test1"); + + private static final X500Principal TEST_DN_2 = new X500Principal("CN=test2"); + + private static final BigInteger TEST_SERIAL_1 = BigInteger.ONE; + + private static final BigInteger TEST_SERIAL_2 = BigInteger.valueOf(2L); + + private static final long NOW_MILLIS = System.currentTimeMillis(); + + /* We have to round this off because X509v3 doesn't store milliseconds. */ + private static final Date NOW = new Date(NOW_MILLIS - (NOW_MILLIS % 1000L)); + + @SuppressWarnings("deprecation") + private static final Date NOW_PLUS_10_YEARS = new Date(NOW.getYear() + 10, 0, 1); + + @Override + protected void setUp() throws Exception { + mAndroidKeyStore = android.security.KeyStore.getInstance(); + + assertTrue(mAndroidKeyStore.reset()); + + assertEquals(android.security.KeyStore.State.UNINITIALIZED, mAndroidKeyStore.state()); + + assertTrue(mAndroidKeyStore.password("1111")); + + assertEquals(android.security.KeyStore.State.UNLOCKED, mAndroidKeyStore.state()); + + assertEquals(0, mAndroidKeyStore.saw("").length); + + mGenerator = java.security.KeyPairGenerator.getInstance(AndroidKeyPairGenerator.NAME); + } + + public void testKeyPairGenerator_Initialize_Params_Success() throws Exception { + mGenerator.initialize(new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, + TEST_DN_1, TEST_SERIAL_1, NOW, NOW_PLUS_10_YEARS)); + } + + public void testKeyPairGenerator_Initialize_KeySize_Failure() throws Exception { + try { + mGenerator.initialize(1024); + fail("KeyPairGenerator should not support setting the key size"); + } catch (IllegalArgumentException success) { + } + } + + public void testKeyPairGenerator_Initialize_KeySizeAndSecureRandom_Failure() throws Exception { + try { + mGenerator.initialize(1024, new SecureRandom()); + fail("KeyPairGenerator should not support setting the key size"); + } catch (IllegalArgumentException success) { + } + } + + public void testKeyPairGenerator_Initialize_ParamsAndSecureRandom_Failure() throws Exception { + mGenerator.initialize(new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, + TEST_DN_1, TEST_SERIAL_1, NOW, NOW_PLUS_10_YEARS), new SecureRandom()); + } + + public void testKeyPairGenerator_GenerateKeyPair_Success() throws Exception { + mGenerator.initialize(new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, + TEST_DN_1, TEST_SERIAL_1, NOW, NOW_PLUS_10_YEARS)); + + final KeyPair pair = mGenerator.generateKeyPair(); + assertNotNull("The KeyPair returned should not be null", pair); + + assertKeyPairCorrect(pair, TEST_ALIAS_1, TEST_DN_1, TEST_SERIAL_1, NOW, NOW_PLUS_10_YEARS); + } + + public void testKeyPairGenerator_GenerateKeyPair_Replaced_Success() throws Exception { + // Generate the first key + { + mGenerator.initialize(new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, + TEST_DN_1, TEST_SERIAL_1, NOW, NOW_PLUS_10_YEARS)); + final KeyPair pair1 = mGenerator.generateKeyPair(); + assertNotNull("The KeyPair returned should not be null", pair1); + assertKeyPairCorrect(pair1, TEST_ALIAS_1, TEST_DN_1, TEST_SERIAL_1, NOW, + NOW_PLUS_10_YEARS); + } + + // Replace the original key + { + mGenerator.initialize(new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_2, + TEST_DN_2, TEST_SERIAL_2, NOW, NOW_PLUS_10_YEARS)); + final KeyPair pair2 = mGenerator.generateKeyPair(); + assertNotNull("The KeyPair returned should not be null", pair2); + assertKeyPairCorrect(pair2, TEST_ALIAS_2, TEST_DN_2, TEST_SERIAL_2, NOW, + NOW_PLUS_10_YEARS); + } + } + + private void assertKeyPairCorrect(KeyPair pair, String alias, X500Principal dn, + BigInteger serial, Date start, Date end) throws Exception { + final PublicKey pubKey = pair.getPublic(); + assertNotNull("The PublicKey for the KeyPair should be not null", pubKey); + + final PrivateKey privKey = pair.getPrivate(); + assertNotNull("The PrivateKey for the KeyPair should be not null", privKey); + + final byte[] userCertBytes = mAndroidKeyStore.get(Credentials.USER_CERTIFICATE + alias); + assertNotNull("The user certificate should exist for the generated entry", userCertBytes); + + final CertificateFactory cf = CertificateFactory.getInstance("X.509"); + final Certificate userCert = cf + .generateCertificate(new ByteArrayInputStream(userCertBytes)); + + assertTrue("Certificate should be in X.509 format", userCert instanceof X509Certificate); + + final X509Certificate x509userCert = (X509Certificate) userCert; + + assertEquals("PublicKey used to sign certificate should match one returned in KeyPair", + pubKey, x509userCert.getPublicKey()); + + assertEquals("The Subject DN should be the one passed into the params", dn, + x509userCert.getSubjectDN()); + + assertEquals("The Issuer DN should be the same as the Subject DN", dn, + x509userCert.getIssuerDN()); + + assertEquals("The Serial should be the one passed into the params", serial, + x509userCert.getSerialNumber()); + + assertEquals("The notBefore date should be the one passed into the params", start, + x509userCert.getNotBefore()); + + assertEquals("The notAfter date should be the one passed into the params", end, + x509userCert.getNotAfter()); + + x509userCert.verify(pubKey); + + final byte[] caCerts = mAndroidKeyStore.get(Credentials.CA_CERTIFICATE + alias); + assertNull("A list of CA certificates should not exist for the generated entry", caCerts); + + final byte[] pubKeyBytes = mAndroidKeyStore.getPubkey(Credentials.USER_PRIVATE_KEY + alias); + assertNotNull("The keystore should return the public key for the generated key", + pubKeyBytes); + } +} |