From dcdaf87ed0aa99073638bcfe645949f130f0c7ad Mon Sep 17 00:00:00 2001 From: Alex Klyubin Date: Wed, 13 May 2015 15:57:09 -0700 Subject: Move Android Keystore impl to android.security.keystore. This moves the non-public API classes backing Android Keystore from android.security to android.security.keystore, a package specially created for Android Keystore. Bug: 18088752 Change-Id: Ibf04d6a26c54d310b0501fc5e34f37b1176324ad --- core/java/android/app/ActivityThread.java | 2 +- .../hardware/fingerprint/FingerprintManager.java | 2 +- .../android/security/AndroidKeyPairGenerator.java | 394 ---- .../java/android/security/AndroidKeyStore.java | 875 -------- .../AndroidKeyStoreBCWorkaroundProvider.java | 83 - .../android/security/AndroidKeyStoreProvider.java | 132 -- keystore/java/android/security/ArrayUtils.java | 62 - keystore/java/android/security/Credentials.java | 6 +- keystore/java/android/security/GateKeeper.java | 16 + keystore/java/android/security/KeyStore.java | 11 +- .../java/android/security/KeyStoreCipherSpi.java | 682 ------ .../android/security/KeyStoreConnectException.java | 30 - .../android/security/KeyStoreCryptoOperation.java | 31 - .../KeyStoreCryptoOperationChunkedStreamer.java | 272 --- .../security/KeyStoreCryptoOperationUtils.java | 113 - .../java/android/security/KeyStoreHmacSpi.java | 259 --- keystore/java/android/security/KeyStoreKey.java | 55 - .../android/security/KeyStoreKeyGeneratorSpi.java | 319 --- .../java/android/security/KeyStoreSecretKey.java | 31 - .../security/KeyStoreSecretKeyFactorySpi.java | 186 -- keystore/java/android/security/KeymasterUtils.java | 116 - .../keystore/AndroidKeyPairGeneratorSpi.java | 395 ++++ .../AndroidKeyStoreBCWorkaroundProvider.java | 83 + .../keystore/AndroidKeyStoreCipherSpi.java | 685 ++++++ .../security/keystore/AndroidKeyStoreHmacSpi.java | 261 +++ .../security/keystore/AndroidKeyStoreKey.java | 55 + .../keystore/AndroidKeyStoreKeyGeneratorSpi.java | 321 +++ .../security/keystore/AndroidKeyStoreProvider.java | 134 ++ .../keystore/AndroidKeyStoreSecretKey.java | 31 + .../AndroidKeyStoreSecretKeyFactorySpi.java | 186 ++ .../security/keystore/AndroidKeyStoreSpi.java | 877 ++++++++ .../java/android/security/keystore/ArrayUtils.java | 78 + .../security/keystore/KeyGenParameterSpec.java | 1 - .../java/android/security/keystore/KeyInfo.java | 1 - .../android/security/keystore/KeyProtection.java | 2 - .../keystore/KeyStoreConnectException.java | 30 + .../security/keystore/KeyStoreCryptoOperation.java | 33 + .../KeyStoreCryptoOperationChunkedStreamer.java | 274 +++ .../keystore/KeyStoreCryptoOperationUtils.java | 113 + .../android/security/keystore/KeymasterUtils.java | 118 ++ .../security/AndroidKeyPairGeneratorTest.java | 373 ---- .../src/android/security/AndroidKeyStoreTest.java | 2231 ------------------- .../keystore/AndroidKeyPairGeneratorTest.java | 375 ++++ .../security/keystore/AndroidKeyStoreTest.java | 2234 ++++++++++++++++++++ preloaded-classes | 4 +- 45 files changed, 6312 insertions(+), 6260 deletions(-) delete mode 100644 keystore/java/android/security/AndroidKeyPairGenerator.java delete mode 100644 keystore/java/android/security/AndroidKeyStore.java delete mode 100644 keystore/java/android/security/AndroidKeyStoreBCWorkaroundProvider.java delete mode 100644 keystore/java/android/security/AndroidKeyStoreProvider.java delete mode 100644 keystore/java/android/security/ArrayUtils.java delete mode 100644 keystore/java/android/security/KeyStoreCipherSpi.java delete mode 100644 keystore/java/android/security/KeyStoreConnectException.java delete mode 100644 keystore/java/android/security/KeyStoreCryptoOperation.java delete mode 100644 keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java delete mode 100644 keystore/java/android/security/KeyStoreCryptoOperationUtils.java delete mode 100644 keystore/java/android/security/KeyStoreHmacSpi.java delete mode 100644 keystore/java/android/security/KeyStoreKey.java delete mode 100644 keystore/java/android/security/KeyStoreKeyGeneratorSpi.java delete mode 100644 keystore/java/android/security/KeyStoreSecretKey.java delete mode 100644 keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java delete mode 100644 keystore/java/android/security/KeymasterUtils.java create mode 100644 keystore/java/android/security/keystore/AndroidKeyPairGeneratorSpi.java create mode 100644 keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java create mode 100644 keystore/java/android/security/keystore/AndroidKeyStoreCipherSpi.java create mode 100644 keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java create mode 100644 keystore/java/android/security/keystore/AndroidKeyStoreKey.java create mode 100644 keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java create mode 100644 keystore/java/android/security/keystore/AndroidKeyStoreProvider.java create mode 100644 keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.java create mode 100644 keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java create mode 100644 keystore/java/android/security/keystore/AndroidKeyStoreSpi.java create mode 100644 keystore/java/android/security/keystore/ArrayUtils.java create mode 100644 keystore/java/android/security/keystore/KeyStoreConnectException.java create mode 100644 keystore/java/android/security/keystore/KeyStoreCryptoOperation.java create mode 100644 keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java create mode 100644 keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java create mode 100644 keystore/java/android/security/keystore/KeymasterUtils.java delete mode 100644 keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java delete mode 100644 keystore/tests/src/android/security/AndroidKeyStoreTest.java create mode 100644 keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java create mode 100644 keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index f506d59..5dcbe37 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -96,7 +96,7 @@ import android.view.Window; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.renderscript.RenderScriptCacheDir; -import android.security.AndroidKeyStoreProvider; +import android.security.keystore.AndroidKeyStoreProvider; import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.ReferrerIntent; diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index cf96145..1a75e81 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -32,7 +32,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback; -import android.security.AndroidKeyStoreProvider; +import android.security.keystore.AndroidKeyStoreProvider; import android.util.Log; import android.util.Slog; diff --git a/keystore/java/android/security/AndroidKeyPairGenerator.java b/keystore/java/android/security/AndroidKeyPairGenerator.java deleted file mode 100644 index e9f8320..0000000 --- a/keystore/java/android/security/AndroidKeyPairGenerator.java +++ /dev/null @@ -1,394 +0,0 @@ -/* - * 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.annotation.NonNull; -import android.security.keystore.KeyGenParameterSpec; -import android.security.keystore.KeyProperties; - -import com.android.org.bouncycastle.x509.X509V3CertificateGenerator; -import com.android.org.conscrypt.NativeConstants; -import com.android.org.conscrypt.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.RSAKeyGenParameterSpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.Locale; - -/** - * 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. - *

- * This class can not be directly instantiated and must instead be used via the - * {@link KeyPairGenerator#getInstance(String) - * KeyPairGenerator.getInstance("AndroidKeyPairGenerator")} API. - * - * {@hide} - */ -public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { - - public static class RSA extends AndroidKeyPairGenerator { - public RSA() { - super(KeyProperties.KEY_ALGORITHM_RSA); - } - } - - public static class EC extends AndroidKeyPairGenerator { - public EC() { - super(KeyProperties.KEY_ALGORITHM_EC); - } - } - - /* - * These must be kept in sync with system/security/keystore/defaults.h - */ - - /* EC */ - private static final int EC_DEFAULT_KEY_SIZE = 256; - private static final int EC_MIN_KEY_SIZE = 192; - private static final int EC_MAX_KEY_SIZE = 521; - - /* RSA */ - private static final int RSA_DEFAULT_KEY_SIZE = 2048; - private static final int RSA_MIN_KEY_SIZE = 512; - private static final int RSA_MAX_KEY_SIZE = 8192; - - private final String mAlgorithm; - - private KeyStore mKeyStore; - - private KeyGenParameterSpec mSpec; - private @KeyProperties.KeyAlgorithmEnum String mKeyAlgorithm; - private int mKeyType; - private int mKeySize; - - protected AndroidKeyPairGenerator(@KeyProperties.KeyAlgorithmEnum String algorithm) { - mAlgorithm = algorithm; - } - - @KeyProperties.KeyAlgorithmEnum String getAlgorithm() { - return mAlgorithm; - } - - /** - * Generate a KeyPair which is backed by the Android keystore service. You - * must call {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)} - * with an {@link KeyPairGeneratorSpec} as the {@code params} - * argument before calling this otherwise an {@code IllegalStateException} - * will be thrown. - *

- * 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("Not initialized"); - - } - - final int flags = mSpec.getFlags(); - if (((flags & KeyStore.FLAG_ENCRYPTED) != 0) - && (mKeyStore.state() != KeyStore.State.UNLOCKED)) { - throw new IllegalStateException( - "Encryption at rest using secure lock screen credential requested for key pair" - + ", but the user has not yet entered the credential"); - } - - final String alias = mSpec.getKeystoreAlias(); - - Credentials.deleteAllTypesForAlias(mKeyStore, alias); - - byte[][] args = getArgsForKeyType(mKeyType, mSpec.getAlgorithmParameterSpec()); - - final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; - - if (!mKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, mKeyType, mKeySize, - flags, args)) { - throw new IllegalStateException("could not generate key in keystore"); - } - - Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias); - - 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(mKeyAlgorithm); - pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes)); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("Can't instantiate key generator", e); - } catch (InvalidKeySpecException e) { - throw new IllegalStateException("keystore returned invalid key encoding", e); - } - - final X509Certificate cert; - try { - cert = generateCertificate(privKey, pubKey); - } 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, KeyStore.UID_SELF, - flags)) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); - throw new IllegalStateException("Can't store certificate in AndroidKeyStore"); - } - - return new KeyPair(pubKey, privKey); - } - - @SuppressWarnings("deprecation") - private X509Certificate generateCertificate(PrivateKey privateKey, PublicKey publicKey) - throws Exception { - final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); - certGen.setPublicKey(publicKey); - certGen.setSerialNumber(mSpec.getCertificateSerialNumber()); - certGen.setSubjectDN(mSpec.getCertificateSubject()); - certGen.setIssuerDN(mSpec.getCertificateSubject()); - certGen.setNotBefore(mSpec.getCertificateNotBefore()); - certGen.setNotAfter(mSpec.getCertificateNotAfter()); - certGen.setSignatureAlgorithm(getDefaultSignatureAlgorithmForKeyAlgorithm(mKeyAlgorithm)); - return certGen.generate(privateKey); - } - - @NonNull - private @KeyProperties.KeyAlgorithmEnum String getKeyAlgorithm(KeyPairGeneratorSpec spec) { - String result = spec.getKeyType(); - if (result != null) { - return result; - } - return getAlgorithm(); - } - - private static int getDefaultKeySize(int keyType) { - if (keyType == NativeConstants.EVP_PKEY_EC) { - return EC_DEFAULT_KEY_SIZE; - } else if (keyType == NativeConstants.EVP_PKEY_RSA) { - return RSA_DEFAULT_KEY_SIZE; - } - return -1; - } - - private static void checkValidKeySize(String keyAlgorithm, int keyType, int keySize) - throws InvalidAlgorithmParameterException { - if (keyType == NativeConstants.EVP_PKEY_EC) { - if (keySize < EC_MIN_KEY_SIZE || keySize > EC_MAX_KEY_SIZE) { - throw new InvalidAlgorithmParameterException("EC keys must be >= " - + EC_MIN_KEY_SIZE + " and <= " + EC_MAX_KEY_SIZE); - } - } else if (keyType == NativeConstants.EVP_PKEY_RSA) { - if (keySize < RSA_MIN_KEY_SIZE || keySize > RSA_MAX_KEY_SIZE) { - throw new InvalidAlgorithmParameterException("RSA keys must be >= " - + RSA_MIN_KEY_SIZE + " and <= " + RSA_MAX_KEY_SIZE); - } - } else { - throw new InvalidAlgorithmParameterException( - "Unsupported key algorithm: " + keyAlgorithm); - } - } - - private static void checkCorrectParametersSpec(int keyType, int keySize, - AlgorithmParameterSpec spec) throws InvalidAlgorithmParameterException { - if (keyType == NativeConstants.EVP_PKEY_RSA && spec != null) { - if (spec instanceof RSAKeyGenParameterSpec) { - RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) spec; - if (keySize != -1 && keySize != rsaSpec.getKeysize()) { - throw new InvalidAlgorithmParameterException("RSA key size must match: " - + keySize + " vs " + rsaSpec.getKeysize()); - } - } else { - throw new InvalidAlgorithmParameterException( - "RSA may only use RSAKeyGenParameterSpec"); - } - } - } - - private static String getDefaultSignatureAlgorithmForKeyAlgorithm( - @KeyProperties.KeyAlgorithmEnum String algorithm) { - if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) { - return "sha256WithRSA"; - } else if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) { - return "sha256WithECDSA"; - } else { - throw new IllegalArgumentException("Unsupported key type " + algorithm); - } - } - - private static byte[][] getArgsForKeyType(int keyType, AlgorithmParameterSpec spec) { - switch (keyType) { - case NativeConstants.EVP_PKEY_RSA: - if (spec instanceof RSAKeyGenParameterSpec) { - RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) spec; - return new byte[][] { rsaSpec.getPublicExponent().toByteArray() }; - } - break; - } - return null; - } - - @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 " + KeyGenParameterSpec.class.getName() - + " or " + KeyPairGeneratorSpec.class.getName()); - } - - String keyAlgorithm; - KeyGenParameterSpec spec; - if (params instanceof KeyPairGeneratorSpec) { - KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params; - try { - KeyGenParameterSpec.Builder specBuilder; - keyAlgorithm = getKeyAlgorithm(legacySpec).toUpperCase(Locale.US); - if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { - specBuilder = new KeyGenParameterSpec.Builder( - legacySpec.getKeystoreAlias(), - KeyProperties.PURPOSE_SIGN - | KeyProperties.PURPOSE_VERIFY); - specBuilder.setDigests( - KeyProperties.DIGEST_NONE, - KeyProperties.DIGEST_MD5, - KeyProperties.DIGEST_SHA1, - KeyProperties.DIGEST_SHA224, - KeyProperties.DIGEST_SHA256, - KeyProperties.DIGEST_SHA384, - KeyProperties.DIGEST_SHA512); - } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { - specBuilder = new KeyGenParameterSpec.Builder( - legacySpec.getKeystoreAlias(), - KeyProperties.PURPOSE_ENCRYPT - | KeyProperties.PURPOSE_DECRYPT - | KeyProperties.PURPOSE_SIGN - | KeyProperties.PURPOSE_VERIFY); - specBuilder.setDigests( - KeyProperties.DIGEST_NONE, - KeyProperties.DIGEST_MD5, - KeyProperties.DIGEST_SHA1, - KeyProperties.DIGEST_SHA224, - KeyProperties.DIGEST_SHA256, - KeyProperties.DIGEST_SHA384, - KeyProperties.DIGEST_SHA512); - specBuilder.setSignaturePaddings( - KeyProperties.SIGNATURE_PADDING_RSA_PKCS1); - specBuilder.setBlockModes(KeyProperties.BLOCK_MODE_ECB); - specBuilder.setEncryptionPaddings( - KeyProperties.ENCRYPTION_PADDING_NONE, - KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1); - // Disable randomized encryption requirement to support encryption padding NONE - // above. - specBuilder.setRandomizedEncryptionRequired(false); - } else { - throw new InvalidAlgorithmParameterException( - "Unsupported key algorithm: " + keyAlgorithm); - } - - if (legacySpec.getKeySize() != -1) { - specBuilder.setKeySize(legacySpec.getKeySize()); - } - if (legacySpec.getAlgorithmParameterSpec() != null) { - specBuilder.setAlgorithmParameterSpec(legacySpec.getAlgorithmParameterSpec()); - } - specBuilder.setCertificateSubject(legacySpec.getSubjectDN()); - specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber()); - specBuilder.setCertificateNotBefore(legacySpec.getStartDate()); - specBuilder.setCertificateNotAfter(legacySpec.getEndDate()); - specBuilder.setEncryptionAtRestRequired(legacySpec.isEncryptionRequired()); - specBuilder.setUserAuthenticationRequired(false); - - spec = specBuilder.build(); - } catch (NullPointerException | IllegalArgumentException e) { - throw new InvalidAlgorithmParameterException(e); - } - } else if (params instanceof KeyGenParameterSpec) { - spec = (KeyGenParameterSpec) params; - keyAlgorithm = getAlgorithm(); - } else { - throw new InvalidAlgorithmParameterException( - "Unsupported params class: " + params.getClass().getName() - + ". Supported: " + KeyGenParameterSpec.class.getName() - + ", " + KeyPairGeneratorSpec.class); - } - - int keyType = KeyStore.getKeyTypeForAlgorithm(keyAlgorithm); - if (keyType == -1) { - throw new InvalidAlgorithmParameterException( - "Unsupported key algorithm: " + keyAlgorithm); - } - int keySize = spec.getKeySize(); - if (keySize == -1) { - keySize = getDefaultKeySize(keyType); - if (keySize == -1) { - throw new InvalidAlgorithmParameterException( - "Unsupported key algorithm: " + keyAlgorithm); - } - } - checkCorrectParametersSpec(keyType, keySize, spec.getAlgorithmParameterSpec()); - checkValidKeySize(keyAlgorithm, keyType, keySize); - - mKeyAlgorithm = keyAlgorithm; - mKeyType = keyType; - mKeySize = keySize; - mSpec = spec; - mKeyStore = KeyStore.getInstance(); - } -} diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java deleted file mode 100644 index 69bf877..0000000 --- a/keystore/java/android/security/AndroidKeyStore.java +++ /dev/null @@ -1,875 +0,0 @@ -/* - * 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.conscrypt.OpenSSLEngine; -import com.android.org.conscrypt.OpenSSLKeyHolder; - -import libcore.util.EmptyArray; - -import android.security.keymaster.KeyCharacteristics; -import android.security.keymaster.KeymasterArguments; -import android.security.keymaster.KeymasterDefs; -import android.security.keystore.KeyProperties; -import android.security.keystore.KeyProtection; -import android.util.Log; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -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.KeyStore.SecretKeyEntry; -import java.security.KeyStoreException; -import java.security.KeyStoreSpi; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.UnrecoverableKeyException; -import java.security.cert.Certificate; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import javax.crypto.SecretKey; - -/** - * 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. - *

- * This is built on top of Android's keystore daemon. The convention of alias - * use is: - *

- * PrivateKeyEntry will have a Credentials.USER_PRIVATE_KEY as the private key, - * Credentials.USER_CERTIFICATE as the first certificate in the chain (the one - * that corresponds to the private key), and then a Credentials.CA_CERTIFICATE - * entry which will have the rest of the chain concatenated in BER format. - *

- * TrustedCertificateEntry will just have a Credentials.CA_CERTIFICATE entry - * with a single certificate. - * - * @hide - */ -public class AndroidKeyStore extends KeyStoreSpi { - public static final String NAME = "AndroidKeyStore"; - - private android.security.KeyStore mKeyStore; - - @Override - public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, - UnrecoverableKeyException { - if (isPrivateKeyEntry(alias)) { - final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore"); - try { - return engine.getPrivateKeyById(Credentials.USER_PRIVATE_KEY + alias); - } catch (InvalidKeyException e) { - UnrecoverableKeyException t = new UnrecoverableKeyException("Can't get key"); - t.initCause(e); - throw t; - } - } else if (isSecretKeyEntry(alias)) { - KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); - String keyAliasInKeystore = Credentials.USER_SECRET_KEY + alias; - int errorCode = mKeyStore.getKeyCharacteristics( - keyAliasInKeystore, null, null, keyCharacteristics); - if ((errorCode != KeymasterDefs.KM_ERROR_OK) - && (errorCode != android.security.KeyStore.NO_ERROR)) { - throw (UnrecoverableKeyException) - new UnrecoverableKeyException("Failed to load information about key") - .initCause(mKeyStore.getInvalidKeyException(alias, errorCode)); - } - - int keymasterAlgorithm = - keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1); - if (keymasterAlgorithm == -1) { - keymasterAlgorithm = - keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1); - } - if (keymasterAlgorithm == -1) { - throw new UnrecoverableKeyException("Key algorithm unknown"); - } - - List keymasterDigests = - keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST); - int keymasterDigest; - if (keymasterDigests.isEmpty()) { - keymasterDigest = -1; - } else { - // More than one digest can be permitted for this key. Use the first one to form the - // JCA key algorithm name. - keymasterDigest = keymasterDigests.get(0); - } - - @KeyProperties.KeyAlgorithmEnum String keyAlgorithmString; - try { - keyAlgorithmString = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm( - keymasterAlgorithm, keymasterDigest); - } catch (IllegalArgumentException e) { - throw (UnrecoverableKeyException) - new UnrecoverableKeyException("Unsupported secret key type").initCause(e); - } - - return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmString); - } - - return null; - } - - @Override - public Certificate[] engineGetCertificateChain(String alias) { - if (alias == null) { - throw new NullPointerException("alias == null"); - } - - final X509Certificate leaf = (X509Certificate) engineGetCertificate(alias); - if (leaf == null) { - return null; - } - - final Certificate[] caList; - - final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); - if (caBytes != null) { - final Collection caChain = toCertificates(caBytes); - - caList = new Certificate[caChain.size() + 1]; - - final Iterator it = caChain.iterator(); - int i = 1; - while (it.hasNext()) { - caList[i++] = it.next(); - } - } else { - caList = new Certificate[1]; - } - - caList[0] = leaf; - - return caList; - } - - @Override - public Certificate engineGetCertificate(String alias) { - if (alias == null) { - throw new NullPointerException("alias == null"); - } - - byte[] certificate = mKeyStore.get(Credentials.USER_CERTIFICATE + alias); - if (certificate != null) { - return toCertificate(certificate); - } - - certificate = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); - if (certificate != null) { - return toCertificate(certificate); - } - - return null; - } - - private static X509Certificate toCertificate(byte[] bytes) { - try { - final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - return (X509Certificate) certFactory - .generateCertificate(new ByteArrayInputStream(bytes)); - } catch (CertificateException e) { - Log.w(NAME, "Couldn't parse certificate in keystore", e); - return null; - } - } - - @SuppressWarnings("unchecked") - private static Collection toCertificates(byte[] bytes) { - try { - final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - return (Collection) certFactory - .generateCertificates(new ByteArrayInputStream(bytes)); - } catch (CertificateException e) { - Log.w(NAME, "Couldn't parse certificates in keystore", e); - return new ArrayList(); - } - } - - private Date getModificationDate(String alias) { - final long epochMillis = mKeyStore.getmtime(alias); - if (epochMillis == -1L) { - return null; - } - - return new Date(epochMillis); - } - - @Override - public Date engineGetCreationDate(String alias) { - if (alias == null) { - throw new NullPointerException("alias == null"); - } - - Date d = getModificationDate(Credentials.USER_PRIVATE_KEY + alias); - if (d != null) { - return d; - } - - d = getModificationDate(Credentials.USER_SECRET_KEY + alias); - if (d != null) { - return d; - } - - d = getModificationDate(Credentials.USER_CERTIFICATE + alias); - if (d != null) { - return d; - } - - return getModificationDate(Credentials.CA_CERTIFICATE + alias); - } - - @Override - public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) - throws KeyStoreException { - if ((password != null) && (password.length > 0)) { - throw new KeyStoreException("entries cannot be protected with passwords"); - } - - if (key instanceof PrivateKey) { - setPrivateKeyEntry(alias, (PrivateKey) key, chain, null); - } else if (key instanceof SecretKey) { - setSecretKeyEntry(alias, (SecretKey) key, null); - } else { - throw new KeyStoreException("Only PrivateKey and SecretKey are supported"); - } - } - - private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain, - java.security.KeyStore.ProtectionParameter param) throws KeyStoreException { - KeyProtection spec; - if (param instanceof KeyStoreParameter) { - KeyStoreParameter legacySpec = (KeyStoreParameter) param; - try { - String keyAlgorithm = key.getAlgorithm(); - KeyProtection.Builder specBuilder; - if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { - specBuilder = - new KeyProtection.Builder( - KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY); - specBuilder.setDigests( - KeyProperties.DIGEST_NONE, - KeyProperties.DIGEST_MD5, - KeyProperties.DIGEST_SHA1, - KeyProperties.DIGEST_SHA224, - KeyProperties.DIGEST_SHA256, - KeyProperties.DIGEST_SHA384, - KeyProperties.DIGEST_SHA512); - } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { - specBuilder = - new KeyProtection.Builder( - KeyProperties.PURPOSE_ENCRYPT - | KeyProperties.PURPOSE_DECRYPT - | KeyProperties.PURPOSE_SIGN - | KeyProperties.PURPOSE_VERIFY); - specBuilder.setDigests( - KeyProperties.DIGEST_NONE, - KeyProperties.DIGEST_MD5, - KeyProperties.DIGEST_SHA1, - KeyProperties.DIGEST_SHA224, - KeyProperties.DIGEST_SHA256, - KeyProperties.DIGEST_SHA384, - KeyProperties.DIGEST_SHA512); - specBuilder.setSignaturePaddings( - KeyProperties.SIGNATURE_PADDING_RSA_PKCS1); - specBuilder.setBlockModes(KeyProperties.BLOCK_MODE_ECB); - specBuilder.setEncryptionPaddings( - KeyProperties.ENCRYPTION_PADDING_NONE, - KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1); - // Disable randomized encryption requirement to support encryption padding NONE - // above. - specBuilder.setRandomizedEncryptionRequired(false); - } else { - throw new KeyStoreException("Unsupported key algorithm: " + keyAlgorithm); - } - specBuilder.setEncryptionAtRestRequired(legacySpec.isEncryptionRequired()); - specBuilder.setUserAuthenticationRequired(false); - - spec = specBuilder.build(); - } catch (NullPointerException | IllegalArgumentException e) { - throw new KeyStoreException("Unsupported protection parameter", e); - } - } else if (param instanceof KeyProtection) { - spec = (KeyProtection) param; - } else if (param != null) { - throw new KeyStoreException( - "Unsupported protection parameter class:" + param.getClass().getName() - + ". Supported: " + KeyStoreParameter.class.getName() + ", " - + KeyProtection.class.getName()); - } else { - spec = null; - } - - byte[] keyBytes = null; - - final String pkeyAlias; - if (key instanceof OpenSSLKeyHolder) { - pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias(); - } else { - pkeyAlias = null; - } - - final boolean shouldReplacePrivateKey; - if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) { - final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length()); - if (!alias.equals(keySubalias)) { - throw new KeyStoreException("Can only replace keys with same alias: " + alias - + " != " + keySubalias); - } - - shouldReplacePrivateKey = false; - } else { - // Make sure the PrivateKey format is the one we support. - final String keyFormat = key.getFormat(); - if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) { - throw new KeyStoreException( - "Only PrivateKeys that can be encoded into PKCS#8 are supported"); - } - - // Make sure we can actually encode the key. - keyBytes = key.getEncoded(); - if (keyBytes == null) { - throw new KeyStoreException("PrivateKey has no encoding"); - } - - shouldReplacePrivateKey = true; - } - - // Make sure the chain exists since this is a PrivateKey - if ((chain == null) || (chain.length == 0)) { - throw new KeyStoreException("Must supply at least one Certificate with PrivateKey"); - } - - // Do chain type checking. - X509Certificate[] x509chain = new X509Certificate[chain.length]; - for (int i = 0; i < chain.length; i++) { - if (!"X.509".equals(chain[i].getType())) { - throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #" - + i); - } - - if (!(chain[i] instanceof X509Certificate)) { - throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #" - + i); - } - - x509chain[i] = (X509Certificate) chain[i]; - } - - final byte[] userCertBytes; - try { - userCertBytes = x509chain[0].getEncoded(); - } catch (CertificateEncodingException e) { - throw new KeyStoreException("Couldn't encode certificate #1", e); - } - - /* - * If we have a chain, store it in the CA certificate slot for this - * alias as concatenated DER-encoded certificates. These can be - * deserialized by {@link CertificateFactory#generateCertificates}. - */ - final byte[] chainBytes; - if (chain.length > 1) { - /* - * The chain is passed in as {user_cert, ca_cert_1, ca_cert_2, ...} - * so we only need the certificates starting at index 1. - */ - final byte[][] certsBytes = new byte[x509chain.length - 1][]; - int totalCertLength = 0; - for (int i = 0; i < certsBytes.length; i++) { - try { - certsBytes[i] = x509chain[i + 1].getEncoded(); - totalCertLength += certsBytes[i].length; - } catch (CertificateEncodingException e) { - throw new KeyStoreException("Can't encode Certificate #" + i, e); - } - } - - /* - * Serialize this into one byte array so we can later call - * CertificateFactory#generateCertificates to recover them. - */ - chainBytes = new byte[totalCertLength]; - int outputOffset = 0; - for (int i = 0; i < certsBytes.length; i++) { - final int certLength = certsBytes[i].length; - System.arraycopy(certsBytes[i], 0, chainBytes, outputOffset, certLength); - outputOffset += certLength; - certsBytes[i] = null; - } - } else { - chainBytes = null; - } - - /* - * Make sure we clear out all the appropriate types before trying to - * write. - */ - if (shouldReplacePrivateKey) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); - } else { - Credentials.deleteCertificateTypesForAlias(mKeyStore, alias); - Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias); - } - - final int flags = (spec == null) ? 0 : spec.getFlags(); - - if (shouldReplacePrivateKey - && !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, - 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, - android.security.KeyStore.UID_SELF, flags)) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); - throw new KeyStoreException("Couldn't put certificate chain in keystore"); - } - } - - private void setSecretKeyEntry(String entryAlias, SecretKey key, - java.security.KeyStore.ProtectionParameter param) - throws KeyStoreException { - if ((param != null) && (!(param instanceof KeyProtection))) { - throw new KeyStoreException( - "Unsupported protection parameter class: " + param.getClass().getName() - + ". Supported: " + KeyProtection.class.getName()); - } - KeyProtection params = (KeyProtection) param; - - if (key instanceof KeyStoreSecretKey) { - // KeyStore-backed secret key. It cannot be duplicated into another entry and cannot - // overwrite its own entry. - String keyAliasInKeystore = ((KeyStoreSecretKey) key).getAlias(); - if (keyAliasInKeystore == null) { - throw new KeyStoreException("KeyStore-backed secret key does not have an alias"); - } - if (!keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) { - throw new KeyStoreException("KeyStore-backed secret key has invalid alias: " - + keyAliasInKeystore); - } - String keyEntryAlias = - keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length()); - if (!entryAlias.equals(keyEntryAlias)) { - throw new KeyStoreException("Can only replace KeyStore-backed keys with same" - + " alias: " + entryAlias + " != " + keyEntryAlias); - } - // This is the entry where this key is already stored. No need to do anything. - if (params != null) { - throw new KeyStoreException("Modifying KeyStore-backed key using protection" - + " parameters not supported"); - } - return; - } - - if (params == null) { - throw new KeyStoreException( - "Protection parameters must be specified when importing a symmetric key"); - } - - // Not a KeyStore-backed secret key -- import its key material into keystore. - String keyExportFormat = key.getFormat(); - if (keyExportFormat == null) { - throw new KeyStoreException( - "Only secret keys that export their key material are supported"); - } else if (!"RAW".equals(keyExportFormat)) { - throw new KeyStoreException( - "Unsupported secret key material export format: " + keyExportFormat); - } - byte[] keyMaterial = key.getEncoded(); - if (keyMaterial == null) { - throw new KeyStoreException("Key did not export its key material despite supporting" - + " RAW format export"); - } - - String keyAlgorithmString = key.getAlgorithm(); - int keymasterAlgorithm; - int keymasterDigest; - try { - keymasterAlgorithm = - KeyProperties.KeyAlgorithm.toKeymasterSecretKeyAlgorithm(keyAlgorithmString); - keymasterDigest = KeyProperties.KeyAlgorithm.toKeymasterDigest(keyAlgorithmString); - } catch (IllegalArgumentException e) { - throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString); - } - - KeymasterArguments args = new KeymasterArguments(); - args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, keymasterAlgorithm); - - int[] keymasterDigests; - if (params.isDigestsSpecified()) { - // Digest(s) specified in parameters - keymasterDigests = KeyProperties.Digest.allToKeymaster(params.getDigests()); - if (keymasterDigest != -1) { - // Digest also specified in the JCA key algorithm name. - if (!com.android.internal.util.ArrayUtils.contains( - keymasterDigests, keymasterDigest)) { - throw new KeyStoreException("Key digest mismatch" - + ". Key: " + keyAlgorithmString - + ", parameter spec: " + Arrays.asList(params.getDigests())); - } - // When the key is read back from keystore we reconstruct the JCA key algorithm - // name from the KM_TAG_ALGORITHM and the first KM_TAG_DIGEST. Thus we need to - // ensure that the digest reflected in the JCA key algorithm name is the first - // KM_TAG_DIGEST tag. - if (keymasterDigests[0] != keymasterDigest) { - // The first digest is not the one implied by the JCA key algorithm name. - // Swap the implied digest with the first one. - for (int i = 0; i < keymasterDigests.length; i++) { - if (keymasterDigests[i] == keymasterDigest) { - keymasterDigests[i] = keymasterDigests[0]; - keymasterDigests[0] = keymasterDigest; - break; - } - } - } - } - } else { - // No digest specified in parameters - if (keymasterDigest != -1) { - // Digest specified in the JCA key algorithm name. - keymasterDigests = new int[] {keymasterDigest}; - } else { - keymasterDigests = EmptyArray.INT; - } - } - args.addInts(KeymasterDefs.KM_TAG_DIGEST, keymasterDigests); - if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { - if (keymasterDigests.length == 0) { - throw new KeyStoreException("At least one digest algorithm must be specified" - + " for key algorithm " + keyAlgorithmString); - } - } - - @KeyProperties.PurposeEnum int purposes = params.getPurposes(); - int[] keymasterBlockModes = - KeyProperties.BlockMode.allToKeymaster(params.getBlockModes()); - if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) - && (params.isRandomizedEncryptionRequired())) { - for (int keymasterBlockMode : keymasterBlockModes) { - if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible(keymasterBlockMode)) { - throw new KeyStoreException( - "Randomized encryption (IND-CPA) required but may be violated by block" - + " mode: " - + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode) - + ". See KeyProtection documentation."); - } - } - } - for (int keymasterPurpose : KeyProperties.Purpose.allToKeymaster(purposes)) { - args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose); - } - args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes); - if (params.getSignaturePaddings().length > 0) { - throw new KeyStoreException("Signature paddings not supported for symmetric keys"); - } - int[] keymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster( - params.getEncryptionPaddings()); - args.addInts(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings); - KeymasterUtils.addUserAuthArgs(args, - params.isUserAuthenticationRequired(), - params.getUserAuthenticationValidityDurationSeconds()); - args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, - (params.getKeyValidityStart() != null) - ? params.getKeyValidityStart() : new Date(0)); - args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, - (params.getKeyValidityForOriginationEnd() != null) - ? params.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE)); - args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, - (params.getKeyValidityForConsumptionEnd() != null) - ? params.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE)); - - // TODO: Remove this once keymaster does not require us to specify the size of imported key. - args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keyMaterial.length * 8); - - if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) - && (!params.isRandomizedEncryptionRequired())) { - // Permit caller-provided IV when encrypting with this key - args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); - } - - Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias); - String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias; - int errorCode = mKeyStore.importKey( - keyAliasInKeystore, - args, - KeymasterDefs.KM_KEY_FORMAT_RAW, - keyMaterial, - params.getFlags(), - new KeyCharacteristics()); - if (errorCode != android.security.KeyStore.NO_ERROR) { - throw new KeyStoreException("Failed to import secret key. Keystore error code: " - + errorCode); - } - } - - @Override - public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain) - throws KeyStoreException { - throw new KeyStoreException("Operation not supported because key encoding is unknown"); - } - - @Override - public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException { - if (isKeyEntry(alias)) { - throw new KeyStoreException("Entry exists and is not a trusted certificate"); - } - - // We can't set something to null. - if (cert == null) { - throw new NullPointerException("cert == null"); - } - - final byte[] encoded; - try { - encoded = cert.getEncoded(); - } catch (CertificateEncodingException e) { - throw new KeyStoreException(e); - } - - 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?"); - } - } - - @Override - public void engineDeleteEntry(String alias) throws KeyStoreException { - if (!isKeyEntry(alias) && !isCertificateEntry(alias)) { - return; - } - - if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) { - throw new KeyStoreException("No such entry " + alias); - } - } - - private Set getUniqueAliases() { - final String[] rawAliases = mKeyStore.saw(""); - if (rawAliases == null) { - return new HashSet(); - } - - final Set aliases = new HashSet(rawAliases.length); - for (String alias : rawAliases) { - final int idx = alias.indexOf('_'); - if ((idx == -1) || (alias.length() <= idx)) { - Log.e(NAME, "invalid alias: " + alias); - continue; - } - - aliases.add(new String(alias.substring(idx + 1))); - } - - return aliases; - } - - @Override - public Enumeration engineAliases() { - return Collections.enumeration(getUniqueAliases()); - } - - @Override - public boolean engineContainsAlias(String alias) { - if (alias == null) { - throw new NullPointerException("alias == null"); - } - - return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias) - || mKeyStore.contains(Credentials.USER_SECRET_KEY + alias) - || mKeyStore.contains(Credentials.USER_CERTIFICATE + alias) - || mKeyStore.contains(Credentials.CA_CERTIFICATE + alias); - } - - @Override - public int engineSize() { - return getUniqueAliases().size(); - } - - @Override - public boolean engineIsKeyEntry(String alias) { - return isKeyEntry(alias); - } - - private boolean isKeyEntry(String alias) { - return isPrivateKeyEntry(alias) || isSecretKeyEntry(alias); - } - - private boolean isPrivateKeyEntry(String alias) { - if (alias == null) { - throw new NullPointerException("alias == null"); - } - - return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias); - } - - private boolean isSecretKeyEntry(String alias) { - if (alias == null) { - throw new NullPointerException("alias == null"); - } - - return mKeyStore.contains(Credentials.USER_SECRET_KEY + alias); - } - - private boolean isCertificateEntry(String alias) { - if (alias == null) { - throw new NullPointerException("alias == null"); - } - - return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias); - } - - @Override - public boolean engineIsCertificateEntry(String alias) { - return !isKeyEntry(alias) && isCertificateEntry(alias); - } - - @Override - public String engineGetCertificateAlias(Certificate cert) { - if (cert == null) { - return null; - } - - final Set nonCaEntries = new HashSet(); - - /* - * First scan the PrivateKeyEntry types. The KeyStoreSpi documentation - * says to only compare the first certificate in the chain which is - * equivalent to the USER_CERTIFICATE prefix for the Android keystore - * convention. - */ - final String[] certAliases = mKeyStore.saw(Credentials.USER_CERTIFICATE); - if (certAliases != null) { - for (String alias : certAliases) { - final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias); - if (certBytes == null) { - continue; - } - - final Certificate c = toCertificate(certBytes); - nonCaEntries.add(alias); - - if (cert.equals(c)) { - return alias; - } - } - } - - /* - * Look at all the TrustedCertificateEntry types. Skip all the - * PrivateKeyEntry we looked at above. - */ - final String[] caAliases = mKeyStore.saw(Credentials.CA_CERTIFICATE); - if (certAliases != null) { - for (String alias : caAliases) { - if (nonCaEntries.contains(alias)) { - continue; - } - - final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); - if (certBytes == null) { - continue; - } - - final Certificate c = - toCertificate(mKeyStore.get(Credentials.CA_CERTIFICATE + alias)); - if (cert.equals(c)) { - return alias; - } - } - } - - return null; - } - - @Override - public void engineStore(OutputStream stream, char[] password) throws IOException, - NoSuchAlgorithmException, CertificateException { - throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream"); - } - - @Override - public void engineLoad(InputStream stream, char[] password) throws IOException, - NoSuchAlgorithmException, CertificateException { - if (stream != null) { - throw new IllegalArgumentException("InputStream not supported"); - } - - if (password != null) { - throw new IllegalArgumentException("password not supported"); - } - - // Unfortunate name collision. - 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 (entry instanceof PrivateKeyEntry) { - PrivateKeyEntry prE = (PrivateKeyEntry) entry; - setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(), param); - } else if (entry instanceof SecretKeyEntry) { - SecretKeyEntry secE = (SecretKeyEntry) entry; - setSecretKeyEntry(alias, secE.getSecretKey(), param); - } else { - throw new KeyStoreException( - "Entry must be a PrivateKeyEntry, SecretKeyEntry or TrustedCertificateEntry" - + "; was " + entry); - } - } - -} diff --git a/keystore/java/android/security/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/AndroidKeyStoreBCWorkaroundProvider.java deleted file mode 100644 index 45329cf..0000000 --- a/keystore/java/android/security/AndroidKeyStoreBCWorkaroundProvider.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2015 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 java.security.Provider; - -/** - * {@link Provider} of JCA crypto operations operating on Android KeyStore keys. - * - *

This provider was separated out of {@link AndroidKeyStoreProvider} to work around the issue - * that Bouncy Castle provider incorrectly declares that it accepts arbitrary keys (incl. Android - * KeyStore ones). This causes JCA to select the Bouncy Castle's implementation of JCA crypto - * operations for Android KeyStore keys unless Android KeyStore's own implementations are installed - * as higher-priority than Bouncy Castle ones. The purpose of this provider is to do just that: to - * offer crypto operations operating on Android KeyStore keys and to be installed at higher priority - * than the Bouncy Castle provider. - * - *

Once Bouncy Castle provider is fixed, this provider can be merged into the - * {@code AndroidKeyStoreProvider}. - * - * @hide - */ -class AndroidKeyStoreBCWorkaroundProvider extends Provider { - - // IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these - // classes when this provider is instantiated and installed early on during each app's - // initialization process. - - private static final String PACKAGE_NAME = "android.security"; - private static final String KEYSTORE_SECRET_KEY_CLASS_NAME = - PACKAGE_NAME + ".KeyStoreSecretKey"; - - AndroidKeyStoreBCWorkaroundProvider() { - super("AndroidKeyStoreBCWorkaround", - 1.0, - "Android KeyStore security provider to work around Bouncy Castle"); - - // javax.crypto.Mac - putMacImpl("HmacSHA1", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA1"); - putMacImpl("HmacSHA224", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA224"); - putMacImpl("HmacSHA256", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA256"); - putMacImpl("HmacSHA384", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA384"); - putMacImpl("HmacSHA512", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA512"); - - // javax.crypto.Cipher - putSymmetricCipherImpl("AES/ECB/NoPadding", - PACKAGE_NAME + ".KeyStoreCipherSpi$AES$ECB$NoPadding"); - putSymmetricCipherImpl("AES/ECB/PKCS7Padding", - PACKAGE_NAME + ".KeyStoreCipherSpi$AES$ECB$PKCS7Padding"); - - putSymmetricCipherImpl("AES/CBC/NoPadding", - PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CBC$NoPadding"); - putSymmetricCipherImpl("AES/CBC/PKCS7Padding", - PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CBC$PKCS7Padding"); - - putSymmetricCipherImpl("AES/CTR/NoPadding", - PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CTR$NoPadding"); - } - - private void putMacImpl(String algorithm, String implClass) { - put("Mac." + algorithm, implClass); - put("Mac." + algorithm + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME); - } - - private void putSymmetricCipherImpl(String transformation, String implClass) { - put("Cipher." + transformation, implClass); - put("Cipher." + transformation + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME); - } -} diff --git a/keystore/java/android/security/AndroidKeyStoreProvider.java b/keystore/java/android/security/AndroidKeyStoreProvider.java deleted file mode 100644 index 257ab54..0000000 --- a/keystore/java/android/security/AndroidKeyStoreProvider.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * 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 java.security.Provider; -import java.security.Security; - -import javax.crypto.Cipher; -import javax.crypto.Mac; - -/** - * A provider focused on providing JCA interfaces for the Android KeyStore. - * - * @hide - */ -public class AndroidKeyStoreProvider extends Provider { - public static final String PROVIDER_NAME = "AndroidKeyStore"; - - // IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these - // classes when this provider is instantiated and installed early on during each app's - // initialization process. - // - // Crypto operations operating on the AndroidKeyStore keys must not be offered by this provider. - // Instead, they need to be offered by AndroidKeyStoreBCWorkaroundProvider. See its Javadoc - // for details. - - private static final String PACKAGE_NAME = "android.security"; - - public AndroidKeyStoreProvider() { - super(PROVIDER_NAME, 1.0, "Android KeyStore security provider"); - - // java.security.KeyStore - put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStore"); - - // java.security.KeyPairGenerator - put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyPairGenerator$EC"); - put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyPairGenerator$RSA"); - - // javax.crypto.KeyGenerator - put("KeyGenerator.AES", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$AES"); - put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA1"); - put("KeyGenerator.HmacSHA224", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA224"); - put("KeyGenerator.HmacSHA256", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA256"); - put("KeyGenerator.HmacSHA384", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA384"); - put("KeyGenerator.HmacSHA512", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA512"); - - // java.security.SecretKeyFactory - putSecretKeyFactoryImpl("AES"); - putSecretKeyFactoryImpl("HmacSHA1"); - putSecretKeyFactoryImpl("HmacSHA224"); - putSecretKeyFactoryImpl("HmacSHA256"); - putSecretKeyFactoryImpl("HmacSHA384"); - putSecretKeyFactoryImpl("HmacSHA512"); - } - - /** - * Installs a new instance of this provider (and the - * {@link AndroidKeyStoreBCWorkaroundProvider}). - */ - public static void install() { - Provider[] providers = Security.getProviders(); - int bcProviderPosition = -1; - for (int position = 0; position < providers.length; position++) { - Provider provider = providers[position]; - if ("BC".equals(provider.getName())) { - bcProviderPosition = position; - break; - } - } - - Security.addProvider(new AndroidKeyStoreProvider()); - Provider workaroundProvider = new AndroidKeyStoreBCWorkaroundProvider(); - if (bcProviderPosition != -1) { - // Bouncy Castle provider found -- install the workaround provider above it. - Security.insertProviderAt(workaroundProvider, bcProviderPosition); - } else { - // Bouncy Castle provider not found -- install the workaround provider at lowest - // priority. - Security.addProvider(workaroundProvider); - } - } - - private void putSecretKeyFactoryImpl(String algorithm) { - put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".KeyStoreSecretKeyFactorySpi"); - } - - /** - * Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto - * primitive. - * - *

The following primitives are supported: {@link Cipher} and {@link Mac}. - * - * @return KeyStore operation handle or {@code 0} if the provided primitive's KeyStore operation - * is not in progress. - * - * @throws IllegalArgumentException if the provided primitive is not supported or is not backed - * by AndroidKeyStore provider. - */ - public static long getKeyStoreOperationHandle(Object cryptoPrimitive) { - if (cryptoPrimitive == null) { - throw new NullPointerException(); - } - Object spi; - if (cryptoPrimitive instanceof Mac) { - spi = ((Mac) cryptoPrimitive).getSpi(); - } else if (cryptoPrimitive instanceof Cipher) { - spi = ((Cipher) cryptoPrimitive).getSpi(); - } else { - throw new IllegalArgumentException("Unsupported crypto primitive: " + cryptoPrimitive); - } - if (!(spi instanceof KeyStoreCryptoOperation)) { - throw new IllegalArgumentException( - "Crypto primitive not backed by AndroidKeyStore: " + cryptoPrimitive - + ", spi: " + spi); - } - return ((KeyStoreCryptoOperation) spi).getOperationHandle(); - } -} diff --git a/keystore/java/android/security/ArrayUtils.java b/keystore/java/android/security/ArrayUtils.java deleted file mode 100644 index 71b99d0..0000000 --- a/keystore/java/android/security/ArrayUtils.java +++ /dev/null @@ -1,62 +0,0 @@ -package android.security; - -import libcore.util.EmptyArray; - -/** - * @hide - */ -public abstract class ArrayUtils { - private ArrayUtils() {} - - public static String[] nullToEmpty(String[] array) { - return (array != null) ? array : EmptyArray.STRING; - } - - public static String[] cloneIfNotEmpty(String[] array) { - return ((array != null) && (array.length > 0)) ? array.clone() : array; - } - - public static byte[] concat(byte[] arr1, byte[] arr2) { - return concat(arr1, 0, (arr1 != null) ? arr1.length : 0, - arr2, 0, (arr2 != null) ? arr2.length : 0); - } - - public static byte[] concat(byte[] arr1, int offset1, int len1, byte[] arr2, int offset2, - int len2) { - if (len1 == 0) { - return subarray(arr2, offset2, len2); - } else if (len2 == 0) { - return subarray(arr1, offset1, len1); - } else { - byte[] result = new byte[len1 + len2]; - System.arraycopy(arr1, offset1, result, 0, len1); - System.arraycopy(arr2, offset2, result, len1, len2); - return result; - } - } - - public static byte[] subarray(byte[] arr, int offset, int len) { - if (len == 0) { - return EmptyArray.BYTE; - } - if ((offset == 0) && (len == arr.length)) { - return arr; - } - byte[] result = new byte[len]; - System.arraycopy(arr, offset, result, 0, len); - return result; - } - - public static int[] concat(int[] arr1, int[] arr2) { - if ((arr1 == null) || (arr1.length == 0)) { - return arr2; - } else if ((arr2 == null) || (arr2.length == 0)) { - return arr1; - } else { - int[] result = new int[arr1.length + arr2.length]; - System.arraycopy(arr1, 0, result, 0, arr1.length); - System.arraycopy(arr2, 0, result, arr1.length, arr2.length); - return result; - } - } -} diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java index 6283e02..5d777b0 100644 --- a/keystore/java/android/security/Credentials.java +++ b/keystore/java/android/security/Credentials.java @@ -216,7 +216,7 @@ public class Credentials { * 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) { + public 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. @@ -231,7 +231,7 @@ public class Credentials { * 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 deleteCertificateTypesForAlias(KeyStore keystore, String alias) { + public static boolean deleteCertificateTypesForAlias(KeyStore keystore, String alias) { /* * Make sure every certificate type is deleted. There can be two types, * so don't use a conditional here. @@ -252,7 +252,7 @@ public class Credentials { * Delete secret key for a particular {@code alias}. * Returns {@code true} if an entry was was deleted. */ - static boolean deleteSecretKeyTypeForAlias(KeyStore keystore, String alias) { + public static boolean deleteSecretKeyTypeForAlias(KeyStore keystore, String alias) { return keystore.delete(Credentials.USER_SECRET_KEY + alias); } } diff --git a/keystore/java/android/security/GateKeeper.java b/keystore/java/android/security/GateKeeper.java index 5617836..c1df28c 100644 --- a/keystore/java/android/security/GateKeeper.java +++ b/keystore/java/android/security/GateKeeper.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2015 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.os.RemoteException; diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 72c74df..06f5b06 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -109,7 +109,7 @@ public class KeyStore { mContext = getApplicationContext(); } - static Context getApplicationContext() { + public static Context getApplicationContext() { ActivityThread activityThread = ActivityThread.currentActivityThread(); if (activityThread == null) { throw new IllegalStateException( @@ -136,7 +136,7 @@ public class KeyStore { return mToken; } - static int getKeyTypeForAlgorithm(@KeyProperties.KeyAlgorithmEnum String keyType) { + public static int getKeyTypeForAlgorithm(@KeyProperties.KeyAlgorithmEnum String keyType) { if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyType)) { return NativeConstants.EVP_PKEY_RSA; } else if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyType)) { @@ -632,7 +632,7 @@ public class KeyStore { * Returns a {@link KeyStoreException} corresponding to the provided keystore/keymaster error * code. */ - static KeyStoreException getKeyStoreException(int errorCode) { + public static KeyStoreException getKeyStoreException(int errorCode) { if (errorCode > 0) { // KeyStore layer error switch (errorCode) { @@ -674,7 +674,8 @@ public class KeyStore { * Returns an {@link InvalidKeyException} corresponding to the provided * {@link KeyStoreException}. */ - InvalidKeyException getInvalidKeyException(String keystoreKeyAlias, KeyStoreException e) { + public InvalidKeyException getInvalidKeyException( + String keystoreKeyAlias, KeyStoreException e) { switch (e.getErrorCode()) { case LOCKED: return new UserNotAuthenticatedException(); @@ -745,7 +746,7 @@ public class KeyStore { * Returns an {@link InvalidKeyException} corresponding to the provided keystore/keymaster error * code. */ - InvalidKeyException getInvalidKeyException(String keystoreKeyAlias, int errorCode) { + public InvalidKeyException getInvalidKeyException(String keystoreKeyAlias, int errorCode) { return getInvalidKeyException(keystoreKeyAlias, getKeyStoreException(errorCode)); } } diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java deleted file mode 100644 index b0f1695..0000000 --- a/keystore/java/android/security/KeyStoreCipherSpi.java +++ /dev/null @@ -1,682 +0,0 @@ -/* - * Copyright (C) 2015 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.os.IBinder; -import android.security.keymaster.KeymasterArguments; -import android.security.keymaster.KeymasterDefs; -import android.security.keymaster.OperationResult; -import android.security.keystore.KeyProperties; - -import java.security.AlgorithmParameters; -import java.security.GeneralSecurityException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; -import java.security.ProviderException; -import java.security.SecureRandom; -import java.security.spec.AlgorithmParameterSpec; -import java.security.spec.InvalidParameterSpecException; -import java.util.Arrays; - -import javax.crypto.AEADBadTagException; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.CipherSpi; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.IvParameterSpec; - -/** - * Base class for {@link CipherSpi} providing Android KeyStore backed ciphers. - * - * @hide - */ -public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCryptoOperation { - - public abstract static class AES extends KeyStoreCipherSpi { - protected AES(int keymasterBlockMode, int keymasterPadding, boolean ivUsed) { - super(KeymasterDefs.KM_ALGORITHM_AES, - keymasterBlockMode, - keymasterPadding, - 16, - ivUsed); - } - - public abstract static class ECB extends AES { - protected ECB(int keymasterPadding) { - super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false); - } - - public static class NoPadding extends ECB { - public NoPadding() { - super(KeymasterDefs.KM_PAD_NONE); - } - } - - public static class PKCS7Padding extends ECB { - public PKCS7Padding() { - super(KeymasterDefs.KM_PAD_PKCS7); - } - } - } - - public abstract static class CBC extends AES { - protected CBC(int keymasterPadding) { - super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true); - } - - public static class NoPadding extends CBC { - public NoPadding() { - super(KeymasterDefs.KM_PAD_NONE); - } - } - - public static class PKCS7Padding extends CBC { - public PKCS7Padding() { - super(KeymasterDefs.KM_PAD_PKCS7); - } - } - } - - public abstract static class CTR extends AES { - protected CTR(int keymasterPadding) { - super(KeymasterDefs.KM_MODE_CTR, keymasterPadding, true); - } - - public static class NoPadding extends CTR { - public NoPadding() { - super(KeymasterDefs.KM_PAD_NONE); - } - } - } - } - - private final KeyStore mKeyStore; - private final int mKeymasterAlgorithm; - private final int mKeymasterBlockMode; - private final int mKeymasterPadding; - private final int mBlockSizeBytes; - - /** Whether this transformation requires an IV. */ - private final boolean mIvRequired; - - // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after - // doFinal finishes. - protected boolean mEncrypting; - private KeyStoreSecretKey mKey; - private SecureRandom mRng; - private boolean mFirstOperationInitiated; - private byte[] mIv; - /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */ - private boolean mIvHasBeenUsed; - - // Fields below must be reset after doFinal - private byte[] mAdditionalEntropyForBegin; - - /** - * Token referencing this operation inside keystore service. It is initialized by - * {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and one some - * error conditions in between. - */ - private IBinder mOperationToken; - private long mOperationHandle; - private KeyStoreCryptoOperationChunkedStreamer mMainDataStreamer; - - /** - * Encountered exception which could not be immediately thrown because it was encountered inside - * a method that does not throw checked exception. This exception will be thrown from - * {@code engineDoFinal}. Once such an exception is encountered, {@code engineUpdate} and - * {@code engineDoFinal} start ignoring input data. - */ - private Exception mCachedException; - - protected KeyStoreCipherSpi( - int keymasterAlgorithm, - int keymasterBlockMode, - int keymasterPadding, - int blockSizeBytes, - boolean ivUsed) { - mKeyStore = KeyStore.getInstance(); - mKeymasterAlgorithm = keymasterAlgorithm; - mKeymasterBlockMode = keymasterBlockMode; - mKeymasterPadding = keymasterPadding; - mBlockSizeBytes = blockSizeBytes; - mIvRequired = ivUsed; - } - - @Override - protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException { - resetAll(); - - boolean success = false; - try { - init(opmode, key, random); - initAlgorithmSpecificParameters(); - try { - ensureKeystoreOperationInitialized(); - } catch (InvalidAlgorithmParameterException e) { - throw new InvalidKeyException(e); - } - success = true; - } finally { - if (!success) { - resetAll(); - } - } - } - - @Override - protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) - throws InvalidKeyException, InvalidAlgorithmParameterException { - resetAll(); - - boolean success = false; - try { - init(opmode, key, random); - initAlgorithmSpecificParameters(params); - ensureKeystoreOperationInitialized(); - success = true; - } finally { - if (!success) { - resetAll(); - } - } - } - - @Override - protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, - SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { - resetAll(); - - boolean success = false; - try { - init(opmode, key, random); - initAlgorithmSpecificParameters(params); - ensureKeystoreOperationInitialized(); - success = true; - } finally { - if (!success) { - resetAll(); - } - } - } - - private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException { - if (!(key instanceof KeyStoreSecretKey)) { - throw new InvalidKeyException( - "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null")); - } - mKey = (KeyStoreSecretKey) key; - mRng = random; - mIv = null; - mFirstOperationInitiated = false; - - if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.DECRYPT_MODE)) { - throw new UnsupportedOperationException( - "Only ENCRYPT and DECRYPT modes supported. Mode: " + opmode); - } - mEncrypting = opmode == Cipher.ENCRYPT_MODE; - } - - private void resetAll() { - IBinder operationToken = mOperationToken; - if (operationToken != null) { - mOperationToken = null; - mKeyStore.abort(operationToken); - } - mEncrypting = false; - mKey = null; - mRng = null; - mFirstOperationInitiated = false; - mIv = null; - mIvHasBeenUsed = false; - mAdditionalEntropyForBegin = null; - mOperationToken = null; - mOperationHandle = 0; - mMainDataStreamer = null; - mCachedException = null; - } - - private void resetWhilePreservingInitState() { - IBinder operationToken = mOperationToken; - if (operationToken != null) { - mOperationToken = null; - mKeyStore.abort(operationToken); - } - mOperationHandle = 0; - mMainDataStreamer = null; - mAdditionalEntropyForBegin = null; - mCachedException = null; - } - - private void ensureKeystoreOperationInitialized() throws InvalidKeyException, - InvalidAlgorithmParameterException { - if (mMainDataStreamer != null) { - return; - } - if (mCachedException != null) { - return; - } - if (mKey == null) { - throw new IllegalStateException("Not initialized"); - } - if ((mEncrypting) && (mIvRequired) && (mIvHasBeenUsed)) { - // IV is being reused for encryption: this violates security best practices. - throw new IllegalStateException( - "IV has already been used. Reusing IV in encryption mode violates security best" - + " practices."); - } - - KeymasterArguments keymasterInputArgs = new KeymasterArguments(); - keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); - keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode); - keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding); - addAlgorithmSpecificParametersToBegin(keymasterInputArgs); - - KeymasterArguments keymasterOutputArgs = new KeymasterArguments(); - OperationResult opResult = mKeyStore.begin( - mKey.getAlias(), - mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT, - true, // permit aborting this operation if keystore runs out of resources - keymasterInputArgs, - mAdditionalEntropyForBegin, - keymasterOutputArgs); - mAdditionalEntropyForBegin = null; - if (opResult == null) { - throw new KeyStoreConnectException(); - } - - // Store operation token and handle regardless of the error code returned by KeyStore to - // ensure that the operation gets aborted immediately if the code below throws an exception. - mOperationToken = opResult.token; - mOperationHandle = opResult.operationHandle; - - // If necessary, throw an exception due to KeyStore operation having failed. - GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit( - mKeyStore, mKey, opResult.resultCode); - if (e != null) { - if (e instanceof InvalidKeyException) { - throw (InvalidKeyException) e; - } else if (e instanceof InvalidAlgorithmParameterException) { - throw (InvalidAlgorithmParameterException) e; - } else { - throw new ProviderException("Unexpected exception type", e); - } - } - - if (mOperationToken == null) { - throw new ProviderException("Keystore returned null operation token"); - } - if (mOperationHandle == 0) { - throw new ProviderException("Keystore returned invalid operation handle"); - } - - loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs); - mFirstOperationInitiated = true; - mIvHasBeenUsed = true; - mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer( - new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( - mKeyStore, opResult.token)); - } - - @Override - protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { - if (mCachedException != null) { - return null; - } - try { - ensureKeystoreOperationInitialized(); - } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { - mCachedException = e; - return null; - } - - if (inputLen == 0) { - return null; - } - - byte[] output; - try { - output = mMainDataStreamer.update(input, inputOffset, inputLen); - } catch (KeyStoreException e) { - mCachedException = e; - return null; - } - - if (output.length == 0) { - return null; - } - - return output; - } - - @Override - protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, - int outputOffset) throws ShortBufferException { - byte[] outputCopy = engineUpdate(input, inputOffset, inputLen); - if (outputCopy == null) { - return 0; - } - int outputAvailable = output.length - outputOffset; - if (outputCopy.length > outputAvailable) { - throw new ShortBufferException("Output buffer too short. Produced: " - + outputCopy.length + ", available: " + outputAvailable); - } - System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); - return outputCopy.length; - } - - @Override - protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) - throws IllegalBlockSizeException, BadPaddingException { - if (mCachedException != null) { - throw (IllegalBlockSizeException) - new IllegalBlockSizeException().initCause(mCachedException); - } - - try { - ensureKeystoreOperationInitialized(); - } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { - throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); - } - - byte[] output; - try { - output = mMainDataStreamer.doFinal(input, inputOffset, inputLen); - } catch (KeyStoreException e) { - switch (e.getErrorCode()) { - case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH: - throw new IllegalBlockSizeException(); - case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT: - throw new BadPaddingException(); - case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED: - throw new AEADBadTagException(); - default: - throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); - } - } - - resetWhilePreservingInitState(); - return output; - } - - @Override - protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, - int outputOffset) throws ShortBufferException, IllegalBlockSizeException, - BadPaddingException { - byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen); - if (outputCopy == null) { - return 0; - } - int outputAvailable = output.length - outputOffset; - if (outputCopy.length > outputAvailable) { - throw new ShortBufferException("Output buffer too short. Produced: " - + outputCopy.length + ", available: " + outputAvailable); - } - System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); - return outputCopy.length; - } - - @Override - protected int engineGetBlockSize() { - return mBlockSizeBytes; - } - - @Override - protected byte[] engineGetIV() { - return (mIv != null) ? mIv.clone() : null; - } - - @Override - protected int engineGetOutputSize(int inputLen) { - return inputLen + 3 * engineGetBlockSize(); - } - - @Override - protected void engineSetMode(String mode) throws NoSuchAlgorithmException { - // This should never be invoked because all algorithms registered with the AndroidKeyStore - // provide explicitly specify block mode. - throw new UnsupportedOperationException(); - } - - @Override - protected void engineSetPadding(String arg0) throws NoSuchPaddingException { - // This should never be invoked because all algorithms registered with the AndroidKeyStore - // provide explicitly specify padding mode. - throw new UnsupportedOperationException(); - } - - @Override - public void finalize() throws Throwable { - try { - IBinder operationToken = mOperationToken; - if (operationToken != null) { - mKeyStore.abort(operationToken); - } - } finally { - super.finalize(); - } - } - - @Override - public long getOperationHandle() { - return mOperationHandle; - } - - // The methods below may need to be overridden by subclasses that use algorithm-specific - // parameters. - - /** - * Returns algorithm-specific parameters used by this {@code CipherSpi} instance or {@code null} - * if no algorithm-specific parameters are used. - * - *

This implementation only handles the IV parameter. - */ - @Override - protected AlgorithmParameters engineGetParameters() { - if (!mIvRequired) { - return null; - } - if ((mIv != null) && (mIv.length > 0)) { - try { - AlgorithmParameters params = - AlgorithmParameters.getInstance(KeyProperties.KEY_ALGORITHM_AES); - params.init(new IvParameterSpec(mIv)); - return params; - } catch (NoSuchAlgorithmException e) { - throw new ProviderException("Failed to obtain AES AlgorithmParameters", e); - } catch (InvalidParameterSpecException e) { - throw new ProviderException( - "Failed to initialize AES AlgorithmParameters with an IV", e); - } - } - return null; - } - - /** - * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters - * may need to be stored to be reused after {@code doFinal}. - * - *

The default implementation only handles the IV parameters. - * - * @param params algorithm parameters. - * - * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be - * automatically configured and thus {@code Cipher.init} needs to be invoked with - * explicitly provided parameters. - */ - protected void initAlgorithmSpecificParameters(AlgorithmParameterSpec params) - throws InvalidAlgorithmParameterException { - if (!mIvRequired) { - if (params != null) { - throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); - } - return; - } - - // IV is used - if (params == null) { - if (!mEncrypting) { - // IV must be provided by the caller - throw new InvalidAlgorithmParameterException( - "IvParameterSpec must be provided when decrypting"); - } - return; - } - if (!(params instanceof IvParameterSpec)) { - throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported"); - } - mIv = ((IvParameterSpec) params).getIV(); - if (mIv == null) { - throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec"); - } - } - - /** - * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters - * may need to be stored to be reused after {@code doFinal}. - * - *

The default implementation only handles the IV parameters. - * - * @param params algorithm parameters. - * - * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be - * automatically configured and thus {@code Cipher.init} needs to be invoked with - * explicitly provided parameters. - */ - protected void initAlgorithmSpecificParameters(AlgorithmParameters params) - throws InvalidAlgorithmParameterException { - if (!mIvRequired) { - if (params != null) { - throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); - } - return; - } - - // IV is used - if (params == null) { - if (!mEncrypting) { - // IV must be provided by the caller - throw new InvalidAlgorithmParameterException("IV required when decrypting" - + ". Use IvParameterSpec or AlgorithmParameters to provide it."); - } - return; - } - - IvParameterSpec ivSpec; - try { - ivSpec = params.getParameterSpec(IvParameterSpec.class); - } catch (InvalidParameterSpecException e) { - if (!mEncrypting) { - // IV must be provided by the caller - throw new InvalidAlgorithmParameterException("IV required when decrypting" - + ", but not found in parameters: " + params, e); - } - mIv = null; - return; - } - mIv = ivSpec.getIV(); - if (mIv == null) { - throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters"); - } - } - - /** - * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters - * may need to be stored to be reused after {@code doFinal}. - * - *

The default implementation only handles the IV parameter. - * - * @throws InvalidKeyException if some/all of the parameters cannot be automatically configured - * and thus {@code Cipher.init} needs to be invoked with explicitly provided parameters. - */ - protected void initAlgorithmSpecificParameters() throws InvalidKeyException { - if (!mIvRequired) { - return; - } - - // IV is used - if (!mEncrypting) { - throw new InvalidKeyException("IV required when decrypting" - + ". Use IvParameterSpec or AlgorithmParameters to provide it."); - } - } - - /** - * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation. - * - *

The default implementation takes care of the IV. - * - * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific - * parameters. - */ - protected void addAlgorithmSpecificParametersToBegin(KeymasterArguments keymasterArgs) { - if (!mFirstOperationInitiated) { - // First begin operation -- see if we need to provide additional entropy for IV - // generation. - if (mIvRequired) { - // IV is needed - if ((mIv == null) && (mEncrypting)) { - // IV was not provided by the caller and thus will be generated by keymaster. - // Mix in some additional entropy from the provided SecureRandom. - mAdditionalEntropyForBegin = - KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( - mRng, mBlockSizeBytes); - } - } - } - - if ((mIvRequired) && (mIv != null)) { - keymasterArgs.addBlob(KeymasterDefs.KM_TAG_NONCE, mIv); - } - } - - /** - * Invoked by {@code engineInit} to obtain algorithm-specific parameters from the result of the - * Keymaster's {@code begin} operation. Some of these parameters may need to be reused after - * {@code doFinal} by {@link #addAlgorithmSpecificParametersToBegin(KeymasterArguments)}. - * - *

The default implementation only takes care of the IV. - * - * @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin} - * operation. - */ - protected void loadAlgorithmSpecificParametersFromBeginResult( - KeymasterArguments keymasterArgs) { - // NOTE: Keymaster doesn't always return an IV, even if it's used. - byte[] returnedIv = keymasterArgs.getBlob(KeymasterDefs.KM_TAG_NONCE, null); - if ((returnedIv != null) && (returnedIv.length == 0)) { - returnedIv = null; - } - - if (mIvRequired) { - if (mIv == null) { - mIv = returnedIv; - } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) { - throw new ProviderException("IV in use differs from provided IV"); - } - } else { - if (returnedIv != null) { - throw new ProviderException( - "IV in use despite IV not being used by this transformation"); - } - } - } -} diff --git a/keystore/java/android/security/KeyStoreConnectException.java b/keystore/java/android/security/KeyStoreConnectException.java deleted file mode 100644 index 885f1f7..0000000 --- a/keystore/java/android/security/KeyStoreConnectException.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2015 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 java.security.ProviderException; - -/** - * Indicates a communications error with keystore service. - * - * @hide - */ -public class KeyStoreConnectException extends ProviderException { - public KeyStoreConnectException() { - super("Failed to communicate with keystore service"); - } -} diff --git a/keystore/java/android/security/KeyStoreCryptoOperation.java b/keystore/java/android/security/KeyStoreCryptoOperation.java deleted file mode 100644 index c5cf211..0000000 --- a/keystore/java/android/security/KeyStoreCryptoOperation.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2015 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; - -/** - * Cryptographic operation backed by {@link KeyStore}. - * - * @hide - */ -public interface KeyStoreCryptoOperation { - /** - * Gets the KeyStore operation handle of this crypto operation. - * - * @return handle or {@code 0} if the KeyStore operation is not in progress. - */ - long getOperationHandle(); -} diff --git a/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java deleted file mode 100644 index 0619199..0000000 --- a/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright (C) 2015 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.os.IBinder; -import android.security.keymaster.OperationResult; - -import libcore.util.EmptyArray; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -/** - * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's - * {@code update} and {@code finish} operations. - * - *

The helper abstracts away to issues that need to be solved in most code that uses KeyStore's - * update and finish operations. Firstly, KeyStore's update operation can consume only a limited - * amount of data in one go because the operations are marshalled via Binder. Secondly, the update - * operation may consume less data than provided, in which case the caller has to buffer the - * remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and - * {@link #doFinal(byte[], int, int) doFinal} operations which can be used to conveniently implement - * various JCA crypto primitives. - * - *

Bidirectional chunked streaming of data via a KeyStore crypto operation is abstracted away as - * a {@link Stream} to avoid having this class deal with operation tokens and occasional additional - * parameters to {@code update} and {@code final} operations. - * - * @hide - */ -public class KeyStoreCryptoOperationChunkedStreamer { - - /** - * Bidirectional chunked data stream over a KeyStore crypto operation. - */ - public interface Stream { - /** - * Returns the result of the KeyStore {@code update} operation or null if keystore couldn't - * be reached. - */ - OperationResult update(byte[] input); - - /** - * Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't - * be reached. - */ - OperationResult finish(); - } - - // Binder buffer is about 1MB, but it's shared between all active transactions of the process. - // Thus, it's safer to use a much smaller upper bound. - private static final int DEFAULT_MAX_CHUNK_SIZE = 64 * 1024; - private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - - private final Stream mKeyStoreStream; - private final int mMaxChunkSize; - - private byte[] mBuffered = EMPTY_BYTE_ARRAY; - private int mBufferedOffset; - private int mBufferedLength; - - public KeyStoreCryptoOperationChunkedStreamer(Stream operation) { - this(operation, DEFAULT_MAX_CHUNK_SIZE); - } - - public KeyStoreCryptoOperationChunkedStreamer(Stream operation, int maxChunkSize) { - mKeyStoreStream = operation; - mMaxChunkSize = maxChunkSize; - } - - public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException { - if (inputLength == 0) { - // No input provided - return EMPTY_BYTE_ARRAY; - } - - ByteArrayOutputStream bufferedOutput = null; - - while (inputLength > 0) { - byte[] chunk; - int inputBytesInChunk; - if ((mBufferedLength + inputLength) > mMaxChunkSize) { - // Too much input for one chunk -- extract one max-sized chunk and feed it into the - // update operation. - inputBytesInChunk = mMaxChunkSize - mBufferedLength; - chunk = ArrayUtils.concat(mBuffered, mBufferedOffset, mBufferedLength, - input, inputOffset, inputBytesInChunk); - } else { - // All of available input fits into one chunk. - if ((mBufferedLength == 0) && (inputOffset == 0) - && (inputLength == input.length)) { - // Nothing buffered and all of input array needs to be fed into the update - // operation. - chunk = input; - inputBytesInChunk = input.length; - } else { - // Need to combine buffered data with input data into one array. - inputBytesInChunk = inputLength; - chunk = ArrayUtils.concat(mBuffered, mBufferedOffset, mBufferedLength, - input, inputOffset, inputBytesInChunk); - } - } - // Update input array references to reflect that some of its bytes are now in mBuffered. - inputOffset += inputBytesInChunk; - inputLength -= inputBytesInChunk; - - OperationResult opResult = mKeyStoreStream.update(chunk); - if (opResult == null) { - throw new KeyStoreConnectException(); - } else if (opResult.resultCode != KeyStore.NO_ERROR) { - throw KeyStore.getKeyStoreException(opResult.resultCode); - } - - if (opResult.inputConsumed == chunk.length) { - // The whole chunk was consumed - mBuffered = EMPTY_BYTE_ARRAY; - mBufferedOffset = 0; - mBufferedLength = 0; - } else if (opResult.inputConsumed == 0) { - // Nothing was consumed. More input needed. - if (inputLength > 0) { - // More input is available, but it wasn't included into the previous chunk - // because the chunk reached its maximum permitted size. - // Shouldn't have happened. - throw new IllegalStateException("Nothing consumed from max-sized chunk: " - + chunk.length + " bytes"); - } - mBuffered = chunk; - mBufferedOffset = 0; - mBufferedLength = chunk.length; - } else if (opResult.inputConsumed < chunk.length) { - // The chunk was consumed only partially -- buffer the rest of the chunk - mBuffered = chunk; - mBufferedOffset = opResult.inputConsumed; - mBufferedLength = chunk.length - opResult.inputConsumed; - } else { - throw new IllegalStateException("Consumed more than provided: " - + opResult.inputConsumed + ", provided: " + chunk.length); - } - - if ((opResult.output != null) && (opResult.output.length > 0)) { - if (inputLength > 0) { - // More output might be produced in this loop -- buffer the current output - if (bufferedOutput == null) { - bufferedOutput = new ByteArrayOutputStream(); - try { - bufferedOutput.write(opResult.output); - } catch (IOException e) { - throw new IllegalStateException("Failed to buffer output", e); - } - } - } else { - // No more output will be produced in this loop - if (bufferedOutput == null) { - // No previously buffered output - return opResult.output; - } else { - // There was some previously buffered output - try { - bufferedOutput.write(opResult.output); - } catch (IOException e) { - throw new IllegalStateException("Failed to buffer output", e); - } - return bufferedOutput.toByteArray(); - } - } - } - } - - if (bufferedOutput == null) { - // No output produced - return EMPTY_BYTE_ARRAY; - } else { - return bufferedOutput.toByteArray(); - } - } - - public byte[] doFinal(byte[] input, int inputOffset, int inputLength) - throws KeyStoreException { - if (inputLength == 0) { - // No input provided -- simplify the rest of the code - input = EMPTY_BYTE_ARRAY; - inputOffset = 0; - } - - // Flush all buffered input and provided input into keystore/keymaster. - byte[] output = update(input, inputOffset, inputLength); - output = ArrayUtils.concat(output, flush()); - - OperationResult opResult = mKeyStoreStream.finish(); - if (opResult == null) { - throw new KeyStoreConnectException(); - } else if (opResult.resultCode != KeyStore.NO_ERROR) { - throw KeyStore.getKeyStoreException(opResult.resultCode); - } - - return ArrayUtils.concat(output, opResult.output); - } - - /** - * Passes all of buffered input into the the KeyStore operation (via the {@code update} - * operation) and returns output. - */ - public byte[] flush() throws KeyStoreException { - if (mBufferedLength <= 0) { - return EmptyArray.BYTE; - } - - byte[] chunk = ArrayUtils.subarray(mBuffered, mBufferedOffset, mBufferedLength); - mBuffered = EmptyArray.BYTE; - mBufferedLength = 0; - mBufferedOffset = 0; - - OperationResult opResult = mKeyStoreStream.update(chunk); - if (opResult == null) { - throw new KeyStoreConnectException(); - } else if (opResult.resultCode != KeyStore.NO_ERROR) { - throw KeyStore.getKeyStoreException(opResult.resultCode); - } - - if (opResult.inputConsumed < chunk.length) { - throw new IllegalStateException("Keystore failed to consume all input. Provided: " - + chunk.length + ", consumed: " + opResult.inputConsumed); - } else if (opResult.inputConsumed > chunk.length) { - throw new IllegalStateException("Keystore consumed more input than provided" - + " . Provided: " + chunk.length + ", consumed: " + opResult.inputConsumed); - } - - return (opResult.output != null) ? opResult.output : EmptyArray.BYTE; - } - - /** - * Main data stream via a KeyStore streaming operation. - * - *

For example, for an encryption operation, this is the stream through which plaintext is - * provided and ciphertext is obtained. - */ - public static class MainDataStream implements Stream { - - private final KeyStore mKeyStore; - private final IBinder mOperationToken; - - public MainDataStream(KeyStore keyStore, IBinder operationToken) { - mKeyStore = keyStore; - mOperationToken = operationToken; - } - - @Override - public OperationResult update(byte[] input) { - return mKeyStore.update(mOperationToken, null, input); - } - - @Override - public OperationResult finish() { - return mKeyStore.finish(mOperationToken, null, null); - } - } -} diff --git a/keystore/java/android/security/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/KeyStoreCryptoOperationUtils.java deleted file mode 100644 index c9bdd41..0000000 --- a/keystore/java/android/security/KeyStoreCryptoOperationUtils.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2015 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.security.keymaster.KeymasterDefs; -import android.security.keystore.UserNotAuthenticatedException; - -import java.security.GeneralSecurityException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.SecureRandom; - -/** - * Assorted utility methods for implementing crypto operations on top of KeyStore. - * - * @hide - */ -abstract class KeyStoreCryptoOperationUtils { - - private static volatile SecureRandom sRng; - - private KeyStoreCryptoOperationUtils() {} - - /** - * Returns the {@link InvalidKeyException} to be thrown by the {@code init} method of - * the crypto operation in response to {@code KeyStore.begin} operation or {@code null} if - * the {@code init} method should succeed. - */ - static InvalidKeyException getInvalidKeyExceptionForInit( - KeyStore keyStore, KeyStoreKey key, int beginOpResultCode) { - if (beginOpResultCode == KeyStore.NO_ERROR) { - return null; - } - - // An error occured. However, some errors should not lead to init throwing an exception. - // See below. - InvalidKeyException e = - keyStore.getInvalidKeyException(key.getAlias(), beginOpResultCode); - switch (beginOpResultCode) { - case KeyStore.OP_AUTH_NEEDED: - // Operation needs to be authorized by authenticating the user. Don't throw an - // exception is such authentication is possible for this key - // (UserNotAuthenticatedException). An example of when it's not possible is where - // the key is permanently invalidated (KeyPermanentlyInvalidatedException). - if (e instanceof UserNotAuthenticatedException) { - return null; - } - break; - } - return e; - } - - /** - * Returns the exception to be thrown by the {@code Cipher.init} method of the crypto operation - * in response to {@code KeyStore.begin} operation or {@code null} if the {@code init} method - * should succeed. - */ - static GeneralSecurityException getExceptionForCipherInit( - KeyStore keyStore, KeyStoreKey key, int beginOpResultCode) { - if (beginOpResultCode == KeyStore.NO_ERROR) { - return null; - } - - // Cipher-specific cases - switch (beginOpResultCode) { - case KeymasterDefs.KM_ERROR_INVALID_NONCE: - return new InvalidAlgorithmParameterException("Invalid IV"); - case KeymasterDefs.KM_ERROR_CALLER_NONCE_PROHIBITED: - return new InvalidAlgorithmParameterException("Caller-provided IV not permitted"); - } - - // General cases - return getInvalidKeyExceptionForInit(keyStore, key, beginOpResultCode); - } - - /** - * Returns the requested number of random bytes to mix into keystore/keymaster RNG. - * - * @param rng RNG from which to obtain the random bytes or {@code null} for the platform-default - * RNG. - */ - static byte[] getRandomBytesToMixIntoKeystoreRng(SecureRandom rng, int sizeBytes) { - if (rng == null) { - rng = getRng(); - } - byte[] result = new byte[sizeBytes]; - rng.nextBytes(result); - return result; - } - - private static SecureRandom getRng() { - // IMPLEMENTATION NOTE: It's OK to share a SecureRandom instance because SecureRandom is - // required to be thread-safe. - if (sRng == null) { - sRng = new SecureRandom(); - } - return sRng; - } -} diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java deleted file mode 100644 index 5089a25..0000000 --- a/keystore/java/android/security/KeyStoreHmacSpi.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (C) 2015 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.os.IBinder; -import android.security.keymaster.KeymasterArguments; -import android.security.keymaster.KeymasterDefs; -import android.security.keymaster.OperationResult; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.ProviderException; -import java.security.spec.AlgorithmParameterSpec; - -import javax.crypto.MacSpi; - -/** - * {@link MacSpi} which provides HMAC implementations backed by Android KeyStore. - * - * @hide - */ -public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation { - - public static class HmacSHA1 extends KeyStoreHmacSpi { - public HmacSHA1() { - super(KeymasterDefs.KM_DIGEST_SHA1); - } - } - - public static class HmacSHA224 extends KeyStoreHmacSpi { - public HmacSHA224() { - super(KeymasterDefs.KM_DIGEST_SHA_2_224); - } - } - - public static class HmacSHA256 extends KeyStoreHmacSpi { - public HmacSHA256() { - super(KeymasterDefs.KM_DIGEST_SHA_2_256); - } - } - - public static class HmacSHA384 extends KeyStoreHmacSpi { - public HmacSHA384() { - super(KeymasterDefs.KM_DIGEST_SHA_2_384); - } - } - - public static class HmacSHA512 extends KeyStoreHmacSpi { - public HmacSHA512() { - super(KeymasterDefs.KM_DIGEST_SHA_2_512); - } - } - - private final KeyStore mKeyStore = KeyStore.getInstance(); - private final int mKeymasterDigest; - private final int mMacSizeBits; - - // Fields below are populated by engineInit and should be preserved after engineDoFinal. - private KeyStoreSecretKey mKey; - - // Fields below are reset when engineDoFinal succeeds. - private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer; - private IBinder mOperationToken; - private long mOperationHandle; - - protected KeyStoreHmacSpi(int keymasterDigest) { - mKeymasterDigest = keymasterDigest; - mMacSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest); - } - - @Override - protected int engineGetMacLength() { - return (mMacSizeBits + 7) / 8; - } - - @Override - protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, - InvalidAlgorithmParameterException { - resetAll(); - - boolean success = false; - try { - init(key, params); - ensureKeystoreOperationInitialized(); - success = true; - } finally { - if (!success) { - resetAll(); - } - } - } - - private void init(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, - InvalidAlgorithmParameterException { - if (key == null) { - throw new InvalidKeyException("key == null"); - } else if (!(key instanceof KeyStoreSecretKey)) { - throw new InvalidKeyException( - "Only Android KeyStore secret keys supported. Key: " + key); - } - mKey = (KeyStoreSecretKey) key; - - if (params != null) { - throw new InvalidAlgorithmParameterException( - "Unsupported algorithm parameters: " + params); - } - - } - - private void resetAll() { - mKey = null; - IBinder operationToken = mOperationToken; - if (operationToken != null) { - mOperationToken = null; - mKeyStore.abort(operationToken); - } - mOperationHandle = 0; - mChunkedStreamer = null; - } - - private void resetWhilePreservingInitState() { - IBinder operationToken = mOperationToken; - if (operationToken != null) { - mOperationToken = null; - mKeyStore.abort(operationToken); - } - mOperationHandle = 0; - mChunkedStreamer = null; - } - - @Override - protected void engineReset() { - resetWhilePreservingInitState(); - } - - private void ensureKeystoreOperationInitialized() throws InvalidKeyException { - if (mChunkedStreamer != null) { - return; - } - if (mKey == null) { - throw new IllegalStateException("Not initialized"); - } - - KeymasterArguments keymasterArgs = new KeymasterArguments(); - keymasterArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC); - keymasterArgs.addInt(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); - keymasterArgs.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mMacSizeBits); - - KeymasterArguments keymasterOutputArgs = new KeymasterArguments(); - OperationResult opResult = mKeyStore.begin( - mKey.getAlias(), - KeymasterDefs.KM_PURPOSE_SIGN, - true, - keymasterArgs, - null, // no additional entropy needed for HMAC because it's deterministic - keymasterOutputArgs); - if (opResult == null) { - throw new KeyStoreConnectException(); - } - - // Store operation token and handle regardless of the error code returned by KeyStore to - // ensure that the operation gets aborted immediately if the code below throws an exception. - mOperationToken = opResult.token; - mOperationHandle = opResult.operationHandle; - - // If necessary, throw an exception due to KeyStore operation having failed. - InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit( - mKeyStore, mKey, opResult.resultCode); - if (e != null) { - throw e; - } - - if (mOperationToken == null) { - throw new ProviderException("Keystore returned null operation token"); - } - if (mOperationHandle == 0) { - throw new ProviderException("Keystore returned invalid operation handle"); - } - - mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer( - new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( - mKeyStore, mOperationToken)); - } - - @Override - protected void engineUpdate(byte input) { - engineUpdate(new byte[] {input}, 0, 1); - } - - @Override - protected void engineUpdate(byte[] input, int offset, int len) { - try { - ensureKeystoreOperationInitialized(); - } catch (InvalidKeyException e) { - throw new ProviderException("Failed to reinitialize MAC", e); - } - - byte[] output; - try { - output = mChunkedStreamer.update(input, offset, len); - } catch (KeyStoreException e) { - throw new ProviderException("Keystore operation failed", e); - } - if ((output != null) && (output.length != 0)) { - throw new ProviderException("Update operation unexpectedly produced output"); - } - } - - @Override - protected byte[] engineDoFinal() { - try { - ensureKeystoreOperationInitialized(); - } catch (InvalidKeyException e) { - throw new ProviderException("Failed to reinitialize MAC", e); - } - - byte[] result; - try { - result = mChunkedStreamer.doFinal(null, 0, 0); - } catch (KeyStoreException e) { - throw new ProviderException("Keystore operation failed", e); - } - - resetWhilePreservingInitState(); - return result; - } - - @Override - public void finalize() throws Throwable { - try { - IBinder operationToken = mOperationToken; - if (operationToken != null) { - mKeyStore.abort(operationToken); - } - } finally { - super.finalize(); - } - } - - @Override - public long getOperationHandle() { - return mOperationHandle; - } -} diff --git a/keystore/java/android/security/KeyStoreKey.java b/keystore/java/android/security/KeyStoreKey.java deleted file mode 100644 index 7a34829..0000000 --- a/keystore/java/android/security/KeyStoreKey.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2015 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 java.security.Key; - -/** - * {@link Key} backed by AndroidKeyStore. - * - * @hide - */ -public class KeyStoreKey implements Key { - private final String mAlias; - private final String mAlgorithm; - - public KeyStoreKey(String alias, String algorithm) { - mAlias = alias; - mAlgorithm = algorithm; - } - - String getAlias() { - return mAlias; - } - - @Override - public String getAlgorithm() { - return mAlgorithm; - } - - @Override - public String getFormat() { - // This key does not export its key material - return null; - } - - @Override - public byte[] getEncoded() { - // This key does not export its key material - return null; - } -} diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java deleted file mode 100644 index feec00f..0000000 --- a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright (C) 2015 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.security.keymaster.KeyCharacteristics; -import android.security.keymaster.KeymasterArguments; -import android.security.keymaster.KeymasterDefs; -import android.security.keystore.KeyGenParameterSpec; -import android.security.keystore.KeyProperties; - -import libcore.util.EmptyArray; - -import java.security.InvalidAlgorithmParameterException; -import java.security.ProviderException; -import java.security.SecureRandom; -import java.security.spec.AlgorithmParameterSpec; -import java.util.Arrays; -import java.util.Date; - -import javax.crypto.KeyGeneratorSpi; -import javax.crypto.SecretKey; - -/** - * {@link KeyGeneratorSpi} backed by Android KeyStore. - * - * @hide - */ -public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { - - public static class AES extends KeyStoreKeyGeneratorSpi { - public AES() { - super(KeymasterDefs.KM_ALGORITHM_AES, 128); - } - - @Override - protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) - throws InvalidAlgorithmParameterException { - super.engineInit(params, random); - if ((mKeySizeBits != 128) && (mKeySizeBits != 192) && (mKeySizeBits != 256)) { - throw new InvalidAlgorithmParameterException( - "Unsupported key size: " + mKeySizeBits - + ". Supported: 128, 192, 256."); - } - } - } - - protected static abstract class HmacBase extends KeyStoreKeyGeneratorSpi { - protected HmacBase(int keymasterDigest) { - super(KeymasterDefs.KM_ALGORITHM_HMAC, - keymasterDigest, - KeymasterUtils.getDigestOutputSizeBits(keymasterDigest)); - } - } - - public static class HmacSHA1 extends HmacBase { - public HmacSHA1() { - super(KeymasterDefs.KM_DIGEST_SHA1); - } - } - - public static class HmacSHA224 extends HmacBase { - public HmacSHA224() { - super(KeymasterDefs.KM_DIGEST_SHA_2_224); - } - } - - public static class HmacSHA256 extends HmacBase { - public HmacSHA256() { - super(KeymasterDefs.KM_DIGEST_SHA_2_256); - } - } - - public static class HmacSHA384 extends HmacBase { - public HmacSHA384() { - super(KeymasterDefs.KM_DIGEST_SHA_2_384); - } - } - - public static class HmacSHA512 extends HmacBase { - public HmacSHA512() { - super(KeymasterDefs.KM_DIGEST_SHA_2_512); - } - } - - private final KeyStore mKeyStore = KeyStore.getInstance(); - private final int mKeymasterAlgorithm; - private final int mKeymasterDigest; - private final int mDefaultKeySizeBits; - - private KeyGenParameterSpec mSpec; - private SecureRandom mRng; - - protected int mKeySizeBits; - private int[] mKeymasterPurposes; - private int[] mKeymasterBlockModes; - private int[] mKeymasterPaddings; - private int[] mKeymasterDigests; - - protected KeyStoreKeyGeneratorSpi( - int keymasterAlgorithm, - int defaultKeySizeBits) { - this(keymasterAlgorithm, -1, defaultKeySizeBits); - } - - protected KeyStoreKeyGeneratorSpi( - int keymasterAlgorithm, - int keymasterDigest, - int defaultKeySizeBits) { - mKeymasterAlgorithm = keymasterAlgorithm; - mKeymasterDigest = keymasterDigest; - mDefaultKeySizeBits = defaultKeySizeBits; - if (mDefaultKeySizeBits <= 0) { - throw new IllegalArgumentException("Default key size must be positive"); - } - - if ((mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) && (mKeymasterDigest == -1)) { - throw new IllegalArgumentException( - "Digest algorithm must be specified for HMAC key"); - } - } - - @Override - protected void engineInit(SecureRandom random) { - throw new UnsupportedOperationException("Cannot initialize without a " - + KeyGenParameterSpec.class.getName() + " parameter"); - } - - @Override - protected void engineInit(int keySize, SecureRandom random) { - throw new UnsupportedOperationException("Cannot initialize without a " - + KeyGenParameterSpec.class.getName() + " parameter"); - } - - @Override - protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) - throws InvalidAlgorithmParameterException { - resetAll(); - - boolean success = false; - try { - if ((params == null) || (!(params instanceof KeyGenParameterSpec))) { - throw new InvalidAlgorithmParameterException("Cannot initialize without a " - + KeyGenParameterSpec.class.getName() + " parameter"); - } - KeyGenParameterSpec spec = (KeyGenParameterSpec) params; - if (spec.getKeystoreAlias() == null) { - throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided"); - } - - mRng = random; - mSpec = spec; - - mKeySizeBits = (spec.getKeySize() != -1) ? spec.getKeySize() : mDefaultKeySizeBits; - if (mKeySizeBits <= 0) { - throw new InvalidAlgorithmParameterException( - "Key size must be positive: " + mKeySizeBits); - } else if ((mKeySizeBits % 8) != 0) { - throw new InvalidAlgorithmParameterException( - "Key size in must be a multiple of 8: " + mKeySizeBits); - } - - try { - mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes()); - mKeymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster( - spec.getEncryptionPaddings()); - mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()); - if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) - && (spec.isRandomizedEncryptionRequired())) { - for (int keymasterBlockMode : mKeymasterBlockModes) { - if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible( - keymasterBlockMode)) { - throw new InvalidAlgorithmParameterException( - "Randomized encryption (IND-CPA) required but may be violated" - + " by block mode: " - + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode) - + ". See " + KeyGenParameterSpec.class.getName() - + " documentation."); - } - } - } - if (spec.isDigestsSpecified()) { - // Digest(s) explicitly specified in the spec - mKeymasterDigests = KeyProperties.Digest.allToKeymaster(spec.getDigests()); - if (mKeymasterDigest != -1) { - // Key algorithm implies a digest -- ensure it's specified in the spec as - // first digest. - if (!com.android.internal.util.ArrayUtils.contains( - mKeymasterDigests, mKeymasterDigest)) { - throw new InvalidAlgorithmParameterException( - "Digests specified in algorithm parameters (" - + Arrays.asList(spec.getDigests()) + ") must include " - + " the digest " - + KeyProperties.Digest.fromKeymaster(mKeymasterDigest) - + " implied by key algorithm"); - } - if (mKeymasterDigests[0] != mKeymasterDigest) { - // The first digest is not the one implied by the key algorithm. - // Swap the implied digest with the first one. - for (int i = 0; i < mKeymasterDigests.length; i++) { - if (mKeymasterDigests[i] == mKeymasterDigest) { - mKeymasterDigests[i] = mKeymasterDigests[0]; - mKeymasterDigests[0] = mKeymasterDigest; - break; - } - } - } - } - } else { - // No digest specified in the spec - if (mKeymasterDigest != -1) { - // Key algorithm implies a digest -- use that digest - mKeymasterDigests = new int[] {mKeymasterDigest}; - } else { - mKeymasterDigests = EmptyArray.INT; - } - } - if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { - if (mKeymasterDigests.length == 0) { - throw new InvalidAlgorithmParameterException( - "At least one digest algorithm must be specified"); - } - } - } catch (IllegalStateException | IllegalArgumentException e) { - throw new InvalidAlgorithmParameterException(e); - } - - success = true; - } finally { - if (!success) { - resetAll(); - } - } - } - - private void resetAll() { - mSpec = null; - mRng = null; - mKeySizeBits = -1; - mKeymasterPurposes = null; - mKeymasterPaddings = null; - mKeymasterBlockModes = null; - } - - @Override - protected SecretKey engineGenerateKey() { - KeyGenParameterSpec spec = mSpec; - if (spec == null) { - throw new IllegalStateException("Not initialized"); - } - - if ((spec.isEncryptionAtRestRequired()) - && (mKeyStore.state() != KeyStore.State.UNLOCKED)) { - throw new IllegalStateException( - "Requested to import a key which must be encrypted at rest using secure lock" - + " screen credential, but the credential hasn't yet been entered by the user"); - } - - KeymasterArguments args = new KeymasterArguments(); - args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits); - args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); - args.addInts(KeymasterDefs.KM_TAG_PURPOSE, mKeymasterPurposes); - args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockModes); - args.addInts(KeymasterDefs.KM_TAG_PADDING, mKeymasterPaddings); - args.addInts(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests); - KeymasterUtils.addUserAuthArgs(args, - spec.isUserAuthenticationRequired(), - spec.getUserAuthenticationValidityDurationSeconds()); - args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, - (spec.getKeyValidityStart() != null) - ? spec.getKeyValidityStart() : new Date(0)); - args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, - (spec.getKeyValidityForOriginationEnd() != null) - ? spec.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE)); - args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, - (spec.getKeyValidityForConsumptionEnd() != null) - ? spec.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE)); - - if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) - && (!spec.isRandomizedEncryptionRequired())) { - // Permit caller-provided IV when encrypting with this key - args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); - } - - byte[] additionalEntropy = - KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( - mRng, (mKeySizeBits + 7) / 8); - int flags = spec.getFlags(); - String keyAliasInKeystore = Credentials.USER_SECRET_KEY + spec.getKeystoreAlias(); - KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); - int errorCode = mKeyStore.generateKey( - keyAliasInKeystore, args, additionalEntropy, flags, resultingKeyCharacteristics); - if (errorCode != KeyStore.NO_ERROR) { - throw new ProviderException( - "Keystore operation failed", KeyStore.getKeyStoreException(errorCode)); - } - @KeyProperties.KeyAlgorithmEnum String keyAlgorithmJCA; - try { - keyAlgorithmJCA = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm( - mKeymasterAlgorithm, mKeymasterDigest); - } catch (IllegalArgumentException e) { - throw new ProviderException("Failed to obtain JCA secret key algorithm name", e); - } - return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmJCA); - } -} diff --git a/keystore/java/android/security/KeyStoreSecretKey.java b/keystore/java/android/security/KeyStoreSecretKey.java deleted file mode 100644 index ee25465..0000000 --- a/keystore/java/android/security/KeyStoreSecretKey.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2015 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 javax.crypto.SecretKey; - -/** - * {@link SecretKey} backed by keystore. - * - * @hide - */ -public class KeyStoreSecretKey extends KeyStoreKey implements SecretKey { - - public KeyStoreSecretKey(String alias, String algorithm) { - super(alias, algorithm); - } -} diff --git a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java deleted file mode 100644 index 618ba47..0000000 --- a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2015 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.security.keymaster.KeyCharacteristics; -import android.security.keymaster.KeymasterDefs; -import android.security.keystore.KeyInfo; -import android.security.keystore.KeyProperties; - -import libcore.util.EmptyArray; - -import java.security.InvalidKeyException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.KeySpec; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactorySpi; -import javax.crypto.spec.SecretKeySpec; - -/** - * {@link SecretKeyFactorySpi} backed by Android KeyStore. - * - * @hide - */ -public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { - - private final KeyStore mKeyStore = KeyStore.getInstance(); - - @Override - protected KeySpec engineGetKeySpec(SecretKey key, - @SuppressWarnings("rawtypes") Class keySpecClass) throws InvalidKeySpecException { - if (keySpecClass == null) { - throw new InvalidKeySpecException("keySpecClass == null"); - } - if (!(key instanceof KeyStoreSecretKey)) { - throw new InvalidKeySpecException("Only Android KeyStore secret keys supported: " + - ((key != null) ? key.getClass().getName() : "null")); - } - if (SecretKeySpec.class.isAssignableFrom(keySpecClass)) { - throw new InvalidKeySpecException( - "Key material export of Android KeyStore keys is not supported"); - } - if (!KeyInfo.class.equals(keySpecClass)) { - throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName()); - } - String keyAliasInKeystore = ((KeyStoreSecretKey) key).getAlias(); - String entryAlias; - if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) { - entryAlias = keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length()); - } else { - throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore); - } - - KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); - int errorCode = - mKeyStore.getKeyCharacteristics(keyAliasInKeystore, null, null, keyCharacteristics); - if (errorCode != KeyStore.NO_ERROR) { - throw new InvalidKeySpecException("Failed to obtain information about key." - + " Keystore error: " + errorCode); - } - - boolean insideSecureHardware; - @KeyProperties.OriginEnum int origin; - int keySize; - @KeyProperties.PurposeEnum int purposes; - String[] encryptionPaddings; - @KeyProperties.DigestEnum String[] digests; - @KeyProperties.BlockModeEnum String[] blockModes; - int keymasterSwEnforcedUserAuthenticators; - int keymasterHwEnforcedUserAuthenticators; - try { - if (keyCharacteristics.hwEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) { - insideSecureHardware = true; - origin = KeyProperties.Origin.fromKeymaster( - keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1)); - } else if (keyCharacteristics.swEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) { - insideSecureHardware = false; - origin = KeyProperties.Origin.fromKeymaster( - keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1)); - } else { - throw new InvalidKeySpecException("Key origin not available"); - } - Integer keySizeInteger = keyCharacteristics.getInteger(KeymasterDefs.KM_TAG_KEY_SIZE); - if (keySizeInteger == null) { - throw new InvalidKeySpecException("Key size not available"); - } - keySize = keySizeInteger; - purposes = KeyProperties.Purpose.allFromKeymaster( - keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PURPOSE)); - - List encryptionPaddingsList = new ArrayList(); - for (int keymasterPadding : keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PADDING)) { - @KeyProperties.EncryptionPaddingEnum String jcaPadding; - try { - jcaPadding = KeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding); - } catch (IllegalArgumentException e) { - throw new InvalidKeySpecException( - "Unsupported encryption padding: " + keymasterPadding); - } - encryptionPaddingsList.add(jcaPadding); - } - encryptionPaddings = - encryptionPaddingsList.toArray(new String[encryptionPaddingsList.size()]); - - digests = KeyProperties.Digest.allFromKeymaster( - keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST)); - blockModes = KeyProperties.BlockMode.allFromKeymaster( - keyCharacteristics.getInts(KeymasterDefs.KM_TAG_BLOCK_MODE)); - keymasterSwEnforcedUserAuthenticators = - keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); - keymasterHwEnforcedUserAuthenticators = - keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); - } catch (IllegalArgumentException e) { - throw new InvalidKeySpecException("Unsupported key characteristic", e); - } - - Date keyValidityStart = keyCharacteristics.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME); - if ((keyValidityStart != null) && (keyValidityStart.getTime() <= 0)) { - keyValidityStart = null; - } - Date keyValidityForOriginationEnd = - keyCharacteristics.getDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME); - if ((keyValidityForOriginationEnd != null) - && (keyValidityForOriginationEnd.getTime() == Long.MAX_VALUE)) { - keyValidityForOriginationEnd = null; - } - Date keyValidityForConsumptionEnd = - keyCharacteristics.getDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME); - if ((keyValidityForConsumptionEnd != null) - && (keyValidityForConsumptionEnd.getTime() == Long.MAX_VALUE)) { - keyValidityForConsumptionEnd = null; - } - boolean userAuthenticationRequired = - !keyCharacteristics.getBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); - int userAuthenticationValidityDurationSeconds = - keyCharacteristics.getInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT, -1); - boolean userAuthenticationRequirementEnforcedBySecureHardware = (userAuthenticationRequired) - && (keymasterHwEnforcedUserAuthenticators != 0) - && (keymasterSwEnforcedUserAuthenticators == 0); - - return new KeyInfo(entryAlias, - insideSecureHardware, - origin, - keySize, - keyValidityStart, - keyValidityForOriginationEnd, - keyValidityForConsumptionEnd, - purposes, - encryptionPaddings, - EmptyArray.STRING, // no signature paddings -- this is symmetric crypto - digests, - blockModes, - userAuthenticationRequired, - userAuthenticationValidityDurationSeconds, - userAuthenticationRequirementEnforcedBySecureHardware); - } - - @Override - protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException { - throw new UnsupportedOperationException( - "Key import into Android KeyStore is not supported"); - } - - @Override - protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException { - throw new UnsupportedOperationException( - "Key import into Android KeyStore is not supported"); - } -} diff --git a/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/KeymasterUtils.java deleted file mode 100644 index 0f8f190..0000000 --- a/keystore/java/android/security/KeymasterUtils.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2015 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.hardware.fingerprint.FingerprintManager; -import android.security.keymaster.KeymasterArguments; -import android.security.keymaster.KeymasterDefs; - -/** - * @hide - */ -public abstract class KeymasterUtils { - - private KeymasterUtils() {} - - public static int getDigestOutputSizeBits(int keymasterDigest) { - switch (keymasterDigest) { - case KeymasterDefs.KM_DIGEST_NONE: - return -1; - case KeymasterDefs.KM_DIGEST_MD5: - return 128; - case KeymasterDefs.KM_DIGEST_SHA1: - return 160; - case KeymasterDefs.KM_DIGEST_SHA_2_224: - return 224; - case KeymasterDefs.KM_DIGEST_SHA_2_256: - return 256; - case KeymasterDefs.KM_DIGEST_SHA_2_384: - return 384; - case KeymasterDefs.KM_DIGEST_SHA_2_512: - return 512; - default: - throw new IllegalArgumentException("Unknown digest: " + keymasterDigest); - } - } - - public static boolean isKeymasterBlockModeIndCpaCompatible(int keymasterBlockMode) { - switch (keymasterBlockMode) { - case KeymasterDefs.KM_MODE_ECB: - return false; - case KeymasterDefs.KM_MODE_CBC: - case KeymasterDefs.KM_MODE_CTR: - case KeymasterDefs.KM_MODE_GCM: - return true; - default: - throw new IllegalArgumentException("Unsupported block mode: " + keymasterBlockMode); - } - } - - /** - * Adds keymaster arguments to express the key's authorization policy supported by user - * authentication. - * - * @param userAuthenticationRequired whether user authentication is required to authorize the - * use of the key. - * @param userAuthenticationValidityDurationSeconds duration of time (seconds) for which user - * authentication is valid as authorization for using the key or {@code -1} if every - * use of the key needs authorization. - */ - public static void addUserAuthArgs(KeymasterArguments args, - boolean userAuthenticationRequired, - int userAuthenticationValidityDurationSeconds) { - if (!userAuthenticationRequired) { - args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); - return; - } - - if (userAuthenticationValidityDurationSeconds == -1) { - // Every use of this key needs to be authorized by the user. This currently means - // fingerprint-only auth. - FingerprintManager fingerprintManager = - KeyStore.getApplicationContext().getSystemService(FingerprintManager.class); - if ((fingerprintManager == null) || (!fingerprintManager.isHardwareDetected())) { - throw new IllegalStateException( - "This device does not support keys which require authentication for every" - + " use -- this requires fingerprint authentication which is not" - + " available on this device"); - } - long fingerprintOnlySid = fingerprintManager.getAuthenticatorId(); - if (fingerprintOnlySid == 0) { - throw new IllegalStateException( - "At least one fingerprint must be enrolled to create keys requiring user" - + " authentication for every use"); - } - args.addLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, fingerprintOnlySid); - args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_FINGERPRINT); - } else { - // The key is authorized for use for the specified amount of time after the user has - // authenticated. Whatever unlocks the secure lock screen should authorize this key. - long rootSid = GateKeeper.getSecureUserId(); - if (rootSid == 0) { - throw new IllegalStateException("Secure lock screen must be enabled" - + " to create keys requiring user authentication"); - } - args.addLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, rootSid); - args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, - KeymasterDefs.HW_AUTH_PASSWORD | KeymasterDefs.HW_AUTH_FINGERPRINT); - args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT, - userAuthenticationValidityDurationSeconds); - } - } -} diff --git a/keystore/java/android/security/keystore/AndroidKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyPairGeneratorSpi.java new file mode 100644 index 0000000..8d3b421 --- /dev/null +++ b/keystore/java/android/security/keystore/AndroidKeyPairGeneratorSpi.java @@ -0,0 +1,395 @@ +/* + * 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.keystore; + +import android.annotation.NonNull; +import android.security.Credentials; +import android.security.KeyPairGeneratorSpec; +import android.security.KeyStore; + +import com.android.org.bouncycastle.x509.X509V3CertificateGenerator; +import com.android.org.conscrypt.NativeConstants; +import com.android.org.conscrypt.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.RSAKeyGenParameterSpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Locale; + +/** + * 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. + *

+ * This class can not be directly instantiated and must instead be used via the + * {@link KeyPairGenerator#getInstance(String) + * KeyPairGenerator.getInstance("AndroidKeyPairGenerator")} API. + * + * {@hide} + */ +public abstract class AndroidKeyPairGeneratorSpi extends KeyPairGeneratorSpi { + + public static class RSA extends AndroidKeyPairGeneratorSpi { + public RSA() { + super(KeyProperties.KEY_ALGORITHM_RSA); + } + } + + public static class EC extends AndroidKeyPairGeneratorSpi { + public EC() { + super(KeyProperties.KEY_ALGORITHM_EC); + } + } + + /* + * These must be kept in sync with system/security/keystore/defaults.h + */ + + /* EC */ + private static final int EC_DEFAULT_KEY_SIZE = 256; + private static final int EC_MIN_KEY_SIZE = 192; + private static final int EC_MAX_KEY_SIZE = 521; + + /* RSA */ + private static final int RSA_DEFAULT_KEY_SIZE = 2048; + private static final int RSA_MIN_KEY_SIZE = 512; + private static final int RSA_MAX_KEY_SIZE = 8192; + + private final String mAlgorithm; + + private KeyStore mKeyStore; + + private KeyGenParameterSpec mSpec; + private @KeyProperties.KeyAlgorithmEnum String mKeyAlgorithm; + private int mKeyType; + private int mKeySize; + + protected AndroidKeyPairGeneratorSpi(@KeyProperties.KeyAlgorithmEnum String algorithm) { + mAlgorithm = algorithm; + } + + @KeyProperties.KeyAlgorithmEnum String getAlgorithm() { + return mAlgorithm; + } + + /** + * Generate a KeyPair which is backed by the Android keystore service. You + * must call {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)} + * with an {@link KeyPairGeneratorSpec} as the {@code params} + * argument before calling this otherwise an {@code IllegalStateException} + * will be thrown. + *

+ * 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("Not initialized"); + + } + + final int flags = mSpec.getFlags(); + if (((flags & KeyStore.FLAG_ENCRYPTED) != 0) + && (mKeyStore.state() != KeyStore.State.UNLOCKED)) { + throw new IllegalStateException( + "Encryption at rest using secure lock screen credential requested for key pair" + + ", but the user has not yet entered the credential"); + } + + final String alias = mSpec.getKeystoreAlias(); + + Credentials.deleteAllTypesForAlias(mKeyStore, alias); + + byte[][] args = getArgsForKeyType(mKeyType, mSpec.getAlgorithmParameterSpec()); + + final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; + + if (!mKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, mKeyType, mKeySize, + flags, args)) { + throw new IllegalStateException("could not generate key in keystore"); + } + + Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias); + + 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(mKeyAlgorithm); + pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes)); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("Can't instantiate key generator", e); + } catch (InvalidKeySpecException e) { + throw new IllegalStateException("keystore returned invalid key encoding", e); + } + + final X509Certificate cert; + try { + cert = generateCertificate(privKey, pubKey); + } 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, KeyStore.UID_SELF, + flags)) { + Credentials.deleteAllTypesForAlias(mKeyStore, alias); + throw new IllegalStateException("Can't store certificate in AndroidKeyStore"); + } + + return new KeyPair(pubKey, privKey); + } + + @SuppressWarnings("deprecation") + private X509Certificate generateCertificate(PrivateKey privateKey, PublicKey publicKey) + throws Exception { + final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); + certGen.setPublicKey(publicKey); + certGen.setSerialNumber(mSpec.getCertificateSerialNumber()); + certGen.setSubjectDN(mSpec.getCertificateSubject()); + certGen.setIssuerDN(mSpec.getCertificateSubject()); + certGen.setNotBefore(mSpec.getCertificateNotBefore()); + certGen.setNotAfter(mSpec.getCertificateNotAfter()); + certGen.setSignatureAlgorithm(getDefaultSignatureAlgorithmForKeyAlgorithm(mKeyAlgorithm)); + return certGen.generate(privateKey); + } + + @NonNull + private @KeyProperties.KeyAlgorithmEnum String getKeyAlgorithm(KeyPairGeneratorSpec spec) { + String result = spec.getKeyType(); + if (result != null) { + return result; + } + return getAlgorithm(); + } + + private static int getDefaultKeySize(int keyType) { + if (keyType == NativeConstants.EVP_PKEY_EC) { + return EC_DEFAULT_KEY_SIZE; + } else if (keyType == NativeConstants.EVP_PKEY_RSA) { + return RSA_DEFAULT_KEY_SIZE; + } + return -1; + } + + private static void checkValidKeySize(String keyAlgorithm, int keyType, int keySize) + throws InvalidAlgorithmParameterException { + if (keyType == NativeConstants.EVP_PKEY_EC) { + if (keySize < EC_MIN_KEY_SIZE || keySize > EC_MAX_KEY_SIZE) { + throw new InvalidAlgorithmParameterException("EC keys must be >= " + + EC_MIN_KEY_SIZE + " and <= " + EC_MAX_KEY_SIZE); + } + } else if (keyType == NativeConstants.EVP_PKEY_RSA) { + if (keySize < RSA_MIN_KEY_SIZE || keySize > RSA_MAX_KEY_SIZE) { + throw new InvalidAlgorithmParameterException("RSA keys must be >= " + + RSA_MIN_KEY_SIZE + " and <= " + RSA_MAX_KEY_SIZE); + } + } else { + throw new InvalidAlgorithmParameterException( + "Unsupported key algorithm: " + keyAlgorithm); + } + } + + private static void checkCorrectParametersSpec(int keyType, int keySize, + AlgorithmParameterSpec spec) throws InvalidAlgorithmParameterException { + if (keyType == NativeConstants.EVP_PKEY_RSA && spec != null) { + if (spec instanceof RSAKeyGenParameterSpec) { + RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) spec; + if (keySize != -1 && keySize != rsaSpec.getKeysize()) { + throw new InvalidAlgorithmParameterException("RSA key size must match: " + + keySize + " vs " + rsaSpec.getKeysize()); + } + } else { + throw new InvalidAlgorithmParameterException( + "RSA may only use RSAKeyGenParameterSpec"); + } + } + } + + private static String getDefaultSignatureAlgorithmForKeyAlgorithm( + @KeyProperties.KeyAlgorithmEnum String algorithm) { + if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) { + return "sha256WithRSA"; + } else if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) { + return "sha256WithECDSA"; + } else { + throw new IllegalArgumentException("Unsupported key type " + algorithm); + } + } + + private static byte[][] getArgsForKeyType(int keyType, AlgorithmParameterSpec spec) { + switch (keyType) { + case NativeConstants.EVP_PKEY_RSA: + if (spec instanceof RSAKeyGenParameterSpec) { + RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) spec; + return new byte[][] { rsaSpec.getPublicExponent().toByteArray() }; + } + break; + } + return null; + } + + @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 " + KeyGenParameterSpec.class.getName() + + " or " + KeyPairGeneratorSpec.class.getName()); + } + + String keyAlgorithm; + KeyGenParameterSpec spec; + if (params instanceof KeyPairGeneratorSpec) { + KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params; + try { + KeyGenParameterSpec.Builder specBuilder; + keyAlgorithm = getKeyAlgorithm(legacySpec).toUpperCase(Locale.US); + if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { + specBuilder = new KeyGenParameterSpec.Builder( + legacySpec.getKeystoreAlias(), + KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_MD5, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { + specBuilder = new KeyGenParameterSpec.Builder( + legacySpec.getKeystoreAlias(), + KeyProperties.PURPOSE_ENCRYPT + | KeyProperties.PURPOSE_DECRYPT + | KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_MD5, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + specBuilder.setSignaturePaddings( + KeyProperties.SIGNATURE_PADDING_RSA_PKCS1); + specBuilder.setBlockModes(KeyProperties.BLOCK_MODE_ECB); + specBuilder.setEncryptionPaddings( + KeyProperties.ENCRYPTION_PADDING_NONE, + KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1); + // Disable randomized encryption requirement to support encryption padding NONE + // above. + specBuilder.setRandomizedEncryptionRequired(false); + } else { + throw new InvalidAlgorithmParameterException( + "Unsupported key algorithm: " + keyAlgorithm); + } + + if (legacySpec.getKeySize() != -1) { + specBuilder.setKeySize(legacySpec.getKeySize()); + } + if (legacySpec.getAlgorithmParameterSpec() != null) { + specBuilder.setAlgorithmParameterSpec(legacySpec.getAlgorithmParameterSpec()); + } + specBuilder.setCertificateSubject(legacySpec.getSubjectDN()); + specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber()); + specBuilder.setCertificateNotBefore(legacySpec.getStartDate()); + specBuilder.setCertificateNotAfter(legacySpec.getEndDate()); + specBuilder.setEncryptionAtRestRequired(legacySpec.isEncryptionRequired()); + specBuilder.setUserAuthenticationRequired(false); + + spec = specBuilder.build(); + } catch (NullPointerException | IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException(e); + } + } else if (params instanceof KeyGenParameterSpec) { + spec = (KeyGenParameterSpec) params; + keyAlgorithm = getAlgorithm(); + } else { + throw new InvalidAlgorithmParameterException( + "Unsupported params class: " + params.getClass().getName() + + ". Supported: " + KeyGenParameterSpec.class.getName() + + ", " + KeyPairGeneratorSpec.class); + } + + int keyType = KeyStore.getKeyTypeForAlgorithm(keyAlgorithm); + if (keyType == -1) { + throw new InvalidAlgorithmParameterException( + "Unsupported key algorithm: " + keyAlgorithm); + } + int keySize = spec.getKeySize(); + if (keySize == -1) { + keySize = getDefaultKeySize(keyType); + if (keySize == -1) { + throw new InvalidAlgorithmParameterException( + "Unsupported key algorithm: " + keyAlgorithm); + } + } + checkCorrectParametersSpec(keyType, keySize, spec.getAlgorithmParameterSpec()); + checkValidKeySize(keyAlgorithm, keyType, keySize); + + mKeyAlgorithm = keyAlgorithm; + mKeyType = keyType; + mKeySize = keySize; + mSpec = spec; + mKeyStore = KeyStore.getInstance(); + } +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java new file mode 100644 index 0000000..3774e36 --- /dev/null +++ b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015 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.keystore; + +import java.security.Provider; + +/** + * {@link Provider} of JCA crypto operations operating on Android KeyStore keys. + * + *

This provider was separated out of {@link AndroidKeyStoreProvider} to work around the issue + * that Bouncy Castle provider incorrectly declares that it accepts arbitrary keys (incl. Android + * KeyStore ones). This causes JCA to select the Bouncy Castle's implementation of JCA crypto + * operations for Android KeyStore keys unless Android KeyStore's own implementations are installed + * as higher-priority than Bouncy Castle ones. The purpose of this provider is to do just that: to + * offer crypto operations operating on Android KeyStore keys and to be installed at higher priority + * than the Bouncy Castle provider. + * + *

Once Bouncy Castle provider is fixed, this provider can be merged into the + * {@code AndroidKeyStoreProvider}. + * + * @hide + */ +class AndroidKeyStoreBCWorkaroundProvider extends Provider { + + // IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these + // classes when this provider is instantiated and installed early on during each app's + // initialization process. + + private static final String PACKAGE_NAME = "android.security.keystore"; + private static final String KEYSTORE_SECRET_KEY_CLASS_NAME = + PACKAGE_NAME + ".AndroidKeyStoreSecretKey"; + + AndroidKeyStoreBCWorkaroundProvider() { + super("AndroidKeyStoreBCWorkaround", + 1.0, + "Android KeyStore security provider to work around Bouncy Castle"); + + // javax.crypto.Mac + putMacImpl("HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA1"); + putMacImpl("HmacSHA224", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA224"); + putMacImpl("HmacSHA256", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA256"); + putMacImpl("HmacSHA384", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA384"); + putMacImpl("HmacSHA512", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA512"); + + // javax.crypto.Cipher + putSymmetricCipherImpl("AES/ECB/NoPadding", + PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$ECB$NoPadding"); + putSymmetricCipherImpl("AES/ECB/PKCS7Padding", + PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$ECB$PKCS7Padding"); + + putSymmetricCipherImpl("AES/CBC/NoPadding", + PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$CBC$NoPadding"); + putSymmetricCipherImpl("AES/CBC/PKCS7Padding", + PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$CBC$PKCS7Padding"); + + putSymmetricCipherImpl("AES/CTR/NoPadding", + PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$CTR$NoPadding"); + } + + private void putMacImpl(String algorithm, String implClass) { + put("Mac." + algorithm, implClass); + put("Mac." + algorithm + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME); + } + + private void putSymmetricCipherImpl(String transformation, String implClass) { + put("Cipher." + transformation, implClass); + put("Cipher." + transformation + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME); + } +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpi.java new file mode 100644 index 0000000..27df5e7 --- /dev/null +++ b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpi.java @@ -0,0 +1,685 @@ +/* + * Copyright (C) 2015 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.keystore; + +import android.os.IBinder; +import android.security.KeyStore; +import android.security.KeyStoreException; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keymaster.OperationResult; +import android.security.keystore.KeyProperties; + +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.ProviderException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.util.Arrays; + +import javax.crypto.AEADBadTagException; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.CipherSpi; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; + +/** + * Base class for {@link CipherSpi} providing Android KeyStore backed ciphers. + * + * @hide + */ +public abstract class AndroidKeyStoreCipherSpi extends CipherSpi + implements KeyStoreCryptoOperation { + + public abstract static class AES extends AndroidKeyStoreCipherSpi { + protected AES(int keymasterBlockMode, int keymasterPadding, boolean ivUsed) { + super(KeymasterDefs.KM_ALGORITHM_AES, + keymasterBlockMode, + keymasterPadding, + 16, + ivUsed); + } + + public abstract static class ECB extends AES { + protected ECB(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false); + } + + public static class NoPadding extends ECB { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + } + + public static class PKCS7Padding extends ECB { + public PKCS7Padding() { + super(KeymasterDefs.KM_PAD_PKCS7); + } + } + } + + public abstract static class CBC extends AES { + protected CBC(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true); + } + + public static class NoPadding extends CBC { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + } + + public static class PKCS7Padding extends CBC { + public PKCS7Padding() { + super(KeymasterDefs.KM_PAD_PKCS7); + } + } + } + + public abstract static class CTR extends AES { + protected CTR(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_CTR, keymasterPadding, true); + } + + public static class NoPadding extends CTR { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + } + } + } + + private final KeyStore mKeyStore; + private final int mKeymasterAlgorithm; + private final int mKeymasterBlockMode; + private final int mKeymasterPadding; + private final int mBlockSizeBytes; + + /** Whether this transformation requires an IV. */ + private final boolean mIvRequired; + + // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after + // doFinal finishes. + protected boolean mEncrypting; + private AndroidKeyStoreSecretKey mKey; + private SecureRandom mRng; + private boolean mFirstOperationInitiated; + private byte[] mIv; + /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */ + private boolean mIvHasBeenUsed; + + // Fields below must be reset after doFinal + private byte[] mAdditionalEntropyForBegin; + + /** + * Token referencing this operation inside keystore service. It is initialized by + * {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and one some + * error conditions in between. + */ + private IBinder mOperationToken; + private long mOperationHandle; + private KeyStoreCryptoOperationChunkedStreamer mMainDataStreamer; + + /** + * Encountered exception which could not be immediately thrown because it was encountered inside + * a method that does not throw checked exception. This exception will be thrown from + * {@code engineDoFinal}. Once such an exception is encountered, {@code engineUpdate} and + * {@code engineDoFinal} start ignoring input data. + */ + private Exception mCachedException; + + protected AndroidKeyStoreCipherSpi( + int keymasterAlgorithm, + int keymasterBlockMode, + int keymasterPadding, + int blockSizeBytes, + boolean ivUsed) { + mKeyStore = KeyStore.getInstance(); + mKeymasterAlgorithm = keymasterAlgorithm; + mKeymasterBlockMode = keymasterBlockMode; + mKeymasterPadding = keymasterPadding; + mBlockSizeBytes = blockSizeBytes; + mIvRequired = ivUsed; + } + + @Override + protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + resetAll(); + + boolean success = false; + try { + init(opmode, key, random); + initAlgorithmSpecificParameters(); + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidKeyException(e); + } + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + init(opmode, key, random); + initAlgorithmSpecificParameters(params); + ensureKeystoreOperationInitialized(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + init(opmode, key, random); + initAlgorithmSpecificParameters(params); + ensureKeystoreOperationInitialized(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + if (!(key instanceof AndroidKeyStoreSecretKey)) { + throw new InvalidKeyException( + "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null")); + } + mKey = (AndroidKeyStoreSecretKey) key; + mRng = random; + mIv = null; + mFirstOperationInitiated = false; + + if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.DECRYPT_MODE)) { + throw new UnsupportedOperationException( + "Only ENCRYPT and DECRYPT modes supported. Mode: " + opmode); + } + mEncrypting = opmode == Cipher.ENCRYPT_MODE; + } + + private void resetAll() { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mOperationToken = null; + mKeyStore.abort(operationToken); + } + mEncrypting = false; + mKey = null; + mRng = null; + mFirstOperationInitiated = false; + mIv = null; + mIvHasBeenUsed = false; + mAdditionalEntropyForBegin = null; + mOperationToken = null; + mOperationHandle = 0; + mMainDataStreamer = null; + mCachedException = null; + } + + private void resetWhilePreservingInitState() { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mOperationToken = null; + mKeyStore.abort(operationToken); + } + mOperationHandle = 0; + mMainDataStreamer = null; + mAdditionalEntropyForBegin = null; + mCachedException = null; + } + + private void ensureKeystoreOperationInitialized() throws InvalidKeyException, + InvalidAlgorithmParameterException { + if (mMainDataStreamer != null) { + return; + } + if (mCachedException != null) { + return; + } + if (mKey == null) { + throw new IllegalStateException("Not initialized"); + } + if ((mEncrypting) && (mIvRequired) && (mIvHasBeenUsed)) { + // IV is being reused for encryption: this violates security best practices. + throw new IllegalStateException( + "IV has already been used. Reusing IV in encryption mode violates security best" + + " practices."); + } + + KeymasterArguments keymasterInputArgs = new KeymasterArguments(); + keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); + keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode); + keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding); + addAlgorithmSpecificParametersToBegin(keymasterInputArgs); + + KeymasterArguments keymasterOutputArgs = new KeymasterArguments(); + OperationResult opResult = mKeyStore.begin( + mKey.getAlias(), + mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT, + true, // permit aborting this operation if keystore runs out of resources + keymasterInputArgs, + mAdditionalEntropyForBegin, + keymasterOutputArgs); + mAdditionalEntropyForBegin = null; + if (opResult == null) { + throw new KeyStoreConnectException(); + } + + // Store operation token and handle regardless of the error code returned by KeyStore to + // ensure that the operation gets aborted immediately if the code below throws an exception. + mOperationToken = opResult.token; + mOperationHandle = opResult.operationHandle; + + // If necessary, throw an exception due to KeyStore operation having failed. + GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit( + mKeyStore, mKey, opResult.resultCode); + if (e != null) { + if (e instanceof InvalidKeyException) { + throw (InvalidKeyException) e; + } else if (e instanceof InvalidAlgorithmParameterException) { + throw (InvalidAlgorithmParameterException) e; + } else { + throw new ProviderException("Unexpected exception type", e); + } + } + + if (mOperationToken == null) { + throw new ProviderException("Keystore returned null operation token"); + } + if (mOperationHandle == 0) { + throw new ProviderException("Keystore returned invalid operation handle"); + } + + loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs); + mFirstOperationInitiated = true; + mIvHasBeenUsed = true; + mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer( + new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( + mKeyStore, opResult.token)); + } + + @Override + protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { + if (mCachedException != null) { + return null; + } + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + mCachedException = e; + return null; + } + + if (inputLen == 0) { + return null; + } + + byte[] output; + try { + output = mMainDataStreamer.update(input, inputOffset, inputLen); + } catch (KeyStoreException e) { + mCachedException = e; + return null; + } + + if (output.length == 0) { + return null; + } + + return output; + } + + @Override + protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, + int outputOffset) throws ShortBufferException { + byte[] outputCopy = engineUpdate(input, inputOffset, inputLen); + if (outputCopy == null) { + return 0; + } + int outputAvailable = output.length - outputOffset; + if (outputCopy.length > outputAvailable) { + throw new ShortBufferException("Output buffer too short. Produced: " + + outputCopy.length + ", available: " + outputAvailable); + } + System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); + return outputCopy.length; + } + + @Override + protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) + throws IllegalBlockSizeException, BadPaddingException { + if (mCachedException != null) { + throw (IllegalBlockSizeException) + new IllegalBlockSizeException().initCause(mCachedException); + } + + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); + } + + byte[] output; + try { + output = mMainDataStreamer.doFinal(input, inputOffset, inputLen); + } catch (KeyStoreException e) { + switch (e.getErrorCode()) { + case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH: + throw new IllegalBlockSizeException(); + case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT: + throw new BadPaddingException(); + case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED: + throw new AEADBadTagException(); + default: + throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); + } + } + + resetWhilePreservingInitState(); + return output; + } + + @Override + protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, + int outputOffset) throws ShortBufferException, IllegalBlockSizeException, + BadPaddingException { + byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen); + if (outputCopy == null) { + return 0; + } + int outputAvailable = output.length - outputOffset; + if (outputCopy.length > outputAvailable) { + throw new ShortBufferException("Output buffer too short. Produced: " + + outputCopy.length + ", available: " + outputAvailable); + } + System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); + return outputCopy.length; + } + + @Override + protected int engineGetBlockSize() { + return mBlockSizeBytes; + } + + @Override + protected byte[] engineGetIV() { + return (mIv != null) ? mIv.clone() : null; + } + + @Override + protected int engineGetOutputSize(int inputLen) { + return inputLen + 3 * engineGetBlockSize(); + } + + @Override + protected void engineSetMode(String mode) throws NoSuchAlgorithmException { + // This should never be invoked because all algorithms registered with the AndroidKeyStore + // provide explicitly specify block mode. + throw new UnsupportedOperationException(); + } + + @Override + protected void engineSetPadding(String arg0) throws NoSuchPaddingException { + // This should never be invoked because all algorithms registered with the AndroidKeyStore + // provide explicitly specify padding mode. + throw new UnsupportedOperationException(); + } + + @Override + public void finalize() throws Throwable { + try { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mKeyStore.abort(operationToken); + } + } finally { + super.finalize(); + } + } + + @Override + public long getOperationHandle() { + return mOperationHandle; + } + + // The methods below may need to be overridden by subclasses that use algorithm-specific + // parameters. + + /** + * Returns algorithm-specific parameters used by this {@code CipherSpi} instance or {@code null} + * if no algorithm-specific parameters are used. + * + *

This implementation only handles the IV parameter. + */ + @Override + protected AlgorithmParameters engineGetParameters() { + if (!mIvRequired) { + return null; + } + if ((mIv != null) && (mIv.length > 0)) { + try { + AlgorithmParameters params = + AlgorithmParameters.getInstance(KeyProperties.KEY_ALGORITHM_AES); + params.init(new IvParameterSpec(mIv)); + return params; + } catch (NoSuchAlgorithmException e) { + throw new ProviderException("Failed to obtain AES AlgorithmParameters", e); + } catch (InvalidParameterSpecException e) { + throw new ProviderException( + "Failed to initialize AES AlgorithmParameters with an IV", e); + } + } + return null; + } + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters + * may need to be stored to be reused after {@code doFinal}. + * + *

The default implementation only handles the IV parameters. + * + * @param params algorithm parameters. + * + * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be + * automatically configured and thus {@code Cipher.init} needs to be invoked with + * explicitly provided parameters. + */ + protected void initAlgorithmSpecificParameters(AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + if (!mIvRequired) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!mEncrypting) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException( + "IvParameterSpec must be provided when decrypting"); + } + return; + } + if (!(params instanceof IvParameterSpec)) { + throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported"); + } + mIv = ((IvParameterSpec) params).getIV(); + if (mIv == null) { + throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec"); + } + } + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters + * may need to be stored to be reused after {@code doFinal}. + * + *

The default implementation only handles the IV parameters. + * + * @param params algorithm parameters. + * + * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be + * automatically configured and thus {@code Cipher.init} needs to be invoked with + * explicitly provided parameters. + */ + protected void initAlgorithmSpecificParameters(AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + if (!mIvRequired) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!mEncrypting) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + return; + } + + IvParameterSpec ivSpec; + try { + ivSpec = params.getParameterSpec(IvParameterSpec.class); + } catch (InvalidParameterSpecException e) { + if (!mEncrypting) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ", but not found in parameters: " + params, e); + } + mIv = null; + return; + } + mIv = ivSpec.getIV(); + if (mIv == null) { + throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters"); + } + } + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters + * may need to be stored to be reused after {@code doFinal}. + * + *

The default implementation only handles the IV parameter. + * + * @throws InvalidKeyException if some/all of the parameters cannot be automatically configured + * and thus {@code Cipher.init} needs to be invoked with explicitly provided parameters. + */ + protected void initAlgorithmSpecificParameters() throws InvalidKeyException { + if (!mIvRequired) { + return; + } + + // IV is used + if (!mEncrypting) { + throw new InvalidKeyException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + } + + /** + * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation. + * + *

The default implementation takes care of the IV. + * + * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific + * parameters. + */ + protected void addAlgorithmSpecificParametersToBegin(KeymasterArguments keymasterArgs) { + if (!mFirstOperationInitiated) { + // First begin operation -- see if we need to provide additional entropy for IV + // generation. + if (mIvRequired) { + // IV is needed + if ((mIv == null) && (mEncrypting)) { + // IV was not provided by the caller and thus will be generated by keymaster. + // Mix in some additional entropy from the provided SecureRandom. + mAdditionalEntropyForBegin = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, mBlockSizeBytes); + } + } + } + + if ((mIvRequired) && (mIv != null)) { + keymasterArgs.addBlob(KeymasterDefs.KM_TAG_NONCE, mIv); + } + } + + /** + * Invoked by {@code engineInit} to obtain algorithm-specific parameters from the result of the + * Keymaster's {@code begin} operation. Some of these parameters may need to be reused after + * {@code doFinal} by {@link #addAlgorithmSpecificParametersToBegin(KeymasterArguments)}. + * + *

The default implementation only takes care of the IV. + * + * @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin} + * operation. + */ + protected void loadAlgorithmSpecificParametersFromBeginResult( + KeymasterArguments keymasterArgs) { + // NOTE: Keymaster doesn't always return an IV, even if it's used. + byte[] returnedIv = keymasterArgs.getBlob(KeymasterDefs.KM_TAG_NONCE, null); + if ((returnedIv != null) && (returnedIv.length == 0)) { + returnedIv = null; + } + + if (mIvRequired) { + if (mIv == null) { + mIv = returnedIv; + } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) { + throw new ProviderException("IV in use differs from provided IV"); + } + } else { + if (returnedIv != null) { + throw new ProviderException( + "IV in use despite IV not being used by this transformation"); + } + } + } +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java new file mode 100644 index 0000000..b82a7f5 --- /dev/null +++ b/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2015 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.keystore; + +import android.os.IBinder; +import android.security.KeyStore; +import android.security.KeyStoreException; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keymaster.OperationResult; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.ProviderException; +import java.security.spec.AlgorithmParameterSpec; + +import javax.crypto.MacSpi; + +/** + * {@link MacSpi} which provides HMAC implementations backed by Android KeyStore. + * + * @hide + */ +public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation { + + public static class HmacSHA1 extends AndroidKeyStoreHmacSpi { + public HmacSHA1() { + super(KeymasterDefs.KM_DIGEST_SHA1); + } + } + + public static class HmacSHA224 extends AndroidKeyStoreHmacSpi { + public HmacSHA224() { + super(KeymasterDefs.KM_DIGEST_SHA_2_224); + } + } + + public static class HmacSHA256 extends AndroidKeyStoreHmacSpi { + public HmacSHA256() { + super(KeymasterDefs.KM_DIGEST_SHA_2_256); + } + } + + public static class HmacSHA384 extends AndroidKeyStoreHmacSpi { + public HmacSHA384() { + super(KeymasterDefs.KM_DIGEST_SHA_2_384); + } + } + + public static class HmacSHA512 extends AndroidKeyStoreHmacSpi { + public HmacSHA512() { + super(KeymasterDefs.KM_DIGEST_SHA_2_512); + } + } + + private final KeyStore mKeyStore = KeyStore.getInstance(); + private final int mKeymasterDigest; + private final int mMacSizeBits; + + // Fields below are populated by engineInit and should be preserved after engineDoFinal. + private AndroidKeyStoreSecretKey mKey; + + // Fields below are reset when engineDoFinal succeeds. + private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer; + private IBinder mOperationToken; + private long mOperationHandle; + + protected AndroidKeyStoreHmacSpi(int keymasterDigest) { + mKeymasterDigest = keymasterDigest; + mMacSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest); + } + + @Override + protected int engineGetMacLength() { + return (mMacSizeBits + 7) / 8; + } + + @Override + protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, + InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + init(key, params); + ensureKeystoreOperationInitialized(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + private void init(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, + InvalidAlgorithmParameterException { + if (key == null) { + throw new InvalidKeyException("key == null"); + } else if (!(key instanceof AndroidKeyStoreSecretKey)) { + throw new InvalidKeyException( + "Only Android KeyStore secret keys supported. Key: " + key); + } + mKey = (AndroidKeyStoreSecretKey) key; + + if (params != null) { + throw new InvalidAlgorithmParameterException( + "Unsupported algorithm parameters: " + params); + } + + } + + private void resetAll() { + mKey = null; + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mOperationToken = null; + mKeyStore.abort(operationToken); + } + mOperationHandle = 0; + mChunkedStreamer = null; + } + + private void resetWhilePreservingInitState() { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mOperationToken = null; + mKeyStore.abort(operationToken); + } + mOperationHandle = 0; + mChunkedStreamer = null; + } + + @Override + protected void engineReset() { + resetWhilePreservingInitState(); + } + + private void ensureKeystoreOperationInitialized() throws InvalidKeyException { + if (mChunkedStreamer != null) { + return; + } + if (mKey == null) { + throw new IllegalStateException("Not initialized"); + } + + KeymasterArguments keymasterArgs = new KeymasterArguments(); + keymasterArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC); + keymasterArgs.addInt(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); + keymasterArgs.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mMacSizeBits); + + KeymasterArguments keymasterOutputArgs = new KeymasterArguments(); + OperationResult opResult = mKeyStore.begin( + mKey.getAlias(), + KeymasterDefs.KM_PURPOSE_SIGN, + true, + keymasterArgs, + null, // no additional entropy needed for HMAC because it's deterministic + keymasterOutputArgs); + if (opResult == null) { + throw new KeyStoreConnectException(); + } + + // Store operation token and handle regardless of the error code returned by KeyStore to + // ensure that the operation gets aborted immediately if the code below throws an exception. + mOperationToken = opResult.token; + mOperationHandle = opResult.operationHandle; + + // If necessary, throw an exception due to KeyStore operation having failed. + InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit( + mKeyStore, mKey, opResult.resultCode); + if (e != null) { + throw e; + } + + if (mOperationToken == null) { + throw new ProviderException("Keystore returned null operation token"); + } + if (mOperationHandle == 0) { + throw new ProviderException("Keystore returned invalid operation handle"); + } + + mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer( + new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( + mKeyStore, mOperationToken)); + } + + @Override + protected void engineUpdate(byte input) { + engineUpdate(new byte[] {input}, 0, 1); + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException e) { + throw new ProviderException("Failed to reinitialize MAC", e); + } + + byte[] output; + try { + output = mChunkedStreamer.update(input, offset, len); + } catch (KeyStoreException e) { + throw new ProviderException("Keystore operation failed", e); + } + if ((output != null) && (output.length != 0)) { + throw new ProviderException("Update operation unexpectedly produced output"); + } + } + + @Override + protected byte[] engineDoFinal() { + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException e) { + throw new ProviderException("Failed to reinitialize MAC", e); + } + + byte[] result; + try { + result = mChunkedStreamer.doFinal(null, 0, 0); + } catch (KeyStoreException e) { + throw new ProviderException("Keystore operation failed", e); + } + + resetWhilePreservingInitState(); + return result; + } + + @Override + public void finalize() throws Throwable { + try { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mKeyStore.abort(operationToken); + } + } finally { + super.finalize(); + } + } + + @Override + public long getOperationHandle() { + return mOperationHandle; + } +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreKey.java new file mode 100644 index 0000000..6098e5c --- /dev/null +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKey.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 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.keystore; + +import java.security.Key; + +/** + * {@link Key} backed by AndroidKeyStore. + * + * @hide + */ +public class AndroidKeyStoreKey implements Key { + private final String mAlias; + private final String mAlgorithm; + + public AndroidKeyStoreKey(String alias, String algorithm) { + mAlias = alias; + mAlgorithm = algorithm; + } + + String getAlias() { + return mAlias; + } + + @Override + public String getAlgorithm() { + return mAlgorithm; + } + + @Override + public String getFormat() { + // This key does not export its key material + return null; + } + + @Override + public byte[] getEncoded() { + // This key does not export its key material + return null; + } +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java new file mode 100644 index 0000000..0821bf5 --- /dev/null +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2015 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.keystore; + +import android.security.Credentials; +import android.security.KeyStore; +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; + +import libcore.util.EmptyArray; + +import java.security.InvalidAlgorithmParameterException; +import java.security.ProviderException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; +import java.util.Date; + +import javax.crypto.KeyGeneratorSpi; +import javax.crypto.SecretKey; + +/** + * {@link KeyGeneratorSpi} backed by Android KeyStore. + * + * @hide + */ +public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { + + public static class AES extends AndroidKeyStoreKeyGeneratorSpi { + public AES() { + super(KeymasterDefs.KM_ALGORITHM_AES, 128); + } + + @Override + protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + super.engineInit(params, random); + if ((mKeySizeBits != 128) && (mKeySizeBits != 192) && (mKeySizeBits != 256)) { + throw new InvalidAlgorithmParameterException( + "Unsupported key size: " + mKeySizeBits + + ". Supported: 128, 192, 256."); + } + } + } + + protected static abstract class HmacBase extends AndroidKeyStoreKeyGeneratorSpi { + protected HmacBase(int keymasterDigest) { + super(KeymasterDefs.KM_ALGORITHM_HMAC, + keymasterDigest, + KeymasterUtils.getDigestOutputSizeBits(keymasterDigest)); + } + } + + public static class HmacSHA1 extends HmacBase { + public HmacSHA1() { + super(KeymasterDefs.KM_DIGEST_SHA1); + } + } + + public static class HmacSHA224 extends HmacBase { + public HmacSHA224() { + super(KeymasterDefs.KM_DIGEST_SHA_2_224); + } + } + + public static class HmacSHA256 extends HmacBase { + public HmacSHA256() { + super(KeymasterDefs.KM_DIGEST_SHA_2_256); + } + } + + public static class HmacSHA384 extends HmacBase { + public HmacSHA384() { + super(KeymasterDefs.KM_DIGEST_SHA_2_384); + } + } + + public static class HmacSHA512 extends HmacBase { + public HmacSHA512() { + super(KeymasterDefs.KM_DIGEST_SHA_2_512); + } + } + + private final KeyStore mKeyStore = KeyStore.getInstance(); + private final int mKeymasterAlgorithm; + private final int mKeymasterDigest; + private final int mDefaultKeySizeBits; + + private KeyGenParameterSpec mSpec; + private SecureRandom mRng; + + protected int mKeySizeBits; + private int[] mKeymasterPurposes; + private int[] mKeymasterBlockModes; + private int[] mKeymasterPaddings; + private int[] mKeymasterDigests; + + protected AndroidKeyStoreKeyGeneratorSpi( + int keymasterAlgorithm, + int defaultKeySizeBits) { + this(keymasterAlgorithm, -1, defaultKeySizeBits); + } + + protected AndroidKeyStoreKeyGeneratorSpi( + int keymasterAlgorithm, + int keymasterDigest, + int defaultKeySizeBits) { + mKeymasterAlgorithm = keymasterAlgorithm; + mKeymasterDigest = keymasterDigest; + mDefaultKeySizeBits = defaultKeySizeBits; + if (mDefaultKeySizeBits <= 0) { + throw new IllegalArgumentException("Default key size must be positive"); + } + + if ((mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) && (mKeymasterDigest == -1)) { + throw new IllegalArgumentException( + "Digest algorithm must be specified for HMAC key"); + } + } + + @Override + protected void engineInit(SecureRandom random) { + throw new UnsupportedOperationException("Cannot initialize without a " + + KeyGenParameterSpec.class.getName() + " parameter"); + } + + @Override + protected void engineInit(int keySize, SecureRandom random) { + throw new UnsupportedOperationException("Cannot initialize without a " + + KeyGenParameterSpec.class.getName() + " parameter"); + } + + @Override + protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + if ((params == null) || (!(params instanceof KeyGenParameterSpec))) { + throw new InvalidAlgorithmParameterException("Cannot initialize without a " + + KeyGenParameterSpec.class.getName() + " parameter"); + } + KeyGenParameterSpec spec = (KeyGenParameterSpec) params; + if (spec.getKeystoreAlias() == null) { + throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided"); + } + + mRng = random; + mSpec = spec; + + mKeySizeBits = (spec.getKeySize() != -1) ? spec.getKeySize() : mDefaultKeySizeBits; + if (mKeySizeBits <= 0) { + throw new InvalidAlgorithmParameterException( + "Key size must be positive: " + mKeySizeBits); + } else if ((mKeySizeBits % 8) != 0) { + throw new InvalidAlgorithmParameterException( + "Key size in must be a multiple of 8: " + mKeySizeBits); + } + + try { + mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes()); + mKeymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster( + spec.getEncryptionPaddings()); + mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()); + if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) + && (spec.isRandomizedEncryptionRequired())) { + for (int keymasterBlockMode : mKeymasterBlockModes) { + if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible( + keymasterBlockMode)) { + throw new InvalidAlgorithmParameterException( + "Randomized encryption (IND-CPA) required but may be violated" + + " by block mode: " + + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode) + + ". See " + KeyGenParameterSpec.class.getName() + + " documentation."); + } + } + } + if (spec.isDigestsSpecified()) { + // Digest(s) explicitly specified in the spec + mKeymasterDigests = KeyProperties.Digest.allToKeymaster(spec.getDigests()); + if (mKeymasterDigest != -1) { + // Key algorithm implies a digest -- ensure it's specified in the spec as + // first digest. + if (!com.android.internal.util.ArrayUtils.contains( + mKeymasterDigests, mKeymasterDigest)) { + throw new InvalidAlgorithmParameterException( + "Digests specified in algorithm parameters (" + + Arrays.asList(spec.getDigests()) + ") must include " + + " the digest " + + KeyProperties.Digest.fromKeymaster(mKeymasterDigest) + + " implied by key algorithm"); + } + if (mKeymasterDigests[0] != mKeymasterDigest) { + // The first digest is not the one implied by the key algorithm. + // Swap the implied digest with the first one. + for (int i = 0; i < mKeymasterDigests.length; i++) { + if (mKeymasterDigests[i] == mKeymasterDigest) { + mKeymasterDigests[i] = mKeymasterDigests[0]; + mKeymasterDigests[0] = mKeymasterDigest; + break; + } + } + } + } + } else { + // No digest specified in the spec + if (mKeymasterDigest != -1) { + // Key algorithm implies a digest -- use that digest + mKeymasterDigests = new int[] {mKeymasterDigest}; + } else { + mKeymasterDigests = EmptyArray.INT; + } + } + if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { + if (mKeymasterDigests.length == 0) { + throw new InvalidAlgorithmParameterException( + "At least one digest algorithm must be specified"); + } + } + } catch (IllegalStateException | IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException(e); + } + + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + private void resetAll() { + mSpec = null; + mRng = null; + mKeySizeBits = -1; + mKeymasterPurposes = null; + mKeymasterPaddings = null; + mKeymasterBlockModes = null; + } + + @Override + protected SecretKey engineGenerateKey() { + KeyGenParameterSpec spec = mSpec; + if (spec == null) { + throw new IllegalStateException("Not initialized"); + } + + if ((spec.isEncryptionAtRestRequired()) + && (mKeyStore.state() != KeyStore.State.UNLOCKED)) { + throw new IllegalStateException( + "Requested to import a key which must be encrypted at rest using secure lock" + + " screen credential, but the credential hasn't yet been entered by the user"); + } + + KeymasterArguments args = new KeymasterArguments(); + args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits); + args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); + args.addInts(KeymasterDefs.KM_TAG_PURPOSE, mKeymasterPurposes); + args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockModes); + args.addInts(KeymasterDefs.KM_TAG_PADDING, mKeymasterPaddings); + args.addInts(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests); + KeymasterUtils.addUserAuthArgs(args, + spec.isUserAuthenticationRequired(), + spec.getUserAuthenticationValidityDurationSeconds()); + args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, + (spec.getKeyValidityStart() != null) + ? spec.getKeyValidityStart() : new Date(0)); + args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + (spec.getKeyValidityForOriginationEnd() != null) + ? spec.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE)); + args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + (spec.getKeyValidityForConsumptionEnd() != null) + ? spec.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE)); + + if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) + && (!spec.isRandomizedEncryptionRequired())) { + // Permit caller-provided IV when encrypting with this key + args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); + } + + byte[] additionalEntropy = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, (mKeySizeBits + 7) / 8); + int flags = spec.getFlags(); + String keyAliasInKeystore = Credentials.USER_SECRET_KEY + spec.getKeystoreAlias(); + KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); + int errorCode = mKeyStore.generateKey( + keyAliasInKeystore, args, additionalEntropy, flags, resultingKeyCharacteristics); + if (errorCode != KeyStore.NO_ERROR) { + throw new ProviderException( + "Keystore operation failed", KeyStore.getKeyStoreException(errorCode)); + } + @KeyProperties.KeyAlgorithmEnum String keyAlgorithmJCA; + try { + keyAlgorithmJCA = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm( + mKeymasterAlgorithm, mKeymasterDigest); + } catch (IllegalArgumentException e) { + throw new ProviderException("Failed to obtain JCA secret key algorithm name", e); + } + return new AndroidKeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmJCA); + } +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java new file mode 100644 index 0000000..b20a122 --- /dev/null +++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java @@ -0,0 +1,134 @@ +/* + * 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.keystore; + +import android.security.KeyStore; + +import java.security.Provider; +import java.security.Security; + +import javax.crypto.Cipher; +import javax.crypto.Mac; + +/** + * A provider focused on providing JCA interfaces for the Android KeyStore. + * + * @hide + */ +public class AndroidKeyStoreProvider extends Provider { + public static final String PROVIDER_NAME = "AndroidKeyStore"; + + // IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these + // classes when this provider is instantiated and installed early on during each app's + // initialization process. + // + // Crypto operations operating on the AndroidKeyStore keys must not be offered by this provider. + // Instead, they need to be offered by AndroidKeyStoreBCWorkaroundProvider. See its Javadoc + // for details. + + private static final String PACKAGE_NAME = "android.security.keystore"; + + public AndroidKeyStoreProvider() { + super(PROVIDER_NAME, 1.0, "Android KeyStore security provider"); + + // java.security.KeyStore + put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStoreSpi"); + + // java.security.KeyPairGenerator + put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyPairGeneratorSpi$EC"); + put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyPairGeneratorSpi$RSA"); + + // javax.crypto.KeyGenerator + put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES"); + put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA1"); + put("KeyGenerator.HmacSHA224", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA224"); + put("KeyGenerator.HmacSHA256", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA256"); + put("KeyGenerator.HmacSHA384", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA384"); + put("KeyGenerator.HmacSHA512", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA512"); + + // java.security.SecretKeyFactory + putSecretKeyFactoryImpl("AES"); + putSecretKeyFactoryImpl("HmacSHA1"); + putSecretKeyFactoryImpl("HmacSHA224"); + putSecretKeyFactoryImpl("HmacSHA256"); + putSecretKeyFactoryImpl("HmacSHA384"); + putSecretKeyFactoryImpl("HmacSHA512"); + } + + /** + * Installs a new instance of this provider (and the + * {@link AndroidKeyStoreBCWorkaroundProvider}). + */ + public static void install() { + Provider[] providers = Security.getProviders(); + int bcProviderPosition = -1; + for (int position = 0; position < providers.length; position++) { + Provider provider = providers[position]; + if ("BC".equals(provider.getName())) { + bcProviderPosition = position; + break; + } + } + + Security.addProvider(new AndroidKeyStoreProvider()); + Provider workaroundProvider = new AndroidKeyStoreBCWorkaroundProvider(); + if (bcProviderPosition != -1) { + // Bouncy Castle provider found -- install the workaround provider above it. + Security.insertProviderAt(workaroundProvider, bcProviderPosition); + } else { + // Bouncy Castle provider not found -- install the workaround provider at lowest + // priority. + Security.addProvider(workaroundProvider); + } + } + + private void putSecretKeyFactoryImpl(String algorithm) { + put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreSecretKeyFactorySpi"); + } + + /** + * Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto + * primitive. + * + *

The following primitives are supported: {@link Cipher} and {@link Mac}. + * + * @return KeyStore operation handle or {@code 0} if the provided primitive's KeyStore operation + * is not in progress. + * + * @throws IllegalArgumentException if the provided primitive is not supported or is not backed + * by AndroidKeyStore provider. + */ + public static long getKeyStoreOperationHandle(Object cryptoPrimitive) { + if (cryptoPrimitive == null) { + throw new NullPointerException(); + } + Object spi; + if (cryptoPrimitive instanceof Mac) { + spi = ((Mac) cryptoPrimitive).getSpi(); + } else if (cryptoPrimitive instanceof Cipher) { + spi = ((Cipher) cryptoPrimitive).getSpi(); + } else { + throw new IllegalArgumentException("Unsupported crypto primitive: " + cryptoPrimitive); + } + if (!(spi instanceof KeyStoreCryptoOperation)) { + throw new IllegalArgumentException( + "Crypto primitive not backed by AndroidKeyStore: " + cryptoPrimitive + + ", spi: " + spi); + } + return ((KeyStoreCryptoOperation) spi).getOperationHandle(); + } +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.java new file mode 100644 index 0000000..f75516b --- /dev/null +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2015 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.keystore; + +import javax.crypto.SecretKey; + +/** + * {@link SecretKey} backed by keystore. + * + * @hide + */ +public class AndroidKeyStoreSecretKey extends AndroidKeyStoreKey implements SecretKey { + + public AndroidKeyStoreSecretKey(String alias, String algorithm) { + super(alias, algorithm); + } +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java new file mode 100644 index 0000000..455f170 --- /dev/null +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2015 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.keystore; + +import android.security.Credentials; +import android.security.KeyStore; +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterDefs; + +import libcore.util.EmptyArray; + +import java.security.InvalidKeyException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactorySpi; +import javax.crypto.spec.SecretKeySpec; + +/** + * {@link SecretKeyFactorySpi} backed by Android KeyStore. + * + * @hide + */ +public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { + + private final KeyStore mKeyStore = KeyStore.getInstance(); + + @Override + protected KeySpec engineGetKeySpec(SecretKey key, + @SuppressWarnings("rawtypes") Class keySpecClass) throws InvalidKeySpecException { + if (keySpecClass == null) { + throw new InvalidKeySpecException("keySpecClass == null"); + } + if (!(key instanceof AndroidKeyStoreSecretKey)) { + throw new InvalidKeySpecException("Only Android KeyStore secret keys supported: " + + ((key != null) ? key.getClass().getName() : "null")); + } + if (SecretKeySpec.class.isAssignableFrom(keySpecClass)) { + throw new InvalidKeySpecException( + "Key material export of Android KeyStore keys is not supported"); + } + if (!KeyInfo.class.equals(keySpecClass)) { + throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName()); + } + String keyAliasInKeystore = ((AndroidKeyStoreSecretKey) key).getAlias(); + String entryAlias; + if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) { + entryAlias = keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length()); + } else { + throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore); + } + + KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); + int errorCode = + mKeyStore.getKeyCharacteristics(keyAliasInKeystore, null, null, keyCharacteristics); + if (errorCode != KeyStore.NO_ERROR) { + throw new InvalidKeySpecException("Failed to obtain information about key." + + " Keystore error: " + errorCode); + } + + boolean insideSecureHardware; + @KeyProperties.OriginEnum int origin; + int keySize; + @KeyProperties.PurposeEnum int purposes; + String[] encryptionPaddings; + @KeyProperties.DigestEnum String[] digests; + @KeyProperties.BlockModeEnum String[] blockModes; + int keymasterSwEnforcedUserAuthenticators; + int keymasterHwEnforcedUserAuthenticators; + try { + if (keyCharacteristics.hwEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) { + insideSecureHardware = true; + origin = KeyProperties.Origin.fromKeymaster( + keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1)); + } else if (keyCharacteristics.swEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) { + insideSecureHardware = false; + origin = KeyProperties.Origin.fromKeymaster( + keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1)); + } else { + throw new InvalidKeySpecException("Key origin not available"); + } + Integer keySizeInteger = keyCharacteristics.getInteger(KeymasterDefs.KM_TAG_KEY_SIZE); + if (keySizeInteger == null) { + throw new InvalidKeySpecException("Key size not available"); + } + keySize = keySizeInteger; + purposes = KeyProperties.Purpose.allFromKeymaster( + keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PURPOSE)); + + List encryptionPaddingsList = new ArrayList(); + for (int keymasterPadding : keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PADDING)) { + @KeyProperties.EncryptionPaddingEnum String jcaPadding; + try { + jcaPadding = KeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding); + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException( + "Unsupported encryption padding: " + keymasterPadding); + } + encryptionPaddingsList.add(jcaPadding); + } + encryptionPaddings = + encryptionPaddingsList.toArray(new String[encryptionPaddingsList.size()]); + + digests = KeyProperties.Digest.allFromKeymaster( + keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST)); + blockModes = KeyProperties.BlockMode.allFromKeymaster( + keyCharacteristics.getInts(KeymasterDefs.KM_TAG_BLOCK_MODE)); + keymasterSwEnforcedUserAuthenticators = + keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); + keymasterHwEnforcedUserAuthenticators = + keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException("Unsupported key characteristic", e); + } + + Date keyValidityStart = keyCharacteristics.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME); + if ((keyValidityStart != null) && (keyValidityStart.getTime() <= 0)) { + keyValidityStart = null; + } + Date keyValidityForOriginationEnd = + keyCharacteristics.getDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME); + if ((keyValidityForOriginationEnd != null) + && (keyValidityForOriginationEnd.getTime() == Long.MAX_VALUE)) { + keyValidityForOriginationEnd = null; + } + Date keyValidityForConsumptionEnd = + keyCharacteristics.getDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME); + if ((keyValidityForConsumptionEnd != null) + && (keyValidityForConsumptionEnd.getTime() == Long.MAX_VALUE)) { + keyValidityForConsumptionEnd = null; + } + boolean userAuthenticationRequired = + !keyCharacteristics.getBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); + int userAuthenticationValidityDurationSeconds = + keyCharacteristics.getInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT, -1); + boolean userAuthenticationRequirementEnforcedBySecureHardware = (userAuthenticationRequired) + && (keymasterHwEnforcedUserAuthenticators != 0) + && (keymasterSwEnforcedUserAuthenticators == 0); + + return new KeyInfo(entryAlias, + insideSecureHardware, + origin, + keySize, + keyValidityStart, + keyValidityForOriginationEnd, + keyValidityForConsumptionEnd, + purposes, + encryptionPaddings, + EmptyArray.STRING, // no signature paddings -- this is symmetric crypto + digests, + blockModes, + userAuthenticationRequired, + userAuthenticationValidityDurationSeconds, + userAuthenticationRequirementEnforcedBySecureHardware); + } + + @Override + protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException { + throw new UnsupportedOperationException( + "Key import into Android KeyStore is not supported"); + } + + @Override + protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException { + throw new UnsupportedOperationException( + "Key import into Android KeyStore is not supported"); + } +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java new file mode 100644 index 0000000..d6145a3 --- /dev/null +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java @@ -0,0 +1,877 @@ +/* + * 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.keystore; + +import com.android.org.conscrypt.OpenSSLEngine; +import com.android.org.conscrypt.OpenSSLKeyHolder; + +import libcore.util.EmptyArray; + +import android.security.Credentials; +import android.security.KeyStoreParameter; +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyProperties; +import android.security.keystore.KeyProtection; +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +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.KeyStore.SecretKeyEntry; +import java.security.KeyStoreException; +import java.security.KeyStoreSpi; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.crypto.SecretKey; + +/** + * 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. + *

+ * This is built on top of Android's keystore daemon. The convention of alias + * use is: + *

+ * PrivateKeyEntry will have a Credentials.USER_PRIVATE_KEY as the private key, + * Credentials.USER_CERTIFICATE as the first certificate in the chain (the one + * that corresponds to the private key), and then a Credentials.CA_CERTIFICATE + * entry which will have the rest of the chain concatenated in BER format. + *

+ * TrustedCertificateEntry will just have a Credentials.CA_CERTIFICATE entry + * with a single certificate. + * + * @hide + */ +public class AndroidKeyStoreSpi extends KeyStoreSpi { + public static final String NAME = "AndroidKeyStore"; + + private android.security.KeyStore mKeyStore; + + @Override + public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, + UnrecoverableKeyException { + if (isPrivateKeyEntry(alias)) { + final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore"); + try { + return engine.getPrivateKeyById(Credentials.USER_PRIVATE_KEY + alias); + } catch (InvalidKeyException e) { + UnrecoverableKeyException t = new UnrecoverableKeyException("Can't get key"); + t.initCause(e); + throw t; + } + } else if (isSecretKeyEntry(alias)) { + KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); + String keyAliasInKeystore = Credentials.USER_SECRET_KEY + alias; + int errorCode = mKeyStore.getKeyCharacteristics( + keyAliasInKeystore, null, null, keyCharacteristics); + if ((errorCode != KeymasterDefs.KM_ERROR_OK) + && (errorCode != android.security.KeyStore.NO_ERROR)) { + throw (UnrecoverableKeyException) + new UnrecoverableKeyException("Failed to load information about key") + .initCause(mKeyStore.getInvalidKeyException(alias, errorCode)); + } + + int keymasterAlgorithm = + keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1); + if (keymasterAlgorithm == -1) { + keymasterAlgorithm = + keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1); + } + if (keymasterAlgorithm == -1) { + throw new UnrecoverableKeyException("Key algorithm unknown"); + } + + List keymasterDigests = + keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST); + int keymasterDigest; + if (keymasterDigests.isEmpty()) { + keymasterDigest = -1; + } else { + // More than one digest can be permitted for this key. Use the first one to form the + // JCA key algorithm name. + keymasterDigest = keymasterDigests.get(0); + } + + @KeyProperties.KeyAlgorithmEnum String keyAlgorithmString; + try { + keyAlgorithmString = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm( + keymasterAlgorithm, keymasterDigest); + } catch (IllegalArgumentException e) { + throw (UnrecoverableKeyException) + new UnrecoverableKeyException("Unsupported secret key type").initCause(e); + } + + return new AndroidKeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmString); + } + + return null; + } + + @Override + public Certificate[] engineGetCertificateChain(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + + final X509Certificate leaf = (X509Certificate) engineGetCertificate(alias); + if (leaf == null) { + return null; + } + + final Certificate[] caList; + + final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); + if (caBytes != null) { + final Collection caChain = toCertificates(caBytes); + + caList = new Certificate[caChain.size() + 1]; + + final Iterator it = caChain.iterator(); + int i = 1; + while (it.hasNext()) { + caList[i++] = it.next(); + } + } else { + caList = new Certificate[1]; + } + + caList[0] = leaf; + + return caList; + } + + @Override + public Certificate engineGetCertificate(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + + byte[] certificate = mKeyStore.get(Credentials.USER_CERTIFICATE + alias); + if (certificate != null) { + return toCertificate(certificate); + } + + certificate = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); + if (certificate != null) { + return toCertificate(certificate); + } + + return null; + } + + private static X509Certificate toCertificate(byte[] bytes) { + try { + final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + return (X509Certificate) certFactory + .generateCertificate(new ByteArrayInputStream(bytes)); + } catch (CertificateException e) { + Log.w(NAME, "Couldn't parse certificate in keystore", e); + return null; + } + } + + @SuppressWarnings("unchecked") + private static Collection toCertificates(byte[] bytes) { + try { + final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + return (Collection) certFactory + .generateCertificates(new ByteArrayInputStream(bytes)); + } catch (CertificateException e) { + Log.w(NAME, "Couldn't parse certificates in keystore", e); + return new ArrayList(); + } + } + + private Date getModificationDate(String alias) { + final long epochMillis = mKeyStore.getmtime(alias); + if (epochMillis == -1L) { + return null; + } + + return new Date(epochMillis); + } + + @Override + public Date engineGetCreationDate(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + + Date d = getModificationDate(Credentials.USER_PRIVATE_KEY + alias); + if (d != null) { + return d; + } + + d = getModificationDate(Credentials.USER_SECRET_KEY + alias); + if (d != null) { + return d; + } + + d = getModificationDate(Credentials.USER_CERTIFICATE + alias); + if (d != null) { + return d; + } + + return getModificationDate(Credentials.CA_CERTIFICATE + alias); + } + + @Override + public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) + throws KeyStoreException { + if ((password != null) && (password.length > 0)) { + throw new KeyStoreException("entries cannot be protected with passwords"); + } + + if (key instanceof PrivateKey) { + setPrivateKeyEntry(alias, (PrivateKey) key, chain, null); + } else if (key instanceof SecretKey) { + setSecretKeyEntry(alias, (SecretKey) key, null); + } else { + throw new KeyStoreException("Only PrivateKey and SecretKey are supported"); + } + } + + private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain, + java.security.KeyStore.ProtectionParameter param) throws KeyStoreException { + KeyProtection spec; + if (param instanceof KeyStoreParameter) { + KeyStoreParameter legacySpec = (KeyStoreParameter) param; + try { + String keyAlgorithm = key.getAlgorithm(); + KeyProtection.Builder specBuilder; + if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { + specBuilder = + new KeyProtection.Builder( + KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY); + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_MD5, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { + specBuilder = + new KeyProtection.Builder( + KeyProperties.PURPOSE_ENCRYPT + | KeyProperties.PURPOSE_DECRYPT + | KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_MD5, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + specBuilder.setSignaturePaddings( + KeyProperties.SIGNATURE_PADDING_RSA_PKCS1); + specBuilder.setBlockModes(KeyProperties.BLOCK_MODE_ECB); + specBuilder.setEncryptionPaddings( + KeyProperties.ENCRYPTION_PADDING_NONE, + KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1); + // Disable randomized encryption requirement to support encryption padding NONE + // above. + specBuilder.setRandomizedEncryptionRequired(false); + } else { + throw new KeyStoreException("Unsupported key algorithm: " + keyAlgorithm); + } + specBuilder.setEncryptionAtRestRequired(legacySpec.isEncryptionRequired()); + specBuilder.setUserAuthenticationRequired(false); + + spec = specBuilder.build(); + } catch (NullPointerException | IllegalArgumentException e) { + throw new KeyStoreException("Unsupported protection parameter", e); + } + } else if (param instanceof KeyProtection) { + spec = (KeyProtection) param; + } else if (param != null) { + throw new KeyStoreException( + "Unsupported protection parameter class:" + param.getClass().getName() + + ". Supported: " + KeyStoreParameter.class.getName() + ", " + + KeyProtection.class.getName()); + } else { + spec = null; + } + + byte[] keyBytes = null; + + final String pkeyAlias; + if (key instanceof OpenSSLKeyHolder) { + pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias(); + } else { + pkeyAlias = null; + } + + final boolean shouldReplacePrivateKey; + if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) { + final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length()); + if (!alias.equals(keySubalias)) { + throw new KeyStoreException("Can only replace keys with same alias: " + alias + + " != " + keySubalias); + } + + shouldReplacePrivateKey = false; + } else { + // Make sure the PrivateKey format is the one we support. + final String keyFormat = key.getFormat(); + if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) { + throw new KeyStoreException( + "Only PrivateKeys that can be encoded into PKCS#8 are supported"); + } + + // Make sure we can actually encode the key. + keyBytes = key.getEncoded(); + if (keyBytes == null) { + throw new KeyStoreException("PrivateKey has no encoding"); + } + + shouldReplacePrivateKey = true; + } + + // Make sure the chain exists since this is a PrivateKey + if ((chain == null) || (chain.length == 0)) { + throw new KeyStoreException("Must supply at least one Certificate with PrivateKey"); + } + + // Do chain type checking. + X509Certificate[] x509chain = new X509Certificate[chain.length]; + for (int i = 0; i < chain.length; i++) { + if (!"X.509".equals(chain[i].getType())) { + throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #" + + i); + } + + if (!(chain[i] instanceof X509Certificate)) { + throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #" + + i); + } + + x509chain[i] = (X509Certificate) chain[i]; + } + + final byte[] userCertBytes; + try { + userCertBytes = x509chain[0].getEncoded(); + } catch (CertificateEncodingException e) { + throw new KeyStoreException("Couldn't encode certificate #1", e); + } + + /* + * If we have a chain, store it in the CA certificate slot for this + * alias as concatenated DER-encoded certificates. These can be + * deserialized by {@link CertificateFactory#generateCertificates}. + */ + final byte[] chainBytes; + if (chain.length > 1) { + /* + * The chain is passed in as {user_cert, ca_cert_1, ca_cert_2, ...} + * so we only need the certificates starting at index 1. + */ + final byte[][] certsBytes = new byte[x509chain.length - 1][]; + int totalCertLength = 0; + for (int i = 0; i < certsBytes.length; i++) { + try { + certsBytes[i] = x509chain[i + 1].getEncoded(); + totalCertLength += certsBytes[i].length; + } catch (CertificateEncodingException e) { + throw new KeyStoreException("Can't encode Certificate #" + i, e); + } + } + + /* + * Serialize this into one byte array so we can later call + * CertificateFactory#generateCertificates to recover them. + */ + chainBytes = new byte[totalCertLength]; + int outputOffset = 0; + for (int i = 0; i < certsBytes.length; i++) { + final int certLength = certsBytes[i].length; + System.arraycopy(certsBytes[i], 0, chainBytes, outputOffset, certLength); + outputOffset += certLength; + certsBytes[i] = null; + } + } else { + chainBytes = null; + } + + /* + * Make sure we clear out all the appropriate types before trying to + * write. + */ + if (shouldReplacePrivateKey) { + Credentials.deleteAllTypesForAlias(mKeyStore, alias); + } else { + Credentials.deleteCertificateTypesForAlias(mKeyStore, alias); + Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias); + } + + final int flags = (spec == null) ? 0 : spec.getFlags(); + + if (shouldReplacePrivateKey + && !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, + 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, + android.security.KeyStore.UID_SELF, flags)) { + Credentials.deleteAllTypesForAlias(mKeyStore, alias); + throw new KeyStoreException("Couldn't put certificate chain in keystore"); + } + } + + private void setSecretKeyEntry(String entryAlias, SecretKey key, + java.security.KeyStore.ProtectionParameter param) + throws KeyStoreException { + if ((param != null) && (!(param instanceof KeyProtection))) { + throw new KeyStoreException( + "Unsupported protection parameter class: " + param.getClass().getName() + + ". Supported: " + KeyProtection.class.getName()); + } + KeyProtection params = (KeyProtection) param; + + if (key instanceof AndroidKeyStoreSecretKey) { + // KeyStore-backed secret key. It cannot be duplicated into another entry and cannot + // overwrite its own entry. + String keyAliasInKeystore = ((AndroidKeyStoreSecretKey) key).getAlias(); + if (keyAliasInKeystore == null) { + throw new KeyStoreException("KeyStore-backed secret key does not have an alias"); + } + if (!keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) { + throw new KeyStoreException("KeyStore-backed secret key has invalid alias: " + + keyAliasInKeystore); + } + String keyEntryAlias = + keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length()); + if (!entryAlias.equals(keyEntryAlias)) { + throw new KeyStoreException("Can only replace KeyStore-backed keys with same" + + " alias: " + entryAlias + " != " + keyEntryAlias); + } + // This is the entry where this key is already stored. No need to do anything. + if (params != null) { + throw new KeyStoreException("Modifying KeyStore-backed key using protection" + + " parameters not supported"); + } + return; + } + + if (params == null) { + throw new KeyStoreException( + "Protection parameters must be specified when importing a symmetric key"); + } + + // Not a KeyStore-backed secret key -- import its key material into keystore. + String keyExportFormat = key.getFormat(); + if (keyExportFormat == null) { + throw new KeyStoreException( + "Only secret keys that export their key material are supported"); + } else if (!"RAW".equals(keyExportFormat)) { + throw new KeyStoreException( + "Unsupported secret key material export format: " + keyExportFormat); + } + byte[] keyMaterial = key.getEncoded(); + if (keyMaterial == null) { + throw new KeyStoreException("Key did not export its key material despite supporting" + + " RAW format export"); + } + + String keyAlgorithmString = key.getAlgorithm(); + int keymasterAlgorithm; + int keymasterDigest; + try { + keymasterAlgorithm = + KeyProperties.KeyAlgorithm.toKeymasterSecretKeyAlgorithm(keyAlgorithmString); + keymasterDigest = KeyProperties.KeyAlgorithm.toKeymasterDigest(keyAlgorithmString); + } catch (IllegalArgumentException e) { + throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString); + } + + KeymasterArguments args = new KeymasterArguments(); + args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, keymasterAlgorithm); + + int[] keymasterDigests; + if (params.isDigestsSpecified()) { + // Digest(s) specified in parameters + keymasterDigests = KeyProperties.Digest.allToKeymaster(params.getDigests()); + if (keymasterDigest != -1) { + // Digest also specified in the JCA key algorithm name. + if (!com.android.internal.util.ArrayUtils.contains( + keymasterDigests, keymasterDigest)) { + throw new KeyStoreException("Key digest mismatch" + + ". Key: " + keyAlgorithmString + + ", parameter spec: " + Arrays.asList(params.getDigests())); + } + // When the key is read back from keystore we reconstruct the JCA key algorithm + // name from the KM_TAG_ALGORITHM and the first KM_TAG_DIGEST. Thus we need to + // ensure that the digest reflected in the JCA key algorithm name is the first + // KM_TAG_DIGEST tag. + if (keymasterDigests[0] != keymasterDigest) { + // The first digest is not the one implied by the JCA key algorithm name. + // Swap the implied digest with the first one. + for (int i = 0; i < keymasterDigests.length; i++) { + if (keymasterDigests[i] == keymasterDigest) { + keymasterDigests[i] = keymasterDigests[0]; + keymasterDigests[0] = keymasterDigest; + break; + } + } + } + } + } else { + // No digest specified in parameters + if (keymasterDigest != -1) { + // Digest specified in the JCA key algorithm name. + keymasterDigests = new int[] {keymasterDigest}; + } else { + keymasterDigests = EmptyArray.INT; + } + } + args.addInts(KeymasterDefs.KM_TAG_DIGEST, keymasterDigests); + if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { + if (keymasterDigests.length == 0) { + throw new KeyStoreException("At least one digest algorithm must be specified" + + " for key algorithm " + keyAlgorithmString); + } + } + + @KeyProperties.PurposeEnum int purposes = params.getPurposes(); + int[] keymasterBlockModes = + KeyProperties.BlockMode.allToKeymaster(params.getBlockModes()); + if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) + && (params.isRandomizedEncryptionRequired())) { + for (int keymasterBlockMode : keymasterBlockModes) { + if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible(keymasterBlockMode)) { + throw new KeyStoreException( + "Randomized encryption (IND-CPA) required but may be violated by block" + + " mode: " + + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode) + + ". See KeyProtection documentation."); + } + } + } + for (int keymasterPurpose : KeyProperties.Purpose.allToKeymaster(purposes)) { + args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose); + } + args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes); + if (params.getSignaturePaddings().length > 0) { + throw new KeyStoreException("Signature paddings not supported for symmetric keys"); + } + int[] keymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster( + params.getEncryptionPaddings()); + args.addInts(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings); + KeymasterUtils.addUserAuthArgs(args, + params.isUserAuthenticationRequired(), + params.getUserAuthenticationValidityDurationSeconds()); + args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, + (params.getKeyValidityStart() != null) + ? params.getKeyValidityStart() : new Date(0)); + args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + (params.getKeyValidityForOriginationEnd() != null) + ? params.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE)); + args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + (params.getKeyValidityForConsumptionEnd() != null) + ? params.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE)); + + // TODO: Remove this once keymaster does not require us to specify the size of imported key. + args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keyMaterial.length * 8); + + if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) + && (!params.isRandomizedEncryptionRequired())) { + // Permit caller-provided IV when encrypting with this key + args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); + } + + Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias); + String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias; + int errorCode = mKeyStore.importKey( + keyAliasInKeystore, + args, + KeymasterDefs.KM_KEY_FORMAT_RAW, + keyMaterial, + params.getFlags(), + new KeyCharacteristics()); + if (errorCode != android.security.KeyStore.NO_ERROR) { + throw new KeyStoreException("Failed to import secret key. Keystore error code: " + + errorCode); + } + } + + @Override + public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain) + throws KeyStoreException { + throw new KeyStoreException("Operation not supported because key encoding is unknown"); + } + + @Override + public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException { + if (isKeyEntry(alias)) { + throw new KeyStoreException("Entry exists and is not a trusted certificate"); + } + + // We can't set something to null. + if (cert == null) { + throw new NullPointerException("cert == null"); + } + + final byte[] encoded; + try { + encoded = cert.getEncoded(); + } catch (CertificateEncodingException e) { + throw new KeyStoreException(e); + } + + 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?"); + } + } + + @Override + public void engineDeleteEntry(String alias) throws KeyStoreException { + if (!isKeyEntry(alias) && !isCertificateEntry(alias)) { + return; + } + + if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) { + throw new KeyStoreException("No such entry " + alias); + } + } + + private Set getUniqueAliases() { + final String[] rawAliases = mKeyStore.saw(""); + if (rawAliases == null) { + return new HashSet(); + } + + final Set aliases = new HashSet(rawAliases.length); + for (String alias : rawAliases) { + final int idx = alias.indexOf('_'); + if ((idx == -1) || (alias.length() <= idx)) { + Log.e(NAME, "invalid alias: " + alias); + continue; + } + + aliases.add(new String(alias.substring(idx + 1))); + } + + return aliases; + } + + @Override + public Enumeration engineAliases() { + return Collections.enumeration(getUniqueAliases()); + } + + @Override + public boolean engineContainsAlias(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + + return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias) + || mKeyStore.contains(Credentials.USER_SECRET_KEY + alias) + || mKeyStore.contains(Credentials.USER_CERTIFICATE + alias) + || mKeyStore.contains(Credentials.CA_CERTIFICATE + alias); + } + + @Override + public int engineSize() { + return getUniqueAliases().size(); + } + + @Override + public boolean engineIsKeyEntry(String alias) { + return isKeyEntry(alias); + } + + private boolean isKeyEntry(String alias) { + return isPrivateKeyEntry(alias) || isSecretKeyEntry(alias); + } + + private boolean isPrivateKeyEntry(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + + return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias); + } + + private boolean isSecretKeyEntry(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + + return mKeyStore.contains(Credentials.USER_SECRET_KEY + alias); + } + + private boolean isCertificateEntry(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + + return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias); + } + + @Override + public boolean engineIsCertificateEntry(String alias) { + return !isKeyEntry(alias) && isCertificateEntry(alias); + } + + @Override + public String engineGetCertificateAlias(Certificate cert) { + if (cert == null) { + return null; + } + + final Set nonCaEntries = new HashSet(); + + /* + * First scan the PrivateKeyEntry types. The KeyStoreSpi documentation + * says to only compare the first certificate in the chain which is + * equivalent to the USER_CERTIFICATE prefix for the Android keystore + * convention. + */ + final String[] certAliases = mKeyStore.saw(Credentials.USER_CERTIFICATE); + if (certAliases != null) { + for (String alias : certAliases) { + final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias); + if (certBytes == null) { + continue; + } + + final Certificate c = toCertificate(certBytes); + nonCaEntries.add(alias); + + if (cert.equals(c)) { + return alias; + } + } + } + + /* + * Look at all the TrustedCertificateEntry types. Skip all the + * PrivateKeyEntry we looked at above. + */ + final String[] caAliases = mKeyStore.saw(Credentials.CA_CERTIFICATE); + if (certAliases != null) { + for (String alias : caAliases) { + if (nonCaEntries.contains(alias)) { + continue; + } + + final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); + if (certBytes == null) { + continue; + } + + final Certificate c = + toCertificate(mKeyStore.get(Credentials.CA_CERTIFICATE + alias)); + if (cert.equals(c)) { + return alias; + } + } + } + + return null; + } + + @Override + public void engineStore(OutputStream stream, char[] password) throws IOException, + NoSuchAlgorithmException, CertificateException { + throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream"); + } + + @Override + public void engineLoad(InputStream stream, char[] password) throws IOException, + NoSuchAlgorithmException, CertificateException { + if (stream != null) { + throw new IllegalArgumentException("InputStream not supported"); + } + + if (password != null) { + throw new IllegalArgumentException("password not supported"); + } + + // Unfortunate name collision. + 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 (entry instanceof PrivateKeyEntry) { + PrivateKeyEntry prE = (PrivateKeyEntry) entry; + setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(), param); + } else if (entry instanceof SecretKeyEntry) { + SecretKeyEntry secE = (SecretKeyEntry) entry; + setSecretKeyEntry(alias, secE.getSecretKey(), param); + } else { + throw new KeyStoreException( + "Entry must be a PrivateKeyEntry, SecretKeyEntry or TrustedCertificateEntry" + + "; was " + entry); + } + } + +} diff --git a/keystore/java/android/security/keystore/ArrayUtils.java b/keystore/java/android/security/keystore/ArrayUtils.java new file mode 100644 index 0000000..81be384 --- /dev/null +++ b/keystore/java/android/security/keystore/ArrayUtils.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015 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.keystore; + +import libcore.util.EmptyArray; + +/** + * @hide + */ +public abstract class ArrayUtils { + private ArrayUtils() {} + + public static String[] nullToEmpty(String[] array) { + return (array != null) ? array : EmptyArray.STRING; + } + + public static String[] cloneIfNotEmpty(String[] array) { + return ((array != null) && (array.length > 0)) ? array.clone() : array; + } + + public static byte[] concat(byte[] arr1, byte[] arr2) { + return concat(arr1, 0, (arr1 != null) ? arr1.length : 0, + arr2, 0, (arr2 != null) ? arr2.length : 0); + } + + public static byte[] concat(byte[] arr1, int offset1, int len1, byte[] arr2, int offset2, + int len2) { + if (len1 == 0) { + return subarray(arr2, offset2, len2); + } else if (len2 == 0) { + return subarray(arr1, offset1, len1); + } else { + byte[] result = new byte[len1 + len2]; + System.arraycopy(arr1, offset1, result, 0, len1); + System.arraycopy(arr2, offset2, result, len1, len2); + return result; + } + } + + public static byte[] subarray(byte[] arr, int offset, int len) { + if (len == 0) { + return EmptyArray.BYTE; + } + if ((offset == 0) && (len == arr.length)) { + return arr; + } + byte[] result = new byte[len]; + System.arraycopy(arr, offset, result, 0, len); + return result; + } + + public static int[] concat(int[] arr1, int[] arr2) { + if ((arr1 == null) || (arr1.length == 0)) { + return arr2; + } else if ((arr2 == null) || (arr2.length == 0)) { + return arr1; + } else { + int[] result = new int[arr1.length + arr2.length]; + System.arraycopy(arr1, 0, result, 0, arr1.length); + System.arraycopy(arr2, 0, result, arr1.length, arr2.length); + return result; + } + } +} diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index fa3b1cb..f598482 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -21,7 +21,6 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.text.TextUtils; -import android.security.ArrayUtils; import android.security.KeyStore; import java.math.BigInteger; diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java index aec2512..e4f921e 100644 --- a/keystore/java/android/security/keystore/KeyInfo.java +++ b/keystore/java/android/security/keystore/KeyInfo.java @@ -18,7 +18,6 @@ package android.security.keystore; import android.annotation.NonNull; import android.annotation.Nullable; -import android.security.ArrayUtils; import java.security.PrivateKey; import java.security.spec.KeySpec; diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index 113159d..48fdd98 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -20,8 +20,6 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.KeyguardManager; -import android.content.Context; -import android.security.ArrayUtils; import android.security.KeyStore; import java.security.Key; diff --git a/keystore/java/android/security/keystore/KeyStoreConnectException.java b/keystore/java/android/security/keystore/KeyStoreConnectException.java new file mode 100644 index 0000000..e008976 --- /dev/null +++ b/keystore/java/android/security/keystore/KeyStoreConnectException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 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.keystore; + +import java.security.ProviderException; + +/** + * Indicates a communications error with keystore service. + * + * @hide + */ +public class KeyStoreConnectException extends ProviderException { + public KeyStoreConnectException() { + super("Failed to communicate with keystore service"); + } +} diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java new file mode 100644 index 0000000..2c709ae --- /dev/null +++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2015 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.keystore; + +import android.security.KeyStore; + +/** + * Cryptographic operation backed by {@link KeyStore}. + * + * @hide + */ +public interface KeyStoreCryptoOperation { + /** + * Gets the KeyStore operation handle of this crypto operation. + * + * @return handle or {@code 0} if the KeyStore operation is not in progress. + */ + long getOperationHandle(); +} diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java new file mode 100644 index 0000000..7d57e5f --- /dev/null +++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2015 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.keystore; + +import android.os.IBinder; +import android.security.KeyStore; +import android.security.KeyStoreException; +import android.security.keymaster.OperationResult; + +import libcore.util.EmptyArray; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's + * {@code update} and {@code finish} operations. + * + *

The helper abstracts away to issues that need to be solved in most code that uses KeyStore's + * update and finish operations. Firstly, KeyStore's update operation can consume only a limited + * amount of data in one go because the operations are marshalled via Binder. Secondly, the update + * operation may consume less data than provided, in which case the caller has to buffer the + * remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and + * {@link #doFinal(byte[], int, int) doFinal} operations which can be used to conveniently implement + * various JCA crypto primitives. + * + *

Bidirectional chunked streaming of data via a KeyStore crypto operation is abstracted away as + * a {@link Stream} to avoid having this class deal with operation tokens and occasional additional + * parameters to {@code update} and {@code final} operations. + * + * @hide + */ +public class KeyStoreCryptoOperationChunkedStreamer { + + /** + * Bidirectional chunked data stream over a KeyStore crypto operation. + */ + public interface Stream { + /** + * Returns the result of the KeyStore {@code update} operation or null if keystore couldn't + * be reached. + */ + OperationResult update(byte[] input); + + /** + * Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't + * be reached. + */ + OperationResult finish(); + } + + // Binder buffer is about 1MB, but it's shared between all active transactions of the process. + // Thus, it's safer to use a much smaller upper bound. + private static final int DEFAULT_MAX_CHUNK_SIZE = 64 * 1024; + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + private final Stream mKeyStoreStream; + private final int mMaxChunkSize; + + private byte[] mBuffered = EMPTY_BYTE_ARRAY; + private int mBufferedOffset; + private int mBufferedLength; + + public KeyStoreCryptoOperationChunkedStreamer(Stream operation) { + this(operation, DEFAULT_MAX_CHUNK_SIZE); + } + + public KeyStoreCryptoOperationChunkedStreamer(Stream operation, int maxChunkSize) { + mKeyStoreStream = operation; + mMaxChunkSize = maxChunkSize; + } + + public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException { + if (inputLength == 0) { + // No input provided + return EMPTY_BYTE_ARRAY; + } + + ByteArrayOutputStream bufferedOutput = null; + + while (inputLength > 0) { + byte[] chunk; + int inputBytesInChunk; + if ((mBufferedLength + inputLength) > mMaxChunkSize) { + // Too much input for one chunk -- extract one max-sized chunk and feed it into the + // update operation. + inputBytesInChunk = mMaxChunkSize - mBufferedLength; + chunk = ArrayUtils.concat(mBuffered, mBufferedOffset, mBufferedLength, + input, inputOffset, inputBytesInChunk); + } else { + // All of available input fits into one chunk. + if ((mBufferedLength == 0) && (inputOffset == 0) + && (inputLength == input.length)) { + // Nothing buffered and all of input array needs to be fed into the update + // operation. + chunk = input; + inputBytesInChunk = input.length; + } else { + // Need to combine buffered data with input data into one array. + inputBytesInChunk = inputLength; + chunk = ArrayUtils.concat(mBuffered, mBufferedOffset, mBufferedLength, + input, inputOffset, inputBytesInChunk); + } + } + // Update input array references to reflect that some of its bytes are now in mBuffered. + inputOffset += inputBytesInChunk; + inputLength -= inputBytesInChunk; + + OperationResult opResult = mKeyStoreStream.update(chunk); + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeyStore.getKeyStoreException(opResult.resultCode); + } + + if (opResult.inputConsumed == chunk.length) { + // The whole chunk was consumed + mBuffered = EMPTY_BYTE_ARRAY; + mBufferedOffset = 0; + mBufferedLength = 0; + } else if (opResult.inputConsumed == 0) { + // Nothing was consumed. More input needed. + if (inputLength > 0) { + // More input is available, but it wasn't included into the previous chunk + // because the chunk reached its maximum permitted size. + // Shouldn't have happened. + throw new IllegalStateException("Nothing consumed from max-sized chunk: " + + chunk.length + " bytes"); + } + mBuffered = chunk; + mBufferedOffset = 0; + mBufferedLength = chunk.length; + } else if (opResult.inputConsumed < chunk.length) { + // The chunk was consumed only partially -- buffer the rest of the chunk + mBuffered = chunk; + mBufferedOffset = opResult.inputConsumed; + mBufferedLength = chunk.length - opResult.inputConsumed; + } else { + throw new IllegalStateException("Consumed more than provided: " + + opResult.inputConsumed + ", provided: " + chunk.length); + } + + if ((opResult.output != null) && (opResult.output.length > 0)) { + if (inputLength > 0) { + // More output might be produced in this loop -- buffer the current output + if (bufferedOutput == null) { + bufferedOutput = new ByteArrayOutputStream(); + try { + bufferedOutput.write(opResult.output); + } catch (IOException e) { + throw new IllegalStateException("Failed to buffer output", e); + } + } + } else { + // No more output will be produced in this loop + if (bufferedOutput == null) { + // No previously buffered output + return opResult.output; + } else { + // There was some previously buffered output + try { + bufferedOutput.write(opResult.output); + } catch (IOException e) { + throw new IllegalStateException("Failed to buffer output", e); + } + return bufferedOutput.toByteArray(); + } + } + } + } + + if (bufferedOutput == null) { + // No output produced + return EMPTY_BYTE_ARRAY; + } else { + return bufferedOutput.toByteArray(); + } + } + + public byte[] doFinal(byte[] input, int inputOffset, int inputLength) + throws KeyStoreException { + if (inputLength == 0) { + // No input provided -- simplify the rest of the code + input = EMPTY_BYTE_ARRAY; + inputOffset = 0; + } + + // Flush all buffered input and provided input into keystore/keymaster. + byte[] output = update(input, inputOffset, inputLength); + output = ArrayUtils.concat(output, flush()); + + OperationResult opResult = mKeyStoreStream.finish(); + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeyStore.getKeyStoreException(opResult.resultCode); + } + + return ArrayUtils.concat(output, opResult.output); + } + + /** + * Passes all of buffered input into the the KeyStore operation (via the {@code update} + * operation) and returns output. + */ + public byte[] flush() throws KeyStoreException { + if (mBufferedLength <= 0) { + return EmptyArray.BYTE; + } + + byte[] chunk = ArrayUtils.subarray(mBuffered, mBufferedOffset, mBufferedLength); + mBuffered = EmptyArray.BYTE; + mBufferedLength = 0; + mBufferedOffset = 0; + + OperationResult opResult = mKeyStoreStream.update(chunk); + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeyStore.getKeyStoreException(opResult.resultCode); + } + + if (opResult.inputConsumed < chunk.length) { + throw new IllegalStateException("Keystore failed to consume all input. Provided: " + + chunk.length + ", consumed: " + opResult.inputConsumed); + } else if (opResult.inputConsumed > chunk.length) { + throw new IllegalStateException("Keystore consumed more input than provided" + + " . Provided: " + chunk.length + ", consumed: " + opResult.inputConsumed); + } + + return (opResult.output != null) ? opResult.output : EmptyArray.BYTE; + } + + /** + * Main data stream via a KeyStore streaming operation. + * + *

For example, for an encryption operation, this is the stream through which plaintext is + * provided and ciphertext is obtained. + */ + public static class MainDataStream implements Stream { + + private final KeyStore mKeyStore; + private final IBinder mOperationToken; + + public MainDataStream(KeyStore keyStore, IBinder operationToken) { + mKeyStore = keyStore; + mOperationToken = operationToken; + } + + @Override + public OperationResult update(byte[] input) { + return mKeyStore.update(mOperationToken, null, input); + } + + @Override + public OperationResult finish() { + return mKeyStore.finish(mOperationToken, null, null); + } + } +} diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java new file mode 100644 index 0000000..6ae76f1 --- /dev/null +++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2015 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.keystore; + +import android.security.KeyStore; +import android.security.keymaster.KeymasterDefs; + +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.SecureRandom; + +/** + * Assorted utility methods for implementing crypto operations on top of KeyStore. + * + * @hide + */ +abstract class KeyStoreCryptoOperationUtils { + + private static volatile SecureRandom sRng; + + private KeyStoreCryptoOperationUtils() {} + + /** + * Returns the {@link InvalidKeyException} to be thrown by the {@code init} method of + * the crypto operation in response to {@code KeyStore.begin} operation or {@code null} if + * the {@code init} method should succeed. + */ + static InvalidKeyException getInvalidKeyExceptionForInit( + KeyStore keyStore, AndroidKeyStoreKey key, int beginOpResultCode) { + if (beginOpResultCode == KeyStore.NO_ERROR) { + return null; + } + + // An error occured. However, some errors should not lead to init throwing an exception. + // See below. + InvalidKeyException e = + keyStore.getInvalidKeyException(key.getAlias(), beginOpResultCode); + switch (beginOpResultCode) { + case KeyStore.OP_AUTH_NEEDED: + // Operation needs to be authorized by authenticating the user. Don't throw an + // exception is such authentication is possible for this key + // (UserNotAuthenticatedException). An example of when it's not possible is where + // the key is permanently invalidated (KeyPermanentlyInvalidatedException). + if (e instanceof UserNotAuthenticatedException) { + return null; + } + break; + } + return e; + } + + /** + * Returns the exception to be thrown by the {@code Cipher.init} method of the crypto operation + * in response to {@code KeyStore.begin} operation or {@code null} if the {@code init} method + * should succeed. + */ + public static GeneralSecurityException getExceptionForCipherInit( + KeyStore keyStore, AndroidKeyStoreKey key, int beginOpResultCode) { + if (beginOpResultCode == KeyStore.NO_ERROR) { + return null; + } + + // Cipher-specific cases + switch (beginOpResultCode) { + case KeymasterDefs.KM_ERROR_INVALID_NONCE: + return new InvalidAlgorithmParameterException("Invalid IV"); + case KeymasterDefs.KM_ERROR_CALLER_NONCE_PROHIBITED: + return new InvalidAlgorithmParameterException("Caller-provided IV not permitted"); + } + + // General cases + return getInvalidKeyExceptionForInit(keyStore, key, beginOpResultCode); + } + + /** + * Returns the requested number of random bytes to mix into keystore/keymaster RNG. + * + * @param rng RNG from which to obtain the random bytes or {@code null} for the platform-default + * RNG. + */ + static byte[] getRandomBytesToMixIntoKeystoreRng(SecureRandom rng, int sizeBytes) { + if (rng == null) { + rng = getRng(); + } + byte[] result = new byte[sizeBytes]; + rng.nextBytes(result); + return result; + } + + private static SecureRandom getRng() { + // IMPLEMENTATION NOTE: It's OK to share a SecureRandom instance because SecureRandom is + // required to be thread-safe. + if (sRng == null) { + sRng = new SecureRandom(); + } + return sRng; + } +} diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java new file mode 100644 index 0000000..e7529e1 --- /dev/null +++ b/keystore/java/android/security/keystore/KeymasterUtils.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2015 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.keystore; + +import android.hardware.fingerprint.FingerprintManager; +import android.security.GateKeeper; +import android.security.KeyStore; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; + +/** + * @hide + */ +public abstract class KeymasterUtils { + + private KeymasterUtils() {} + + public static int getDigestOutputSizeBits(int keymasterDigest) { + switch (keymasterDigest) { + case KeymasterDefs.KM_DIGEST_NONE: + return -1; + case KeymasterDefs.KM_DIGEST_MD5: + return 128; + case KeymasterDefs.KM_DIGEST_SHA1: + return 160; + case KeymasterDefs.KM_DIGEST_SHA_2_224: + return 224; + case KeymasterDefs.KM_DIGEST_SHA_2_256: + return 256; + case KeymasterDefs.KM_DIGEST_SHA_2_384: + return 384; + case KeymasterDefs.KM_DIGEST_SHA_2_512: + return 512; + default: + throw new IllegalArgumentException("Unknown digest: " + keymasterDigest); + } + } + + public static boolean isKeymasterBlockModeIndCpaCompatible(int keymasterBlockMode) { + switch (keymasterBlockMode) { + case KeymasterDefs.KM_MODE_ECB: + return false; + case KeymasterDefs.KM_MODE_CBC: + case KeymasterDefs.KM_MODE_CTR: + case KeymasterDefs.KM_MODE_GCM: + return true; + default: + throw new IllegalArgumentException("Unsupported block mode: " + keymasterBlockMode); + } + } + + /** + * Adds keymaster arguments to express the key's authorization policy supported by user + * authentication. + * + * @param userAuthenticationRequired whether user authentication is required to authorize the + * use of the key. + * @param userAuthenticationValidityDurationSeconds duration of time (seconds) for which user + * authentication is valid as authorization for using the key or {@code -1} if every + * use of the key needs authorization. + */ + public static void addUserAuthArgs(KeymasterArguments args, + boolean userAuthenticationRequired, + int userAuthenticationValidityDurationSeconds) { + if (!userAuthenticationRequired) { + args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); + return; + } + + if (userAuthenticationValidityDurationSeconds == -1) { + // Every use of this key needs to be authorized by the user. This currently means + // fingerprint-only auth. + FingerprintManager fingerprintManager = + KeyStore.getApplicationContext().getSystemService(FingerprintManager.class); + if ((fingerprintManager == null) || (!fingerprintManager.isHardwareDetected())) { + throw new IllegalStateException( + "This device does not support keys which require authentication for every" + + " use -- this requires fingerprint authentication which is not" + + " available on this device"); + } + long fingerprintOnlySid = fingerprintManager.getAuthenticatorId(); + if (fingerprintOnlySid == 0) { + throw new IllegalStateException( + "At least one fingerprint must be enrolled to create keys requiring user" + + " authentication for every use"); + } + args.addLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, fingerprintOnlySid); + args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_FINGERPRINT); + } else { + // The key is authorized for use for the specified amount of time after the user has + // authenticated. Whatever unlocks the secure lock screen should authorize this key. + long rootSid = GateKeeper.getSecureUserId(); + if (rootSid == 0) { + throw new IllegalStateException("Secure lock screen must be enabled" + + " to create keys requiring user authentication"); + } + args.addLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, rootSid); + args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, + KeymasterDefs.HW_AUTH_PASSWORD | KeymasterDefs.HW_AUTH_FINGERPRINT); + args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT, + userAuthenticationValidityDurationSeconds); + } + } +} diff --git a/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java b/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java deleted file mode 100644 index 9c2f358..0000000 --- a/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java +++ /dev/null @@ -1,373 +0,0 @@ -/* - * 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.security.interfaces.ECPublicKey; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.AlgorithmParameterSpec; -import java.security.spec.RSAKeyGenParameterSpec; -import java.text.SimpleDateFormat; -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()); - - assertFalse(mAndroidKeyStore.isUnlocked()); - - mGenerator = java.security.KeyPairGenerator.getInstance("RSA", "AndroidKeyStore"); - } - - private void setupPassword() { - assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111")); - assertTrue(mAndroidKeyStore.isUnlocked()); - - String[] aliases = mAndroidKeyStore.saw(""); - assertNotNull(aliases); - assertEquals(0, aliases.length); - } - - public void testKeyPairGenerator_Initialize_Params_Encrypted_Success() throws Exception { - setupPassword(); - - mGenerator.initialize(new KeyPairGeneratorSpec.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_Encrypted_Failure() throws Exception { - setupPassword(); - - try { - mGenerator.initialize(1024); - fail("KeyPairGenerator should not support setting the key size"); - } catch (IllegalArgumentException success) { - } - } - - 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"); - } catch (IllegalArgumentException success) { - } - } - - public void testKeyPairGenerator_Initialize_ParamsAndSecureRandom_Encrypted_Failure() - throws Exception { - setupPassword(); - - mGenerator.initialize( - new KeyPairGeneratorSpec.Builder(getContext()) - .setAlias(TEST_ALIAS_1) - .setKeyType("RSA") - .setKeySize(1024) - .setSubject(TEST_DN_1) - .setSerialNumber(TEST_SERIAL_1) - .setStartDate(NOW) - .setEndDate(NOW_PLUS_10_YEARS) - .setEncryptionRequired() - .build(), - new SecureRandom()); - } - - public void testKeyPairGenerator_GenerateKeyPair_Encrypted_Success() throws Exception { - setupPassword(); - - mGenerator.initialize(new KeyPairGeneratorSpec.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, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, NOW, - NOW_PLUS_10_YEARS); - } - - public void testKeyPairGenerator_GenerateKeyPair_EC_Unencrypted_Success() throws Exception { - mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext()) - .setAlias(TEST_ALIAS_1) - .setKeyType("EC") - .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); - - assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 256, null, TEST_DN_1, TEST_SERIAL_1, NOW, - NOW_PLUS_10_YEARS); - } - - public void testKeyPairGenerator_GenerateKeyPair_EC_P521_Unencrypted_Success() throws Exception { - mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext()) - .setAlias(TEST_ALIAS_1) - .setKeyType("EC") - .setKeySize(521) - .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); - - assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 521, null, TEST_DN_1, TEST_SERIAL_1, NOW, - NOW_PLUS_10_YEARS); - } - - public void testKeyPairGenerator_GenerateKeyPair_RSA_Unencrypted_Success() throws Exception { - mGenerator.initialize(new KeyPairGeneratorSpec.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); - - assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, NOW, - NOW_PLUS_10_YEARS); - } - - public void testKeyPairGenerator_GenerateKeyPair_RSA_WithParams_Unencrypted_Success() - throws Exception { - AlgorithmParameterSpec spec = new RSAKeyGenParameterSpec(1024, BigInteger.valueOf(3L)); - mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext()) - .setAlias(TEST_ALIAS_1) - .setKeySize(1024) - .setAlgorithmParameterSpec(spec) - .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); - - assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 1024, spec, 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 KeyPairGeneratorSpec.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, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, - NOW, NOW_PLUS_10_YEARS); - } - - // Replace the original key - { - mGenerator.initialize(new KeyPairGeneratorSpec.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, "RSA", 2048, null, TEST_DN_2, TEST_SERIAL_2, - NOW, NOW_PLUS_10_YEARS); - } - } - - public void testKeyPairGenerator_GenerateKeyPair_Replaced_UnencryptedToEncrypted_Success() - throws Exception { - // Generate the first key - { - mGenerator.initialize(new KeyPairGeneratorSpec.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, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, - NOW, NOW_PLUS_10_YEARS); - } - - // Attempt to replace previous key - { - mGenerator.initialize(new KeyPairGeneratorSpec.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.onUserPasswordChanged("1111")); - assertTrue(mAndroidKeyStore.isUnlocked()); - - final KeyPair pair2 = mGenerator.generateKeyPair(); - assertNotNull("The KeyPair returned should not be null", pair2); - assertKeyPairCorrect(pair2, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_2, TEST_SERIAL_2, - NOW, NOW_PLUS_10_YEARS); - } - } - - private void assertKeyPairCorrect(KeyPair pair, String alias, String keyType, int keySize, - AlgorithmParameterSpec spec, 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); - assertEquals(keyType, pubKey.getAlgorithm()); - - if ("EC".equalsIgnoreCase(keyType)) { - assertEquals("Curve should be what was specified during initialization", keySize, - ((ECPublicKey) pubKey).getParams().getCurve().getField().getFieldSize()); - } else if ("RSA".equalsIgnoreCase(keyType)) { - RSAPublicKey rsaPubKey = (RSAPublicKey) pubKey; - assertEquals("Modulus size should be what is specified during initialization", - (keySize + 7) & ~7, (rsaPubKey.getModulus().bitLength() + 7) & ~7); - if (spec != null) { - RSAKeyGenParameterSpec params = (RSAKeyGenParameterSpec) spec; - assertEquals((keySize + 7) & ~7, (params.getKeysize() + 7) & ~7); - assertEquals(params.getPublicExponent(), rsaPubKey.getPublicExponent()); - } - } - - final PrivateKey privKey = pair.getPrivate(); - assertNotNull("The PrivateKey for the KeyPair should be not null", privKey); - assertEquals(keyType, privKey.getAlgorithm()); - - 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()); - - assertDateEquals("The notBefore date should be the one passed into the params", start, - x509userCert.getNotBefore()); - - assertDateEquals("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); - } - - 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 deleted file mode 100644 index 4b2b9b5..0000000 --- a/keystore/tests/src/android/security/AndroidKeyStoreTest.java +++ /dev/null @@ -1,2231 +0,0 @@ -/* - * 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 com.android.org.conscrypt.NativeConstants; -import com.android.org.conscrypt.OpenSSLEngine; - -import android.test.AndroidTestCase; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.OutputStream; -import java.math.BigInteger; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.KeyFactory; -import java.security.KeyStore.Entry; -import java.security.KeyStore.PrivateKeyEntry; -import java.security.KeyStore.TrustedCertificateEntry; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.interfaces.RSAPrivateKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.Arrays; -import java.util.Collection; -import java.util.Date; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import javax.security.auth.x500.X500Principal; - -public class AndroidKeyStoreTest extends AndroidTestCase { - private android.security.KeyStore mAndroidKeyStore; - - private java.security.KeyStore mKeyStore; - - private static final String TEST_ALIAS_1 = "test1"; - - private static final String TEST_ALIAS_2 = "test2"; - - private static final String TEST_ALIAS_3 = "test3"; - - 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); - - /* - * The keys and certificates below are generated with: - * - * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem - * openssl req -newkey rsa:1024 -keyout userkey.pem -nodes -days 3650 -out userkey.req - * mkdir -p demoCA/newcerts - * touch demoCA/index.txt - * echo "01" > demoCA/serial - * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650 - */ - - /** - * Generated from above and converted with: - * - * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g' - */ - private static final byte[] FAKE_RSA_CA_1 = { - (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0xce, (byte) 0x30, (byte) 0x82, - (byte) 0x02, (byte) 0x37, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01, - (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xe1, (byte) 0x6a, - (byte) 0xa2, (byte) 0xf4, (byte) 0x2e, (byte) 0x55, (byte) 0x48, (byte) 0x0a, - (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, - (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, - (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x4f, (byte) 0x31, - (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, - (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, - (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, - (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, - (byte) 0x41, (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, - (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, - (byte) 0x4d, (byte) 0x6f, (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, - (byte) 0x69, (byte) 0x6e, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, - (byte) 0x77, (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, - (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, - (byte) 0x41, (byte) 0x6e, (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, - (byte) 0x64, (byte) 0x20, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, - (byte) 0x20, (byte) 0x43, (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, - (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x32, - (byte) 0x30, (byte) 0x38, (byte) 0x31, (byte) 0x34, (byte) 0x31, (byte) 0x36, - (byte) 0x35, (byte) 0x35, (byte) 0x34, (byte) 0x34, (byte) 0x5a, (byte) 0x17, - (byte) 0x0d, (byte) 0x32, (byte) 0x32, (byte) 0x30, (byte) 0x38, (byte) 0x31, - (byte) 0x32, (byte) 0x31, (byte) 0x36, (byte) 0x35, (byte) 0x35, (byte) 0x34, - (byte) 0x34, (byte) 0x5a, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b, - (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, - (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, - (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, - (byte) 0x04, (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, - (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03, - (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d, - (byte) 0x6f, (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69, - (byte) 0x6e, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77, - (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, - (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, - (byte) 0x6e, (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, - (byte) 0x20, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, - (byte) 0x43, (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x30, - (byte) 0x81, (byte) 0x9f, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, - (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, - (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, - (byte) 0x81, (byte) 0x8d, (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, - (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xa3, (byte) 0x72, - (byte) 0xab, (byte) 0xd0, (byte) 0xe4, (byte) 0xad, (byte) 0x2f, (byte) 0xe7, - (byte) 0xe2, (byte) 0x79, (byte) 0x07, (byte) 0x36, (byte) 0x3d, (byte) 0x0c, - (byte) 0x8d, (byte) 0x42, (byte) 0x9a, (byte) 0x0a, (byte) 0x33, (byte) 0x64, - (byte) 0xb3, (byte) 0xcd, (byte) 0xb2, (byte) 0xd7, (byte) 0x3a, (byte) 0x42, - (byte) 0x06, (byte) 0x77, (byte) 0x45, (byte) 0x29, (byte) 0xe9, (byte) 0xcb, - (byte) 0xb7, (byte) 0x4a, (byte) 0xd6, (byte) 0xee, (byte) 0xad, (byte) 0x01, - (byte) 0x91, (byte) 0x9b, (byte) 0x0c, (byte) 0x59, (byte) 0xa1, (byte) 0x03, - (byte) 0xfa, (byte) 0xf0, (byte) 0x5a, (byte) 0x7c, (byte) 0x4f, (byte) 0xf7, - (byte) 0x8d, (byte) 0x36, (byte) 0x0f, (byte) 0x1f, (byte) 0x45, (byte) 0x7d, - (byte) 0x1b, (byte) 0x31, (byte) 0xa1, (byte) 0x35, (byte) 0x0b, (byte) 0x00, - (byte) 0xed, (byte) 0x7a, (byte) 0xb6, (byte) 0xc8, (byte) 0x4e, (byte) 0xa9, - (byte) 0x86, (byte) 0x4c, (byte) 0x7b, (byte) 0x99, (byte) 0x57, (byte) 0x41, - (byte) 0x12, (byte) 0xef, (byte) 0x6b, (byte) 0xbc, (byte) 0x3d, (byte) 0x60, - (byte) 0xf2, (byte) 0x99, (byte) 0x1a, (byte) 0xcd, (byte) 0xed, (byte) 0x56, - (byte) 0xa4, (byte) 0xe5, (byte) 0x36, (byte) 0x9f, (byte) 0x24, (byte) 0x1f, - (byte) 0xdc, (byte) 0x89, (byte) 0x40, (byte) 0xc8, (byte) 0x99, (byte) 0x92, - (byte) 0xab, (byte) 0x4a, (byte) 0xb5, (byte) 0x61, (byte) 0x45, (byte) 0x62, - (byte) 0xff, (byte) 0xa3, (byte) 0x45, (byte) 0x65, (byte) 0xaf, (byte) 0xf6, - (byte) 0x27, (byte) 0x30, (byte) 0x51, (byte) 0x0e, (byte) 0x0e, (byte) 0xeb, - (byte) 0x79, (byte) 0x0c, (byte) 0xbe, (byte) 0xb3, (byte) 0x0a, (byte) 0x6f, - (byte) 0x29, (byte) 0x06, (byte) 0xdc, (byte) 0x2f, (byte) 0x6b, (byte) 0x51, - (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3, - (byte) 0x81, (byte) 0xb1, (byte) 0x30, (byte) 0x81, (byte) 0xae, (byte) 0x30, - (byte) 0x1d, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e, - (byte) 0x04, (byte) 0x16, (byte) 0x04, (byte) 0x14, (byte) 0x33, (byte) 0x05, - (byte) 0xee, (byte) 0xfe, (byte) 0x6f, (byte) 0x60, (byte) 0xc7, (byte) 0xf9, - (byte) 0xa9, (byte) 0xd2, (byte) 0x73, (byte) 0x5c, (byte) 0x8f, (byte) 0x6d, - (byte) 0xa2, (byte) 0x2f, (byte) 0x97, (byte) 0x8e, (byte) 0x5d, (byte) 0x51, - (byte) 0x30, (byte) 0x7f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, - (byte) 0x23, (byte) 0x04, (byte) 0x78, (byte) 0x30, (byte) 0x76, (byte) 0x80, - (byte) 0x14, (byte) 0x33, (byte) 0x05, (byte) 0xee, (byte) 0xfe, (byte) 0x6f, - (byte) 0x60, (byte) 0xc7, (byte) 0xf9, (byte) 0xa9, (byte) 0xd2, (byte) 0x73, - (byte) 0x5c, (byte) 0x8f, (byte) 0x6d, (byte) 0xa2, (byte) 0x2f, (byte) 0x97, - (byte) 0x8e, (byte) 0x5d, (byte) 0x51, (byte) 0xa1, (byte) 0x53, (byte) 0xa4, - (byte) 0x51, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b, (byte) 0x30, - (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, - (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b, - (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, - (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31, - (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03, (byte) 0x55, - (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d, (byte) 0x6f, - (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69, (byte) 0x6e, - (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77, (byte) 0x31, - (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55, - (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e, - (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20, - (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43, - (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x82, (byte) 0x09, - (byte) 0x00, (byte) 0xe1, (byte) 0x6a, (byte) 0xa2, (byte) 0xf4, (byte) 0x2e, - (byte) 0x55, (byte) 0x48, (byte) 0x0a, (byte) 0x30, (byte) 0x0c, (byte) 0x06, - (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x05, - (byte) 0x30, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0xff, (byte) 0x30, - (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, - (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, - (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x81, (byte) 0x00, - (byte) 0x8c, (byte) 0x30, (byte) 0x42, (byte) 0xfa, (byte) 0xeb, (byte) 0x1a, - (byte) 0x26, (byte) 0xeb, (byte) 0xda, (byte) 0x56, (byte) 0x32, (byte) 0xf2, - (byte) 0x9d, (byte) 0xa5, (byte) 0x24, (byte) 0xd8, (byte) 0x3a, (byte) 0xda, - (byte) 0x30, (byte) 0xa6, (byte) 0x8b, (byte) 0x46, (byte) 0xfe, (byte) 0xfe, - (byte) 0xdb, (byte) 0xf1, (byte) 0xe6, (byte) 0xe1, (byte) 0x7c, (byte) 0x1b, - (byte) 0xe7, (byte) 0x77, (byte) 0x00, (byte) 0xa1, (byte) 0x1c, (byte) 0x19, - (byte) 0x17, (byte) 0x73, (byte) 0xb0, (byte) 0xf0, (byte) 0x9d, (byte) 0xf3, - (byte) 0x4f, (byte) 0xb6, (byte) 0xbc, (byte) 0xc7, (byte) 0x47, (byte) 0x85, - (byte) 0x2a, (byte) 0x4a, (byte) 0xa1, (byte) 0xa5, (byte) 0x58, (byte) 0xf5, - (byte) 0xc5, (byte) 0x1a, (byte) 0x51, (byte) 0xb1, (byte) 0x04, (byte) 0x80, - (byte) 0xee, (byte) 0x3a, (byte) 0xec, (byte) 0x2f, (byte) 0xe1, (byte) 0xfd, - (byte) 0x58, (byte) 0xeb, (byte) 0xed, (byte) 0x82, (byte) 0x9e, (byte) 0x38, - (byte) 0xa3, (byte) 0x24, (byte) 0x75, (byte) 0xf7, (byte) 0x3e, (byte) 0xc2, - (byte) 0xc5, (byte) 0x27, (byte) 0xeb, (byte) 0x6f, (byte) 0x7b, (byte) 0x50, - (byte) 0xda, (byte) 0x43, (byte) 0xdc, (byte) 0x3b, (byte) 0x0b, (byte) 0x6f, - (byte) 0x78, (byte) 0x8f, (byte) 0xb0, (byte) 0x66, (byte) 0xe1, (byte) 0x12, - (byte) 0x87, (byte) 0x5f, (byte) 0x97, (byte) 0x7b, (byte) 0xca, (byte) 0x14, - (byte) 0x79, (byte) 0xf7, (byte) 0xe8, (byte) 0x6c, (byte) 0x72, (byte) 0xdb, - (byte) 0x91, (byte) 0x65, (byte) 0x17, (byte) 0x54, (byte) 0xe0, (byte) 0x74, - (byte) 0x1d, (byte) 0xac, (byte) 0x47, (byte) 0x04, (byte) 0x12, (byte) 0xe0, - (byte) 0xc3, (byte) 0x66, (byte) 0x19, (byte) 0x05, (byte) 0x2e, (byte) 0x7e, - (byte) 0xf1, (byte) 0x61 - }; - - /** - * Generated from above and converted with: - * - * openssl pkcs8 -topk8 -outform d -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g' - */ - private static final byte[] FAKE_RSA_KEY_1 = new byte[] { - (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x78, (byte) 0x02, (byte) 0x01, - (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, - (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, - (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x82, - (byte) 0x02, (byte) 0x62, (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x5e, - (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x81, (byte) 0x81, - (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6, (byte) 0x5b, - (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c, (byte) 0x66, - (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86, (byte) 0x8a, - (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3, (byte) 0x02, - (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08, (byte) 0xf3, - (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04, (byte) 0x6d, - (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f, (byte) 0x67, - (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c, (byte) 0xcb, - (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30, (byte) 0xe2, - (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5, (byte) 0x79, - (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b, (byte) 0xce, - (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb, (byte) 0x08, - (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff, (byte) 0x3b, - (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9, (byte) 0xc4, - (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29, (byte) 0x0d, - (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b, (byte) 0x23, - (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78, (byte) 0x08, - (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5, (byte) 0xf1, - (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19, (byte) 0xb4, - (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03, (byte) 0x16, - (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce, (byte) 0x9e, - (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03, (byte) 0x01, - (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x81, (byte) 0x80, (byte) 0x16, - (byte) 0x59, (byte) 0xc3, (byte) 0x24, (byte) 0x1d, (byte) 0x33, (byte) 0x98, - (byte) 0x9c, (byte) 0xc9, (byte) 0xc8, (byte) 0x2c, (byte) 0x88, (byte) 0xbf, - (byte) 0x0a, (byte) 0x01, (byte) 0xce, (byte) 0xfb, (byte) 0x34, (byte) 0x7a, - (byte) 0x58, (byte) 0x7a, (byte) 0xb0, (byte) 0xbf, (byte) 0xa6, (byte) 0xb2, - (byte) 0x60, (byte) 0xbe, (byte) 0x70, (byte) 0x21, (byte) 0xf5, (byte) 0xfc, - (byte) 0x85, (byte) 0x0d, (byte) 0x33, (byte) 0x58, (byte) 0xa1, (byte) 0xe5, - (byte) 0x09, (byte) 0x36, (byte) 0x84, (byte) 0xb2, (byte) 0x04, (byte) 0x0a, - (byte) 0x02, (byte) 0xd3, (byte) 0x88, (byte) 0x1f, (byte) 0x0c, (byte) 0x2b, - (byte) 0x1d, (byte) 0xe9, (byte) 0x3d, (byte) 0xe7, (byte) 0x79, (byte) 0xf9, - (byte) 0x32, (byte) 0x5c, (byte) 0x8a, (byte) 0x75, (byte) 0x49, (byte) 0x12, - (byte) 0xe4, (byte) 0x05, (byte) 0x26, (byte) 0xd4, (byte) 0x2e, (byte) 0x9e, - (byte) 0x1f, (byte) 0xcc, (byte) 0x54, (byte) 0xad, (byte) 0x33, (byte) 0x8d, - (byte) 0x99, (byte) 0x00, (byte) 0xdc, (byte) 0xf5, (byte) 0xb4, (byte) 0xa2, - (byte) 0x2f, (byte) 0xba, (byte) 0xe5, (byte) 0x62, (byte) 0x30, (byte) 0x6d, - (byte) 0xe6, (byte) 0x3d, (byte) 0xeb, (byte) 0x24, (byte) 0xc2, (byte) 0xdc, - (byte) 0x5f, (byte) 0xb7, (byte) 0x16, (byte) 0x35, (byte) 0xa3, (byte) 0x98, - (byte) 0x98, (byte) 0xa8, (byte) 0xef, (byte) 0xe8, (byte) 0xc4, (byte) 0x96, - (byte) 0x6d, (byte) 0x38, (byte) 0xab, (byte) 0x26, (byte) 0x6d, (byte) 0x30, - (byte) 0xc2, (byte) 0xa0, (byte) 0x44, (byte) 0xe4, (byte) 0xff, (byte) 0x7e, - (byte) 0xbe, (byte) 0x7c, (byte) 0x33, (byte) 0xa5, (byte) 0x10, (byte) 0xad, - (byte) 0xd7, (byte) 0x1e, (byte) 0x13, (byte) 0x20, (byte) 0xb3, (byte) 0x1f, - (byte) 0x41, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xf1, (byte) 0x89, - (byte) 0x07, (byte) 0x0f, (byte) 0xe8, (byte) 0xcf, (byte) 0xab, (byte) 0x13, - (byte) 0x2a, (byte) 0x8f, (byte) 0x88, (byte) 0x80, (byte) 0x11, (byte) 0x9a, - (byte) 0x79, (byte) 0xb6, (byte) 0x59, (byte) 0x3a, (byte) 0x50, (byte) 0x6e, - (byte) 0x57, (byte) 0x37, (byte) 0xab, (byte) 0x2a, (byte) 0xd2, (byte) 0xaa, - (byte) 0xd9, (byte) 0x72, (byte) 0x73, (byte) 0xff, (byte) 0x8b, (byte) 0x47, - (byte) 0x76, (byte) 0xdd, (byte) 0xdc, (byte) 0xf5, (byte) 0x97, (byte) 0x44, - (byte) 0x3a, (byte) 0x78, (byte) 0xbe, (byte) 0x17, (byte) 0xb4, (byte) 0x22, - (byte) 0x6f, (byte) 0xe5, (byte) 0x23, (byte) 0x70, (byte) 0x1d, (byte) 0x10, - (byte) 0x5d, (byte) 0xba, (byte) 0x16, (byte) 0x81, (byte) 0xf1, (byte) 0x45, - (byte) 0xce, (byte) 0x30, (byte) 0xb4, (byte) 0xab, (byte) 0x80, (byte) 0xe4, - (byte) 0x98, (byte) 0x31, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xda, - (byte) 0x82, (byte) 0x9d, (byte) 0x3f, (byte) 0xca, (byte) 0x2f, (byte) 0xe1, - (byte) 0xd4, (byte) 0x86, (byte) 0x77, (byte) 0x48, (byte) 0xa6, (byte) 0xab, - (byte) 0xab, (byte) 0x1c, (byte) 0x42, (byte) 0x5c, (byte) 0xd5, (byte) 0xc7, - (byte) 0x46, (byte) 0x59, (byte) 0x91, (byte) 0x3f, (byte) 0xfc, (byte) 0xcc, - (byte) 0xec, (byte) 0xc2, (byte) 0x40, (byte) 0x12, (byte) 0x2c, (byte) 0x8d, - (byte) 0x1f, (byte) 0xa2, (byte) 0x18, (byte) 0x88, (byte) 0xee, (byte) 0x82, - (byte) 0x4a, (byte) 0x5a, (byte) 0x5e, (byte) 0x88, (byte) 0x20, (byte) 0xe3, - (byte) 0x7b, (byte) 0xe0, (byte) 0xd8, (byte) 0x3a, (byte) 0x52, (byte) 0x9a, - (byte) 0x26, (byte) 0x6a, (byte) 0x04, (byte) 0xec, (byte) 0xe8, (byte) 0xb9, - (byte) 0x48, (byte) 0x40, (byte) 0xe1, (byte) 0xe1, (byte) 0x83, (byte) 0xa6, - (byte) 0x67, (byte) 0xa6, (byte) 0xfd, (byte) 0x02, (byte) 0x41, (byte) 0x00, - (byte) 0x89, (byte) 0x72, (byte) 0x3e, (byte) 0xb0, (byte) 0x90, (byte) 0xfd, - (byte) 0x4c, (byte) 0x0e, (byte) 0xd6, (byte) 0x13, (byte) 0x63, (byte) 0xcb, - (byte) 0xed, (byte) 0x38, (byte) 0x88, (byte) 0xb6, (byte) 0x79, (byte) 0xc4, - (byte) 0x33, (byte) 0x6c, (byte) 0xf6, (byte) 0xf8, (byte) 0xd8, (byte) 0xd0, - (byte) 0xbf, (byte) 0x9d, (byte) 0x35, (byte) 0xac, (byte) 0x69, (byte) 0xd2, - (byte) 0x2b, (byte) 0xc1, (byte) 0xf9, (byte) 0x24, (byte) 0x7b, (byte) 0xce, - (byte) 0xcd, (byte) 0xcb, (byte) 0xa7, (byte) 0xb2, (byte) 0x7a, (byte) 0x0a, - (byte) 0x27, (byte) 0x19, (byte) 0xc9, (byte) 0xaf, (byte) 0x0d, (byte) 0x21, - (byte) 0x89, (byte) 0x88, (byte) 0x7c, (byte) 0xad, (byte) 0x9e, (byte) 0x8d, - (byte) 0x47, (byte) 0x6d, (byte) 0x3f, (byte) 0xce, (byte) 0x7b, (byte) 0xa1, - (byte) 0x74, (byte) 0xf1, (byte) 0xa0, (byte) 0xa1, (byte) 0x02, (byte) 0x41, - (byte) 0x00, (byte) 0xd9, (byte) 0xa8, (byte) 0xf5, (byte) 0xfe, (byte) 0xce, - (byte) 0xe6, (byte) 0x77, (byte) 0x6b, (byte) 0xfe, (byte) 0x2d, (byte) 0xe0, - (byte) 0x1e, (byte) 0xb6, (byte) 0x2e, (byte) 0x12, (byte) 0x4e, (byte) 0x40, - (byte) 0xaf, (byte) 0x6a, (byte) 0x7b, (byte) 0x37, (byte) 0x49, (byte) 0x2a, - (byte) 0x96, (byte) 0x25, (byte) 0x83, (byte) 0x49, (byte) 0xd4, (byte) 0x0c, - (byte) 0xc6, (byte) 0x78, (byte) 0x25, (byte) 0x24, (byte) 0x90, (byte) 0x90, - (byte) 0x06, (byte) 0x15, (byte) 0x9e, (byte) 0xfe, (byte) 0xf9, (byte) 0xdf, - (byte) 0x5b, (byte) 0xf3, (byte) 0x7e, (byte) 0x38, (byte) 0x70, (byte) 0xeb, - (byte) 0x57, (byte) 0xd0, (byte) 0xd9, (byte) 0xa7, (byte) 0x0e, (byte) 0x14, - (byte) 0xf7, (byte) 0x95, (byte) 0x68, (byte) 0xd5, (byte) 0xc8, (byte) 0xab, - (byte) 0x9d, (byte) 0x3a, (byte) 0x2b, (byte) 0x51, (byte) 0xf9, (byte) 0x02, - (byte) 0x41, (byte) 0x00, (byte) 0x96, (byte) 0xdf, (byte) 0xe9, (byte) 0x67, - (byte) 0x6c, (byte) 0xdc, (byte) 0x90, (byte) 0x14, (byte) 0xb4, (byte) 0x1d, - (byte) 0x22, (byte) 0x33, (byte) 0x4a, (byte) 0x31, (byte) 0xc1, (byte) 0x9d, - (byte) 0x2e, (byte) 0xff, (byte) 0x9a, (byte) 0x2a, (byte) 0x95, (byte) 0x4b, - (byte) 0x27, (byte) 0x74, (byte) 0xcb, (byte) 0x21, (byte) 0xc3, (byte) 0xd2, - (byte) 0x0b, (byte) 0xb2, (byte) 0x46, (byte) 0x87, (byte) 0xf8, (byte) 0x28, - (byte) 0x01, (byte) 0x8b, (byte) 0xd8, (byte) 0xb9, (byte) 0x4b, (byte) 0xcd, - (byte) 0x9a, (byte) 0x96, (byte) 0x41, (byte) 0x0e, (byte) 0x36, (byte) 0x6d, - (byte) 0x40, (byte) 0x42, (byte) 0xbc, (byte) 0xd9, (byte) 0xd3, (byte) 0x7b, - (byte) 0xbc, (byte) 0xa7, (byte) 0x92, (byte) 0x90, (byte) 0xdd, (byte) 0xa1, - (byte) 0x9c, (byte) 0xce, (byte) 0xa1, (byte) 0x87, (byte) 0x11, (byte) 0x51 - }; - - /** - * Generated from above and converted with: - * - * openssl x509 -outform d -in usercert.pem | xxd -i | sed 's/0x/(byte) 0x/g' - */ - private static final byte[] FAKE_RSA_USER_1 = new byte[] { - (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x95, (byte) 0x30, (byte) 0x82, - (byte) 0x01, (byte) 0xfe, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01, - (byte) 0x02, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x0d, - (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, - (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, - (byte) 0x00, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b, (byte) 0x30, - (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, - (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b, - (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, - (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31, - (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03, (byte) 0x55, - (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d, (byte) 0x6f, - (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69, (byte) 0x6e, - (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77, (byte) 0x31, - (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55, - (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e, - (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20, - (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43, - (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x30, (byte) 0x1e, - (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x32, (byte) 0x30, (byte) 0x38, - (byte) 0x31, (byte) 0x34, (byte) 0x32, (byte) 0x33, (byte) 0x32, (byte) 0x35, - (byte) 0x34, (byte) 0x38, (byte) 0x5a, (byte) 0x17, (byte) 0x0d, (byte) 0x32, - (byte) 0x32, (byte) 0x30, (byte) 0x38, (byte) 0x31, (byte) 0x32, (byte) 0x32, - (byte) 0x33, (byte) 0x32, (byte) 0x35, (byte) 0x34, (byte) 0x38, (byte) 0x5a, - (byte) 0x30, (byte) 0x55, (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, - (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13, - (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b, (byte) 0x30, - (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08, - (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31, (byte) 0x1b, - (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, - (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e, (byte) 0x64, - (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20, (byte) 0x54, - (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43, (byte) 0x61, - (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x31, (byte) 0x1c, (byte) 0x30, - (byte) 0x1a, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, - (byte) 0x13, (byte) 0x13, (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76, - (byte) 0x65, (byte) 0x72, (byte) 0x31, (byte) 0x2e, (byte) 0x65, (byte) 0x78, - (byte) 0x61, (byte) 0x6d, (byte) 0x70, (byte) 0x6c, (byte) 0x65, (byte) 0x2e, - (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x30, (byte) 0x81, (byte) 0x9f, - (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, - (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, - (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x8d, - (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, (byte) 0x02, (byte) 0x81, - (byte) 0x81, (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6, - (byte) 0x5b, (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c, - (byte) 0x66, (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86, - (byte) 0x8a, (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3, - (byte) 0x02, (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08, - (byte) 0xf3, (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04, - (byte) 0x6d, (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f, - (byte) 0x67, (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c, - (byte) 0xcb, (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30, - (byte) 0xe2, (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5, - (byte) 0x79, (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b, - (byte) 0xce, (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb, - (byte) 0x08, (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff, - (byte) 0x3b, (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9, - (byte) 0xc4, (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29, - (byte) 0x0d, (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b, - (byte) 0x23, (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78, - (byte) 0x08, (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5, - (byte) 0xf1, (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19, - (byte) 0xb4, (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03, - (byte) 0x16, (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce, - (byte) 0x9e, (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03, - (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3, (byte) 0x7b, (byte) 0x30, - (byte) 0x79, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, - (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x02, (byte) 0x30, (byte) 0x00, - (byte) 0x30, (byte) 0x2c, (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, - (byte) 0x48, (byte) 0x01, (byte) 0x86, (byte) 0xf8, (byte) 0x42, (byte) 0x01, - (byte) 0x0d, (byte) 0x04, (byte) 0x1f, (byte) 0x16, (byte) 0x1d, (byte) 0x4f, - (byte) 0x70, (byte) 0x65, (byte) 0x6e, (byte) 0x53, (byte) 0x53, (byte) 0x4c, - (byte) 0x20, (byte) 0x47, (byte) 0x65, (byte) 0x6e, (byte) 0x65, (byte) 0x72, - (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x64, (byte) 0x20, (byte) 0x43, - (byte) 0x65, (byte) 0x72, (byte) 0x74, (byte) 0x69, (byte) 0x66, (byte) 0x69, - (byte) 0x63, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x30, (byte) 0x1d, - (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e, (byte) 0x04, - (byte) 0x16, (byte) 0x04, (byte) 0x14, (byte) 0x32, (byte) 0xa1, (byte) 0x1e, - (byte) 0x6b, (byte) 0x69, (byte) 0x04, (byte) 0xfe, (byte) 0xb3, (byte) 0xcd, - (byte) 0xf8, (byte) 0xbb, (byte) 0x14, (byte) 0xcd, (byte) 0xff, (byte) 0xd4, - (byte) 0x16, (byte) 0xc3, (byte) 0xab, (byte) 0x44, (byte) 0x2f, (byte) 0x30, - (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x23, - (byte) 0x04, (byte) 0x18, (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14, - (byte) 0x33, (byte) 0x05, (byte) 0xee, (byte) 0xfe, (byte) 0x6f, (byte) 0x60, - (byte) 0xc7, (byte) 0xf9, (byte) 0xa9, (byte) 0xd2, (byte) 0x73, (byte) 0x5c, - (byte) 0x8f, (byte) 0x6d, (byte) 0xa2, (byte) 0x2f, (byte) 0x97, (byte) 0x8e, - (byte) 0x5d, (byte) 0x51, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, - (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, - (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, - (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0x46, (byte) 0x42, (byte) 0xef, - (byte) 0x56, (byte) 0x89, (byte) 0x78, (byte) 0x90, (byte) 0x38, (byte) 0x24, - (byte) 0x9f, (byte) 0x8c, (byte) 0x7a, (byte) 0xce, (byte) 0x7a, (byte) 0xa5, - (byte) 0xb5, (byte) 0x1e, (byte) 0x74, (byte) 0x96, (byte) 0x34, (byte) 0x49, - (byte) 0x8b, (byte) 0xed, (byte) 0x44, (byte) 0xb3, (byte) 0xc9, (byte) 0x05, - (byte) 0xd7, (byte) 0x48, (byte) 0x55, (byte) 0x52, (byte) 0x59, (byte) 0x15, - (byte) 0x0b, (byte) 0xaa, (byte) 0x16, (byte) 0x86, (byte) 0xd2, (byte) 0x8e, - (byte) 0x16, (byte) 0x99, (byte) 0xe8, (byte) 0x5f, (byte) 0x11, (byte) 0x71, - (byte) 0x42, (byte) 0x55, (byte) 0xd1, (byte) 0xc4, (byte) 0x6f, (byte) 0x2e, - (byte) 0xa9, (byte) 0x64, (byte) 0x6f, (byte) 0xd8, (byte) 0xfd, (byte) 0x43, - (byte) 0x13, (byte) 0x24, (byte) 0xaa, (byte) 0x67, (byte) 0xe6, (byte) 0xf5, - (byte) 0xca, (byte) 0x80, (byte) 0x5e, (byte) 0x3a, (byte) 0x3e, (byte) 0xcc, - (byte) 0x4f, (byte) 0xba, (byte) 0x87, (byte) 0xe6, (byte) 0xae, (byte) 0xbf, - (byte) 0x8f, (byte) 0xd5, (byte) 0x28, (byte) 0x38, (byte) 0x58, (byte) 0x30, - (byte) 0x24, (byte) 0xf6, (byte) 0x53, (byte) 0x5b, (byte) 0x41, (byte) 0x53, - (byte) 0xe6, (byte) 0x45, (byte) 0xbc, (byte) 0xbe, (byte) 0xe6, (byte) 0xbb, - (byte) 0x5d, (byte) 0xd8, (byte) 0xa7, (byte) 0xf9, (byte) 0x64, (byte) 0x99, - (byte) 0x04, (byte) 0x43, (byte) 0x75, (byte) 0xd7, (byte) 0x2d, (byte) 0x32, - (byte) 0x0a, (byte) 0x94, (byte) 0xaf, (byte) 0x06, (byte) 0x34, (byte) 0xae, - (byte) 0x46, (byte) 0xbd, (byte) 0xda, (byte) 0x00, (byte) 0x0e, (byte) 0x25, - (byte) 0xc2, (byte) 0xf7, (byte) 0xc9, (byte) 0xc3, (byte) 0x65, (byte) 0xd2, - (byte) 0x08, (byte) 0x41, (byte) 0x0a, (byte) 0xf3, (byte) 0x72 - }; - - /* - * The keys and certificates below are generated with: - * - * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem - * openssl ecparam -name prime256v1 -out ecparam.pem - * openssl req -newkey ec:ecparam.pem -keyout userkey.pem -nodes -days 3650 -out userkey.req - * mkdir -p demoCA/newcerts - * touch demoCA/index.txt - * echo "01" > demoCA/serial - * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650 - */ - - /** - * Generated from above and converted with: - * - * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g' - */ - private static final byte[] FAKE_EC_CA_1 = { - (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x58, (byte) 0x30, (byte) 0x82, - (byte) 0x01, (byte) 0xc1, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01, - (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xe1, (byte) 0xb2, - (byte) 0x8c, (byte) 0x04, (byte) 0x95, (byte) 0xeb, (byte) 0x10, (byte) 0xcb, - (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, - (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, - (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x45, (byte) 0x31, - (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, - (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, - (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, - (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, - (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, - (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, - (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, - (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, - (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, - (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, - (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, - (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, - (byte) 0x31, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x37, - (byte) 0x31, (byte) 0x36, (byte) 0x32, (byte) 0x38, (byte) 0x32, (byte) 0x38, - (byte) 0x5a, (byte) 0x17, (byte) 0x0d, (byte) 0x32, (byte) 0x33, (byte) 0x30, - (byte) 0x38, (byte) 0x32, (byte) 0x35, (byte) 0x31, (byte) 0x36, (byte) 0x32, - (byte) 0x38, (byte) 0x32, (byte) 0x38, (byte) 0x5a, (byte) 0x30, (byte) 0x45, - (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, - (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, - (byte) 0x55, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, - (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, - (byte) 0x53, (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, - (byte) 0x74, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, - (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, - (byte) 0x0a, (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, - (byte) 0x65, (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, - (byte) 0x57, (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, - (byte) 0x73, (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, - (byte) 0x4c, (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x81, (byte) 0x9f, - (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, - (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, - (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x8d, - (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, (byte) 0x02, (byte) 0x81, - (byte) 0x81, (byte) 0x00, (byte) 0xb5, (byte) 0xf6, (byte) 0x08, (byte) 0x0f, - (byte) 0xc4, (byte) 0x4d, (byte) 0xe4, (byte) 0x0d, (byte) 0x34, (byte) 0x1d, - (byte) 0xe2, (byte) 0x23, (byte) 0x18, (byte) 0x63, (byte) 0x03, (byte) 0xf7, - (byte) 0x14, (byte) 0x0e, (byte) 0x98, (byte) 0xcd, (byte) 0x45, (byte) 0x1f, - (byte) 0xfe, (byte) 0xfb, (byte) 0x09, (byte) 0x3f, (byte) 0x5d, (byte) 0x36, - (byte) 0x3b, (byte) 0x0f, (byte) 0xf9, (byte) 0x5e, (byte) 0x86, (byte) 0x56, - (byte) 0x64, (byte) 0xd7, (byte) 0x3f, (byte) 0xae, (byte) 0x33, (byte) 0x09, - (byte) 0xd3, (byte) 0xdd, (byte) 0x06, (byte) 0x17, (byte) 0x26, (byte) 0xdc, - (byte) 0xa2, (byte) 0x8c, (byte) 0x3c, (byte) 0x65, (byte) 0xed, (byte) 0x03, - (byte) 0x82, (byte) 0x78, (byte) 0x9b, (byte) 0xee, (byte) 0xe3, (byte) 0x98, - (byte) 0x58, (byte) 0xe1, (byte) 0xf1, (byte) 0xa0, (byte) 0x85, (byte) 0xae, - (byte) 0x63, (byte) 0x84, (byte) 0x41, (byte) 0x46, (byte) 0xa7, (byte) 0x4f, - (byte) 0xdc, (byte) 0xbb, (byte) 0x1c, (byte) 0x6e, (byte) 0xec, (byte) 0x7b, - (byte) 0xd5, (byte) 0xab, (byte) 0x3d, (byte) 0x6a, (byte) 0x05, (byte) 0x58, - (byte) 0x0f, (byte) 0x9b, (byte) 0x6a, (byte) 0x67, (byte) 0x4b, (byte) 0xe9, - (byte) 0x2a, (byte) 0x6d, (byte) 0x96, (byte) 0x11, (byte) 0x53, (byte) 0x95, - (byte) 0x78, (byte) 0xaa, (byte) 0xd1, (byte) 0x91, (byte) 0x4a, (byte) 0xf8, - (byte) 0x54, (byte) 0x52, (byte) 0x6d, (byte) 0xb9, (byte) 0xca, (byte) 0x74, - (byte) 0x81, (byte) 0xf8, (byte) 0x99, (byte) 0x64, (byte) 0xd1, (byte) 0x4f, - (byte) 0x01, (byte) 0x38, (byte) 0x4f, (byte) 0x08, (byte) 0x5c, (byte) 0x31, - (byte) 0xcb, (byte) 0x7c, (byte) 0x5c, (byte) 0x78, (byte) 0x5d, (byte) 0x47, - (byte) 0xd9, (byte) 0xf0, (byte) 0x1a, (byte) 0xeb, (byte) 0x02, (byte) 0x03, - (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3, (byte) 0x50, (byte) 0x30, - (byte) 0x4e, (byte) 0x30, (byte) 0x1d, (byte) 0x06, (byte) 0x03, (byte) 0x55, - (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16, (byte) 0x04, (byte) 0x14, - (byte) 0x5f, (byte) 0x5b, (byte) 0x5e, (byte) 0xac, (byte) 0x29, (byte) 0xfa, - (byte) 0xa1, (byte) 0x9f, (byte) 0x9e, (byte) 0xad, (byte) 0x46, (byte) 0xe1, - (byte) 0xbc, (byte) 0x20, (byte) 0x72, (byte) 0xcf, (byte) 0x4a, (byte) 0xd4, - (byte) 0xfa, (byte) 0xe3, (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03, - (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04, (byte) 0x18, (byte) 0x30, - (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x5f, (byte) 0x5b, (byte) 0x5e, - (byte) 0xac, (byte) 0x29, (byte) 0xfa, (byte) 0xa1, (byte) 0x9f, (byte) 0x9e, - (byte) 0xad, (byte) 0x46, (byte) 0xe1, (byte) 0xbc, (byte) 0x20, (byte) 0x72, - (byte) 0xcf, (byte) 0x4a, (byte) 0xd4, (byte) 0xfa, (byte) 0xe3, (byte) 0x30, - (byte) 0x0c, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13, - (byte) 0x04, (byte) 0x05, (byte) 0x30, (byte) 0x03, (byte) 0x01, (byte) 0x01, - (byte) 0xff, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, - (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, - (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, - (byte) 0x81, (byte) 0x00, (byte) 0xa1, (byte) 0x4a, (byte) 0xe6, (byte) 0xfc, - (byte) 0x7f, (byte) 0x17, (byte) 0xaa, (byte) 0x65, (byte) 0x4a, (byte) 0x34, - (byte) 0xde, (byte) 0x69, (byte) 0x67, (byte) 0x54, (byte) 0x4d, (byte) 0xa2, - (byte) 0xc2, (byte) 0x98, (byte) 0x02, (byte) 0x43, (byte) 0x6a, (byte) 0x0e, - (byte) 0x0b, (byte) 0x7f, (byte) 0xa4, (byte) 0x46, (byte) 0xaf, (byte) 0xa4, - (byte) 0x65, (byte) 0xa0, (byte) 0xdb, (byte) 0xf1, (byte) 0x5b, (byte) 0xd5, - (byte) 0x09, (byte) 0xbc, (byte) 0xee, (byte) 0x37, (byte) 0x51, (byte) 0x19, - (byte) 0x36, (byte) 0xc0, (byte) 0x90, (byte) 0xd3, (byte) 0x5f, (byte) 0xf3, - (byte) 0x4f, (byte) 0xb9, (byte) 0x08, (byte) 0x45, (byte) 0x0e, (byte) 0x01, - (byte) 0x8a, (byte) 0x95, (byte) 0xef, (byte) 0x92, (byte) 0x95, (byte) 0x33, - (byte) 0x78, (byte) 0xdd, (byte) 0x90, (byte) 0xbb, (byte) 0xf3, (byte) 0x06, - (byte) 0x75, (byte) 0xd0, (byte) 0x66, (byte) 0xe6, (byte) 0xd0, (byte) 0x18, - (byte) 0x6e, (byte) 0xeb, (byte) 0x1c, (byte) 0x52, (byte) 0xc3, (byte) 0x2e, - (byte) 0x57, (byte) 0x7d, (byte) 0xa9, (byte) 0x03, (byte) 0xdb, (byte) 0xf4, - (byte) 0x57, (byte) 0x5f, (byte) 0x6c, (byte) 0x7e, (byte) 0x00, (byte) 0x0d, - (byte) 0x8f, (byte) 0xe8, (byte) 0x91, (byte) 0xf7, (byte) 0xae, (byte) 0x24, - (byte) 0x35, (byte) 0x07, (byte) 0xb5, (byte) 0x48, (byte) 0x2d, (byte) 0x36, - (byte) 0x30, (byte) 0x5d, (byte) 0xe9, (byte) 0x49, (byte) 0x2d, (byte) 0xd1, - (byte) 0x5d, (byte) 0xc5, (byte) 0xf4, (byte) 0x33, (byte) 0x77, (byte) 0x3c, - (byte) 0x71, (byte) 0xad, (byte) 0x90, (byte) 0x65, (byte) 0xa9, (byte) 0xc1, - (byte) 0x0b, (byte) 0x5c, (byte) 0x62, (byte) 0x55, (byte) 0x50, (byte) 0x6f, - (byte) 0x9b, (byte) 0xc9, (byte) 0x0d, (byte) 0xee - }; - - /** - * Generated from above and converted with: - * - * openssl pkcs8 -topk8 -outform d -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g' - */ - private static final byte[] FAKE_EC_KEY_1 = new byte[] { - (byte) 0x30, (byte) 0x81, (byte) 0x87, (byte) 0x02, (byte) 0x01, (byte) 0x00, - (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a, (byte) 0x86, - (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06, - (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, - (byte) 0x03, (byte) 0x01, (byte) 0x07, (byte) 0x04, (byte) 0x6d, (byte) 0x30, - (byte) 0x6b, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x04, (byte) 0x20, - (byte) 0x3a, (byte) 0x8a, (byte) 0x02, (byte) 0xdc, (byte) 0xde, (byte) 0x70, - (byte) 0x84, (byte) 0x45, (byte) 0x34, (byte) 0xaf, (byte) 0xbd, (byte) 0xd5, - (byte) 0x02, (byte) 0x17, (byte) 0x69, (byte) 0x90, (byte) 0x65, (byte) 0x1e, - (byte) 0x87, (byte) 0xf1, (byte) 0x3d, (byte) 0x17, (byte) 0xb6, (byte) 0xf4, - (byte) 0x31, (byte) 0x94, (byte) 0x86, (byte) 0x76, (byte) 0x55, (byte) 0xf7, - (byte) 0xcc, (byte) 0xba, (byte) 0xa1, (byte) 0x44, (byte) 0x03, (byte) 0x42, - (byte) 0x00, (byte) 0x04, (byte) 0xd9, (byte) 0xcf, (byte) 0xe7, (byte) 0x9b, - (byte) 0x23, (byte) 0xc8, (byte) 0xa3, (byte) 0xb8, (byte) 0x33, (byte) 0x14, - (byte) 0xa4, (byte) 0x4d, (byte) 0x75, (byte) 0x90, (byte) 0xf3, (byte) 0xcd, - (byte) 0x43, (byte) 0xe5, (byte) 0x1b, (byte) 0x05, (byte) 0x1d, (byte) 0xf3, - (byte) 0xd0, (byte) 0xa3, (byte) 0xb7, (byte) 0x32, (byte) 0x5f, (byte) 0x79, - (byte) 0xdc, (byte) 0x88, (byte) 0xb8, (byte) 0x4d, (byte) 0xb3, (byte) 0xd1, - (byte) 0x6d, (byte) 0xf7, (byte) 0x75, (byte) 0xf3, (byte) 0xbf, (byte) 0x50, - (byte) 0xa1, (byte) 0xbc, (byte) 0x03, (byte) 0x64, (byte) 0x22, (byte) 0xe6, - (byte) 0x1a, (byte) 0xa1, (byte) 0xe1, (byte) 0x06, (byte) 0x68, (byte) 0x3b, - (byte) 0xbc, (byte) 0x9f, (byte) 0xd3, (byte) 0xae, (byte) 0x77, (byte) 0x5e, - (byte) 0x88, (byte) 0x0c, (byte) 0x5e, (byte) 0x0c, (byte) 0xb2, (byte) 0x38 - }; - - /** - * Generated from above and converted with: - * - * openssl x509 -outform d -in usercert.pem | xxd -i | sed 's/0x/(byte) 0x/g' - */ - private static final byte[] FAKE_EC_USER_1 = new byte[] { - (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x51, (byte) 0x30, (byte) 0x82, - (byte) 0x01, (byte) 0xba, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01, - (byte) 0x02, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x0d, - (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, - (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, - (byte) 0x00, (byte) 0x30, (byte) 0x45, (byte) 0x31, (byte) 0x0b, (byte) 0x30, - (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, - (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, (byte) 0x31, (byte) 0x13, - (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, - (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, (byte) 0x6f, (byte) 0x6d, - (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, (byte) 0x61, (byte) 0x74, - (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, (byte) 0x1f, (byte) 0x06, - (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x0c, (byte) 0x18, - (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x6e, - (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, (byte) 0x69, (byte) 0x64, - (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, (byte) 0x20, (byte) 0x50, - (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, (byte) 0x74, (byte) 0x64, - (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x33, - (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x37, (byte) 0x31, (byte) 0x36, - (byte) 0x33, (byte) 0x30, (byte) 0x30, (byte) 0x38, (byte) 0x5a, (byte) 0x17, - (byte) 0x0d, (byte) 0x32, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32, - (byte) 0x35, (byte) 0x31, (byte) 0x36, (byte) 0x33, (byte) 0x30, (byte) 0x30, - (byte) 0x38, (byte) 0x5a, (byte) 0x30, (byte) 0x62, (byte) 0x31, (byte) 0x0b, - (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, - (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, (byte) 0x31, - (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55, - (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, (byte) 0x6f, - (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, (byte) 0x61, - (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, (byte) 0x1f, - (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x0c, - (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, (byte) 0x72, - (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, (byte) 0x69, - (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, (byte) 0x20, - (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, (byte) 0x74, - (byte) 0x64, (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, - (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x0c, (byte) 0x12, - (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76, (byte) 0x65, (byte) 0x72, - (byte) 0x2e, (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70, - (byte) 0x6c, (byte) 0x65, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, (byte) 0x6d, - (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, - (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, - (byte) 0x01, (byte) 0x06, (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, - (byte) 0xce, (byte) 0x3d, (byte) 0x03, (byte) 0x01, (byte) 0x07, (byte) 0x03, - (byte) 0x42, (byte) 0x00, (byte) 0x04, (byte) 0xd9, (byte) 0xcf, (byte) 0xe7, - (byte) 0x9b, (byte) 0x23, (byte) 0xc8, (byte) 0xa3, (byte) 0xb8, (byte) 0x33, - (byte) 0x14, (byte) 0xa4, (byte) 0x4d, (byte) 0x75, (byte) 0x90, (byte) 0xf3, - (byte) 0xcd, (byte) 0x43, (byte) 0xe5, (byte) 0x1b, (byte) 0x05, (byte) 0x1d, - (byte) 0xf3, (byte) 0xd0, (byte) 0xa3, (byte) 0xb7, (byte) 0x32, (byte) 0x5f, - (byte) 0x79, (byte) 0xdc, (byte) 0x88, (byte) 0xb8, (byte) 0x4d, (byte) 0xb3, - (byte) 0xd1, (byte) 0x6d, (byte) 0xf7, (byte) 0x75, (byte) 0xf3, (byte) 0xbf, - (byte) 0x50, (byte) 0xa1, (byte) 0xbc, (byte) 0x03, (byte) 0x64, (byte) 0x22, - (byte) 0xe6, (byte) 0x1a, (byte) 0xa1, (byte) 0xe1, (byte) 0x06, (byte) 0x68, - (byte) 0x3b, (byte) 0xbc, (byte) 0x9f, (byte) 0xd3, (byte) 0xae, (byte) 0x77, - (byte) 0x5e, (byte) 0x88, (byte) 0x0c, (byte) 0x5e, (byte) 0x0c, (byte) 0xb2, - (byte) 0x38, (byte) 0xa3, (byte) 0x7b, (byte) 0x30, (byte) 0x79, (byte) 0x30, - (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13, - (byte) 0x04, (byte) 0x02, (byte) 0x30, (byte) 0x00, (byte) 0x30, (byte) 0x2c, - (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01, - (byte) 0x86, (byte) 0xf8, (byte) 0x42, (byte) 0x01, (byte) 0x0d, (byte) 0x04, - (byte) 0x1f, (byte) 0x16, (byte) 0x1d, (byte) 0x4f, (byte) 0x70, (byte) 0x65, - (byte) 0x6e, (byte) 0x53, (byte) 0x53, (byte) 0x4c, (byte) 0x20, (byte) 0x47, - (byte) 0x65, (byte) 0x6e, (byte) 0x65, (byte) 0x72, (byte) 0x61, (byte) 0x74, - (byte) 0x65, (byte) 0x64, (byte) 0x20, (byte) 0x43, (byte) 0x65, (byte) 0x72, - (byte) 0x74, (byte) 0x69, (byte) 0x66, (byte) 0x69, (byte) 0x63, (byte) 0x61, - (byte) 0x74, (byte) 0x65, (byte) 0x30, (byte) 0x1d, (byte) 0x06, (byte) 0x03, - (byte) 0x55, (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16, (byte) 0x04, - (byte) 0x14, (byte) 0xd5, (byte) 0xc4, (byte) 0x72, (byte) 0xbd, (byte) 0xd2, - (byte) 0x4e, (byte) 0x90, (byte) 0x1b, (byte) 0x14, (byte) 0x32, (byte) 0xdb, - (byte) 0x03, (byte) 0xae, (byte) 0xfa, (byte) 0x27, (byte) 0x7d, (byte) 0x8d, - (byte) 0xe4, (byte) 0x80, (byte) 0x58, (byte) 0x30, (byte) 0x1f, (byte) 0x06, - (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04, (byte) 0x18, - (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x5f, (byte) 0x5b, - (byte) 0x5e, (byte) 0xac, (byte) 0x29, (byte) 0xfa, (byte) 0xa1, (byte) 0x9f, - (byte) 0x9e, (byte) 0xad, (byte) 0x46, (byte) 0xe1, (byte) 0xbc, (byte) 0x20, - (byte) 0x72, (byte) 0xcf, (byte) 0x4a, (byte) 0xd4, (byte) 0xfa, (byte) 0xe3, - (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, - (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, - (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x81, - (byte) 0x00, (byte) 0x43, (byte) 0x99, (byte) 0x9f, (byte) 0x67, (byte) 0x08, - (byte) 0x43, (byte) 0xd5, (byte) 0x6b, (byte) 0x6f, (byte) 0xd7, (byte) 0x05, - (byte) 0xd6, (byte) 0x75, (byte) 0x34, (byte) 0x30, (byte) 0xca, (byte) 0x20, - (byte) 0x47, (byte) 0x61, (byte) 0xa1, (byte) 0x89, (byte) 0xb6, (byte) 0xf1, - (byte) 0x49, (byte) 0x7b, (byte) 0xd9, (byte) 0xb9, (byte) 0xe8, (byte) 0x1e, - (byte) 0x29, (byte) 0x74, (byte) 0x0a, (byte) 0x67, (byte) 0xc0, (byte) 0x7d, - (byte) 0xb8, (byte) 0xe6, (byte) 0x39, (byte) 0xa8, (byte) 0x5e, (byte) 0xc3, - (byte) 0xb0, (byte) 0xa1, (byte) 0x30, (byte) 0x6a, (byte) 0x1f, (byte) 0x1d, - (byte) 0xfc, (byte) 0x11, (byte) 0x59, (byte) 0x0b, (byte) 0xb9, (byte) 0xad, - (byte) 0x3a, (byte) 0x4e, (byte) 0x50, (byte) 0x0a, (byte) 0x61, (byte) 0xdb, - (byte) 0x75, (byte) 0x6b, (byte) 0xe5, (byte) 0x3f, (byte) 0x8d, (byte) 0xde, - (byte) 0x28, (byte) 0x68, (byte) 0xb1, (byte) 0x29, (byte) 0x9a, (byte) 0x18, - (byte) 0x8a, (byte) 0xfc, (byte) 0x3f, (byte) 0x13, (byte) 0x93, (byte) 0x29, - (byte) 0xed, (byte) 0x22, (byte) 0x7c, (byte) 0xb4, (byte) 0x50, (byte) 0xd5, - (byte) 0x4d, (byte) 0x32, (byte) 0x4d, (byte) 0x42, (byte) 0x2b, (byte) 0x29, - (byte) 0x97, (byte) 0x86, (byte) 0xc0, (byte) 0x01, (byte) 0x00, (byte) 0x25, - (byte) 0xf6, (byte) 0xd3, (byte) 0x2a, (byte) 0xd8, (byte) 0xda, (byte) 0x13, - (byte) 0x94, (byte) 0x12, (byte) 0x78, (byte) 0x14, (byte) 0x0b, (byte) 0x51, - (byte) 0xc0, (byte) 0x45, (byte) 0xb4, (byte) 0x02, (byte) 0x37, (byte) 0x98, - (byte) 0x42, (byte) 0x3c, (byte) 0xcb, (byte) 0x2e, (byte) 0xe4, (byte) 0x38, - (byte) 0x69, (byte) 0x1b, (byte) 0x72, (byte) 0xf0, (byte) 0xaa, (byte) 0x89, - (byte) 0x7e, (byte) 0xde, (byte) 0xb2 - }; - - /** - * The amount of time to allow before and after expected time for variance - * in timing tests. - */ - private static final long SLOP_TIME_MILLIS = 15000L; - - @Override - protected void setUp() throws Exception { - mAndroidKeyStore = android.security.KeyStore.getInstance(); - - assertTrue(mAndroidKeyStore.reset()); - assertFalse(mAndroidKeyStore.isUnlocked()); - - mKeyStore = java.security.KeyStore.getInstance("AndroidKeyStore"); - } - - private void setupPassword() { - assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111")); - assertTrue(mAndroidKeyStore.isUnlocked()); - - assertEquals(0, mAndroidKeyStore.saw("").length); - } - - private void assertAliases(final String[] expectedAliases) throws KeyStoreException { - final Enumeration aliases = mKeyStore.aliases(); - int count = 0; - - final Set expectedSet = new HashSet(); - expectedSet.addAll(Arrays.asList(expectedAliases)); - - while (aliases.hasMoreElements()) { - count++; - final String alias = aliases.nextElement(); - assertTrue("The alias should be in the expected set", expectedSet.contains(alias)); - expectedSet.remove(alias); - } - assertTrue("The expected set and actual set should be exactly equal", expectedSet.isEmpty()); - assertEquals("There should be the correct number of keystore entries", - expectedAliases.length, count); - } - - public void testKeyStore_Aliases_Encrypted_Success() throws Exception { - setupPassword(); - - mKeyStore.load(null, null); - - assertAliases(new String[] {}); - - assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, - null)); - - assertAliases(new String[] { TEST_ALIAS_1 }); - - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 }); - } - - public void testKeyStore_Aliases_NotInitialized_Encrypted_Failure() throws Exception { - setupPassword(); - - try { - mKeyStore.aliases(); - fail("KeyStore should throw exception when not initialized"); - } catch (KeyStoreException success) { - } - } - - public void testKeyStore_ContainsAliases_PrivateAndCA_Encrypted_Success() throws Exception { - setupPassword(); - - mKeyStore.load(null, null); - - assertAliases(new String[] {}); - - assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, - null)); - - assertTrue("Should contain generated private key", mKeyStore.containsAlias(TEST_ALIAS_1)); - - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2)); - - assertFalse("Should not contain unadded certificate alias", - mKeyStore.containsAlias(TEST_ALIAS_3)); - } - - public void testKeyStore_ContainsAliases_CAOnly_Encrypted_Success() throws Exception { - setupPassword(); - - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2)); - } - - 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_Encrypted_Success() throws Exception { - setupPassword(); - - mKeyStore.load(null, null); - - // TEST_ALIAS_1 - assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - // TEST_ALIAS_2 - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - // TEST_ALIAS_3 - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_3, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 }); - - mKeyStore.deleteEntry(TEST_ALIAS_1); - - assertAliases(new String[] { TEST_ALIAS_2, TEST_ALIAS_3 }); - - mKeyStore.deleteEntry(TEST_ALIAS_3); - - assertAliases(new String[] { TEST_ALIAS_2 }); - - mKeyStore.deleteEntry(TEST_ALIAS_2); - - assertAliases(new String[] { }); - } - - 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_Encrypted_Success() throws Exception { - setupPassword(); - - mKeyStore.load(null, null); - - // TEST_ALIAS_1 - assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - // Should not throw when a non-existent entry is requested for delete. - mKeyStore.deleteEntry(TEST_ALIAS_2); - } - - public void testKeyStore_GetCertificate_Single_Encrypted_Success() throws Exception { - setupPassword(); - - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - assertAliases(new String[] { TEST_ALIAS_1 }); - - assertNull("Certificate should not exist in keystore", - mKeyStore.getCertificate(TEST_ALIAS_2)); - - Certificate retrieved = mKeyStore.getCertificate(TEST_ALIAS_1); - - assertNotNull("Retrieved certificate should not be null", retrieved); - - CertificateFactory f = CertificateFactory.getInstance("X.509"); - Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); - - assertEquals("Actual and retrieved certificates should be the same", actual, retrieved); - } - - 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_Encrypted_Success() throws Exception { - setupPassword(); - - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - CertificateFactory f = CertificateFactory.getInstance("X.509"); - Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); - - assertEquals("Stored certificate alias should be found", TEST_ALIAS_1, - mKeyStore.getCertificateAlias(actual)); - } - - public void testKeyStore_GetCertificateAlias_PrivateKeyEntry_Encrypted_Success() - throws Exception { - setupPassword(); - - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - CertificateFactory f = CertificateFactory.getInstance("X.509"); - Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); - - assertEquals("Stored certificate alias should be found", TEST_ALIAS_1, - mKeyStore.getCertificateAlias(actual)); - } - - public void testKeyStore_GetCertificateAlias_CAEntry_WithPrivateKeyUsingCA_Encrypted_Success() - throws Exception { - setupPassword(); - - mKeyStore.load(null, null); - - // Insert TrustedCertificateEntry with CA name - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - // Insert PrivateKeyEntry that uses the same CA - assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - CertificateFactory f = CertificateFactory.getInstance("X.509"); - Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); - - assertEquals("Stored certificate alias should be found", TEST_ALIAS_2, - mKeyStore.getCertificateAlias(actual)); - } - - public void testKeyStore_GetCertificateAlias_NonExist_Empty_Encrypted_Failure() - throws Exception { - setupPassword(); - - mKeyStore.load(null, null); - - CertificateFactory f = CertificateFactory.getInstance("X.509"); - Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); - - assertNull("Stored certificate alias should not be found", - mKeyStore.getCertificateAlias(actual)); - } - - public void testKeyStore_GetCertificateAlias_NonExist_Encrypted_Failure() throws Exception { - setupPassword(); - - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - CertificateFactory f = CertificateFactory.getInstance("X.509"); - Certificate userCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); - - assertNull("Stored certificate alias should be found", - mKeyStore.getCertificateAlias(userCert)); - } - - public void testKeyStore_GetCertificateChain_SingleLength_Encrypted_Success() throws Exception { - setupPassword(); - - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - Certificate[] expected = new Certificate[2]; - expected[0] = cf.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); - expected[1] = cf.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); - - Certificate[] actual = mKeyStore.getCertificateChain(TEST_ALIAS_1); - - assertNotNull("Returned certificate chain should not be null", actual); - assertEquals("Returned certificate chain should be correct size", expected.length, - actual.length); - assertEquals("First certificate should be user certificate", expected[0], actual[0]); - assertEquals("Second certificate should be CA certificate", expected[1], actual[1]); - - // Negative test when keystore is populated. - assertNull("Stored certificate alias should not be found", - mKeyStore.getCertificateChain(TEST_ALIAS_2)); - } - - 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_Encrypted_Success() throws Exception { - setupPassword(); - - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - 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_PrivateKeyEntry_Unencrypted_Success() throws Exception { - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, - KeyStore.UID_SELF, KeyStore.FLAG_NONE)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_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_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - Date now = new Date(); - Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1); - assertNotNull("Certificate should be found", actual); - - 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_GetEntry_NullParams_Encrypted_Success() throws Exception { - setupPassword(); - - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - 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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, - FAKE_RSA_CA_1); - } - - public void testKeyStore_GetEntry_EC_NullParams_Unencrypted_Success() throws Exception { - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_EC_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, - FAKE_EC_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_EC_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, "EC", FAKE_EC_KEY_1, FAKE_EC_USER_1, FAKE_EC_CA_1); - } - - public void testKeyStore_GetEntry_RSA_NullParams_Unencrypted_Success() throws Exception { - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, - FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, - FAKE_RSA_CA_1); - } - - @SuppressWarnings("unchecked") - private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, String keyType, byte[] key, - byte[] cert, byte[] ca) throws Exception { - KeyFactory keyFact = KeyFactory.getInstance(keyType); - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(key)); - - CertificateFactory certFact = CertificateFactory.getInstance("X.509"); - Certificate expectedCert = certFact.generateCertificate(new ByteArrayInputStream(cert)); - - final Collection expectedChain; - if (ca != null) { - expectedChain = (Collection) certFact - .generateCertificates(new ByteArrayInputStream(ca)); - } else { - expectedChain = null; - } - - assertPrivateKeyEntryEquals(keyEntry, expectedKey, expectedCert, expectedChain); - } - - private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, PrivateKey expectedKey, - Certificate expectedCert, Collection expectedChain) throws Exception { - if (expectedKey instanceof ECPrivateKey) { - assertEquals("Returned PrivateKey should be what we inserted", - ((ECPrivateKey) expectedKey).getParams().getCurve(), - ((ECPublicKey) keyEntry.getCertificate().getPublicKey()).getParams().getCurve()); - } else if (expectedKey instanceof RSAPrivateKey) { - 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()); - - Certificate[] actualChain = keyEntry.getCertificateChain(); - - assertEquals("First certificate in chain should be user cert", expectedCert, actualChain[0]); - - if (expectedChain == null) { - assertEquals("Certificate chain should not include CAs", 1, actualChain.length); - } else { - int i = 1; - final Iterator it = expectedChain.iterator(); - while (it.hasNext()) { - assertEquals("CA chain certificate should equal what we put in", it.next(), - actualChain[i++]); - } - } - } - - 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_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, - FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - 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_RSA_KEY_1)); - - assertEquals("Inserted key should be same as retrieved key", - ((RSAPrivateKey) expectedKey).getModulus(), actualKey.getModulus()); - } - - public void testKeyStore_GetKey_NoPassword_Unencrypted_Success() throws Exception { - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, - KeyStore.UID_SELF, KeyStore.FLAG_NONE)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_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_RSA_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_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - assertNull("Certificate entries should return null", mKeyStore.getKey(TEST_ALIAS_1, null)); - } - - 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_Encrypted_Success() throws Exception { - assertEquals(AndroidKeyStoreProvider.PROVIDER_NAME, mKeyStore.getProvider().getName()); - setupPassword(); - assertEquals(AndroidKeyStoreProvider.PROVIDER_NAME, mKeyStore.getProvider().getName()); - } - - public void testKeyStore_GetType_Encrypted_Success() throws Exception { - assertEquals(AndroidKeyStore.NAME, mKeyStore.getType()); - setupPassword(); - assertEquals(AndroidKeyStore.NAME, mKeyStore.getType()); - } - - public void testKeyStore_IsCertificateEntry_CA_Encrypted_Success() throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - assertTrue("Should return true for CA certificate", - mKeyStore.isCertificateEntry(TEST_ALIAS_1)); - } - - public void testKeyStore_IsCertificateEntry_PrivateKey_Encrypted_Failure() throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - assertFalse("Should return false for PrivateKeyEntry", - mKeyStore.isCertificateEntry(TEST_ALIAS_1)); - } - - 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_Encrypted_Success() throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - assertTrue("Should return true for PrivateKeyEntry", mKeyStore.isKeyEntry(TEST_ALIAS_1)); - } - - public void testKeyStore_IsKeyEntry_CA_Encrypted_Failure() throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - assertFalse("Should return false for CA certificate", mKeyStore.isKeyEntry(TEST_ALIAS_1)); - } - - 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_Encrypted_Success() throws Exception { - final CertificateFactory f = CertificateFactory.getInstance("X.509"); - final Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); - - setupPassword(); - mKeyStore.load(null, null); - - mKeyStore.setCertificateEntry(TEST_ALIAS_1, actual); - assertAliases(new String[] { TEST_ALIAS_1 }); - - Certificate retrieved = mKeyStore.getCertificate(TEST_ALIAS_1); - - assertEquals("Retrieved certificate should be the same as the one inserted", actual, - retrieved); - } - - 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_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - assertAliases(new String[] { TEST_ALIAS_1 }); - - final CertificateFactory f = CertificateFactory.getInstance("X.509"); - final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); - - // TODO have separate FAKE_CA for second test - mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert); - - assertAliases(new String[] { TEST_ALIAS_1 }); - } - - public void testKeyStore_SetCertificate_PrivateKeyExists_Encrypted_Failure() throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - assertAliases(new String[] { TEST_ALIAS_1 }); - - final CertificateFactory f = CertificateFactory.getInstance("X.509"); - final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); - - try { - mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert); - fail("Should throw when trying to overwrite a PrivateKey entry with a Certificate"); - } catch (KeyStoreException success) { - } - } - - public void testKeyStore_SetEntry_PrivateKeyEntry_Encrypted_Success() throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); - - final CertificateFactory f = CertificateFactory.getInstance("X.509"); - - final Certificate[] expectedChain = new Certificate[2]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); - expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1); - } - - public void testKeyStore_SetEntry_PrivateKeyEntry_EC_Unencrypted_Success() throws Exception { - mKeyStore.load(null, null); - - KeyFactory keyFact = KeyFactory.getInstance("EC"); - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_EC_KEY_1)); - - final CertificateFactory f = CertificateFactory.getInstance("X.509"); - - final Certificate[] expectedChain = new Certificate[2]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_EC_USER_1)); - expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_EC_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, "EC", FAKE_EC_KEY_1, FAKE_EC_USER_1, FAKE_EC_CA_1); - } - - public void testKeyStore_SetEntry_PrivateKeyEntry_RSA_Unencrypted_Success() throws Exception { - mKeyStore.load(null, null); - - KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); - - final CertificateFactory f = CertificateFactory.getInstance("X.509"); - - final Certificate[] expectedChain = new Certificate[2]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); - expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_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_RSA_KEY_1)); - - final CertificateFactory f = CertificateFactory.getInstance("X.509"); - - final Certificate[] expectedChain = new Certificate[2]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); - expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); - - PrivateKeyEntry entry = new PrivateKeyEntry(expectedKey, expectedChain); - - try { - mKeyStore.setEntry(TEST_ALIAS_1, entry, - new KeyStoreParameter.Builder(getContext()) - .setEncryptionRequired(true) - .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"); - final CertificateFactory f = CertificateFactory.getInstance("X.509"); - - // Start with PrivateKeyEntry - { - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); - - final Certificate[] expectedChain = new Certificate[2]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); - expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, - FAKE_RSA_CA_1); - } - - // TODO make entirely new test vector for the overwrite - // Replace with PrivateKeyEntry - { - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); - - final Certificate[] expectedChain = new Certificate[2]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); - expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, - FAKE_RSA_CA_1); - } - } - - public void testKeyStore_SetEntry_CAEntry_Overwrites_PrivateKeyEntry_Encrypted_Success() - throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - final CertificateFactory f = CertificateFactory.getInstance("X.509"); - - // Start with TrustedCertificateEntry - { - final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); - - TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert); - mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null); - - Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null); - assertNotNull("Retrieved entry should exist", actualEntry); - assertTrue("Retrieved entry should be of type TrustedCertificateEntry", - actualEntry instanceof TrustedCertificateEntry); - TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry; - assertEquals("Stored and retrieved certificates should be the same", - expectedCertEntry.getTrustedCertificate(), - actualCertEntry.getTrustedCertificate()); - } - - // Replace with PrivateKeyEntry - { - KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); - final Certificate[] expectedChain = new Certificate[2]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); - expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); - - PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain); - - mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, 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 actualPrivEntry = (PrivateKeyEntry) actualEntry; - assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, - FAKE_RSA_CA_1); - } - } - - public void testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_CAEntry_Encrypted_Success() - throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - final CertificateFactory f = CertificateFactory.getInstance("X.509"); - - final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); - - // Start with PrivateKeyEntry - { - KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); - final Certificate[] expectedChain = new Certificate[2]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); - expectedChain[1] = caCert; - - PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain); - - mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, 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 actualPrivEntry = (PrivateKeyEntry) actualEntry; - assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, - FAKE_RSA_CA_1); - } - - // Replace with TrustedCertificateEntry - { - TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert); - mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null); - - Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null); - assertNotNull("Retrieved entry should exist", actualEntry); - assertTrue("Retrieved entry should be of type TrustedCertificateEntry", - actualEntry instanceof TrustedCertificateEntry); - TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry; - assertEquals("Stored and retrieved certificates should be the same", - expectedCertEntry.getTrustedCertificate(), - actualCertEntry.getTrustedCertificate()); - } - } - - public - void - testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_ShortPrivateKeyEntry_Encrypted_Success() - throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - final CertificateFactory f = CertificateFactory.getInstance("X.509"); - - final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); - - // Start with PrivateKeyEntry - { - KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); - final Certificate[] expectedChain = new Certificate[2]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); - expectedChain[1] = caCert; - - PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain); - - mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, 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 actualPrivEntry = (PrivateKeyEntry) actualEntry; - assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, - FAKE_RSA_CA_1); - } - - // Replace with PrivateKeyEntry that has no chain - { - KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); - final Certificate[] expectedChain = new Certificate[1]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); - - PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain); - - mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, 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 actualPrivEntry = (PrivateKeyEntry) actualEntry; - assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, - null); - } - } - - public void testKeyStore_SetEntry_CAEntry_Overwrites_CAEntry_Encrypted_Success() - throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - final CertificateFactory f = CertificateFactory.getInstance("X.509"); - - // Insert TrustedCertificateEntry - { - final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); - - TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert); - mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null); - - Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null); - assertNotNull("Retrieved entry should exist", actualEntry); - assertTrue("Retrieved entry should be of type TrustedCertificateEntry", - actualEntry instanceof TrustedCertificateEntry); - TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry; - assertEquals("Stored and retrieved certificates should be the same", - expectedCertEntry.getTrustedCertificate(), - actualCertEntry.getTrustedCertificate()); - } - - // Replace with TrustedCertificateEntry of USER - { - final Certificate userCert = f - .generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); - - TrustedCertificateEntry expectedUserEntry = new TrustedCertificateEntry(userCert); - mKeyStore.setEntry(TEST_ALIAS_1, expectedUserEntry, null); - - Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null); - assertNotNull("Retrieved entry should exist", actualEntry); - assertTrue("Retrieved entry should be of type TrustedCertificateEntry", - actualEntry instanceof TrustedCertificateEntry); - TrustedCertificateEntry actualUserEntry = (TrustedCertificateEntry) actualEntry; - assertEquals("Stored and retrieved certificates should be the same", - expectedUserEntry.getTrustedCertificate(), - actualUserEntry.getTrustedCertificate()); - } - } - - public void testKeyStore_SetKeyEntry_ProtectedKey_Encrypted_Failure() throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - final CertificateFactory f = CertificateFactory.getInstance("X.509"); - - final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); - - KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); - final Certificate[] chain = new Certificate[2]; - chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); - chain[1] = caCert; - - try { - mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, "foo".toCharArray(), chain); - fail("Should fail when a password is specified"); - } catch (KeyStoreException success) { - } - } - - public void testKeyStore_SetKeyEntry_Encrypted_Success() throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - final CertificateFactory f = CertificateFactory.getInstance("X.509"); - - final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); - - KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); - final Certificate[] chain = new Certificate[2]; - chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); - chain[1] = caCert; - - mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain); - - 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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1); - } - - public void testKeyStore_SetKeyEntry_Replaced_Encrypted_Success() throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - final CertificateFactory f = CertificateFactory.getInstance("X.509"); - - final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); - - // Insert initial key - { - KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); - final Certificate[] chain = new Certificate[2]; - chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); - chain[1] = caCert; - - mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain); - - 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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, - FAKE_RSA_CA_1); - } - - // TODO make a separate key - // Replace key - { - KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); - final Certificate[] chain = new Certificate[2]; - chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); - chain[1] = caCert; - - mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain); - - 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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, - FAKE_RSA_CA_1); - } - } - - @SuppressWarnings("deprecation") - private static X509Certificate generateCertificate(android.security.KeyStore keyStore, - String alias, BigInteger serialNumber, X500Principal subjectDN, Date notBefore, - Date notAfter) throws Exception { - final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; - - 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 = keyStore.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(serialNumber); - certGen.setSubjectDN(subjectDN); - certGen.setIssuerDN(subjectDN); - certGen.setNotBefore(notBefore); - certGen.setNotAfter(notAfter); - certGen.setSignatureAlgorithm("sha1WithRSA"); - - final X509Certificate cert = certGen.generate(privKey); - - return cert; - } - - public void testKeyStore_SetKeyEntry_ReplacedChain_Encrypted_Success() throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - // Create key #1 - { - final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1; - assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, - NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null)); - - Key key = mKeyStore.getKey(TEST_ALIAS_1, null); - - assertTrue(key instanceof PrivateKey); - - PrivateKey expectedKey = (PrivateKey) key; - - X509Certificate expectedCert = 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, - expectedCert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null); - - assertTrue(entry instanceof PrivateKeyEntry); - - PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry; - - assertPrivateKeyEntryEquals(keyEntry, expectedKey, expectedCert, null); - } - - // Replace key #1 with new chain - { - Key key = mKeyStore.getKey(TEST_ALIAS_1, null); - - assertTrue(key instanceof PrivateKey); - - PrivateKey expectedKey = (PrivateKey) key; - - X509Certificate expectedCert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1, - TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS); - - mKeyStore.setKeyEntry(TEST_ALIAS_1, expectedKey, null, - new Certificate[] { expectedCert }); - - Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null); - - assertTrue(entry instanceof PrivateKeyEntry); - - PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry; - - assertPrivateKeyEntryEquals(keyEntry, expectedKey, expectedCert, null); - } - } - - public void testKeyStore_SetKeyEntry_ReplacedChain_DifferentPrivateKey_Encrypted_Failure() - throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - // Create key #1 - { - final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1; - assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, - NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null)); - - 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(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - } - - // Create key #2 - { - final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_2; - assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, - NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null)); - - X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_2, - TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS); - - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_2, - cert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - } - - // Replace key #1 with key #2 - { - Key key1 = mKeyStore.getKey(TEST_ALIAS_2, null); - - X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_2, - TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS); - - try { - mKeyStore.setKeyEntry(TEST_ALIAS_1, key1, null, new Certificate[] { cert }); - fail("Should not allow setting of KeyEntry with wrong PrivaetKey"); - } catch (KeyStoreException success) { - } - } - } - - 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, NativeConstants.EVP_PKEY_RSA, 1024, - android.security.KeyStore.FLAG_NONE, null)); - - 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 KeyStoreParameter.Builder(getContext()) - .setEncryptionRequired(true) - .build()); - fail("Should not allow setting of Entry without unlocked keystore"); - } catch (KeyStoreException success) { - } - - assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111")); - assertTrue(mAndroidKeyStore.isUnlocked()); - - mKeyStore.setEntry(TEST_ALIAS_1, entry, - new KeyStoreParameter.Builder(getContext()) - .setEncryptionRequired(true) - .build()); - } - } - - public void testKeyStore_Size_Encrypted_Success() throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - assertEquals("The keystore size should match expected", 1, mKeyStore.size()); - assertAliases(new String[] { TEST_ALIAS_1 }); - - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - - assertEquals("The keystore size should match expected", 2, mKeyStore.size()); - assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 }); - - assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3, - KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, - null)); - - assertEquals("The keystore size should match expected", 3, mKeyStore.size()); - assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 }); - - assertTrue(mAndroidKeyStore.delete(Credentials.CA_CERTIFICATE + TEST_ALIAS_1)); - - assertEquals("The keystore size should match expected", 2, mKeyStore.size()); - assertAliases(new String[] { TEST_ALIAS_2, TEST_ALIAS_3 }); - - assertTrue(mAndroidKeyStore.delete(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3)); - - assertEquals("The keystore size should match expected", 1, mKeyStore.size()); - assertAliases(new String[] { TEST_ALIAS_2 }); - } - - public void testKeyStore_Store_LoadStoreParam_Encrypted_Failure() throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - try { - mKeyStore.store(null); - fail("Should throw UnsupportedOperationException when trying to store"); - } catch (UnsupportedOperationException success) { - } - } - - public void testKeyStore_Load_InputStreamSupplied_Encrypted_Failure() throws Exception { - byte[] buf = "FAKE KEYSTORE".getBytes(); - ByteArrayInputStream is = new ByteArrayInputStream(buf); - - try { - mKeyStore.load(is, null); - fail("Should throw IllegalArgumentException when InputStream is supplied"); - } catch (IllegalArgumentException success) { - } - } - - public void testKeyStore_Load_PasswordSupplied_Encrypted_Failure() throws Exception { - try { - mKeyStore.load(null, "password".toCharArray()); - fail("Should throw IllegalArgumentException when password is supplied"); - } catch (IllegalArgumentException success) { - } - } - - public void testKeyStore_Store_OutputStream_Encrypted_Failure() throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - OutputStream sink = new ByteArrayOutputStream(); - try { - mKeyStore.store(sink, null); - fail("Should throw UnsupportedOperationException when trying to store"); - } catch (UnsupportedOperationException success) { - } - - try { - mKeyStore.store(sink, "blah".toCharArray()); - fail("Should throw UnsupportedOperationException when trying to store"); - } catch (UnsupportedOperationException success) { - } - } - - private void setupKey() throws Exception { - final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1; - assertTrue(mAndroidKeyStore - .generate(privateKeyAlias, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024, - KeyStore.FLAG_ENCRYPTED, null)); - - 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(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - } - - public void testKeyStore_KeyOperations_Wrap_Encrypted_Success() throws Exception { - setupPassword(); - mKeyStore.load(null, null); - - setupKey(); - - // Test key usage - Entry e = mKeyStore.getEntry(TEST_ALIAS_1, null); - assertNotNull(e); - assertTrue(e instanceof PrivateKeyEntry); - - PrivateKeyEntry privEntry = (PrivateKeyEntry) e; - PrivateKey privKey = privEntry.getPrivateKey(); - assertNotNull(privKey); - - PublicKey pubKey = privEntry.getCertificate().getPublicKey(); - - Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding"); - c.init(Cipher.WRAP_MODE, pubKey); - - byte[] expectedKey = new byte[] { - 0x00, 0x05, (byte) 0xAA, (byte) 0x0A5, (byte) 0xFF, 0x55, 0x0A - }; - - SecretKey expectedSecret = new SecretKeySpec(expectedKey, "AES"); - - byte[] wrappedExpected = c.wrap(expectedSecret); - - c.init(Cipher.UNWRAP_MODE, privKey); - SecretKey actualSecret = (SecretKey) c.unwrap(wrappedExpected, "AES", Cipher.SECRET_KEY); - - assertEquals(Arrays.toString(expectedSecret.getEncoded()), - Arrays.toString(actualSecret.getEncoded())); - } -} diff --git a/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java b/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java new file mode 100644 index 0000000..cad4e54 --- /dev/null +++ b/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java @@ -0,0 +1,375 @@ +/* + * 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.keystore; + +import android.security.Credentials; +import android.security.KeyPairGeneratorSpec; +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.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.RSAKeyGenParameterSpec; +import java.text.SimpleDateFormat; +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()); + + assertFalse(mAndroidKeyStore.isUnlocked()); + + mGenerator = java.security.KeyPairGenerator.getInstance("RSA", "AndroidKeyStore"); + } + + private void setupPassword() { + assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111")); + assertTrue(mAndroidKeyStore.isUnlocked()); + + String[] aliases = mAndroidKeyStore.saw(""); + assertNotNull(aliases); + assertEquals(0, aliases.length); + } + + public void testKeyPairGenerator_Initialize_Params_Encrypted_Success() throws Exception { + setupPassword(); + + mGenerator.initialize(new KeyPairGeneratorSpec.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_Encrypted_Failure() throws Exception { + setupPassword(); + + try { + mGenerator.initialize(1024); + fail("KeyPairGenerator should not support setting the key size"); + } catch (IllegalArgumentException success) { + } + } + + 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"); + } catch (IllegalArgumentException success) { + } + } + + public void testKeyPairGenerator_Initialize_ParamsAndSecureRandom_Encrypted_Failure() + throws Exception { + setupPassword(); + + mGenerator.initialize( + new KeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_1) + .setKeyType("RSA") + .setKeySize(1024) + .setSubject(TEST_DN_1) + .setSerialNumber(TEST_SERIAL_1) + .setStartDate(NOW) + .setEndDate(NOW_PLUS_10_YEARS) + .setEncryptionRequired() + .build(), + new SecureRandom()); + } + + public void testKeyPairGenerator_GenerateKeyPair_Encrypted_Success() throws Exception { + setupPassword(); + + mGenerator.initialize(new KeyPairGeneratorSpec.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, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, NOW, + NOW_PLUS_10_YEARS); + } + + public void testKeyPairGenerator_GenerateKeyPair_EC_Unencrypted_Success() throws Exception { + mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_1) + .setKeyType("EC") + .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); + + assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 256, null, TEST_DN_1, TEST_SERIAL_1, NOW, + NOW_PLUS_10_YEARS); + } + + public void testKeyPairGenerator_GenerateKeyPair_EC_P521_Unencrypted_Success() throws Exception { + mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_1) + .setKeyType("EC") + .setKeySize(521) + .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); + + assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 521, null, TEST_DN_1, TEST_SERIAL_1, NOW, + NOW_PLUS_10_YEARS); + } + + public void testKeyPairGenerator_GenerateKeyPair_RSA_Unencrypted_Success() throws Exception { + mGenerator.initialize(new KeyPairGeneratorSpec.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); + + assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, NOW, + NOW_PLUS_10_YEARS); + } + + public void testKeyPairGenerator_GenerateKeyPair_RSA_WithParams_Unencrypted_Success() + throws Exception { + AlgorithmParameterSpec spec = new RSAKeyGenParameterSpec(1024, BigInteger.valueOf(3L)); + mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_1) + .setKeySize(1024) + .setAlgorithmParameterSpec(spec) + .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); + + assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 1024, spec, 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 KeyPairGeneratorSpec.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, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, + NOW, NOW_PLUS_10_YEARS); + } + + // Replace the original key + { + mGenerator.initialize(new KeyPairGeneratorSpec.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, "RSA", 2048, null, TEST_DN_2, TEST_SERIAL_2, + NOW, NOW_PLUS_10_YEARS); + } + } + + public void testKeyPairGenerator_GenerateKeyPair_Replaced_UnencryptedToEncrypted_Success() + throws Exception { + // Generate the first key + { + mGenerator.initialize(new KeyPairGeneratorSpec.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, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, + NOW, NOW_PLUS_10_YEARS); + } + + // Attempt to replace previous key + { + mGenerator.initialize(new KeyPairGeneratorSpec.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.onUserPasswordChanged("1111")); + assertTrue(mAndroidKeyStore.isUnlocked()); + + final KeyPair pair2 = mGenerator.generateKeyPair(); + assertNotNull("The KeyPair returned should not be null", pair2); + assertKeyPairCorrect(pair2, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_2, TEST_SERIAL_2, + NOW, NOW_PLUS_10_YEARS); + } + } + + private void assertKeyPairCorrect(KeyPair pair, String alias, String keyType, int keySize, + AlgorithmParameterSpec spec, 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); + assertEquals(keyType, pubKey.getAlgorithm()); + + if ("EC".equalsIgnoreCase(keyType)) { + assertEquals("Curve should be what was specified during initialization", keySize, + ((ECPublicKey) pubKey).getParams().getCurve().getField().getFieldSize()); + } else if ("RSA".equalsIgnoreCase(keyType)) { + RSAPublicKey rsaPubKey = (RSAPublicKey) pubKey; + assertEquals("Modulus size should be what is specified during initialization", + (keySize + 7) & ~7, (rsaPubKey.getModulus().bitLength() + 7) & ~7); + if (spec != null) { + RSAKeyGenParameterSpec params = (RSAKeyGenParameterSpec) spec; + assertEquals((keySize + 7) & ~7, (params.getKeysize() + 7) & ~7); + assertEquals(params.getPublicExponent(), rsaPubKey.getPublicExponent()); + } + } + + final PrivateKey privKey = pair.getPrivate(); + assertNotNull("The PrivateKey for the KeyPair should be not null", privKey); + assertEquals(keyType, privKey.getAlgorithm()); + + 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()); + + assertDateEquals("The notBefore date should be the one passed into the params", start, + x509userCert.getNotBefore()); + + assertDateEquals("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); + } + + 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/keystore/AndroidKeyStoreTest.java b/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java new file mode 100644 index 0000000..2d4e4a0 --- /dev/null +++ b/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java @@ -0,0 +1,2234 @@ +/* + * 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.keystore; + +import com.android.org.bouncycastle.x509.X509V3CertificateGenerator; + +import com.android.org.conscrypt.NativeConstants; +import com.android.org.conscrypt.OpenSSLEngine; + +import android.security.Credentials; +import android.security.KeyStore; +import android.security.KeyStoreParameter; +import android.test.AndroidTestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyStore.Entry; +import java.security.KeyStore.PrivateKeyEntry; +import java.security.KeyStore.TrustedCertificateEntry; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import javax.security.auth.x500.X500Principal; + +public class AndroidKeyStoreTest extends AndroidTestCase { + private android.security.KeyStore mAndroidKeyStore; + + private java.security.KeyStore mKeyStore; + + private static final String TEST_ALIAS_1 = "test1"; + + private static final String TEST_ALIAS_2 = "test2"; + + private static final String TEST_ALIAS_3 = "test3"; + + 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); + + /* + * The keys and certificates below are generated with: + * + * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem + * openssl req -newkey rsa:1024 -keyout userkey.pem -nodes -days 3650 -out userkey.req + * mkdir -p demoCA/newcerts + * touch demoCA/index.txt + * echo "01" > demoCA/serial + * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650 + */ + + /** + * Generated from above and converted with: + * + * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] FAKE_RSA_CA_1 = { + (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0xce, (byte) 0x30, (byte) 0x82, + (byte) 0x02, (byte) 0x37, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01, + (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xe1, (byte) 0x6a, + (byte) 0xa2, (byte) 0xf4, (byte) 0x2e, (byte) 0x55, (byte) 0x48, (byte) 0x0a, + (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, + (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, + (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x4f, (byte) 0x31, + (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, + (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, + (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, + (byte) 0x41, (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, + (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, + (byte) 0x4d, (byte) 0x6f, (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, + (byte) 0x69, (byte) 0x6e, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, + (byte) 0x77, (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, + (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, + (byte) 0x41, (byte) 0x6e, (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, + (byte) 0x64, (byte) 0x20, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, + (byte) 0x20, (byte) 0x43, (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, + (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x32, + (byte) 0x30, (byte) 0x38, (byte) 0x31, (byte) 0x34, (byte) 0x31, (byte) 0x36, + (byte) 0x35, (byte) 0x35, (byte) 0x34, (byte) 0x34, (byte) 0x5a, (byte) 0x17, + (byte) 0x0d, (byte) 0x32, (byte) 0x32, (byte) 0x30, (byte) 0x38, (byte) 0x31, + (byte) 0x32, (byte) 0x31, (byte) 0x36, (byte) 0x35, (byte) 0x35, (byte) 0x34, + (byte) 0x34, (byte) 0x5a, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b, + (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, + (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, + (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x04, (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, + (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03, + (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d, + (byte) 0x6f, (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69, + (byte) 0x6e, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77, + (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, + (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, + (byte) 0x6e, (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, + (byte) 0x20, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, + (byte) 0x43, (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x30, + (byte) 0x81, (byte) 0x9f, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, + (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, + (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, + (byte) 0x81, (byte) 0x8d, (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, + (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xa3, (byte) 0x72, + (byte) 0xab, (byte) 0xd0, (byte) 0xe4, (byte) 0xad, (byte) 0x2f, (byte) 0xe7, + (byte) 0xe2, (byte) 0x79, (byte) 0x07, (byte) 0x36, (byte) 0x3d, (byte) 0x0c, + (byte) 0x8d, (byte) 0x42, (byte) 0x9a, (byte) 0x0a, (byte) 0x33, (byte) 0x64, + (byte) 0xb3, (byte) 0xcd, (byte) 0xb2, (byte) 0xd7, (byte) 0x3a, (byte) 0x42, + (byte) 0x06, (byte) 0x77, (byte) 0x45, (byte) 0x29, (byte) 0xe9, (byte) 0xcb, + (byte) 0xb7, (byte) 0x4a, (byte) 0xd6, (byte) 0xee, (byte) 0xad, (byte) 0x01, + (byte) 0x91, (byte) 0x9b, (byte) 0x0c, (byte) 0x59, (byte) 0xa1, (byte) 0x03, + (byte) 0xfa, (byte) 0xf0, (byte) 0x5a, (byte) 0x7c, (byte) 0x4f, (byte) 0xf7, + (byte) 0x8d, (byte) 0x36, (byte) 0x0f, (byte) 0x1f, (byte) 0x45, (byte) 0x7d, + (byte) 0x1b, (byte) 0x31, (byte) 0xa1, (byte) 0x35, (byte) 0x0b, (byte) 0x00, + (byte) 0xed, (byte) 0x7a, (byte) 0xb6, (byte) 0xc8, (byte) 0x4e, (byte) 0xa9, + (byte) 0x86, (byte) 0x4c, (byte) 0x7b, (byte) 0x99, (byte) 0x57, (byte) 0x41, + (byte) 0x12, (byte) 0xef, (byte) 0x6b, (byte) 0xbc, (byte) 0x3d, (byte) 0x60, + (byte) 0xf2, (byte) 0x99, (byte) 0x1a, (byte) 0xcd, (byte) 0xed, (byte) 0x56, + (byte) 0xa4, (byte) 0xe5, (byte) 0x36, (byte) 0x9f, (byte) 0x24, (byte) 0x1f, + (byte) 0xdc, (byte) 0x89, (byte) 0x40, (byte) 0xc8, (byte) 0x99, (byte) 0x92, + (byte) 0xab, (byte) 0x4a, (byte) 0xb5, (byte) 0x61, (byte) 0x45, (byte) 0x62, + (byte) 0xff, (byte) 0xa3, (byte) 0x45, (byte) 0x65, (byte) 0xaf, (byte) 0xf6, + (byte) 0x27, (byte) 0x30, (byte) 0x51, (byte) 0x0e, (byte) 0x0e, (byte) 0xeb, + (byte) 0x79, (byte) 0x0c, (byte) 0xbe, (byte) 0xb3, (byte) 0x0a, (byte) 0x6f, + (byte) 0x29, (byte) 0x06, (byte) 0xdc, (byte) 0x2f, (byte) 0x6b, (byte) 0x51, + (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3, + (byte) 0x81, (byte) 0xb1, (byte) 0x30, (byte) 0x81, (byte) 0xae, (byte) 0x30, + (byte) 0x1d, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e, + (byte) 0x04, (byte) 0x16, (byte) 0x04, (byte) 0x14, (byte) 0x33, (byte) 0x05, + (byte) 0xee, (byte) 0xfe, (byte) 0x6f, (byte) 0x60, (byte) 0xc7, (byte) 0xf9, + (byte) 0xa9, (byte) 0xd2, (byte) 0x73, (byte) 0x5c, (byte) 0x8f, (byte) 0x6d, + (byte) 0xa2, (byte) 0x2f, (byte) 0x97, (byte) 0x8e, (byte) 0x5d, (byte) 0x51, + (byte) 0x30, (byte) 0x7f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, + (byte) 0x23, (byte) 0x04, (byte) 0x78, (byte) 0x30, (byte) 0x76, (byte) 0x80, + (byte) 0x14, (byte) 0x33, (byte) 0x05, (byte) 0xee, (byte) 0xfe, (byte) 0x6f, + (byte) 0x60, (byte) 0xc7, (byte) 0xf9, (byte) 0xa9, (byte) 0xd2, (byte) 0x73, + (byte) 0x5c, (byte) 0x8f, (byte) 0x6d, (byte) 0xa2, (byte) 0x2f, (byte) 0x97, + (byte) 0x8e, (byte) 0x5d, (byte) 0x51, (byte) 0xa1, (byte) 0x53, (byte) 0xa4, + (byte) 0x51, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b, (byte) 0x30, + (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, + (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b, + (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, + (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31, + (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d, (byte) 0x6f, + (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69, (byte) 0x6e, + (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77, (byte) 0x31, + (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e, + (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20, + (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43, + (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x82, (byte) 0x09, + (byte) 0x00, (byte) 0xe1, (byte) 0x6a, (byte) 0xa2, (byte) 0xf4, (byte) 0x2e, + (byte) 0x55, (byte) 0x48, (byte) 0x0a, (byte) 0x30, (byte) 0x0c, (byte) 0x06, + (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x05, + (byte) 0x30, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0xff, (byte) 0x30, + (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, + (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, + (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x81, (byte) 0x00, + (byte) 0x8c, (byte) 0x30, (byte) 0x42, (byte) 0xfa, (byte) 0xeb, (byte) 0x1a, + (byte) 0x26, (byte) 0xeb, (byte) 0xda, (byte) 0x56, (byte) 0x32, (byte) 0xf2, + (byte) 0x9d, (byte) 0xa5, (byte) 0x24, (byte) 0xd8, (byte) 0x3a, (byte) 0xda, + (byte) 0x30, (byte) 0xa6, (byte) 0x8b, (byte) 0x46, (byte) 0xfe, (byte) 0xfe, + (byte) 0xdb, (byte) 0xf1, (byte) 0xe6, (byte) 0xe1, (byte) 0x7c, (byte) 0x1b, + (byte) 0xe7, (byte) 0x77, (byte) 0x00, (byte) 0xa1, (byte) 0x1c, (byte) 0x19, + (byte) 0x17, (byte) 0x73, (byte) 0xb0, (byte) 0xf0, (byte) 0x9d, (byte) 0xf3, + (byte) 0x4f, (byte) 0xb6, (byte) 0xbc, (byte) 0xc7, (byte) 0x47, (byte) 0x85, + (byte) 0x2a, (byte) 0x4a, (byte) 0xa1, (byte) 0xa5, (byte) 0x58, (byte) 0xf5, + (byte) 0xc5, (byte) 0x1a, (byte) 0x51, (byte) 0xb1, (byte) 0x04, (byte) 0x80, + (byte) 0xee, (byte) 0x3a, (byte) 0xec, (byte) 0x2f, (byte) 0xe1, (byte) 0xfd, + (byte) 0x58, (byte) 0xeb, (byte) 0xed, (byte) 0x82, (byte) 0x9e, (byte) 0x38, + (byte) 0xa3, (byte) 0x24, (byte) 0x75, (byte) 0xf7, (byte) 0x3e, (byte) 0xc2, + (byte) 0xc5, (byte) 0x27, (byte) 0xeb, (byte) 0x6f, (byte) 0x7b, (byte) 0x50, + (byte) 0xda, (byte) 0x43, (byte) 0xdc, (byte) 0x3b, (byte) 0x0b, (byte) 0x6f, + (byte) 0x78, (byte) 0x8f, (byte) 0xb0, (byte) 0x66, (byte) 0xe1, (byte) 0x12, + (byte) 0x87, (byte) 0x5f, (byte) 0x97, (byte) 0x7b, (byte) 0xca, (byte) 0x14, + (byte) 0x79, (byte) 0xf7, (byte) 0xe8, (byte) 0x6c, (byte) 0x72, (byte) 0xdb, + (byte) 0x91, (byte) 0x65, (byte) 0x17, (byte) 0x54, (byte) 0xe0, (byte) 0x74, + (byte) 0x1d, (byte) 0xac, (byte) 0x47, (byte) 0x04, (byte) 0x12, (byte) 0xe0, + (byte) 0xc3, (byte) 0x66, (byte) 0x19, (byte) 0x05, (byte) 0x2e, (byte) 0x7e, + (byte) 0xf1, (byte) 0x61 + }; + + /** + * Generated from above and converted with: + * + * openssl pkcs8 -topk8 -outform d -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] FAKE_RSA_KEY_1 = new byte[] { + (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x78, (byte) 0x02, (byte) 0x01, + (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, + (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, + (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x82, + (byte) 0x02, (byte) 0x62, (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x5e, + (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x81, (byte) 0x81, + (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6, (byte) 0x5b, + (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c, (byte) 0x66, + (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86, (byte) 0x8a, + (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3, (byte) 0x02, + (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08, (byte) 0xf3, + (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04, (byte) 0x6d, + (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f, (byte) 0x67, + (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c, (byte) 0xcb, + (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30, (byte) 0xe2, + (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5, (byte) 0x79, + (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b, (byte) 0xce, + (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb, (byte) 0x08, + (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff, (byte) 0x3b, + (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9, (byte) 0xc4, + (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29, (byte) 0x0d, + (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b, (byte) 0x23, + (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78, (byte) 0x08, + (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5, (byte) 0xf1, + (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19, (byte) 0xb4, + (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03, (byte) 0x16, + (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce, (byte) 0x9e, + (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03, (byte) 0x01, + (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x81, (byte) 0x80, (byte) 0x16, + (byte) 0x59, (byte) 0xc3, (byte) 0x24, (byte) 0x1d, (byte) 0x33, (byte) 0x98, + (byte) 0x9c, (byte) 0xc9, (byte) 0xc8, (byte) 0x2c, (byte) 0x88, (byte) 0xbf, + (byte) 0x0a, (byte) 0x01, (byte) 0xce, (byte) 0xfb, (byte) 0x34, (byte) 0x7a, + (byte) 0x58, (byte) 0x7a, (byte) 0xb0, (byte) 0xbf, (byte) 0xa6, (byte) 0xb2, + (byte) 0x60, (byte) 0xbe, (byte) 0x70, (byte) 0x21, (byte) 0xf5, (byte) 0xfc, + (byte) 0x85, (byte) 0x0d, (byte) 0x33, (byte) 0x58, (byte) 0xa1, (byte) 0xe5, + (byte) 0x09, (byte) 0x36, (byte) 0x84, (byte) 0xb2, (byte) 0x04, (byte) 0x0a, + (byte) 0x02, (byte) 0xd3, (byte) 0x88, (byte) 0x1f, (byte) 0x0c, (byte) 0x2b, + (byte) 0x1d, (byte) 0xe9, (byte) 0x3d, (byte) 0xe7, (byte) 0x79, (byte) 0xf9, + (byte) 0x32, (byte) 0x5c, (byte) 0x8a, (byte) 0x75, (byte) 0x49, (byte) 0x12, + (byte) 0xe4, (byte) 0x05, (byte) 0x26, (byte) 0xd4, (byte) 0x2e, (byte) 0x9e, + (byte) 0x1f, (byte) 0xcc, (byte) 0x54, (byte) 0xad, (byte) 0x33, (byte) 0x8d, + (byte) 0x99, (byte) 0x00, (byte) 0xdc, (byte) 0xf5, (byte) 0xb4, (byte) 0xa2, + (byte) 0x2f, (byte) 0xba, (byte) 0xe5, (byte) 0x62, (byte) 0x30, (byte) 0x6d, + (byte) 0xe6, (byte) 0x3d, (byte) 0xeb, (byte) 0x24, (byte) 0xc2, (byte) 0xdc, + (byte) 0x5f, (byte) 0xb7, (byte) 0x16, (byte) 0x35, (byte) 0xa3, (byte) 0x98, + (byte) 0x98, (byte) 0xa8, (byte) 0xef, (byte) 0xe8, (byte) 0xc4, (byte) 0x96, + (byte) 0x6d, (byte) 0x38, (byte) 0xab, (byte) 0x26, (byte) 0x6d, (byte) 0x30, + (byte) 0xc2, (byte) 0xa0, (byte) 0x44, (byte) 0xe4, (byte) 0xff, (byte) 0x7e, + (byte) 0xbe, (byte) 0x7c, (byte) 0x33, (byte) 0xa5, (byte) 0x10, (byte) 0xad, + (byte) 0xd7, (byte) 0x1e, (byte) 0x13, (byte) 0x20, (byte) 0xb3, (byte) 0x1f, + (byte) 0x41, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xf1, (byte) 0x89, + (byte) 0x07, (byte) 0x0f, (byte) 0xe8, (byte) 0xcf, (byte) 0xab, (byte) 0x13, + (byte) 0x2a, (byte) 0x8f, (byte) 0x88, (byte) 0x80, (byte) 0x11, (byte) 0x9a, + (byte) 0x79, (byte) 0xb6, (byte) 0x59, (byte) 0x3a, (byte) 0x50, (byte) 0x6e, + (byte) 0x57, (byte) 0x37, (byte) 0xab, (byte) 0x2a, (byte) 0xd2, (byte) 0xaa, + (byte) 0xd9, (byte) 0x72, (byte) 0x73, (byte) 0xff, (byte) 0x8b, (byte) 0x47, + (byte) 0x76, (byte) 0xdd, (byte) 0xdc, (byte) 0xf5, (byte) 0x97, (byte) 0x44, + (byte) 0x3a, (byte) 0x78, (byte) 0xbe, (byte) 0x17, (byte) 0xb4, (byte) 0x22, + (byte) 0x6f, (byte) 0xe5, (byte) 0x23, (byte) 0x70, (byte) 0x1d, (byte) 0x10, + (byte) 0x5d, (byte) 0xba, (byte) 0x16, (byte) 0x81, (byte) 0xf1, (byte) 0x45, + (byte) 0xce, (byte) 0x30, (byte) 0xb4, (byte) 0xab, (byte) 0x80, (byte) 0xe4, + (byte) 0x98, (byte) 0x31, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xda, + (byte) 0x82, (byte) 0x9d, (byte) 0x3f, (byte) 0xca, (byte) 0x2f, (byte) 0xe1, + (byte) 0xd4, (byte) 0x86, (byte) 0x77, (byte) 0x48, (byte) 0xa6, (byte) 0xab, + (byte) 0xab, (byte) 0x1c, (byte) 0x42, (byte) 0x5c, (byte) 0xd5, (byte) 0xc7, + (byte) 0x46, (byte) 0x59, (byte) 0x91, (byte) 0x3f, (byte) 0xfc, (byte) 0xcc, + (byte) 0xec, (byte) 0xc2, (byte) 0x40, (byte) 0x12, (byte) 0x2c, (byte) 0x8d, + (byte) 0x1f, (byte) 0xa2, (byte) 0x18, (byte) 0x88, (byte) 0xee, (byte) 0x82, + (byte) 0x4a, (byte) 0x5a, (byte) 0x5e, (byte) 0x88, (byte) 0x20, (byte) 0xe3, + (byte) 0x7b, (byte) 0xe0, (byte) 0xd8, (byte) 0x3a, (byte) 0x52, (byte) 0x9a, + (byte) 0x26, (byte) 0x6a, (byte) 0x04, (byte) 0xec, (byte) 0xe8, (byte) 0xb9, + (byte) 0x48, (byte) 0x40, (byte) 0xe1, (byte) 0xe1, (byte) 0x83, (byte) 0xa6, + (byte) 0x67, (byte) 0xa6, (byte) 0xfd, (byte) 0x02, (byte) 0x41, (byte) 0x00, + (byte) 0x89, (byte) 0x72, (byte) 0x3e, (byte) 0xb0, (byte) 0x90, (byte) 0xfd, + (byte) 0x4c, (byte) 0x0e, (byte) 0xd6, (byte) 0x13, (byte) 0x63, (byte) 0xcb, + (byte) 0xed, (byte) 0x38, (byte) 0x88, (byte) 0xb6, (byte) 0x79, (byte) 0xc4, + (byte) 0x33, (byte) 0x6c, (byte) 0xf6, (byte) 0xf8, (byte) 0xd8, (byte) 0xd0, + (byte) 0xbf, (byte) 0x9d, (byte) 0x35, (byte) 0xac, (byte) 0x69, (byte) 0xd2, + (byte) 0x2b, (byte) 0xc1, (byte) 0xf9, (byte) 0x24, (byte) 0x7b, (byte) 0xce, + (byte) 0xcd, (byte) 0xcb, (byte) 0xa7, (byte) 0xb2, (byte) 0x7a, (byte) 0x0a, + (byte) 0x27, (byte) 0x19, (byte) 0xc9, (byte) 0xaf, (byte) 0x0d, (byte) 0x21, + (byte) 0x89, (byte) 0x88, (byte) 0x7c, (byte) 0xad, (byte) 0x9e, (byte) 0x8d, + (byte) 0x47, (byte) 0x6d, (byte) 0x3f, (byte) 0xce, (byte) 0x7b, (byte) 0xa1, + (byte) 0x74, (byte) 0xf1, (byte) 0xa0, (byte) 0xa1, (byte) 0x02, (byte) 0x41, + (byte) 0x00, (byte) 0xd9, (byte) 0xa8, (byte) 0xf5, (byte) 0xfe, (byte) 0xce, + (byte) 0xe6, (byte) 0x77, (byte) 0x6b, (byte) 0xfe, (byte) 0x2d, (byte) 0xe0, + (byte) 0x1e, (byte) 0xb6, (byte) 0x2e, (byte) 0x12, (byte) 0x4e, (byte) 0x40, + (byte) 0xaf, (byte) 0x6a, (byte) 0x7b, (byte) 0x37, (byte) 0x49, (byte) 0x2a, + (byte) 0x96, (byte) 0x25, (byte) 0x83, (byte) 0x49, (byte) 0xd4, (byte) 0x0c, + (byte) 0xc6, (byte) 0x78, (byte) 0x25, (byte) 0x24, (byte) 0x90, (byte) 0x90, + (byte) 0x06, (byte) 0x15, (byte) 0x9e, (byte) 0xfe, (byte) 0xf9, (byte) 0xdf, + (byte) 0x5b, (byte) 0xf3, (byte) 0x7e, (byte) 0x38, (byte) 0x70, (byte) 0xeb, + (byte) 0x57, (byte) 0xd0, (byte) 0xd9, (byte) 0xa7, (byte) 0x0e, (byte) 0x14, + (byte) 0xf7, (byte) 0x95, (byte) 0x68, (byte) 0xd5, (byte) 0xc8, (byte) 0xab, + (byte) 0x9d, (byte) 0x3a, (byte) 0x2b, (byte) 0x51, (byte) 0xf9, (byte) 0x02, + (byte) 0x41, (byte) 0x00, (byte) 0x96, (byte) 0xdf, (byte) 0xe9, (byte) 0x67, + (byte) 0x6c, (byte) 0xdc, (byte) 0x90, (byte) 0x14, (byte) 0xb4, (byte) 0x1d, + (byte) 0x22, (byte) 0x33, (byte) 0x4a, (byte) 0x31, (byte) 0xc1, (byte) 0x9d, + (byte) 0x2e, (byte) 0xff, (byte) 0x9a, (byte) 0x2a, (byte) 0x95, (byte) 0x4b, + (byte) 0x27, (byte) 0x74, (byte) 0xcb, (byte) 0x21, (byte) 0xc3, (byte) 0xd2, + (byte) 0x0b, (byte) 0xb2, (byte) 0x46, (byte) 0x87, (byte) 0xf8, (byte) 0x28, + (byte) 0x01, (byte) 0x8b, (byte) 0xd8, (byte) 0xb9, (byte) 0x4b, (byte) 0xcd, + (byte) 0x9a, (byte) 0x96, (byte) 0x41, (byte) 0x0e, (byte) 0x36, (byte) 0x6d, + (byte) 0x40, (byte) 0x42, (byte) 0xbc, (byte) 0xd9, (byte) 0xd3, (byte) 0x7b, + (byte) 0xbc, (byte) 0xa7, (byte) 0x92, (byte) 0x90, (byte) 0xdd, (byte) 0xa1, + (byte) 0x9c, (byte) 0xce, (byte) 0xa1, (byte) 0x87, (byte) 0x11, (byte) 0x51 + }; + + /** + * Generated from above and converted with: + * + * openssl x509 -outform d -in usercert.pem | xxd -i | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] FAKE_RSA_USER_1 = new byte[] { + (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x95, (byte) 0x30, (byte) 0x82, + (byte) 0x01, (byte) 0xfe, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01, + (byte) 0x02, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x0d, + (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, + (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, + (byte) 0x00, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b, (byte) 0x30, + (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, + (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b, + (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, + (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31, + (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d, (byte) 0x6f, + (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69, (byte) 0x6e, + (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77, (byte) 0x31, + (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e, + (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20, + (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43, + (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x30, (byte) 0x1e, + (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x32, (byte) 0x30, (byte) 0x38, + (byte) 0x31, (byte) 0x34, (byte) 0x32, (byte) 0x33, (byte) 0x32, (byte) 0x35, + (byte) 0x34, (byte) 0x38, (byte) 0x5a, (byte) 0x17, (byte) 0x0d, (byte) 0x32, + (byte) 0x32, (byte) 0x30, (byte) 0x38, (byte) 0x31, (byte) 0x32, (byte) 0x32, + (byte) 0x33, (byte) 0x32, (byte) 0x35, (byte) 0x34, (byte) 0x38, (byte) 0x5a, + (byte) 0x30, (byte) 0x55, (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, + (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13, + (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b, (byte) 0x30, + (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08, + (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31, (byte) 0x1b, + (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, + (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e, (byte) 0x64, + (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20, (byte) 0x54, + (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43, (byte) 0x61, + (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x31, (byte) 0x1c, (byte) 0x30, + (byte) 0x1a, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, + (byte) 0x13, (byte) 0x13, (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76, + (byte) 0x65, (byte) 0x72, (byte) 0x31, (byte) 0x2e, (byte) 0x65, (byte) 0x78, + (byte) 0x61, (byte) 0x6d, (byte) 0x70, (byte) 0x6c, (byte) 0x65, (byte) 0x2e, + (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x30, (byte) 0x81, (byte) 0x9f, + (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, + (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, + (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x8d, + (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, (byte) 0x02, (byte) 0x81, + (byte) 0x81, (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6, + (byte) 0x5b, (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c, + (byte) 0x66, (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86, + (byte) 0x8a, (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3, + (byte) 0x02, (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08, + (byte) 0xf3, (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04, + (byte) 0x6d, (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f, + (byte) 0x67, (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c, + (byte) 0xcb, (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30, + (byte) 0xe2, (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5, + (byte) 0x79, (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b, + (byte) 0xce, (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb, + (byte) 0x08, (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff, + (byte) 0x3b, (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9, + (byte) 0xc4, (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29, + (byte) 0x0d, (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b, + (byte) 0x23, (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78, + (byte) 0x08, (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5, + (byte) 0xf1, (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19, + (byte) 0xb4, (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03, + (byte) 0x16, (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce, + (byte) 0x9e, (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03, + (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3, (byte) 0x7b, (byte) 0x30, + (byte) 0x79, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x02, (byte) 0x30, (byte) 0x00, + (byte) 0x30, (byte) 0x2c, (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, + (byte) 0x48, (byte) 0x01, (byte) 0x86, (byte) 0xf8, (byte) 0x42, (byte) 0x01, + (byte) 0x0d, (byte) 0x04, (byte) 0x1f, (byte) 0x16, (byte) 0x1d, (byte) 0x4f, + (byte) 0x70, (byte) 0x65, (byte) 0x6e, (byte) 0x53, (byte) 0x53, (byte) 0x4c, + (byte) 0x20, (byte) 0x47, (byte) 0x65, (byte) 0x6e, (byte) 0x65, (byte) 0x72, + (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x64, (byte) 0x20, (byte) 0x43, + (byte) 0x65, (byte) 0x72, (byte) 0x74, (byte) 0x69, (byte) 0x66, (byte) 0x69, + (byte) 0x63, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x30, (byte) 0x1d, + (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e, (byte) 0x04, + (byte) 0x16, (byte) 0x04, (byte) 0x14, (byte) 0x32, (byte) 0xa1, (byte) 0x1e, + (byte) 0x6b, (byte) 0x69, (byte) 0x04, (byte) 0xfe, (byte) 0xb3, (byte) 0xcd, + (byte) 0xf8, (byte) 0xbb, (byte) 0x14, (byte) 0xcd, (byte) 0xff, (byte) 0xd4, + (byte) 0x16, (byte) 0xc3, (byte) 0xab, (byte) 0x44, (byte) 0x2f, (byte) 0x30, + (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x23, + (byte) 0x04, (byte) 0x18, (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14, + (byte) 0x33, (byte) 0x05, (byte) 0xee, (byte) 0xfe, (byte) 0x6f, (byte) 0x60, + (byte) 0xc7, (byte) 0xf9, (byte) 0xa9, (byte) 0xd2, (byte) 0x73, (byte) 0x5c, + (byte) 0x8f, (byte) 0x6d, (byte) 0xa2, (byte) 0x2f, (byte) 0x97, (byte) 0x8e, + (byte) 0x5d, (byte) 0x51, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, + (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, + (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, + (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0x46, (byte) 0x42, (byte) 0xef, + (byte) 0x56, (byte) 0x89, (byte) 0x78, (byte) 0x90, (byte) 0x38, (byte) 0x24, + (byte) 0x9f, (byte) 0x8c, (byte) 0x7a, (byte) 0xce, (byte) 0x7a, (byte) 0xa5, + (byte) 0xb5, (byte) 0x1e, (byte) 0x74, (byte) 0x96, (byte) 0x34, (byte) 0x49, + (byte) 0x8b, (byte) 0xed, (byte) 0x44, (byte) 0xb3, (byte) 0xc9, (byte) 0x05, + (byte) 0xd7, (byte) 0x48, (byte) 0x55, (byte) 0x52, (byte) 0x59, (byte) 0x15, + (byte) 0x0b, (byte) 0xaa, (byte) 0x16, (byte) 0x86, (byte) 0xd2, (byte) 0x8e, + (byte) 0x16, (byte) 0x99, (byte) 0xe8, (byte) 0x5f, (byte) 0x11, (byte) 0x71, + (byte) 0x42, (byte) 0x55, (byte) 0xd1, (byte) 0xc4, (byte) 0x6f, (byte) 0x2e, + (byte) 0xa9, (byte) 0x64, (byte) 0x6f, (byte) 0xd8, (byte) 0xfd, (byte) 0x43, + (byte) 0x13, (byte) 0x24, (byte) 0xaa, (byte) 0x67, (byte) 0xe6, (byte) 0xf5, + (byte) 0xca, (byte) 0x80, (byte) 0x5e, (byte) 0x3a, (byte) 0x3e, (byte) 0xcc, + (byte) 0x4f, (byte) 0xba, (byte) 0x87, (byte) 0xe6, (byte) 0xae, (byte) 0xbf, + (byte) 0x8f, (byte) 0xd5, (byte) 0x28, (byte) 0x38, (byte) 0x58, (byte) 0x30, + (byte) 0x24, (byte) 0xf6, (byte) 0x53, (byte) 0x5b, (byte) 0x41, (byte) 0x53, + (byte) 0xe6, (byte) 0x45, (byte) 0xbc, (byte) 0xbe, (byte) 0xe6, (byte) 0xbb, + (byte) 0x5d, (byte) 0xd8, (byte) 0xa7, (byte) 0xf9, (byte) 0x64, (byte) 0x99, + (byte) 0x04, (byte) 0x43, (byte) 0x75, (byte) 0xd7, (byte) 0x2d, (byte) 0x32, + (byte) 0x0a, (byte) 0x94, (byte) 0xaf, (byte) 0x06, (byte) 0x34, (byte) 0xae, + (byte) 0x46, (byte) 0xbd, (byte) 0xda, (byte) 0x00, (byte) 0x0e, (byte) 0x25, + (byte) 0xc2, (byte) 0xf7, (byte) 0xc9, (byte) 0xc3, (byte) 0x65, (byte) 0xd2, + (byte) 0x08, (byte) 0x41, (byte) 0x0a, (byte) 0xf3, (byte) 0x72 + }; + + /* + * The keys and certificates below are generated with: + * + * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem + * openssl ecparam -name prime256v1 -out ecparam.pem + * openssl req -newkey ec:ecparam.pem -keyout userkey.pem -nodes -days 3650 -out userkey.req + * mkdir -p demoCA/newcerts + * touch demoCA/index.txt + * echo "01" > demoCA/serial + * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650 + */ + + /** + * Generated from above and converted with: + * + * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] FAKE_EC_CA_1 = { + (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x58, (byte) 0x30, (byte) 0x82, + (byte) 0x01, (byte) 0xc1, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01, + (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xe1, (byte) 0xb2, + (byte) 0x8c, (byte) 0x04, (byte) 0x95, (byte) 0xeb, (byte) 0x10, (byte) 0xcb, + (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, + (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, + (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x45, (byte) 0x31, + (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, + (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, + (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, + (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, + (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, + (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, + (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, + (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, + (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, + (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, + (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, + (byte) 0x31, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x37, + (byte) 0x31, (byte) 0x36, (byte) 0x32, (byte) 0x38, (byte) 0x32, (byte) 0x38, + (byte) 0x5a, (byte) 0x17, (byte) 0x0d, (byte) 0x32, (byte) 0x33, (byte) 0x30, + (byte) 0x38, (byte) 0x32, (byte) 0x35, (byte) 0x31, (byte) 0x36, (byte) 0x32, + (byte) 0x38, (byte) 0x32, (byte) 0x38, (byte) 0x5a, (byte) 0x30, (byte) 0x45, + (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, + (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, + (byte) 0x55, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, + (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, + (byte) 0x53, (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, + (byte) 0x74, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, + (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, + (byte) 0x0a, (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, + (byte) 0x65, (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, + (byte) 0x57, (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, + (byte) 0x73, (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, + (byte) 0x4c, (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x81, (byte) 0x9f, + (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, + (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, + (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x8d, + (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, (byte) 0x02, (byte) 0x81, + (byte) 0x81, (byte) 0x00, (byte) 0xb5, (byte) 0xf6, (byte) 0x08, (byte) 0x0f, + (byte) 0xc4, (byte) 0x4d, (byte) 0xe4, (byte) 0x0d, (byte) 0x34, (byte) 0x1d, + (byte) 0xe2, (byte) 0x23, (byte) 0x18, (byte) 0x63, (byte) 0x03, (byte) 0xf7, + (byte) 0x14, (byte) 0x0e, (byte) 0x98, (byte) 0xcd, (byte) 0x45, (byte) 0x1f, + (byte) 0xfe, (byte) 0xfb, (byte) 0x09, (byte) 0x3f, (byte) 0x5d, (byte) 0x36, + (byte) 0x3b, (byte) 0x0f, (byte) 0xf9, (byte) 0x5e, (byte) 0x86, (byte) 0x56, + (byte) 0x64, (byte) 0xd7, (byte) 0x3f, (byte) 0xae, (byte) 0x33, (byte) 0x09, + (byte) 0xd3, (byte) 0xdd, (byte) 0x06, (byte) 0x17, (byte) 0x26, (byte) 0xdc, + (byte) 0xa2, (byte) 0x8c, (byte) 0x3c, (byte) 0x65, (byte) 0xed, (byte) 0x03, + (byte) 0x82, (byte) 0x78, (byte) 0x9b, (byte) 0xee, (byte) 0xe3, (byte) 0x98, + (byte) 0x58, (byte) 0xe1, (byte) 0xf1, (byte) 0xa0, (byte) 0x85, (byte) 0xae, + (byte) 0x63, (byte) 0x84, (byte) 0x41, (byte) 0x46, (byte) 0xa7, (byte) 0x4f, + (byte) 0xdc, (byte) 0xbb, (byte) 0x1c, (byte) 0x6e, (byte) 0xec, (byte) 0x7b, + (byte) 0xd5, (byte) 0xab, (byte) 0x3d, (byte) 0x6a, (byte) 0x05, (byte) 0x58, + (byte) 0x0f, (byte) 0x9b, (byte) 0x6a, (byte) 0x67, (byte) 0x4b, (byte) 0xe9, + (byte) 0x2a, (byte) 0x6d, (byte) 0x96, (byte) 0x11, (byte) 0x53, (byte) 0x95, + (byte) 0x78, (byte) 0xaa, (byte) 0xd1, (byte) 0x91, (byte) 0x4a, (byte) 0xf8, + (byte) 0x54, (byte) 0x52, (byte) 0x6d, (byte) 0xb9, (byte) 0xca, (byte) 0x74, + (byte) 0x81, (byte) 0xf8, (byte) 0x99, (byte) 0x64, (byte) 0xd1, (byte) 0x4f, + (byte) 0x01, (byte) 0x38, (byte) 0x4f, (byte) 0x08, (byte) 0x5c, (byte) 0x31, + (byte) 0xcb, (byte) 0x7c, (byte) 0x5c, (byte) 0x78, (byte) 0x5d, (byte) 0x47, + (byte) 0xd9, (byte) 0xf0, (byte) 0x1a, (byte) 0xeb, (byte) 0x02, (byte) 0x03, + (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3, (byte) 0x50, (byte) 0x30, + (byte) 0x4e, (byte) 0x30, (byte) 0x1d, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16, (byte) 0x04, (byte) 0x14, + (byte) 0x5f, (byte) 0x5b, (byte) 0x5e, (byte) 0xac, (byte) 0x29, (byte) 0xfa, + (byte) 0xa1, (byte) 0x9f, (byte) 0x9e, (byte) 0xad, (byte) 0x46, (byte) 0xe1, + (byte) 0xbc, (byte) 0x20, (byte) 0x72, (byte) 0xcf, (byte) 0x4a, (byte) 0xd4, + (byte) 0xfa, (byte) 0xe3, (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03, + (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04, (byte) 0x18, (byte) 0x30, + (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x5f, (byte) 0x5b, (byte) 0x5e, + (byte) 0xac, (byte) 0x29, (byte) 0xfa, (byte) 0xa1, (byte) 0x9f, (byte) 0x9e, + (byte) 0xad, (byte) 0x46, (byte) 0xe1, (byte) 0xbc, (byte) 0x20, (byte) 0x72, + (byte) 0xcf, (byte) 0x4a, (byte) 0xd4, (byte) 0xfa, (byte) 0xe3, (byte) 0x30, + (byte) 0x0c, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13, + (byte) 0x04, (byte) 0x05, (byte) 0x30, (byte) 0x03, (byte) 0x01, (byte) 0x01, + (byte) 0xff, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, + (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, + (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, + (byte) 0x81, (byte) 0x00, (byte) 0xa1, (byte) 0x4a, (byte) 0xe6, (byte) 0xfc, + (byte) 0x7f, (byte) 0x17, (byte) 0xaa, (byte) 0x65, (byte) 0x4a, (byte) 0x34, + (byte) 0xde, (byte) 0x69, (byte) 0x67, (byte) 0x54, (byte) 0x4d, (byte) 0xa2, + (byte) 0xc2, (byte) 0x98, (byte) 0x02, (byte) 0x43, (byte) 0x6a, (byte) 0x0e, + (byte) 0x0b, (byte) 0x7f, (byte) 0xa4, (byte) 0x46, (byte) 0xaf, (byte) 0xa4, + (byte) 0x65, (byte) 0xa0, (byte) 0xdb, (byte) 0xf1, (byte) 0x5b, (byte) 0xd5, + (byte) 0x09, (byte) 0xbc, (byte) 0xee, (byte) 0x37, (byte) 0x51, (byte) 0x19, + (byte) 0x36, (byte) 0xc0, (byte) 0x90, (byte) 0xd3, (byte) 0x5f, (byte) 0xf3, + (byte) 0x4f, (byte) 0xb9, (byte) 0x08, (byte) 0x45, (byte) 0x0e, (byte) 0x01, + (byte) 0x8a, (byte) 0x95, (byte) 0xef, (byte) 0x92, (byte) 0x95, (byte) 0x33, + (byte) 0x78, (byte) 0xdd, (byte) 0x90, (byte) 0xbb, (byte) 0xf3, (byte) 0x06, + (byte) 0x75, (byte) 0xd0, (byte) 0x66, (byte) 0xe6, (byte) 0xd0, (byte) 0x18, + (byte) 0x6e, (byte) 0xeb, (byte) 0x1c, (byte) 0x52, (byte) 0xc3, (byte) 0x2e, + (byte) 0x57, (byte) 0x7d, (byte) 0xa9, (byte) 0x03, (byte) 0xdb, (byte) 0xf4, + (byte) 0x57, (byte) 0x5f, (byte) 0x6c, (byte) 0x7e, (byte) 0x00, (byte) 0x0d, + (byte) 0x8f, (byte) 0xe8, (byte) 0x91, (byte) 0xf7, (byte) 0xae, (byte) 0x24, + (byte) 0x35, (byte) 0x07, (byte) 0xb5, (byte) 0x48, (byte) 0x2d, (byte) 0x36, + (byte) 0x30, (byte) 0x5d, (byte) 0xe9, (byte) 0x49, (byte) 0x2d, (byte) 0xd1, + (byte) 0x5d, (byte) 0xc5, (byte) 0xf4, (byte) 0x33, (byte) 0x77, (byte) 0x3c, + (byte) 0x71, (byte) 0xad, (byte) 0x90, (byte) 0x65, (byte) 0xa9, (byte) 0xc1, + (byte) 0x0b, (byte) 0x5c, (byte) 0x62, (byte) 0x55, (byte) 0x50, (byte) 0x6f, + (byte) 0x9b, (byte) 0xc9, (byte) 0x0d, (byte) 0xee + }; + + /** + * Generated from above and converted with: + * + * openssl pkcs8 -topk8 -outform d -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] FAKE_EC_KEY_1 = new byte[] { + (byte) 0x30, (byte) 0x81, (byte) 0x87, (byte) 0x02, (byte) 0x01, (byte) 0x00, + (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a, (byte) 0x86, + (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06, + (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, + (byte) 0x03, (byte) 0x01, (byte) 0x07, (byte) 0x04, (byte) 0x6d, (byte) 0x30, + (byte) 0x6b, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x04, (byte) 0x20, + (byte) 0x3a, (byte) 0x8a, (byte) 0x02, (byte) 0xdc, (byte) 0xde, (byte) 0x70, + (byte) 0x84, (byte) 0x45, (byte) 0x34, (byte) 0xaf, (byte) 0xbd, (byte) 0xd5, + (byte) 0x02, (byte) 0x17, (byte) 0x69, (byte) 0x90, (byte) 0x65, (byte) 0x1e, + (byte) 0x87, (byte) 0xf1, (byte) 0x3d, (byte) 0x17, (byte) 0xb6, (byte) 0xf4, + (byte) 0x31, (byte) 0x94, (byte) 0x86, (byte) 0x76, (byte) 0x55, (byte) 0xf7, + (byte) 0xcc, (byte) 0xba, (byte) 0xa1, (byte) 0x44, (byte) 0x03, (byte) 0x42, + (byte) 0x00, (byte) 0x04, (byte) 0xd9, (byte) 0xcf, (byte) 0xe7, (byte) 0x9b, + (byte) 0x23, (byte) 0xc8, (byte) 0xa3, (byte) 0xb8, (byte) 0x33, (byte) 0x14, + (byte) 0xa4, (byte) 0x4d, (byte) 0x75, (byte) 0x90, (byte) 0xf3, (byte) 0xcd, + (byte) 0x43, (byte) 0xe5, (byte) 0x1b, (byte) 0x05, (byte) 0x1d, (byte) 0xf3, + (byte) 0xd0, (byte) 0xa3, (byte) 0xb7, (byte) 0x32, (byte) 0x5f, (byte) 0x79, + (byte) 0xdc, (byte) 0x88, (byte) 0xb8, (byte) 0x4d, (byte) 0xb3, (byte) 0xd1, + (byte) 0x6d, (byte) 0xf7, (byte) 0x75, (byte) 0xf3, (byte) 0xbf, (byte) 0x50, + (byte) 0xa1, (byte) 0xbc, (byte) 0x03, (byte) 0x64, (byte) 0x22, (byte) 0xe6, + (byte) 0x1a, (byte) 0xa1, (byte) 0xe1, (byte) 0x06, (byte) 0x68, (byte) 0x3b, + (byte) 0xbc, (byte) 0x9f, (byte) 0xd3, (byte) 0xae, (byte) 0x77, (byte) 0x5e, + (byte) 0x88, (byte) 0x0c, (byte) 0x5e, (byte) 0x0c, (byte) 0xb2, (byte) 0x38 + }; + + /** + * Generated from above and converted with: + * + * openssl x509 -outform d -in usercert.pem | xxd -i | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] FAKE_EC_USER_1 = new byte[] { + (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x51, (byte) 0x30, (byte) 0x82, + (byte) 0x01, (byte) 0xba, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01, + (byte) 0x02, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x0d, + (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, + (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, + (byte) 0x00, (byte) 0x30, (byte) 0x45, (byte) 0x31, (byte) 0x0b, (byte) 0x30, + (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, + (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, (byte) 0x31, (byte) 0x13, + (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, + (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, (byte) 0x6f, (byte) 0x6d, + (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, (byte) 0x61, (byte) 0x74, + (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, (byte) 0x1f, (byte) 0x06, + (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x0c, (byte) 0x18, + (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x6e, + (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, (byte) 0x69, (byte) 0x64, + (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, (byte) 0x20, (byte) 0x50, + (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, (byte) 0x74, (byte) 0x64, + (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x33, + (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x37, (byte) 0x31, (byte) 0x36, + (byte) 0x33, (byte) 0x30, (byte) 0x30, (byte) 0x38, (byte) 0x5a, (byte) 0x17, + (byte) 0x0d, (byte) 0x32, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32, + (byte) 0x35, (byte) 0x31, (byte) 0x36, (byte) 0x33, (byte) 0x30, (byte) 0x30, + (byte) 0x38, (byte) 0x5a, (byte) 0x30, (byte) 0x62, (byte) 0x31, (byte) 0x0b, + (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, + (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, (byte) 0x31, + (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, (byte) 0x6f, + (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, (byte) 0x61, + (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, (byte) 0x1f, + (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x0c, + (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, (byte) 0x72, + (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, (byte) 0x69, + (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, (byte) 0x20, + (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, (byte) 0x74, + (byte) 0x64, (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, + (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x0c, (byte) 0x12, + (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76, (byte) 0x65, (byte) 0x72, + (byte) 0x2e, (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70, + (byte) 0x6c, (byte) 0x65, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, (byte) 0x6d, + (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, + (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, + (byte) 0x01, (byte) 0x06, (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, + (byte) 0xce, (byte) 0x3d, (byte) 0x03, (byte) 0x01, (byte) 0x07, (byte) 0x03, + (byte) 0x42, (byte) 0x00, (byte) 0x04, (byte) 0xd9, (byte) 0xcf, (byte) 0xe7, + (byte) 0x9b, (byte) 0x23, (byte) 0xc8, (byte) 0xa3, (byte) 0xb8, (byte) 0x33, + (byte) 0x14, (byte) 0xa4, (byte) 0x4d, (byte) 0x75, (byte) 0x90, (byte) 0xf3, + (byte) 0xcd, (byte) 0x43, (byte) 0xe5, (byte) 0x1b, (byte) 0x05, (byte) 0x1d, + (byte) 0xf3, (byte) 0xd0, (byte) 0xa3, (byte) 0xb7, (byte) 0x32, (byte) 0x5f, + (byte) 0x79, (byte) 0xdc, (byte) 0x88, (byte) 0xb8, (byte) 0x4d, (byte) 0xb3, + (byte) 0xd1, (byte) 0x6d, (byte) 0xf7, (byte) 0x75, (byte) 0xf3, (byte) 0xbf, + (byte) 0x50, (byte) 0xa1, (byte) 0xbc, (byte) 0x03, (byte) 0x64, (byte) 0x22, + (byte) 0xe6, (byte) 0x1a, (byte) 0xa1, (byte) 0xe1, (byte) 0x06, (byte) 0x68, + (byte) 0x3b, (byte) 0xbc, (byte) 0x9f, (byte) 0xd3, (byte) 0xae, (byte) 0x77, + (byte) 0x5e, (byte) 0x88, (byte) 0x0c, (byte) 0x5e, (byte) 0x0c, (byte) 0xb2, + (byte) 0x38, (byte) 0xa3, (byte) 0x7b, (byte) 0x30, (byte) 0x79, (byte) 0x30, + (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13, + (byte) 0x04, (byte) 0x02, (byte) 0x30, (byte) 0x00, (byte) 0x30, (byte) 0x2c, + (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01, + (byte) 0x86, (byte) 0xf8, (byte) 0x42, (byte) 0x01, (byte) 0x0d, (byte) 0x04, + (byte) 0x1f, (byte) 0x16, (byte) 0x1d, (byte) 0x4f, (byte) 0x70, (byte) 0x65, + (byte) 0x6e, (byte) 0x53, (byte) 0x53, (byte) 0x4c, (byte) 0x20, (byte) 0x47, + (byte) 0x65, (byte) 0x6e, (byte) 0x65, (byte) 0x72, (byte) 0x61, (byte) 0x74, + (byte) 0x65, (byte) 0x64, (byte) 0x20, (byte) 0x43, (byte) 0x65, (byte) 0x72, + (byte) 0x74, (byte) 0x69, (byte) 0x66, (byte) 0x69, (byte) 0x63, (byte) 0x61, + (byte) 0x74, (byte) 0x65, (byte) 0x30, (byte) 0x1d, (byte) 0x06, (byte) 0x03, + (byte) 0x55, (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16, (byte) 0x04, + (byte) 0x14, (byte) 0xd5, (byte) 0xc4, (byte) 0x72, (byte) 0xbd, (byte) 0xd2, + (byte) 0x4e, (byte) 0x90, (byte) 0x1b, (byte) 0x14, (byte) 0x32, (byte) 0xdb, + (byte) 0x03, (byte) 0xae, (byte) 0xfa, (byte) 0x27, (byte) 0x7d, (byte) 0x8d, + (byte) 0xe4, (byte) 0x80, (byte) 0x58, (byte) 0x30, (byte) 0x1f, (byte) 0x06, + (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04, (byte) 0x18, + (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x5f, (byte) 0x5b, + (byte) 0x5e, (byte) 0xac, (byte) 0x29, (byte) 0xfa, (byte) 0xa1, (byte) 0x9f, + (byte) 0x9e, (byte) 0xad, (byte) 0x46, (byte) 0xe1, (byte) 0xbc, (byte) 0x20, + (byte) 0x72, (byte) 0xcf, (byte) 0x4a, (byte) 0xd4, (byte) 0xfa, (byte) 0xe3, + (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, + (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, + (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x81, + (byte) 0x00, (byte) 0x43, (byte) 0x99, (byte) 0x9f, (byte) 0x67, (byte) 0x08, + (byte) 0x43, (byte) 0xd5, (byte) 0x6b, (byte) 0x6f, (byte) 0xd7, (byte) 0x05, + (byte) 0xd6, (byte) 0x75, (byte) 0x34, (byte) 0x30, (byte) 0xca, (byte) 0x20, + (byte) 0x47, (byte) 0x61, (byte) 0xa1, (byte) 0x89, (byte) 0xb6, (byte) 0xf1, + (byte) 0x49, (byte) 0x7b, (byte) 0xd9, (byte) 0xb9, (byte) 0xe8, (byte) 0x1e, + (byte) 0x29, (byte) 0x74, (byte) 0x0a, (byte) 0x67, (byte) 0xc0, (byte) 0x7d, + (byte) 0xb8, (byte) 0xe6, (byte) 0x39, (byte) 0xa8, (byte) 0x5e, (byte) 0xc3, + (byte) 0xb0, (byte) 0xa1, (byte) 0x30, (byte) 0x6a, (byte) 0x1f, (byte) 0x1d, + (byte) 0xfc, (byte) 0x11, (byte) 0x59, (byte) 0x0b, (byte) 0xb9, (byte) 0xad, + (byte) 0x3a, (byte) 0x4e, (byte) 0x50, (byte) 0x0a, (byte) 0x61, (byte) 0xdb, + (byte) 0x75, (byte) 0x6b, (byte) 0xe5, (byte) 0x3f, (byte) 0x8d, (byte) 0xde, + (byte) 0x28, (byte) 0x68, (byte) 0xb1, (byte) 0x29, (byte) 0x9a, (byte) 0x18, + (byte) 0x8a, (byte) 0xfc, (byte) 0x3f, (byte) 0x13, (byte) 0x93, (byte) 0x29, + (byte) 0xed, (byte) 0x22, (byte) 0x7c, (byte) 0xb4, (byte) 0x50, (byte) 0xd5, + (byte) 0x4d, (byte) 0x32, (byte) 0x4d, (byte) 0x42, (byte) 0x2b, (byte) 0x29, + (byte) 0x97, (byte) 0x86, (byte) 0xc0, (byte) 0x01, (byte) 0x00, (byte) 0x25, + (byte) 0xf6, (byte) 0xd3, (byte) 0x2a, (byte) 0xd8, (byte) 0xda, (byte) 0x13, + (byte) 0x94, (byte) 0x12, (byte) 0x78, (byte) 0x14, (byte) 0x0b, (byte) 0x51, + (byte) 0xc0, (byte) 0x45, (byte) 0xb4, (byte) 0x02, (byte) 0x37, (byte) 0x98, + (byte) 0x42, (byte) 0x3c, (byte) 0xcb, (byte) 0x2e, (byte) 0xe4, (byte) 0x38, + (byte) 0x69, (byte) 0x1b, (byte) 0x72, (byte) 0xf0, (byte) 0xaa, (byte) 0x89, + (byte) 0x7e, (byte) 0xde, (byte) 0xb2 + }; + + /** + * The amount of time to allow before and after expected time for variance + * in timing tests. + */ + private static final long SLOP_TIME_MILLIS = 15000L; + + @Override + protected void setUp() throws Exception { + mAndroidKeyStore = android.security.KeyStore.getInstance(); + + assertTrue(mAndroidKeyStore.reset()); + assertFalse(mAndroidKeyStore.isUnlocked()); + + mKeyStore = java.security.KeyStore.getInstance("AndroidKeyStore"); + } + + private void setupPassword() { + assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111")); + assertTrue(mAndroidKeyStore.isUnlocked()); + + assertEquals(0, mAndroidKeyStore.saw("").length); + } + + private void assertAliases(final String[] expectedAliases) throws KeyStoreException { + final Enumeration aliases = mKeyStore.aliases(); + int count = 0; + + final Set expectedSet = new HashSet(); + expectedSet.addAll(Arrays.asList(expectedAliases)); + + while (aliases.hasMoreElements()) { + count++; + final String alias = aliases.nextElement(); + assertTrue("The alias should be in the expected set", expectedSet.contains(alias)); + expectedSet.remove(alias); + } + assertTrue("The expected set and actual set should be exactly equal", expectedSet.isEmpty()); + assertEquals("There should be the correct number of keystore entries", + expectedAliases.length, count); + } + + public void testKeyStore_Aliases_Encrypted_Success() throws Exception { + setupPassword(); + + mKeyStore.load(null, null); + + assertAliases(new String[] {}); + + assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, + null)); + + assertAliases(new String[] { TEST_ALIAS_1 }); + + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 }); + } + + public void testKeyStore_Aliases_NotInitialized_Encrypted_Failure() throws Exception { + setupPassword(); + + try { + mKeyStore.aliases(); + fail("KeyStore should throw exception when not initialized"); + } catch (KeyStoreException success) { + } + } + + public void testKeyStore_ContainsAliases_PrivateAndCA_Encrypted_Success() throws Exception { + setupPassword(); + + mKeyStore.load(null, null); + + assertAliases(new String[] {}); + + assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, + null)); + + assertTrue("Should contain generated private key", mKeyStore.containsAlias(TEST_ALIAS_1)); + + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2)); + + assertFalse("Should not contain unadded certificate alias", + mKeyStore.containsAlias(TEST_ALIAS_3)); + } + + public void testKeyStore_ContainsAliases_CAOnly_Encrypted_Success() throws Exception { + setupPassword(); + + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2)); + } + + 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_Encrypted_Success() throws Exception { + setupPassword(); + + mKeyStore.load(null, null); + + // TEST_ALIAS_1 + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + // TEST_ALIAS_2 + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + // TEST_ALIAS_3 + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_3, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 }); + + mKeyStore.deleteEntry(TEST_ALIAS_1); + + assertAliases(new String[] { TEST_ALIAS_2, TEST_ALIAS_3 }); + + mKeyStore.deleteEntry(TEST_ALIAS_3); + + assertAliases(new String[] { TEST_ALIAS_2 }); + + mKeyStore.deleteEntry(TEST_ALIAS_2); + + assertAliases(new String[] { }); + } + + 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_Encrypted_Success() throws Exception { + setupPassword(); + + mKeyStore.load(null, null); + + // TEST_ALIAS_1 + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + // Should not throw when a non-existent entry is requested for delete. + mKeyStore.deleteEntry(TEST_ALIAS_2); + } + + public void testKeyStore_GetCertificate_Single_Encrypted_Success() throws Exception { + setupPassword(); + + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + assertAliases(new String[] { TEST_ALIAS_1 }); + + assertNull("Certificate should not exist in keystore", + mKeyStore.getCertificate(TEST_ALIAS_2)); + + Certificate retrieved = mKeyStore.getCertificate(TEST_ALIAS_1); + + assertNotNull("Retrieved certificate should not be null", retrieved); + + CertificateFactory f = CertificateFactory.getInstance("X.509"); + Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); + + assertEquals("Actual and retrieved certificates should be the same", actual, retrieved); + } + + 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_Encrypted_Success() throws Exception { + setupPassword(); + + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + CertificateFactory f = CertificateFactory.getInstance("X.509"); + Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); + + assertEquals("Stored certificate alias should be found", TEST_ALIAS_1, + mKeyStore.getCertificateAlias(actual)); + } + + public void testKeyStore_GetCertificateAlias_PrivateKeyEntry_Encrypted_Success() + throws Exception { + setupPassword(); + + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + CertificateFactory f = CertificateFactory.getInstance("X.509"); + Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + + assertEquals("Stored certificate alias should be found", TEST_ALIAS_1, + mKeyStore.getCertificateAlias(actual)); + } + + public void testKeyStore_GetCertificateAlias_CAEntry_WithPrivateKeyUsingCA_Encrypted_Success() + throws Exception { + setupPassword(); + + mKeyStore.load(null, null); + + // Insert TrustedCertificateEntry with CA name + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + // Insert PrivateKeyEntry that uses the same CA + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + CertificateFactory f = CertificateFactory.getInstance("X.509"); + Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); + + assertEquals("Stored certificate alias should be found", TEST_ALIAS_2, + mKeyStore.getCertificateAlias(actual)); + } + + public void testKeyStore_GetCertificateAlias_NonExist_Empty_Encrypted_Failure() + throws Exception { + setupPassword(); + + mKeyStore.load(null, null); + + CertificateFactory f = CertificateFactory.getInstance("X.509"); + Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); + + assertNull("Stored certificate alias should not be found", + mKeyStore.getCertificateAlias(actual)); + } + + public void testKeyStore_GetCertificateAlias_NonExist_Encrypted_Failure() throws Exception { + setupPassword(); + + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + CertificateFactory f = CertificateFactory.getInstance("X.509"); + Certificate userCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + + assertNull("Stored certificate alias should be found", + mKeyStore.getCertificateAlias(userCert)); + } + + public void testKeyStore_GetCertificateChain_SingleLength_Encrypted_Success() throws Exception { + setupPassword(); + + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate[] expected = new Certificate[2]; + expected[0] = cf.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + expected[1] = cf.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); + + Certificate[] actual = mKeyStore.getCertificateChain(TEST_ALIAS_1); + + assertNotNull("Returned certificate chain should not be null", actual); + assertEquals("Returned certificate chain should be correct size", expected.length, + actual.length); + assertEquals("First certificate should be user certificate", expected[0], actual[0]); + assertEquals("Second certificate should be CA certificate", expected[1], actual[1]); + + // Negative test when keystore is populated. + assertNull("Stored certificate alias should not be found", + mKeyStore.getCertificateChain(TEST_ALIAS_2)); + } + + 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_Encrypted_Success() throws Exception { + setupPassword(); + + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + 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_PrivateKeyEntry_Unencrypted_Success() throws Exception { + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, + KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_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_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + Date now = new Date(); + Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1); + assertNotNull("Certificate should be found", actual); + + 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_GetEntry_NullParams_Encrypted_Success() throws Exception { + setupPassword(); + + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + 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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); + } + + public void testKeyStore_GetEntry_EC_NullParams_Unencrypted_Success() throws Exception { + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_EC_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, + FAKE_EC_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_EC_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, "EC", FAKE_EC_KEY_1, FAKE_EC_USER_1, FAKE_EC_CA_1); + } + + public void testKeyStore_GetEntry_RSA_NullParams_Unencrypted_Success() throws Exception { + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, + FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); + } + + @SuppressWarnings("unchecked") + private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, String keyType, byte[] key, + byte[] cert, byte[] ca) throws Exception { + KeyFactory keyFact = KeyFactory.getInstance(keyType); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(key)); + + CertificateFactory certFact = CertificateFactory.getInstance("X.509"); + Certificate expectedCert = certFact.generateCertificate(new ByteArrayInputStream(cert)); + + final Collection expectedChain; + if (ca != null) { + expectedChain = (Collection) certFact + .generateCertificates(new ByteArrayInputStream(ca)); + } else { + expectedChain = null; + } + + assertPrivateKeyEntryEquals(keyEntry, expectedKey, expectedCert, expectedChain); + } + + private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, PrivateKey expectedKey, + Certificate expectedCert, Collection expectedChain) throws Exception { + if (expectedKey instanceof ECPrivateKey) { + assertEquals("Returned PrivateKey should be what we inserted", + ((ECPrivateKey) expectedKey).getParams().getCurve(), + ((ECPublicKey) keyEntry.getCertificate().getPublicKey()).getParams().getCurve()); + } else if (expectedKey instanceof RSAPrivateKey) { + 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()); + + Certificate[] actualChain = keyEntry.getCertificateChain(); + + assertEquals("First certificate in chain should be user cert", expectedCert, actualChain[0]); + + if (expectedChain == null) { + assertEquals("Certificate chain should not include CAs", 1, actualChain.length); + } else { + int i = 1; + final Iterator it = expectedChain.iterator(); + while (it.hasNext()) { + assertEquals("CA chain certificate should equal what we put in", it.next(), + actualChain[i++]); + } + } + } + + 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_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, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + 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_RSA_KEY_1)); + + assertEquals("Inserted key should be same as retrieved key", + ((RSAPrivateKey) expectedKey).getModulus(), actualKey.getModulus()); + } + + public void testKeyStore_GetKey_NoPassword_Unencrypted_Success() throws Exception { + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, + KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_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_RSA_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_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + assertNull("Certificate entries should return null", mKeyStore.getKey(TEST_ALIAS_1, null)); + } + + 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_Encrypted_Success() throws Exception { + assertEquals(AndroidKeyStoreProvider.PROVIDER_NAME, mKeyStore.getProvider().getName()); + setupPassword(); + assertEquals(AndroidKeyStoreProvider.PROVIDER_NAME, mKeyStore.getProvider().getName()); + } + + public void testKeyStore_GetType_Encrypted_Success() throws Exception { + assertEquals(AndroidKeyStoreSpi.NAME, mKeyStore.getType()); + setupPassword(); + assertEquals(AndroidKeyStoreSpi.NAME, mKeyStore.getType()); + } + + public void testKeyStore_IsCertificateEntry_CA_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + assertTrue("Should return true for CA certificate", + mKeyStore.isCertificateEntry(TEST_ALIAS_1)); + } + + public void testKeyStore_IsCertificateEntry_PrivateKey_Encrypted_Failure() throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + assertFalse("Should return false for PrivateKeyEntry", + mKeyStore.isCertificateEntry(TEST_ALIAS_1)); + } + + 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_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + assertTrue("Should return true for PrivateKeyEntry", mKeyStore.isKeyEntry(TEST_ALIAS_1)); + } + + public void testKeyStore_IsKeyEntry_CA_Encrypted_Failure() throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + assertFalse("Should return false for CA certificate", mKeyStore.isKeyEntry(TEST_ALIAS_1)); + } + + 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_Encrypted_Success() throws Exception { + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + final Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); + + setupPassword(); + mKeyStore.load(null, null); + + mKeyStore.setCertificateEntry(TEST_ALIAS_1, actual); + assertAliases(new String[] { TEST_ALIAS_1 }); + + Certificate retrieved = mKeyStore.getCertificate(TEST_ALIAS_1); + + assertEquals("Retrieved certificate should be the same as the one inserted", actual, + retrieved); + } + + 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_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + assertAliases(new String[] { TEST_ALIAS_1 }); + + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); + + // TODO have separate FAKE_CA for second test + mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert); + + assertAliases(new String[] { TEST_ALIAS_1 }); + } + + public void testKeyStore_SetCertificate_PrivateKeyExists_Encrypted_Failure() throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + assertAliases(new String[] { TEST_ALIAS_1 }); + + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); + + try { + mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert); + fail("Should throw when trying to overwrite a PrivateKey entry with a Certificate"); + } catch (KeyStoreException success) { + } + } + + public void testKeyStore_SetEntry_PrivateKeyEntry_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + KeyFactory keyFact = KeyFactory.getInstance("RSA"); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); + + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + + final Certificate[] expectedChain = new Certificate[2]; + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1); + } + + public void testKeyStore_SetEntry_PrivateKeyEntry_EC_Unencrypted_Success() throws Exception { + mKeyStore.load(null, null); + + KeyFactory keyFact = KeyFactory.getInstance("EC"); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_EC_KEY_1)); + + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + + final Certificate[] expectedChain = new Certificate[2]; + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_EC_USER_1)); + expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_EC_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, "EC", FAKE_EC_KEY_1, FAKE_EC_USER_1, FAKE_EC_CA_1); + } + + public void testKeyStore_SetEntry_PrivateKeyEntry_RSA_Unencrypted_Success() throws Exception { + mKeyStore.load(null, null); + + KeyFactory keyFact = KeyFactory.getInstance("RSA"); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); + + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + + final Certificate[] expectedChain = new Certificate[2]; + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_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_RSA_KEY_1)); + + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + + final Certificate[] expectedChain = new Certificate[2]; + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); + + PrivateKeyEntry entry = new PrivateKeyEntry(expectedKey, expectedChain); + + try { + mKeyStore.setEntry(TEST_ALIAS_1, entry, + new KeyStoreParameter.Builder(getContext()) + .setEncryptionRequired(true) + .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"); + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + + // Start with PrivateKeyEntry + { + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); + + final Certificate[] expectedChain = new Certificate[2]; + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); + } + + // TODO make entirely new test vector for the overwrite + // Replace with PrivateKeyEntry + { + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); + + final Certificate[] expectedChain = new Certificate[2]; + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); + } + } + + public void testKeyStore_SetEntry_CAEntry_Overwrites_PrivateKeyEntry_Encrypted_Success() + throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + + // Start with TrustedCertificateEntry + { + final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); + + TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert); + mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null); + + Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null); + assertNotNull("Retrieved entry should exist", actualEntry); + assertTrue("Retrieved entry should be of type TrustedCertificateEntry", + actualEntry instanceof TrustedCertificateEntry); + TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry; + assertEquals("Stored and retrieved certificates should be the same", + expectedCertEntry.getTrustedCertificate(), + actualCertEntry.getTrustedCertificate()); + } + + // Replace with PrivateKeyEntry + { + KeyFactory keyFact = KeyFactory.getInstance("RSA"); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); + final Certificate[] expectedChain = new Certificate[2]; + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); + + PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain); + + mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, 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 actualPrivEntry = (PrivateKeyEntry) actualEntry; + assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); + } + } + + public void testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_CAEntry_Encrypted_Success() + throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + + final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); + + // Start with PrivateKeyEntry + { + KeyFactory keyFact = KeyFactory.getInstance("RSA"); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); + final Certificate[] expectedChain = new Certificate[2]; + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + expectedChain[1] = caCert; + + PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain); + + mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, 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 actualPrivEntry = (PrivateKeyEntry) actualEntry; + assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); + } + + // Replace with TrustedCertificateEntry + { + TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert); + mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null); + + Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null); + assertNotNull("Retrieved entry should exist", actualEntry); + assertTrue("Retrieved entry should be of type TrustedCertificateEntry", + actualEntry instanceof TrustedCertificateEntry); + TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry; + assertEquals("Stored and retrieved certificates should be the same", + expectedCertEntry.getTrustedCertificate(), + actualCertEntry.getTrustedCertificate()); + } + } + + public + void + testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_ShortPrivateKeyEntry_Encrypted_Success() + throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + + final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); + + // Start with PrivateKeyEntry + { + KeyFactory keyFact = KeyFactory.getInstance("RSA"); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); + final Certificate[] expectedChain = new Certificate[2]; + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + expectedChain[1] = caCert; + + PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain); + + mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, 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 actualPrivEntry = (PrivateKeyEntry) actualEntry; + assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); + } + + // Replace with PrivateKeyEntry that has no chain + { + KeyFactory keyFact = KeyFactory.getInstance("RSA"); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); + final Certificate[] expectedChain = new Certificate[1]; + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + + PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain); + + mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, 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 actualPrivEntry = (PrivateKeyEntry) actualEntry; + assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + null); + } + } + + public void testKeyStore_SetEntry_CAEntry_Overwrites_CAEntry_Encrypted_Success() + throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + + // Insert TrustedCertificateEntry + { + final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); + + TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert); + mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null); + + Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null); + assertNotNull("Retrieved entry should exist", actualEntry); + assertTrue("Retrieved entry should be of type TrustedCertificateEntry", + actualEntry instanceof TrustedCertificateEntry); + TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry; + assertEquals("Stored and retrieved certificates should be the same", + expectedCertEntry.getTrustedCertificate(), + actualCertEntry.getTrustedCertificate()); + } + + // Replace with TrustedCertificateEntry of USER + { + final Certificate userCert = f + .generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + + TrustedCertificateEntry expectedUserEntry = new TrustedCertificateEntry(userCert); + mKeyStore.setEntry(TEST_ALIAS_1, expectedUserEntry, null); + + Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null); + assertNotNull("Retrieved entry should exist", actualEntry); + assertTrue("Retrieved entry should be of type TrustedCertificateEntry", + actualEntry instanceof TrustedCertificateEntry); + TrustedCertificateEntry actualUserEntry = (TrustedCertificateEntry) actualEntry; + assertEquals("Stored and retrieved certificates should be the same", + expectedUserEntry.getTrustedCertificate(), + actualUserEntry.getTrustedCertificate()); + } + } + + public void testKeyStore_SetKeyEntry_ProtectedKey_Encrypted_Failure() throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + + final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); + + KeyFactory keyFact = KeyFactory.getInstance("RSA"); + PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); + final Certificate[] chain = new Certificate[2]; + chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + chain[1] = caCert; + + try { + mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, "foo".toCharArray(), chain); + fail("Should fail when a password is specified"); + } catch (KeyStoreException success) { + } + } + + public void testKeyStore_SetKeyEntry_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + + final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); + + KeyFactory keyFact = KeyFactory.getInstance("RSA"); + PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); + final Certificate[] chain = new Certificate[2]; + chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + chain[1] = caCert; + + mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain); + + 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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1); + } + + public void testKeyStore_SetKeyEntry_Replaced_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + + final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); + + // Insert initial key + { + KeyFactory keyFact = KeyFactory.getInstance("RSA"); + PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); + final Certificate[] chain = new Certificate[2]; + chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + chain[1] = caCert; + + mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain); + + 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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); + } + + // TODO make a separate key + // Replace key + { + KeyFactory keyFact = KeyFactory.getInstance("RSA"); + PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); + final Certificate[] chain = new Certificate[2]; + chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + chain[1] = caCert; + + mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain); + + 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, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); + } + } + + @SuppressWarnings("deprecation") + private static X509Certificate generateCertificate(android.security.KeyStore keyStore, + String alias, BigInteger serialNumber, X500Principal subjectDN, Date notBefore, + Date notAfter) throws Exception { + final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; + + 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 = keyStore.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(serialNumber); + certGen.setSubjectDN(subjectDN); + certGen.setIssuerDN(subjectDN); + certGen.setNotBefore(notBefore); + certGen.setNotAfter(notAfter); + certGen.setSignatureAlgorithm("sha1WithRSA"); + + final X509Certificate cert = certGen.generate(privKey); + + return cert; + } + + public void testKeyStore_SetKeyEntry_ReplacedChain_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + // Create key #1 + { + final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1; + assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, + NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null)); + + Key key = mKeyStore.getKey(TEST_ALIAS_1, null); + + assertTrue(key instanceof PrivateKey); + + PrivateKey expectedKey = (PrivateKey) key; + + X509Certificate expectedCert = 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, + expectedCert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null); + + assertTrue(entry instanceof PrivateKeyEntry); + + PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry; + + assertPrivateKeyEntryEquals(keyEntry, expectedKey, expectedCert, null); + } + + // Replace key #1 with new chain + { + Key key = mKeyStore.getKey(TEST_ALIAS_1, null); + + assertTrue(key instanceof PrivateKey); + + PrivateKey expectedKey = (PrivateKey) key; + + X509Certificate expectedCert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1, + TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS); + + mKeyStore.setKeyEntry(TEST_ALIAS_1, expectedKey, null, + new Certificate[] { expectedCert }); + + Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null); + + assertTrue(entry instanceof PrivateKeyEntry); + + PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry; + + assertPrivateKeyEntryEquals(keyEntry, expectedKey, expectedCert, null); + } + } + + public void testKeyStore_SetKeyEntry_ReplacedChain_DifferentPrivateKey_Encrypted_Failure() + throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + // Create key #1 + { + final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1; + assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, + NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null)); + + 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(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + } + + // Create key #2 + { + final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_2; + assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, + NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null)); + + X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_2, + TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS); + + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_2, + cert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + } + + // Replace key #1 with key #2 + { + Key key1 = mKeyStore.getKey(TEST_ALIAS_2, null); + + X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_2, + TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS); + + try { + mKeyStore.setKeyEntry(TEST_ALIAS_1, key1, null, new Certificate[] { cert }); + fail("Should not allow setting of KeyEntry with wrong PrivaetKey"); + } catch (KeyStoreException success) { + } + } + } + + 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, NativeConstants.EVP_PKEY_RSA, 1024, + android.security.KeyStore.FLAG_NONE, null)); + + 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 KeyStoreParameter.Builder(getContext()) + .setEncryptionRequired(true) + .build()); + fail("Should not allow setting of Entry without unlocked keystore"); + } catch (KeyStoreException success) { + } + + assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111")); + assertTrue(mAndroidKeyStore.isUnlocked()); + + mKeyStore.setEntry(TEST_ALIAS_1, entry, + new KeyStoreParameter.Builder(getContext()) + .setEncryptionRequired(true) + .build()); + } + } + + public void testKeyStore_Size_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + assertEquals("The keystore size should match expected", 1, mKeyStore.size()); + assertAliases(new String[] { TEST_ALIAS_1 }); + + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + + assertEquals("The keystore size should match expected", 2, mKeyStore.size()); + assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 }); + + assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3, + KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, + null)); + + assertEquals("The keystore size should match expected", 3, mKeyStore.size()); + assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 }); + + assertTrue(mAndroidKeyStore.delete(Credentials.CA_CERTIFICATE + TEST_ALIAS_1)); + + assertEquals("The keystore size should match expected", 2, mKeyStore.size()); + assertAliases(new String[] { TEST_ALIAS_2, TEST_ALIAS_3 }); + + assertTrue(mAndroidKeyStore.delete(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3)); + + assertEquals("The keystore size should match expected", 1, mKeyStore.size()); + assertAliases(new String[] { TEST_ALIAS_2 }); + } + + public void testKeyStore_Store_LoadStoreParam_Encrypted_Failure() throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + try { + mKeyStore.store(null); + fail("Should throw UnsupportedOperationException when trying to store"); + } catch (UnsupportedOperationException success) { + } + } + + public void testKeyStore_Load_InputStreamSupplied_Encrypted_Failure() throws Exception { + byte[] buf = "FAKE KEYSTORE".getBytes(); + ByteArrayInputStream is = new ByteArrayInputStream(buf); + + try { + mKeyStore.load(is, null); + fail("Should throw IllegalArgumentException when InputStream is supplied"); + } catch (IllegalArgumentException success) { + } + } + + public void testKeyStore_Load_PasswordSupplied_Encrypted_Failure() throws Exception { + try { + mKeyStore.load(null, "password".toCharArray()); + fail("Should throw IllegalArgumentException when password is supplied"); + } catch (IllegalArgumentException success) { + } + } + + public void testKeyStore_Store_OutputStream_Encrypted_Failure() throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + OutputStream sink = new ByteArrayOutputStream(); + try { + mKeyStore.store(sink, null); + fail("Should throw UnsupportedOperationException when trying to store"); + } catch (UnsupportedOperationException success) { + } + + try { + mKeyStore.store(sink, "blah".toCharArray()); + fail("Should throw UnsupportedOperationException when trying to store"); + } catch (UnsupportedOperationException success) { + } + } + + private void setupKey() throws Exception { + final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1; + assertTrue(mAndroidKeyStore + .generate(privateKeyAlias, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024, + KeyStore.FLAG_ENCRYPTED, null)); + + 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(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + } + + public void testKeyStore_KeyOperations_Wrap_Encrypted_Success() throws Exception { + setupPassword(); + mKeyStore.load(null, null); + + setupKey(); + + // Test key usage + Entry e = mKeyStore.getEntry(TEST_ALIAS_1, null); + assertNotNull(e); + assertTrue(e instanceof PrivateKeyEntry); + + PrivateKeyEntry privEntry = (PrivateKeyEntry) e; + PrivateKey privKey = privEntry.getPrivateKey(); + assertNotNull(privKey); + + PublicKey pubKey = privEntry.getCertificate().getPublicKey(); + + Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + c.init(Cipher.WRAP_MODE, pubKey); + + byte[] expectedKey = new byte[] { + 0x00, 0x05, (byte) 0xAA, (byte) 0x0A5, (byte) 0xFF, 0x55, 0x0A + }; + + SecretKey expectedSecret = new SecretKeySpec(expectedKey, "AES"); + + byte[] wrappedExpected = c.wrap(expectedSecret); + + c.init(Cipher.UNWRAP_MODE, privKey); + SecretKey actualSecret = (SecretKey) c.unwrap(wrappedExpected, "AES", Cipher.SECRET_KEY); + + assertEquals(Arrays.toString(expectedSecret.getEncoded()), + Arrays.toString(actualSecret.getEncoded())); + } +} diff --git a/preloaded-classes b/preloaded-classes index d2ed762..41a8857 100644 --- a/preloaded-classes +++ b/preloaded-classes @@ -1152,8 +1152,8 @@ android.provider.Settings$SettingNotFoundException android.provider.Settings$System android.provider.Telephony$Mms android.renderscript.RenderScript -android.security.AndroidKeyStoreBCWorkaroundProvider -android.security.AndroidKeyStoreProvider +android.security.keystore.AndroidKeyStoreBCWorkaroundProvider +android.security.keystore.AndroidKeyStoreProvider android.speech.tts.TextToSpeechService android.speech.tts.TextToSpeechService$SpeechItemV1 android.speech.tts.TextToSpeechService$SynthesisSpeechItemV1 -- cgit v1.1