From 2eeda7286f3c7cb79f7eb71ae6464cad213d12a3 Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Wed, 10 Apr 2013 11:30:58 -0700 Subject: [PATCH] AndroidKeyStore: Add encrypted flag Add the encrypted flag for the KeyPairGenerator and the KeyStore so that applications can choose to allow entries when there is no lockscreen. Bug: 8122243 Change-Id: Ia802afe965f2377ad3f282dab8c512388c705850 --- api/current.txt | 22 +- .../android/security/AndroidKeyPairGenerator.java | 17 +- .../security/AndroidKeyPairGeneratorSpec.java | 101 ++++-- .../java/android/security/AndroidKeyStore.java | 57 +++- .../android/security/AndroidKeyStoreParameter.java | 123 +++++++ .../android/security/AndroidKeyStoreProvider.java | 3 +- keystore/java/android/security/KeyStore.java | 20 +- .../security/AndroidKeyPairGeneratorSpecTest.java | 22 +- .../security/AndroidKeyPairGeneratorTest.java | 150 +++++++-- .../src/android/security/AndroidKeyStoreTest.java | 373 +++++++++++++++++---- 10 files changed, 750 insertions(+), 138 deletions(-) create mode 100644 keystore/java/android/security/AndroidKeyStoreParameter.java diff --git a/api/current.txt b/api/current.txt index ef00be7595e1..9ce72fe0a623 100644 --- a/api/current.txt +++ b/api/current.txt @@ -20482,19 +20482,37 @@ package android.sax { package android.security { - public class AndroidKeyPairGeneratorSpec implements java.security.spec.AlgorithmParameterSpec { + public final class AndroidKeyPairGeneratorSpec implements java.security.spec.AlgorithmParameterSpec { + method public android.content.Context getContext(); + method public java.util.Date getEndDate(); + method public java.lang.String getKeystoreAlias(); + method public java.math.BigInteger getSerialNumber(); + method public java.util.Date getStartDate(); + method public javax.security.auth.x500.X500Principal getSubjectDN(); + method public boolean isEncryptionRequired(); } - public static class AndroidKeyPairGeneratorSpec.Builder { + public static final class AndroidKeyPairGeneratorSpec.Builder { ctor public AndroidKeyPairGeneratorSpec.Builder(android.content.Context); method public android.security.AndroidKeyPairGeneratorSpec build(); method public android.security.AndroidKeyPairGeneratorSpec.Builder setAlias(java.lang.String); + method public android.security.AndroidKeyPairGeneratorSpec.Builder setEncryptionRequired(); method public android.security.AndroidKeyPairGeneratorSpec.Builder setEndDate(java.util.Date); method public android.security.AndroidKeyPairGeneratorSpec.Builder setSerialNumber(java.math.BigInteger); method public android.security.AndroidKeyPairGeneratorSpec.Builder setStartDate(java.util.Date); method public android.security.AndroidKeyPairGeneratorSpec.Builder setSubject(javax.security.auth.x500.X500Principal); } + public final class AndroidKeyStoreParameter implements java.security.KeyStore.ProtectionParameter { + method public boolean isEncryptionRequired(); + } + + public static final class AndroidKeyStoreParameter.Builder { + ctor public AndroidKeyStoreParameter.Builder(android.content.Context); + method public android.security.AndroidKeyStoreParameter build(); + method public android.security.AndroidKeyStoreParameter.Builder setEncryptionRequired(); + } + public final class KeyChain { ctor public KeyChain(); method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int, java.lang.String); diff --git a/keystore/java/android/security/AndroidKeyPairGenerator.java b/keystore/java/android/security/AndroidKeyPairGenerator.java index c42001b79705..69755833e6f4 100644 --- a/keystore/java/android/security/AndroidKeyPairGenerator.java +++ b/keystore/java/android/security/AndroidKeyPairGenerator.java @@ -49,10 +49,7 @@ import java.security.spec.X509EncodedKeySpec; * * {@hide} */ -@SuppressWarnings("deprecation") public class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { - public static final String NAME = "AndroidKeyPairGenerator"; - private android.security.KeyStore mKeyStore; private AndroidKeyPairGeneratorSpec mSpec; @@ -79,12 +76,21 @@ public class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { "Must call initialize with an AndroidKeyPairGeneratorSpec first"); } + if (((mSpec.getFlags() & KeyStore.FLAG_ENCRYPTED) != 0) + && (mKeyStore.state() != KeyStore.State.UNLOCKED)) { + throw new IllegalStateException( + "Android keystore must be in initialized and unlocked state " + + "if encryption is required"); + } + final String alias = mSpec.getKeystoreAlias(); Credentials.deleteAllTypesForAlias(mKeyStore, alias); final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; - mKeyStore.generate(privateKeyAlias); + if (!mKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, mSpec.getFlags())) { + throw new IllegalStateException("could not generate key in keystore"); + } final PrivateKey privKey; final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore"); @@ -131,7 +137,8 @@ public class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { throw new IllegalStateException("Can't get encoding of certificate", e); } - if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, certBytes)) { + if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, certBytes, KeyStore.UID_SELF, + mSpec.getFlags())) { Credentials.deleteAllTypesForAlias(mKeyStore, alias); throw new IllegalStateException("Can't store certificate in AndroidKeyStore"); } diff --git a/keystore/java/android/security/AndroidKeyPairGeneratorSpec.java b/keystore/java/android/security/AndroidKeyPairGeneratorSpec.java index 18225a542f02..b126f0361572 100644 --- a/keystore/java/android/security/AndroidKeyPairGeneratorSpec.java +++ b/keystore/java/android/security/AndroidKeyPairGeneratorSpec.java @@ -32,10 +32,9 @@ import javax.security.auth.x500.X500Principal; * {@code KeyPairGenerator} that works with Android KeyStore facility. The * Android KeyStore facility is accessed through a - * {@link java.security.KeyPairGenerator} API using the - * {@code AndroidKeyPairGenerator} provider. 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. + * {@link java.security.KeyPairGenerator} API using the {@code AndroidKeyStore} + * provider. 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. *

* After generation, the {@code keyStoreAlias} is used with the * {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter)} @@ -47,10 +46,10 @@ import javax.security.auth.x500.X500Principal; * Distinguished Name along with the other parameters specified with the * {@link Builder}. *

- * The self-signed certificate may be replaced at a later time by a certificate - * signed by a real Certificate Authority. + * The self-signed X.509 certificate may be replaced at a later time by a + * certificate signed by a real Certificate Authority. */ -public class AndroidKeyPairGeneratorSpec implements AlgorithmParameterSpec { +public final class AndroidKeyPairGeneratorSpec implements AlgorithmParameterSpec { private final String mKeystoreAlias; private final Context mContext; @@ -63,6 +62,8 @@ public class AndroidKeyPairGeneratorSpec implements AlgorithmParameterSpec { private final Date mEndDate; + private final int mFlags; + /** * Parameter specification for the "{@code AndroidKeyPairGenerator}" * instance of the {@link java.security.KeyPairGenerator} API. The @@ -93,7 +94,8 @@ public class AndroidKeyPairGeneratorSpec implements AlgorithmParameterSpec { * @hide should be built with AndroidKeyPairGeneratorSpecBuilder */ public AndroidKeyPairGeneratorSpec(Context context, String keyStoreAlias, - X500Principal subjectDN, BigInteger serialNumber, Date startDate, Date endDate) { + X500Principal subjectDN, BigInteger serialNumber, Date startDate, Date endDate, + int flags) { if (context == null) { throw new IllegalArgumentException("context == null"); } else if (TextUtils.isEmpty(keyStoreAlias)) { @@ -116,51 +118,72 @@ public class AndroidKeyPairGeneratorSpec implements AlgorithmParameterSpec { mSerialNumber = serialNumber; mStartDate = startDate; mEndDate = endDate; + mFlags = flags; } /** - * @hide + * Returns the alias that will be used in the {@code java.security.KeyStore} + * in conjunction with the {@code AndroidKeyStore}. */ - String getKeystoreAlias() { + public String getKeystoreAlias() { return mKeystoreAlias; } /** - * @hide + * Gets the Android context used for operations with this instance. */ - Context getContext() { + public Context getContext() { return mContext; } /** - * @hide + * Gets the subject distinguished name to be used on the X.509 certificate + * that will be put in the {@link java.security.KeyStore}. */ - X500Principal getSubjectDN() { + public X500Principal getSubjectDN() { return mSubjectDN; } /** - * @hide + * Gets the serial number to be used on the X.509 certificate that will be + * put in the {@link java.security.KeyStore}. */ - BigInteger getSerialNumber() { + public BigInteger getSerialNumber() { return mSerialNumber; } /** - * @hide + * Gets the start date to be used on the X.509 certificate that will be put + * in the {@link java.security.KeyStore}. */ - Date getStartDate() { + public Date getStartDate() { return mStartDate; } /** - * @hide + * Gets the end date to be used on the X.509 certificate that will be put in + * the {@link java.security.KeyStore}. */ - Date getEndDate() { + public Date getEndDate() { return mEndDate; } /** + * @hide + */ + int getFlags() { + return mFlags; + } + + /** + * Returns {@code true} if this parameter will require generated keys to be + * encrypted in the {@link java.security.KeyStore}. + */ + public boolean isEncryptionRequired() { + return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0; + } + + /** * Builder class for {@link AndroidKeyPairGeneratorSpec} objects. *

* This will build a parameter spec for use with the */ - public static class Builder { + public final static class Builder { private final Context mContext; private String mKeystoreAlias; @@ -197,6 +221,14 @@ public class AndroidKeyPairGeneratorSpec implements AlgorithmParameterSpec { private Date mEndDate; + private int mFlags; + + /** + * Creates a new instance of the {@code Builder} with the given + * {@code context}. 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. + */ public Builder(Context context) { if (context == null) { throw new NullPointerException("context == null"); @@ -266,6 +298,17 @@ public class AndroidKeyPairGeneratorSpec implements AlgorithmParameterSpec { } /** + * Indicates that this key must be encrypted at rest on storage. Note + * that enabling this will require that the user enable a strong lock + * screen (e.g., PIN, password) before creating or using the generated + * key is successful. + */ + public Builder setEncryptionRequired() { + mFlags |= KeyStore.FLAG_ENCRYPTED; + return this; + } + + /** * Builds the instance of the {@code AndroidKeyPairGeneratorSpec}. * * @throws IllegalArgumentException if a required field is missing @@ -273,7 +316,7 @@ public class AndroidKeyPairGeneratorSpec implements AlgorithmParameterSpec { */ public AndroidKeyPairGeneratorSpec build() { return new AndroidKeyPairGeneratorSpec(mContext, mKeystoreAlias, mSubjectDN, - mSerialNumber, mStartDate, mEndDate); + mSerialNumber, mStartDate, mEndDate, mFlags); } } } diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java index 8a9826bd6176..dcc951685d59 100644 --- a/keystore/java/android/security/AndroidKeyStore.java +++ b/keystore/java/android/security/AndroidKeyStore.java @@ -27,6 +27,10 @@ import java.io.InputStream; import java.io.OutputStream; import java.security.InvalidKeyException; import java.security.Key; +import java.security.KeyStore.Entry; +import java.security.KeyStore.PrivateKeyEntry; +import java.security.KeyStore.ProtectionParameter; +import java.security.KeyStore; import java.security.KeyStoreException; import java.security.KeyStoreSpi; import java.security.NoSuchAlgorithmException; @@ -198,14 +202,14 @@ public class AndroidKeyStore extends KeyStoreSpi { } if (key instanceof PrivateKey) { - setPrivateKeyEntry(alias, (PrivateKey) key, chain); + setPrivateKeyEntry(alias, (PrivateKey) key, chain, null); } else { throw new KeyStoreException("Only PrivateKeys are supported"); } } - private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain) - throws KeyStoreException { + private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain, + AndroidKeyStoreParameter params) throws KeyStoreException { byte[] keyBytes = null; final String pkeyAlias; @@ -317,15 +321,20 @@ public class AndroidKeyStore extends KeyStoreSpi { Credentials.deleteCertificateTypesForAlias(mKeyStore, alias); } + final int flags = (params == null) ? 0 : params.getFlags(); + if (shouldReplacePrivateKey - && !mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes)) { + && !mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes, + android.security.KeyStore.UID_SELF, flags)) { Credentials.deleteAllTypesForAlias(mKeyStore, alias); throw new KeyStoreException("Couldn't put private key in keystore"); - } else if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertBytes)) { + } else if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertBytes, + android.security.KeyStore.UID_SELF, flags)) { Credentials.deleteAllTypesForAlias(mKeyStore, alias); throw new KeyStoreException("Couldn't put certificate #1 in keystore"); } else if (chainBytes != null - && !mKeyStore.put(Credentials.CA_CERTIFICATE + alias, chainBytes)) { + && !mKeyStore.put(Credentials.CA_CERTIFICATE + alias, chainBytes, + android.security.KeyStore.UID_SELF, flags)) { Credentials.deleteAllTypesForAlias(mKeyStore, alias); throw new KeyStoreException("Couldn't put certificate chain in keystore"); } @@ -355,7 +364,8 @@ public class AndroidKeyStore extends KeyStoreSpi { throw new KeyStoreException(e); } - if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded)) { + if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded, + android.security.KeyStore.UID_SELF, android.security.KeyStore.FLAG_NONE)) { throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?"); } } @@ -517,4 +527,37 @@ public class AndroidKeyStore extends KeyStoreSpi { mKeyStore = android.security.KeyStore.getInstance(); } + @Override + public void engineSetEntry(String alias, Entry entry, ProtectionParameter param) + throws KeyStoreException { + if (entry == null) { + throw new KeyStoreException("entry == null"); + } + + if (engineContainsAlias(alias)) { + engineDeleteEntry(alias); + } + + if (entry instanceof KeyStore.TrustedCertificateEntry) { + KeyStore.TrustedCertificateEntry trE = (KeyStore.TrustedCertificateEntry) entry; + engineSetCertificateEntry(alias, trE.getTrustedCertificate()); + return; + } + + if (param != null && !(param instanceof AndroidKeyStoreParameter)) { + throw new KeyStoreException("protParam should be AndroidKeyStoreParameter; was: " + + param.getClass().getName()); + } + + if (entry instanceof PrivateKeyEntry) { + PrivateKeyEntry prE = (PrivateKeyEntry) entry; + setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(), + (AndroidKeyStoreParameter) param); + return; + } + + throw new KeyStoreException( + "Entry must be a PrivateKeyEntry or TrustedCertificateEntry; was " + entry); + } + } diff --git a/keystore/java/android/security/AndroidKeyStoreParameter.java b/keystore/java/android/security/AndroidKeyStoreParameter.java new file mode 100644 index 000000000000..44f57c4ee568 --- /dev/null +++ b/keystore/java/android/security/AndroidKeyStoreParameter.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2013 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.security.AndroidKeyPairGeneratorSpec.Builder; + +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.KeyStore.ProtectionParameter; +import java.security.cert.Certificate; + +/** + * This provides the optional parameters that can be specified for + * {@code KeyStore} entries that work with Android KeyStore facility. The + * Android KeyStore facility is accessed through a + * {@link java.security.KeyStore} API using the {@code AndroidKeyStore} + * provider. 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. + *

+ * Any entries placed in the {@code KeyStore} may be retrieved later. Note that + * there is only one logical instance of the {@code KeyStore} per application + * UID so apps using the {@code sharedUid} facility will also share a + * {@code KeyStore}. + *

+ * Keys may be generated using the {@link KeyPairGenerator} facility with a + * {@link AndroidKeyPairGeneratorSpec} to specify the entry's {@code alias}. A + * self-signed X.509 certificate will be attached to generated entries, but that + * may be replaced at a later time by a certificate signed by a real Certificate + * Authority. + */ +public final class AndroidKeyStoreParameter implements ProtectionParameter { + private int mFlags; + + private AndroidKeyStoreParameter(int flags) { + mFlags = flags; + } + + /** + * @hide + */ + public int getFlags() { + return mFlags; + } + + /** + * Returns {@code true} if this parameter requires entries to be encrypted + * on the disk. + */ + public boolean isEncryptionRequired() { + return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0; + } + + /** + * Builder class for {@link AndroidKeyStoreParameter} objects. + *

+ * This will build protection parameters for use with the Android KeyStore + * facility. + *

+ * This can be used to require that KeyStore entries be stored encrypted. + *

+ * Example: + * + *

+     * AndroidKeyStoreParameter params =
+     *         new AndroidKeyStoreParameter.Builder(mContext).setEncryptionRequired().build();
+     * 
+ */ + public final static class Builder { + private int mFlags; + + /** + * Creates a new instance of the {@code Builder} with the given + * {@code context}. 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. + */ + public Builder(Context context) { + if (context == null) { + throw new NullPointerException("context == null"); + } + + // Context is currently not used, but will be in the future. + } + + /** + * Indicates that this key must be encrypted at rest on storage. Note + * that enabling this will require that the user enable a strong lock + * screen (e.g., PIN, password) before creating or using the generated + * key is successful. + */ + public Builder setEncryptionRequired() { + mFlags |= KeyStore.FLAG_ENCRYPTED; + return this; + } + + /** + * Builds the instance of the {@code AndroidKeyPairGeneratorSpec}. + * + * @throws IllegalArgumentException if a required field is missing + * @return built instance of {@code AndroidKeyPairGeneratorSpec} + */ + public AndroidKeyStoreParameter build() { + return new AndroidKeyStoreParameter(mFlags); + } + } +} diff --git a/keystore/java/android/security/AndroidKeyStoreProvider.java b/keystore/java/android/security/AndroidKeyStoreProvider.java index 40d7e1a210ab..8ca301e6b521 100644 --- a/keystore/java/android/security/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/AndroidKeyStoreProvider.java @@ -33,7 +33,6 @@ public class AndroidKeyStoreProvider extends Provider { put("KeyStore." + AndroidKeyStore.NAME, AndroidKeyStore.class.getName()); // java.security.KeyPairGenerator - put("KeyPairGenerator." + AndroidKeyPairGenerator.NAME, - AndroidKeyPairGenerator.class.getName()); + put("KeyPairGenerator." + AndroidKeyStore.NAME, AndroidKeyPairGenerator.class.getName()); } } diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 309d3d3c17ab..45385ee975c0 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -40,7 +40,11 @@ public class KeyStore { public static final int UNDEFINED_ACTION = 9; public static final int WRONG_PASSWORD = 10; - // Flags for "put" and "import" + // Used for UID field to indicate the calling UID. + public static final int UID_SELF = -1; + + // Flags for "put" "import" and "generate" + public static final int FLAG_NONE = 0; public static final int FLAG_ENCRYPTED = 1; // States @@ -104,7 +108,7 @@ public class KeyStore { } public boolean put(String key, byte[] value) { - return put(key, value, -1); + return put(key, value, UID_SELF); } public boolean delete(String key, int uid) { @@ -117,7 +121,7 @@ public class KeyStore { } public boolean delete(String key) { - return delete(key, -1); + return delete(key, UID_SELF); } public boolean contains(String key, int uid) { @@ -130,7 +134,7 @@ public class KeyStore { } public boolean contains(String key) { - return contains(key, -1); + return contains(key, UID_SELF); } public String[] saw(String prefix, int uid) { @@ -143,7 +147,7 @@ public class KeyStore { } public String[] saw(String prefix) { - return saw(prefix, -1); + return saw(prefix, UID_SELF); } public boolean reset() { @@ -206,7 +210,7 @@ public class KeyStore { } public boolean generate(String key) { - return generate(key, -1); + return generate(key, UID_SELF); } public boolean importKey(String keyName, byte[] key, int uid, int flags) { @@ -223,7 +227,7 @@ public class KeyStore { } public boolean importKey(String keyName, byte[] key) { - return importKey(keyName, key, -1); + return importKey(keyName, key, UID_SELF); } public byte[] getPubkey(String key) { @@ -245,7 +249,7 @@ public class KeyStore { } public boolean delKey(String key) { - return delKey(key, -1); + return delKey(key, UID_SELF); } public byte[] sign(String key, byte[] data) { diff --git a/keystore/tests/src/android/security/AndroidKeyPairGeneratorSpecTest.java b/keystore/tests/src/android/security/AndroidKeyPairGeneratorSpecTest.java index 3d275cd72d0b..5d4ab9cfde03 100644 --- a/keystore/tests/src/android/security/AndroidKeyPairGeneratorSpecTest.java +++ b/keystore/tests/src/android/security/AndroidKeyPairGeneratorSpecTest.java @@ -39,8 +39,9 @@ public class AndroidKeyPairGeneratorSpecTest extends AndroidTestCase { 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); + AndroidKeyPairGeneratorSpec spec = + new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1, + NOW, NOW_PLUS_10_YEARS, 0); assertEquals("Context should be the one specified", getContext(), spec.getContext()); @@ -60,6 +61,7 @@ public class AndroidKeyPairGeneratorSpecTest extends AndroidTestCase { .setSerialNumber(SERIAL_1) .setStartDate(NOW) .setEndDate(NOW_PLUS_10_YEARS) + .setEncryptionRequired() .build(); assertEquals("Context should be the one specified", getContext(), spec.getContext()); @@ -71,12 +73,14 @@ public class AndroidKeyPairGeneratorSpecTest extends AndroidTestCase { assertEquals("startDate should be the one specified", NOW, spec.getStartDate()); assertEquals("endDate should be the one specified", NOW_PLUS_10_YEARS, spec.getEndDate()); + + assertEquals("encryption flag should be on", KeyStore.FLAG_ENCRYPTED, spec.getFlags()); } public void testConstructor_NullContext_Failure() throws Exception { try { new AndroidKeyPairGeneratorSpec(null, TEST_ALIAS_1, TEST_DN_1, SERIAL_1, NOW, - NOW_PLUS_10_YEARS); + NOW_PLUS_10_YEARS, 0); fail("Should throw IllegalArgumentException when context is null"); } catch (IllegalArgumentException success) { } @@ -85,7 +89,7 @@ public class AndroidKeyPairGeneratorSpecTest extends AndroidTestCase { public void testConstructor_NullKeystoreAlias_Failure() throws Exception { try { new AndroidKeyPairGeneratorSpec(getContext(), null, TEST_DN_1, SERIAL_1, NOW, - NOW_PLUS_10_YEARS); + NOW_PLUS_10_YEARS, 0); fail("Should throw IllegalArgumentException when keystoreAlias is null"); } catch (IllegalArgumentException success) { } @@ -94,7 +98,7 @@ public class AndroidKeyPairGeneratorSpecTest extends AndroidTestCase { public void testConstructor_NullSubjectDN_Failure() throws Exception { try { new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, null, SERIAL_1, NOW, - NOW_PLUS_10_YEARS); + NOW_PLUS_10_YEARS, 0); fail("Should throw IllegalArgumentException when subjectDN is null"); } catch (IllegalArgumentException success) { } @@ -103,7 +107,7 @@ public class AndroidKeyPairGeneratorSpecTest extends AndroidTestCase { public void testConstructor_NullSerial_Failure() throws Exception { try { new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, null, NOW, - NOW_PLUS_10_YEARS); + NOW_PLUS_10_YEARS, 0); fail("Should throw IllegalArgumentException when startDate is null"); } catch (IllegalArgumentException success) { } @@ -112,7 +116,7 @@ public class AndroidKeyPairGeneratorSpecTest extends AndroidTestCase { public void testConstructor_NullStartDate_Failure() throws Exception { try { new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1, null, - NOW_PLUS_10_YEARS); + NOW_PLUS_10_YEARS, 0); fail("Should throw IllegalArgumentException when startDate is null"); } catch (IllegalArgumentException success) { } @@ -121,7 +125,7 @@ public class AndroidKeyPairGeneratorSpecTest extends AndroidTestCase { public void testConstructor_NullEndDate_Failure() throws Exception { try { new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1, NOW, - null); + null, 0); fail("Should throw IllegalArgumentException when keystoreAlias is null"); } catch (IllegalArgumentException success) { } @@ -130,7 +134,7 @@ public class AndroidKeyPairGeneratorSpecTest extends AndroidTestCase { public void testConstructor_EndBeforeStart_Failure() throws Exception { try { new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1, - NOW_PLUS_10_YEARS, NOW); + NOW_PLUS_10_YEARS, NOW, 0); 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 index 69007c43f978..c5cf51448869 100644 --- a/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java +++ b/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java @@ -27,6 +27,7 @@ import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; import java.util.Date; import javax.security.auth.x500.X500Principal; @@ -64,22 +65,34 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { assertFalse(mAndroidKeyStore.isUnlocked()); + mGenerator = java.security.KeyPairGenerator.getInstance("AndroidKeyStore"); + } + + private void setupPassword() { assertTrue(mAndroidKeyStore.password("1111")); assertTrue(mAndroidKeyStore.isUnlocked()); String[] aliases = mAndroidKeyStore.saw(""); assertNotNull(aliases); assertEquals(0, aliases.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_Params_Encrypted_Success() throws Exception { + setupPassword(); + + mGenerator.initialize(new AndroidKeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_1) + .setSubject(TEST_DN_1) + .setSerialNumber(TEST_SERIAL_1) + .setStartDate(NOW) + .setEndDate(NOW_PLUS_10_YEARS) + .setEncryptionRequired() + .build()); } - public void testKeyPairGenerator_Initialize_KeySize_Failure() throws Exception { + public void testKeyPairGenerator_Initialize_KeySize_Encrypted_Failure() throws Exception { + setupPassword(); + try { mGenerator.initialize(1024); fail("KeyPairGenerator should not support setting the key size"); @@ -87,7 +100,10 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { } } - public void testKeyPairGenerator_Initialize_KeySizeAndSecureRandom_Failure() throws Exception { + public void testKeyPairGenerator_Initialize_KeySizeAndSecureRandom_Encrypted_Failure() + throws Exception { + setupPassword(); + try { mGenerator.initialize(1024, new SecureRandom()); fail("KeyPairGenerator should not support setting the key size"); @@ -95,14 +111,48 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { } } - 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_Initialize_ParamsAndSecureRandom_Encrypted_Failure() + throws Exception { + setupPassword(); + + mGenerator.initialize( + new AndroidKeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_1) + .setSubject(TEST_DN_1) + .setSerialNumber(TEST_SERIAL_1) + .setStartDate(NOW) + .setEndDate(NOW_PLUS_10_YEARS) + .setEncryptionRequired() + .build(), + 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)); + public void testKeyPairGenerator_GenerateKeyPair_Encrypted_Success() throws Exception { + setupPassword(); + + mGenerator.initialize(new AndroidKeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_1) + .setSubject(TEST_DN_1) + .setSerialNumber(TEST_SERIAL_1) + .setStartDate(NOW) + .setEndDate(NOW_PLUS_10_YEARS) + .setEncryptionRequired() + .build()); + + 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_Unencrypted_Success() throws Exception { + mGenerator.initialize(new AndroidKeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_1) + .setSubject(TEST_DN_1) + .setSerialNumber(TEST_SERIAL_1) + .setStartDate(NOW) + .setEndDate(NOW_PLUS_10_YEARS) + .build()); final KeyPair pair = mGenerator.generateKeyPair(); assertNotNull("The KeyPair returned should not be null", pair); @@ -113,8 +163,13 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { 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)); + mGenerator.initialize(new AndroidKeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_1) + .setSubject(TEST_DN_1) + .setSerialNumber(TEST_SERIAL_1) + .setStartDate(NOW) + .setEndDate(NOW_PLUS_10_YEARS) + .build()); 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, @@ -123,8 +178,13 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { // Replace the original key { - mGenerator.initialize(new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_2, - TEST_DN_2, TEST_SERIAL_2, NOW, NOW_PLUS_10_YEARS)); + mGenerator.initialize(new AndroidKeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_2) + .setSubject(TEST_DN_2) + .setSerialNumber(TEST_SERIAL_2) + .setStartDate(NOW) + .setEndDate(NOW_PLUS_10_YEARS) + .build()); 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, @@ -132,6 +192,49 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { } } + public void testKeyPairGenerator_GenerateKeyPair_Replaced_UnencryptedToEncrypted_Success() + throws Exception { + // Generate the first key + { + mGenerator.initialize(new AndroidKeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_1) + .setSubject(TEST_DN_1) + .setSerialNumber(TEST_SERIAL_1) + .setStartDate(NOW) + .setEndDate(NOW_PLUS_10_YEARS) + .build()); + 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); + } + + // Attempt to replace previous key + { + mGenerator.initialize(new AndroidKeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_1) + .setSubject(TEST_DN_2) + .setSerialNumber(TEST_SERIAL_2) + .setStartDate(NOW) + .setEndDate(NOW_PLUS_10_YEARS) + .setEncryptionRequired() + .build()); + try { + mGenerator.generateKeyPair(); + fail("Should not be able to generate encrypted key while not initialized"); + } catch (IllegalStateException expected) { + } + + assertTrue(mAndroidKeyStore.password("1111")); + assertTrue(mAndroidKeyStore.isUnlocked()); + + final KeyPair pair2 = mGenerator.generateKeyPair(); + assertNotNull("The KeyPair returned should not be null", pair2); + assertKeyPairCorrect(pair2, TEST_ALIAS_1, 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(); @@ -163,10 +266,10 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { 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, + assertDateEquals("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, + assertDateEquals("The notAfter date should be the one passed into the params", end, x509userCert.getNotAfter()); x509userCert.verify(pubKey); @@ -178,4 +281,13 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { assertNotNull("The keystore should return the public key for the generated key", pubKeyBytes); } + + private static void assertDateEquals(String message, Date date1, Date date2) throws Exception { + SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy HH:mm:ss"); + + String result1 = formatter.format(date1); + String result2 = formatter.format(date2); + + assertEquals(message, result1, result2); + } } diff --git a/keystore/tests/src/android/security/AndroidKeyStoreTest.java b/keystore/tests/src/android/security/AndroidKeyStoreTest.java index 8928e065945a..05ffe109289a 100644 --- a/keystore/tests/src/android/security/AndroidKeyStoreTest.java +++ b/keystore/tests/src/android/security/AndroidKeyStoreTest.java @@ -469,12 +469,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertTrue(mAndroidKeyStore.reset()); assertFalse(mAndroidKeyStore.isUnlocked()); + mKeyStore = java.security.KeyStore.getInstance("AndroidKeyStore"); + } + + private void setupPassword() { assertTrue(mAndroidKeyStore.password("1111")); assertTrue(mAndroidKeyStore.isUnlocked()); assertEquals(0, mAndroidKeyStore.saw("").length); - - mKeyStore = java.security.KeyStore.getInstance(AndroidKeyStore.NAME); } private void assertAliases(final String[] expectedAliases) throws KeyStoreException { @@ -495,7 +497,9 @@ public class AndroidKeyStoreTest extends AndroidTestCase { expectedAliases.length, count); } - public void testKeyStore_Aliases_Success() throws Exception { + public void testKeyStore_Aliases_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertAliases(new String[] {}); @@ -509,7 +513,9 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 }); } - public void testKeyStore_Aliases_NotInitialized_Failure() throws Exception { + public void testKeyStore_Aliases_NotInitialized_Encrypted_Failure() throws Exception { + setupPassword(); + try { mKeyStore.aliases(); fail("KeyStore should throw exception when not initialized"); @@ -517,7 +523,9 @@ public class AndroidKeyStoreTest extends AndroidTestCase { } } - public void testKeyStore_ContainsAliases_PrivateAndCA_Success() throws Exception { + public void testKeyStore_ContainsAliases_PrivateAndCA_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertAliases(new String[] {}); @@ -534,7 +542,9 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.containsAlias(TEST_ALIAS_3)); } - public void testKeyStore_ContainsAliases_CAOnly_Success() throws Exception { + public void testKeyStore_ContainsAliases_CAOnly_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1)); @@ -542,13 +552,17 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2)); } - public void testKeyStore_ContainsAliases_NonExistent_Failure() throws Exception { + public void testKeyStore_ContainsAliases_NonExistent_Encrypted_Failure() throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertFalse("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_1)); } - public void testKeyStore_DeleteEntry_Success() throws Exception { + public void testKeyStore_DeleteEntry_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); // TEST_ALIAS_1 @@ -578,14 +592,18 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertAliases(new String[] { }); } - public void testKeyStore_DeleteEntry_EmptyStore_Success() throws Exception { + public void testKeyStore_DeleteEntry_EmptyStore_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); // Should not throw when a non-existent entry is requested for delete. mKeyStore.deleteEntry(TEST_ALIAS_1); } - public void testKeyStore_DeleteEntry_NonExistent_Success() throws Exception { + public void testKeyStore_DeleteEntry_NonExistent_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); // TEST_ALIAS_1 @@ -598,7 +616,9 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.deleteEntry(TEST_ALIAS_2); } - public void testKeyStore_GetCertificate_Single_Success() throws Exception { + public void testKeyStore_GetCertificate_Single_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1)); @@ -618,14 +638,18 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertEquals("Actual and retrieved certificates should be the same", actual, retrieved); } - public void testKeyStore_GetCertificate_NonExist_Failure() throws Exception { + public void testKeyStore_GetCertificate_NonExist_Encrypted_Failure() throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertNull("Certificate should not exist in keystore", mKeyStore.getCertificate(TEST_ALIAS_1)); } - public void testKeyStore_GetCertificateAlias_CAEntry_Success() throws Exception { + public void testKeyStore_GetCertificateAlias_CAEntry_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1)); @@ -637,7 +661,10 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.getCertificateAlias(actual)); } - public void testKeyStore_GetCertificateAlias_PrivateKeyEntry_Success() throws Exception { + public void testKeyStore_GetCertificateAlias_PrivateKeyEntry_Encrypted_Success() + throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, @@ -652,8 +679,10 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.getCertificateAlias(actual)); } - public void testKeyStore_GetCertificateAlias_CAEntry_WithPrivateKeyUsingCA_Success() + public void testKeyStore_GetCertificateAlias_CAEntry_WithPrivateKeyUsingCA_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); // Insert TrustedCertificateEntry with CA name @@ -672,7 +701,10 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.getCertificateAlias(actual)); } - public void testKeyStore_GetCertificateAlias_NonExist_Empty_Failure() throws Exception { + public void testKeyStore_GetCertificateAlias_NonExist_Empty_Encrypted_Failure() + throws Exception { + setupPassword(); + mKeyStore.load(null, null); CertificateFactory f = CertificateFactory.getInstance("X.509"); @@ -682,7 +714,9 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.getCertificateAlias(actual)); } - public void testKeyStore_GetCertificateAlias_NonExist_Failure() throws Exception { + public void testKeyStore_GetCertificateAlias_NonExist_Encrypted_Failure() throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1)); @@ -694,7 +728,9 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.getCertificateAlias(userCert)); } - public void testKeyStore_GetCertificateChain_SingleLength_Success() throws Exception { + public void testKeyStore_GetCertificateChain_SingleLength_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, @@ -720,14 +756,18 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.getCertificateChain(TEST_ALIAS_2)); } - public void testKeyStore_GetCertificateChain_NonExist_Failure() throws Exception { + public void testKeyStore_GetCertificateChain_NonExist_Encrypted_Failure() throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertNull("Stored certificate alias should not be found", mKeyStore.getCertificateChain(TEST_ALIAS_1)); } - public void testKeyStore_GetCreationDate_PrivateKeyEntry_Success() throws Exception { + public void testKeyStore_GetCreationDate_PrivateKeyEntry_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, @@ -745,7 +785,29 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertTrue("Time should be close to current time", actual.after(expectedAfter)); } - public void testKeyStore_GetCreationDate_CAEntry_Success() throws Exception { + public void testKeyStore_GetCreationDate_PrivateKeyEntry_Unencrypted_Success() throws Exception { + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1, + KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + + Date now = new Date(); + Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1); + + Date expectedAfter = new Date(now.getTime() - SLOP_TIME_MILLIS); + Date expectedBefore = new Date(now.getTime() + SLOP_TIME_MILLIS); + + assertTrue("Time should be close to current time", actual.before(expectedBefore)); + assertTrue("Time should be close to current time", actual.after(expectedAfter)); + } + + public void testKeyStore_GetCreationDate_CAEntry_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1)); @@ -761,7 +823,9 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertTrue("Time should be close to current time", actual.after(expectedAfter)); } - public void testKeyStore_GetEntry_NullParams_Success() throws Exception { + public void testKeyStore_GetEntry_NullParams_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, @@ -779,6 +843,26 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertPrivateKeyEntryEquals(keyEntry, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1); } + public void testKeyStore_GetEntry_NullParams_Unencrypted_Success() throws Exception { + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1, + KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + + Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null); + assertNotNull("Entry should exist", entry); + + assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry); + + PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry; + + assertPrivateKeyEntryEquals(keyEntry, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1); + } + @SuppressWarnings("unchecked") private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, byte[] key, byte[] cert, byte[] ca) throws Exception { @@ -801,8 +885,9 @@ public class AndroidKeyStoreTest extends AndroidTestCase { private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, PrivateKey expectedKey, Certificate expectedCert, Collection expectedChain) throws Exception { - assertEquals("Returned PrivateKey should be what we inserted", expectedKey, - keyEntry.getPrivateKey()); + assertEquals("Returned PrivateKey should be what we inserted", + ((RSAPrivateKey) expectedKey).getModulus(), + ((RSAPrivateKey) keyEntry.getPrivateKey()).getModulus()); assertEquals("Returned Certificate should be what we inserted", expectedCert, keyEntry.getCertificate()); @@ -823,14 +908,25 @@ public class AndroidKeyStoreTest extends AndroidTestCase { } } - public void testKeyStore_GetEntry_Nonexistent_NullParams_Failure() throws Exception { + public void testKeyStore_GetEntry_Nonexistent_NullParams_Encrypted_Failure() throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertNull("A non-existent entry should return null", mKeyStore.getEntry(TEST_ALIAS_1, null)); } - public void testKeyStore_GetKey_NoPassword_Success() throws Exception { + public void testKeyStore_GetEntry_Nonexistent_NullParams_Unencrypted_Failure() throws Exception { + mKeyStore.load(null, null); + + assertNull("A non-existent entry should return null", + mKeyStore.getEntry(TEST_ALIAS_1, null)); + } + + public void testKeyStore_GetKey_NoPassword_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, @@ -848,10 +944,37 @@ public class AndroidKeyStoreTest extends AndroidTestCase { KeyFactory keyFact = KeyFactory.getInstance("RSA"); PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); - assertEquals("Inserted key should be same as retrieved key", actualKey, expectedKey); + assertEquals("Inserted key should be same as retrieved key", + ((RSAPrivateKey) expectedKey).getModulus(), actualKey.getModulus()); } - public void testKeyStore_GetKey_Certificate_Failure() throws Exception { + public void testKeyStore_GetKey_NoPassword_Unencrypted_Success() throws Exception { + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1, + KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + + Key key = mKeyStore.getKey(TEST_ALIAS_1, null); + assertNotNull("Key should exist", key); + + assertTrue("Should be a RSAPrivateKey", key instanceof RSAPrivateKey); + + RSAPrivateKey actualKey = (RSAPrivateKey) key; + + KeyFactory keyFact = KeyFactory.getInstance("RSA"); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + + assertEquals("Inserted key should be same as retrieved key", + ((RSAPrivateKey) expectedKey).getModulus(), actualKey.getModulus()); + } + + public void testKeyStore_GetKey_Certificate_Encrypted_Failure() throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1)); @@ -859,21 +982,28 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertNull("Certificate entries should return null", mKeyStore.getKey(TEST_ALIAS_1, null)); } - public void testKeyStore_GetKey_NonExistent_Failure() throws Exception { + public void testKeyStore_GetKey_NonExistent_Encrypted_Failure() throws Exception { + setupPassword(); + mKeyStore.load(null, null); assertNull("A non-existent entry should return null", mKeyStore.getKey(TEST_ALIAS_1, null)); } - public void testKeyStore_GetProvider_Success() throws Exception { + public void testKeyStore_GetProvider_Encrypted_Success() throws Exception { + assertEquals(AndroidKeyStoreProvider.PROVIDER_NAME, mKeyStore.getProvider().getName()); + setupPassword(); assertEquals(AndroidKeyStoreProvider.PROVIDER_NAME, mKeyStore.getProvider().getName()); } - public void testKeyStore_GetType_Success() throws Exception { + public void testKeyStore_GetType_Encrypted_Success() throws Exception { + assertEquals(AndroidKeyStore.NAME, mKeyStore.getType()); + setupPassword(); assertEquals(AndroidKeyStore.NAME, mKeyStore.getType()); } - public void testKeyStore_IsCertificateEntry_CA_Success() throws Exception { + public void testKeyStore_IsCertificateEntry_CA_Encrypted_Success() throws Exception { + setupPassword(); mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1)); @@ -882,7 +1012,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.isCertificateEntry(TEST_ALIAS_1)); } - public void testKeyStore_IsCertificateEntry_PrivateKey_Failure() throws Exception { + public void testKeyStore_IsCertificateEntry_PrivateKey_Encrypted_Failure() throws Exception { + setupPassword(); mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, @@ -894,14 +1025,23 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.isCertificateEntry(TEST_ALIAS_1)); } - public void testKeyStore_IsCertificateEntry_NonExist_Failure() throws Exception { + public void testKeyStore_IsCertificateEntry_NonExist_Encrypted_Failure() throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + assertFalse("Should return false for non-existent entry", + mKeyStore.isCertificateEntry(TEST_ALIAS_1)); + } + + public void testKeyStore_IsCertificateEntry_NonExist_Unencrypted_Failure() throws Exception { mKeyStore.load(null, null); assertFalse("Should return false for non-existent entry", mKeyStore.isCertificateEntry(TEST_ALIAS_1)); } - public void testKeyStore_IsKeyEntry_PrivateKey_Success() throws Exception { + public void testKeyStore_IsKeyEntry_PrivateKey_Encrypted_Success() throws Exception { + setupPassword(); mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, @@ -912,7 +1052,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertTrue("Should return true for PrivateKeyEntry", mKeyStore.isKeyEntry(TEST_ALIAS_1)); } - public void testKeyStore_IsKeyEntry_CA_Failure() throws Exception { + public void testKeyStore_IsKeyEntry_CA_Encrypted_Failure() throws Exception { + setupPassword(); mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1)); @@ -920,17 +1061,19 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertFalse("Should return false for CA certificate", mKeyStore.isKeyEntry(TEST_ALIAS_1)); } - public void testKeyStore_IsKeyEntry_NonExist_Failure() throws Exception { + public void testKeyStore_IsKeyEntry_NonExist_Encrypted_Failure() throws Exception { + setupPassword(); mKeyStore.load(null, null); assertFalse("Should return false for non-existent entry", mKeyStore.isKeyEntry(TEST_ALIAS_1)); } - public void testKeyStore_SetCertificate_CA_Success() throws Exception { + public void testKeyStore_SetCertificate_CA_Encrypted_Success() throws Exception { final CertificateFactory f = CertificateFactory.getInstance("X.509"); final Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + setupPassword(); mKeyStore.load(null, null); mKeyStore.setCertificateEntry(TEST_ALIAS_1, actual); @@ -942,7 +1085,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { retrieved); } - public void testKeyStore_SetCertificate_CAExists_Overwrite_Success() throws Exception { + public void testKeyStore_SetCertificate_CAExists_Overwrite_Encrypted_Success() throws Exception { + setupPassword(); mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1)); @@ -958,7 +1102,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertAliases(new String[] { TEST_ALIAS_1 }); } - public void testKeyStore_SetCertificate_PrivateKeyExists_Failure() throws Exception { + public void testKeyStore_SetCertificate_PrivateKeyExists_Encrypted_Failure() throws Exception { + setupPassword(); mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, @@ -978,7 +1123,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { } } - public void testKeyStore_SetEntry_PrivateKeyEntry_Success() throws Exception { + public void testKeyStore_SetEntry_PrivateKeyEntry_Encrypted_Success() throws Exception { + setupPassword(); mKeyStore.load(null, null); KeyFactory keyFact = KeyFactory.getInstance("RSA"); @@ -1005,8 +1151,63 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1); } - public void testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_PrivateKeyEntry_Success() + public void testKeyStore_SetEntry_PrivateKeyEntry_Unencrypted_Success() throws Exception { + mKeyStore.load(null, null); + + KeyFactory keyFact = KeyFactory.getInstance("RSA"); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + + final Certificate[] expectedChain = new Certificate[2]; + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); + expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + + PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain); + + mKeyStore.setEntry(TEST_ALIAS_1, expected, null); + + Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null); + assertNotNull("Retrieved entry should exist", actualEntry); + + assertTrue("Retrieved entry should be of type PrivateKeyEntry", + actualEntry instanceof PrivateKeyEntry); + + PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry; + + assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1); + } + + public void testKeyStore_SetEntry_PrivateKeyEntry_Params_Unencrypted_Failure() throws Exception { + mKeyStore.load(null, null); + + KeyFactory keyFact = KeyFactory.getInstance("RSA"); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + + final Certificate[] expectedChain = new Certificate[2]; + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); + expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + + PrivateKeyEntry entry = new PrivateKeyEntry(expectedKey, expectedChain); + + try { + mKeyStore.setEntry(TEST_ALIAS_1, entry, + new AndroidKeyStoreParameter.Builder(getContext()) + .setEncryptionRequired() + .build()); + fail("Shouldn't be able to insert encrypted entry when KeyStore uninitialized"); + } catch (KeyStoreException expected) { + } + + assertNull(mKeyStore.getEntry(TEST_ALIAS_1, null)); + } + + public void + testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_PrivateKeyEntry_Encrypted_Success() throws Exception { + setupPassword(); mKeyStore.load(null, null); final KeyFactory keyFact = KeyFactory.getInstance("RSA"); @@ -1060,7 +1261,9 @@ public class AndroidKeyStoreTest extends AndroidTestCase { } } - public void testKeyStore_SetEntry_CAEntry_Overwrites_PrivateKeyEntry_Success() throws Exception { + public void testKeyStore_SetEntry_CAEntry_Overwrites_PrivateKeyEntry_Encrypted_Success() + throws Exception { + setupPassword(); mKeyStore.load(null, null); final CertificateFactory f = CertificateFactory.getInstance("X.509"); @@ -1104,7 +1307,9 @@ public class AndroidKeyStoreTest extends AndroidTestCase { } } - public void testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_CAEntry_Success() throws Exception { + public void testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_CAEntry_Encrypted_Success() + throws Exception { + setupPassword(); mKeyStore.load(null, null); final CertificateFactory f = CertificateFactory.getInstance("X.509"); @@ -1148,8 +1353,11 @@ public class AndroidKeyStoreTest extends AndroidTestCase { } } - public void testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_ShortPrivateKeyEntry_Success() + public + void + testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_ShortPrivateKeyEntry_Encrypted_Success() throws Exception { + setupPassword(); mKeyStore.load(null, null); final CertificateFactory f = CertificateFactory.getInstance("X.509"); @@ -1198,7 +1406,9 @@ public class AndroidKeyStoreTest extends AndroidTestCase { } } - public void testKeyStore_SetEntry_CAEntry_Overwrites_CAEntry_Success() throws Exception { + public void testKeyStore_SetEntry_CAEntry_Overwrites_CAEntry_Encrypted_Success() + throws Exception { + setupPassword(); mKeyStore.load(null, null); final CertificateFactory f = CertificateFactory.getInstance("X.509"); @@ -1239,7 +1449,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { } } - public void testKeyStore_SetKeyEntry_ProtectedKey_Failure() throws Exception { + public void testKeyStore_SetKeyEntry_ProtectedKey_Encrypted_Failure() throws Exception { + setupPassword(); mKeyStore.load(null, null); final CertificateFactory f = CertificateFactory.getInstance("X.509"); @@ -1259,7 +1470,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { } } - public void testKeyStore_SetKeyEntry_Success() throws Exception { + public void testKeyStore_SetKeyEntry_Encrypted_Success() throws Exception { + setupPassword(); mKeyStore.load(null, null); final CertificateFactory f = CertificateFactory.getInstance("X.509"); @@ -1285,7 +1497,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1); } - public void testKeyStore_SetKeyEntry_Replaced_Success() throws Exception { + public void testKeyStore_SetKeyEntry_Replaced_Encrypted_Success() throws Exception { + setupPassword(); mKeyStore.load(null, null); final CertificateFactory f = CertificateFactory.getInstance("X.509"); @@ -1376,7 +1589,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { return cert; } - public void testKeyStore_SetKeyEntry_ReplacedChain_Success() throws Exception { + public void testKeyStore_SetKeyEntry_ReplacedChain_Encrypted_Success() throws Exception { + setupPassword(); mKeyStore.load(null, null); // Create key #1 @@ -1429,8 +1643,9 @@ public class AndroidKeyStoreTest extends AndroidTestCase { } } - public void testKeyStore_SetKeyEntry_ReplacedChain_DifferentPrivateKey_Failure() + public void testKeyStore_SetKeyEntry_ReplacedChain_DifferentPrivateKey_Encrypted_Failure() throws Exception { + setupPassword(); mKeyStore.load(null, null); // Create key #1 @@ -1472,7 +1687,48 @@ public class AndroidKeyStoreTest extends AndroidTestCase { } } - public void testKeyStore_Size_Success() throws Exception { + public void testKeyStore_SetKeyEntry_ReplacedChain_UnencryptedToEncrypted_Failure() + throws Exception { + mKeyStore.load(null, null); + + // Create key #1 + { + final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1; + assertTrue(mAndroidKeyStore.generate(privateKeyAlias, + android.security.KeyStore.UID_SELF, android.security.KeyStore.FLAG_NONE)); + + X509Certificate cert = + generateCertificate(mAndroidKeyStore, TEST_ALIAS_1, TEST_SERIAL_1, TEST_DN_1, + NOW, NOW_PLUS_10_YEARS); + + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, + cert.getEncoded(), android.security.KeyStore.UID_SELF, + android.security.KeyStore.FLAG_NONE)); + } + + // Replace with one that requires encryption + { + Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null); + + try { + mKeyStore.setEntry(TEST_ALIAS_1, entry, new AndroidKeyStoreParameter.Builder( + getContext()).setEncryptionRequired().build()); + fail("Should not allow setting of Entry without unlocked keystore"); + } catch (KeyStoreException success) { + } + + assertTrue(mAndroidKeyStore.password("1111")); + assertTrue(mAndroidKeyStore.isUnlocked()); + + mKeyStore.setEntry(TEST_ALIAS_1, entry, + new AndroidKeyStoreParameter.Builder(getContext()) + .setEncryptionRequired() + .build()); + } + } + + public void testKeyStore_Size_Encrypted_Success() throws Exception { + setupPassword(); mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1)); @@ -1501,7 +1757,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertAliases(new String[] { TEST_ALIAS_2 }); } - public void testKeyStore_Store_LoadStoreParam_Failure() throws Exception { + public void testKeyStore_Store_LoadStoreParam_Encrypted_Failure() throws Exception { + setupPassword(); mKeyStore.load(null, null); try { @@ -1511,7 +1768,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { } } - public void testKeyStore_Load_InputStreamSupplied_Failure() throws Exception { + public void testKeyStore_Load_InputStreamSupplied_Encrypted_Failure() throws Exception { byte[] buf = "FAKE KEYSTORE".getBytes(); ByteArrayInputStream is = new ByteArrayInputStream(buf); @@ -1522,7 +1779,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { } } - public void testKeyStore_Load_PasswordSupplied_Failure() throws Exception { + public void testKeyStore_Load_PasswordSupplied_Encrypted_Failure() throws Exception { try { mKeyStore.load(null, "password".toCharArray()); fail("Should throw IllegalArgumentException when password is supplied"); @@ -1530,7 +1787,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { } } - public void testKeyStore_Store_OutputStream_Failure() throws Exception { + public void testKeyStore_Store_OutputStream_Encrypted_Failure() throws Exception { + setupPassword(); mKeyStore.load(null, null); OutputStream sink = new ByteArrayOutputStream(); @@ -1558,7 +1816,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { cert.getEncoded())); } - public void testKeyStore_KeyOperations_Wrap_Success() throws Exception { + public void testKeyStore_KeyOperations_Wrap_Encrypted_Success() throws Exception { + setupPassword(); mKeyStore.load(null, null); setupKey(); -- 2.11.0