summaryrefslogtreecommitdiffstats
path: root/keystore
diff options
context:
space:
mode:
authorKenny Root <kroot@google.com>2012-08-20 10:48:46 -0700
committerKenny Root <kroot@google.com>2012-08-22 08:52:55 -0700
commitdb026710ec0adcf7f72dfb24c65d38a882ee26d8 (patch)
treecff080fbecd17c5d6e6a60a7bc2adccbd6761b20 /keystore
parente29df16cb57b69995df597e8a6d95d986c1c43fc (diff)
downloadframeworks_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')
-rw-r--r--keystore/java/android/security/AndroidKeyPairGenerator.java163
-rw-r--r--keystore/java/android/security/AndroidKeyPairGeneratorSpec.java144
-rw-r--r--keystore/java/android/security/AndroidKeyStore.java24
-rw-r--r--keystore/java/android/security/AndroidKeyStoreProvider.java5
-rw-r--r--keystore/java/android/security/Credentials.java15
-rw-r--r--keystore/java/android/security/package.html9
-rw-r--r--keystore/tests/src/android/security/AndroidKeyPairGeneratorSpecTest.java118
-rw-r--r--keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java180
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);
+ }
+}