summaryrefslogtreecommitdiffstats
path: root/keystore
diff options
context:
space:
mode:
Diffstat (limited to 'keystore')
-rw-r--r--keystore/java/android/security/EcIesParameterSpec.java272
-rw-r--r--keystore/java/android/security/KeyChain.java15
-rw-r--r--keystore/java/android/security/KeyPairGeneratorSpec.java4
-rw-r--r--keystore/java/android/security/KeyStore.java30
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java442
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java3
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java157
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreECDSASignatureSpi.java4
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreECPrivateKey.java40
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java5
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreKey.java38
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java142
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreProvider.java158
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java27
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java33
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreRSAPrivateKey.java41
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreRSASignatureSpi.java6
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreSignatureSpiBase.java26
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreSpi.java161
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java21
-rw-r--r--keystore/java/android/security/keystore/DelegatingX509Certificate.java212
-rw-r--r--keystore/java/android/security/keystore/KeyGenParameterSpec.java122
-rw-r--r--keystore/java/android/security/keystore/KeyInfo.java14
-rw-r--r--keystore/java/android/security/keystore/KeyProperties.java6
-rw-r--r--keystore/java/android/security/keystore/KeyProtection.java61
-rw-r--r--keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java50
-rw-r--r--keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java9
-rw-r--r--keystore/java/android/security/keystore/KeymasterUtils.java11
-rw-r--r--keystore/java/android/security/keystore/Utils.java32
-rw-r--r--keystore/tests/src/android/security/KeyStoreTest.java5
-rw-r--r--keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java56
-rw-r--r--keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java67
32 files changed, 1574 insertions, 696 deletions
diff --git a/keystore/java/android/security/EcIesParameterSpec.java b/keystore/java/android/security/EcIesParameterSpec.java
deleted file mode 100644
index 1cd8784..0000000
--- a/keystore/java/android/security/EcIesParameterSpec.java
+++ /dev/null
@@ -1,272 +0,0 @@
-package android.security;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.security.spec.AlgorithmParameterSpec;
-
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-
-/**
- * {@link AlgorithmParameterSpec} for ECIES (Integrated Encryption Scheme using Elliptic Curve
- * cryptography) based on {@code ISO/IEC 18033-2}.
- *
- * <p>ECIES is a hybrid authenticated encryption scheme. Encryption is performed using an Elliptic
- * Curve (EC) public key. The resulting ciphertext can be decrypted only using the corresponding EC
- * private key. The scheme is called hybrid because the EC key is only used to securely encapsulate
- * symmetric key material. Encryption of plaintext and authentication of the corresponding
- * ciphertext is performed using symmetric cryptography.
- *
- * <p>Encryption using ECIES consists of two stages:
- * <ol>
- * <li>Key Encapsulation Mechanism (KEM) randomly generates symmetric key material and securely
- * encapsulates it in the output so that it can be extracted by the KEM when decrypting.
- * Encapsulated key material is represented in the output as an EC point.</li>
- * <li>The above symmetric key material is used by Data Encapsulation Mechanism (DEM) to encrypt the
- * provided plaintext and authenticate the ciphertext. The resulting authenticated ciphertext is
- * then output. When decrypting, the DEM first authenticates the ciphertext and, only if it
- * authenticates, decrypts the ciphertext and outputs the plaintext.</li>
- * </ol>
- *
- * <p>Details of KEM:
- * <ul>
- * <li>Only curves with cofactor of {@code 1} are supported.</li>
- * <li>{@code CheckMode}, {@code OldCofactorMode}, {@code CofactorMode}, and {@code SingleHashMode}
- * are {@code 0}.
- * <li>Point format is specified by {@link #getKemPointFormat()}.</li>
- * <li>KDF algorithm is specified by {@link #getKemKdfAlgorithm()}.</li>
- * </ul>
- *
- * <p>Details of DEM:
- * <ul>
- * <li>Only DEM1-like mechanism is supported, with its symmetric cipher (SC) specified by
- * {@link #getDemCipherTransformation()} (e.g., {@code AES/CBC/NoPadding} for standard DEM1) and
- * MAC algorithm specified by {@link #getDemMacAlgorithm()} (e.g., {@code HmacSHA1} for standard
- * DEM1).</li>
- * </ul>
- */
-public class EcIesParameterSpec implements AlgorithmParameterSpec {
-
- /**
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({
- POINT_FORMAT_UNSPECIFIED,
- POINT_FORMAT_UNCOMPRESSED,
- POINT_FORMAT_COMPRESSED,
- })
- public @interface PointFormatEnum {}
-
- /** Unspecified EC point format. */
- public static final int POINT_FORMAT_UNSPECIFIED = -1;
-
- /**
- * Uncompressed EC point format: both coordinates are stored separately.
- *
- * <p>The wire format is byte {@code 0x04} followed by binary representation of the {@code x}
- * coordinate followed by binary representation of the {@code y} coordinate. See
- * {@code ISO 18033-2} section {@code 5.4.3}.
- */
- public static final int POINT_FORMAT_UNCOMPRESSED = 0;
-
- /**
- * Compressed EC point format: only one coordinate is stored.
- *
- * <p>The wire format is byte {@code 0x02} or {@code 0x03} (depending on the value of the stored
- * coordinate) followed by the binary representation of the {@code x} coordinate. See
- * {@code ISO 18033-2} section {@code 5.4.3}.
- */
- public static final int POINT_FORMAT_COMPRESSED = 1;
-
- /**
- * Default parameter spec: compressed point format, {@code HKDFwithSHA256}, DEM uses 128-bit AES
- * GCM.
- */
- public static final EcIesParameterSpec DEFAULT = new EcIesParameterSpec(
- POINT_FORMAT_COMPRESSED,
- "HKDFwithSHA256",
- "AES/GCM/NoPadding",
- 128,
- null,
- 0);
-
- private final @PointFormatEnum int mKemPointFormat;
- private final String mKemKdfAlgorithm;
- private final String mDemCipherTransformation;
- private final int mDemCipherKeySize;
- private final String mDemMacAlgorithm;
- private final int mDemMacKeySize;
-
- private EcIesParameterSpec(
- @PointFormatEnum int kemPointFormat,
- String kemKdfAlgorithm,
- String demCipherTransformation,
- int demCipherKeySize,
- String demMacAlgorithm,
- int demMacKeySize) {
- mKemPointFormat = kemPointFormat;
- mKemKdfAlgorithm = kemKdfAlgorithm;
- mDemCipherTransformation = demCipherTransformation;
- mDemCipherKeySize = demCipherKeySize;
- mDemMacAlgorithm = demMacAlgorithm;
- mDemMacKeySize = demMacKeySize;
- }
-
- /**
- * Returns KEM EC point wire format or {@link #POINT_FORMAT_UNSPECIFIED} if not specified.
- */
- public @PointFormatEnum int getKemPointFormat() {
- return mKemPointFormat;
- }
-
- /**
- * Returns KEM KDF algorithm (e.g., {@code HKDFwithSHA256} or {@code KDF1withSHA1}) or
- * {@code null} if not specified.
- */
- @Nullable
- public String getKemKdfAlgorithm() {
- return mKemKdfAlgorithm;
- }
-
- /**
- * Returns DEM {@link Cipher} transformation (e.g., {@code AES/GCM/NoPadding} or
- * {@code AES/CBC/PKCS7Padding}) or {@code null} if not specified.
- *
- * @see Cipher#getInstance(String)
- * @see #getDemCipherKeySize()
- */
- @Nullable
- public String getDemCipherTransformation() {
- return mDemCipherTransformation;
- }
-
- /**
- * Returns DEM {@link Cipher} key size in bits.
- *
- * @see #getDemCipherTransformation()
- */
- public int getDemCipherKeySize() {
- return mDemCipherKeySize;
- }
-
- /**
- * Returns DEM {@link Mac} algorithm (e.g., {@code HmacSHA256} or {@code HmacSHA1}) or
- * {@code null} if not specified.
- *
- * @see Mac#getInstance(String)
- * @see #getDemMacKeySize()
- */
- @Nullable
- public String getDemMacAlgorithm() {
- return mDemMacAlgorithm;
- }
-
- /**
- * Returns DEM {@link Mac} key size in bits.
- *
- * @see #getDemCipherTransformation()
- */
- public int getDemMacKeySize() {
- return mDemMacKeySize;
- }
-
- /**
- * Builder of {@link EcIesParameterSpec}.
- */
- public static class Builder {
- private @PointFormatEnum int mKemPointFormat = POINT_FORMAT_UNSPECIFIED;
- private String mKemKdfAlgorithm;
- private String mDemCipherTransformation;
- private int mDemCipherKeySize = 128;
- private String mDemMacAlgorithm;
- private int mDemMacKeySize = -1;
-
- /**
- * Sets KEM EC point wire format.
- */
- public Builder setKemPointFormat(@PointFormatEnum int pointFormat) {
- mKemPointFormat = pointFormat;
- return this;
- }
-
- /**
- * Sets KEM KDF algorithm. For example, {@code HKDFwithSHA256}, {@code KDF2withSHA256}, or
- * {@code KDF1withSHA1}.
- */
- @NonNull
- public Builder setKemKdfAlgorithm(@Nullable String algorithm) {
- mKemKdfAlgorithm = algorithm;
- return this;
- }
-
- /**
- * Sets DEM {@link Cipher} transformation. For example, {@code AES/GCM/NoPadding},
- * {@code AES/CBC/PKCS7Padding} or {@code AES/CTR/NoPadding}.
- *
- * @see Cipher#getInstance(String)
- */
- @NonNull
- public Builder setDemCipherTransformation(@Nullable String transformation) {
- mDemCipherTransformation = transformation;
- return this;
- }
-
- /**
- * Returns DEM {@link Cipher} key size in bits.
- *
- * <p>The default value is {@code 128} bits.
- *
- * @see #setDemCipherTransformation(String)
- */
- @NonNull
- public Builder setDemCipherKeySize(int sizeBits) {
- mDemCipherKeySize = sizeBits;
- return this;
- }
-
- /**
- * Sets DEM {@link Mac} algorithm. For example, {@code HmacSHA256} or {@code HmacSHA1}.
- *
- * @see Mac#getInstance(String)
- */
- @NonNull
- public Builder setDemMacAlgorithm(@Nullable String algorithm) {
- mDemMacAlgorithm = algorithm;
- return this;
- }
-
- /**
- * Sets DEM {@link Mac} key size in bits.
- *
- * <p>By default, {@code Mac} key size is the same as the {@code Cipher} key size.
- *
- * @see #setDemCipherKeySize(int)
- */
- @NonNull
- public Builder setDemMacKeySize(int sizeBits) {
- mDemMacKeySize = sizeBits;
- return this;
- }
-
- /**
- * Returns a new {@link EcIesParameterSpec} based on the current state of this builder.
- */
- @NonNull
- public EcIesParameterSpec build() {
- int demMacKeySize = (mDemMacKeySize != -1) ? mDemMacKeySize : mDemCipherKeySize;
- return new EcIesParameterSpec(
- mKemPointFormat,
- mKemKdfAlgorithm,
- mDemCipherTransformation,
- mDemCipherKeySize,
- mDemMacAlgorithm,
- demMacKeySize
- );
- }
- }
-}
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 059d8e6..f482bf0 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -29,15 +29,14 @@ import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.security.keystore.KeyInfo;
+import android.security.keystore.AndroidKeyStoreProvider;
import android.security.keystore.KeyProperties;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
-import java.security.InvalidKeyException;
-import java.security.KeyFactory;
import java.security.Principal;
import java.security.PrivateKey;
+import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
@@ -47,7 +46,6 @@ import java.util.Locale;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
-import com.android.org.conscrypt.OpenSSLEngine;
import com.android.org.conscrypt.TrustedCertificateStore;
/**
@@ -90,8 +88,6 @@ import com.android.org.conscrypt.TrustedCertificateStore;
// TODO reference intent for credential installation when public
public final class KeyChain {
- private static final String TAG = "KeyChain";
-
/**
* @hide Also used by KeyChainService implementation
*/
@@ -372,15 +368,14 @@ public final class KeyChain {
if (keyId == null) {
throw new KeyChainException("keystore had a problem");
}
-
- final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
- return engine.getPrivateKeyById(keyId);
+ return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
+ KeyStore.getInstance(), keyId);
} catch (RemoteException e) {
throw new KeyChainException(e);
} catch (RuntimeException e) {
// only certain RuntimeExceptions can be propagated across the IKeyChainService call
throw new KeyChainException(e);
- } catch (InvalidKeyException e) {
+ } catch (UnrecoverableKeyException e) {
throw new KeyChainException(e);
} finally {
keyChainConnection.close();
diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java
index efbce41..d849317 100644
--- a/keystore/java/android/security/KeyPairGeneratorSpec.java
+++ b/keystore/java/android/security/KeyPairGeneratorSpec.java
@@ -331,7 +331,9 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
if (keyType == null) {
throw new NullPointerException("keyType == null");
} else {
- if (KeyStore.getKeyTypeForAlgorithm(keyType) == -1) {
+ try {
+ KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(keyType);
+ } catch (IllegalArgumentException e) {
throw new NoSuchAlgorithmException("Unsupported key type: " + keyType);
}
}
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 893771a..6a08368 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -19,7 +19,6 @@ package android.security;
import android.app.ActivityThread;
import android.app.Application;
import android.app.KeyguardManager;
-import com.android.org.conscrypt.NativeConstants;
import android.content.Context;
import android.hardware.fingerprint.FingerprintManager;
@@ -38,7 +37,6 @@ import android.security.keymaster.OperationResult;
import android.security.keystore.KeyExpiredException;
import android.security.keystore.KeyNotYetValidException;
import android.security.keystore.KeyPermanentlyInvalidatedException;
-import android.security.keystore.KeyProperties;
import android.security.keystore.UserNotAuthenticatedException;
import android.util.Log;
@@ -110,15 +108,10 @@ public class KeyStore {
}
public static Context getApplicationContext() {
- ActivityThread activityThread = ActivityThread.currentActivityThread();
- if (activityThread == null) {
- throw new IllegalStateException(
- "Failed to obtain application Context: no ActivityThread");
- }
- Application application = activityThread.getApplication();
+ Application application = ActivityThread.currentApplication();
if (application == null) {
throw new IllegalStateException(
- "Failed to obtain application Context: no Application");
+ "Failed to obtain application Context from ActivityThread");
}
return application;
}
@@ -136,16 +129,6 @@ public class KeyStore {
return mToken;
}
- 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)) {
- return NativeConstants.EVP_PKEY_EC;
- } else {
- return -1;
- }
- }
-
public State state(int userId) {
final int ret;
try {
@@ -710,16 +693,13 @@ public class KeyStore {
}
private long getFingerprintOnlySid() {
- FingerprintManager fingerprintManager =
- mContext.getSystemService(FingerprintManager.class);
+ FingerprintManager fingerprintManager = mContext.getSystemService(FingerprintManager.class);
if (fingerprintManager == null) {
return 0;
}
- if (!fingerprintManager.isHardwareDetected()) {
- return 0;
- }
-
+ // TODO: Restore USE_FINGERPRINT permission check in
+ // FingerprintManager.getAuthenticatorId once the ID is no longer needed here.
return fingerprintManager.getAuthenticatorId();
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java
new file mode 100644
index 0000000..f412743
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java
@@ -0,0 +1,442 @@
+/*
+ * 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.annotation.NonNull;
+import android.annotation.Nullable;
+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.KeyStoreCryptoOperationChunkedStreamer.Stream;
+
+import libcore.util.EmptyArray;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.ProviderException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+import java.util.Arrays;
+
+import javax.crypto.CipherSpi;
+import javax.crypto.spec.GCMParameterSpec;
+
+/**
+ * Base class for Android Keystore authenticated AES {@link CipherSpi} implementations.
+ *
+ * @hide
+ */
+abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase {
+
+ abstract static class GCM extends AndroidKeyStoreAuthenticatedAESCipherSpi {
+ private static final int MIN_SUPPORTED_TAG_LENGTH_BITS = 96;
+ private static final int MAX_SUPPORTED_TAG_LENGTH_BITS = 128;
+ private static final int DEFAULT_TAG_LENGTH_BITS = 128;
+ private static final int IV_LENGTH_BYTES = 12;
+
+ private int mTagLengthBits = DEFAULT_TAG_LENGTH_BITS;
+
+ GCM(int keymasterPadding) {
+ super(KeymasterDefs.KM_MODE_GCM, keymasterPadding);
+ }
+
+ @Override
+ protected final void resetAll() {
+ mTagLengthBits = DEFAULT_TAG_LENGTH_BITS;
+ super.resetAll();
+ }
+
+ @Override
+ protected final void resetWhilePreservingInitState() {
+ super.resetWhilePreservingInitState();
+ }
+
+ @Override
+ protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {
+ if (!isEncrypting()) {
+ throw new InvalidKeyException("IV required when decrypting"
+ + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
+ }
+ }
+
+ @Override
+ protected final void initAlgorithmSpecificParameters(AlgorithmParameterSpec params)
+ throws InvalidAlgorithmParameterException {
+ // IV is used
+ if (params == null) {
+ if (!isEncrypting()) {
+ // IV must be provided by the caller
+ throw new InvalidAlgorithmParameterException(
+ "GCMParameterSpec must be provided when decrypting");
+ }
+ return;
+ }
+ if (!(params instanceof GCMParameterSpec)) {
+ throw new InvalidAlgorithmParameterException("Only GCMParameterSpec supported");
+ }
+ GCMParameterSpec spec = (GCMParameterSpec) params;
+ byte[] iv = spec.getIV();
+ if (iv == null) {
+ throw new InvalidAlgorithmParameterException("Null IV in GCMParameterSpec");
+ } else if (iv.length != IV_LENGTH_BYTES) {
+ throw new InvalidAlgorithmParameterException("Unsupported IV length: "
+ + iv.length + " bytes. Only " + IV_LENGTH_BYTES
+ + " bytes long IV supported");
+ }
+ int tagLengthBits = spec.getTLen();
+ if ((tagLengthBits < MIN_SUPPORTED_TAG_LENGTH_BITS)
+ || (tagLengthBits > MAX_SUPPORTED_TAG_LENGTH_BITS)
+ || ((tagLengthBits % 8) != 0)) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported tag length: " + tagLengthBits + " bits"
+ + ". Supported lengths: 96, 104, 112, 120, 128");
+ }
+ setIv(iv);
+ mTagLengthBits = tagLengthBits;
+ }
+
+ @Override
+ protected final void initAlgorithmSpecificParameters(AlgorithmParameters params)
+ throws InvalidAlgorithmParameterException {
+ if (params == null) {
+ if (!isEncrypting()) {
+ // IV must be provided by the caller
+ throw new InvalidAlgorithmParameterException("IV required when decrypting"
+ + ". Use GCMParameterSpec or GCM AlgorithmParameters to provide it.");
+ }
+ return;
+ }
+
+ GCMParameterSpec spec;
+ try {
+ spec = params.getParameterSpec(GCMParameterSpec.class);
+ } catch (InvalidParameterSpecException e) {
+ if (!isEncrypting()) {
+ // IV must be provided by the caller
+ throw new InvalidAlgorithmParameterException("IV and tag length required when"
+ + " decrypting, but not found in parameters: " + params, e);
+ }
+ setIv(null);
+ return;
+ }
+ initAlgorithmSpecificParameters(spec);
+ }
+
+ @Nullable
+ @Override
+ protected final AlgorithmParameters engineGetParameters() {
+ byte[] iv = getIv();
+ if ((iv != null) && (iv.length > 0)) {
+ try {
+ AlgorithmParameters params = AlgorithmParameters.getInstance("GCM");
+ params.init(new GCMParameterSpec(mTagLengthBits, iv));
+ return params;
+ } catch (NoSuchAlgorithmException e) {
+ throw new ProviderException(
+ "Failed to obtain GCM AlgorithmParameters", e);
+ } catch (InvalidParameterSpecException e) {
+ throw new ProviderException(
+ "Failed to initialize GCM AlgorithmParameters", e);
+ }
+ }
+ return null;
+ }
+
+ @NonNull
+ @Override
+ protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
+ KeyStore keyStore, IBinder operationToken) {
+ KeyStoreCryptoOperationStreamer streamer = new KeyStoreCryptoOperationChunkedStreamer(
+ new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
+ keyStore, operationToken));
+ if (isEncrypting()) {
+ return streamer;
+ } else {
+ // When decrypting, to avoid leaking unauthenticated plaintext, do not return any
+ // plaintext before ciphertext is authenticated by KeyStore.finish.
+ return new BufferAllOutputUntilDoFinalStreamer(streamer);
+ }
+ }
+
+ @NonNull
+ @Override
+ protected final KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer(
+ KeyStore keyStore, IBinder operationToken) {
+ return new KeyStoreCryptoOperationChunkedStreamer(
+ new AdditionalAuthenticationDataStream(keyStore, operationToken));
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForBegin() {
+ if ((getIv() == null) && (isEncrypting())) {
+ // IV will need to be generated
+ return IV_LENGTH_BYTES;
+ }
+
+ return 0;
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForFinish() {
+ return 0;
+ }
+
+ @Override
+ protected final void addAlgorithmSpecificParametersToBegin(
+ @NonNull KeymasterArguments keymasterArgs) {
+ super.addAlgorithmSpecificParametersToBegin(keymasterArgs);
+ keymasterArgs.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mTagLengthBits);
+ }
+
+ protected final int getTagLengthBits() {
+ return mTagLengthBits;
+ }
+
+ public static final class NoPadding extends GCM {
+ public NoPadding() {
+ super(KeymasterDefs.KM_PAD_NONE);
+ }
+
+ @Override
+ protected final int engineGetOutputSize(int inputLen) {
+ int tagLengthBytes = (getTagLengthBits() + 7) / 8;
+ long result;
+ if (isEncrypting()) {
+ result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen
+ + tagLengthBytes;
+ } else {
+ result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen
+ - tagLengthBytes;
+ }
+ if (result < 0) {
+ return 0;
+ } else if (result > Integer.MAX_VALUE) {
+ return Integer.MAX_VALUE;
+ }
+ return (int) result;
+ }
+ }
+ }
+
+ private static final int BLOCK_SIZE_BYTES = 16;
+
+ private final int mKeymasterBlockMode;
+ private final int mKeymasterPadding;
+
+ private byte[] mIv;
+
+ /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
+ private boolean mIvHasBeenUsed;
+
+ AndroidKeyStoreAuthenticatedAESCipherSpi(
+ int keymasterBlockMode,
+ int keymasterPadding) {
+ mKeymasterBlockMode = keymasterBlockMode;
+ mKeymasterPadding = keymasterPadding;
+ }
+
+ @Override
+ protected void resetAll() {
+ mIv = null;
+ mIvHasBeenUsed = false;
+ super.resetAll();
+ }
+
+ @Override
+ protected final void initKey(int opmode, Key key) throws InvalidKeyException {
+ if (!(key instanceof AndroidKeyStoreSecretKey)) {
+ throw new InvalidKeyException(
+ "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
+ }
+ if (!KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(key.getAlgorithm())) {
+ throw new InvalidKeyException(
+ "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " +
+ KeyProperties.KEY_ALGORITHM_AES + " supported");
+ }
+ setKey((AndroidKeyStoreSecretKey) key);
+ }
+
+ @Override
+ protected void addAlgorithmSpecificParametersToBegin(
+ @NonNull KeymasterArguments keymasterArgs) {
+ if ((isEncrypting()) && (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.");
+ }
+
+ keymasterArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
+ keymasterArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
+ keymasterArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
+ if (mIv != null) {
+ keymasterArgs.addBlob(KeymasterDefs.KM_TAG_NONCE, mIv);
+ }
+ }
+
+ @Override
+ protected final void loadAlgorithmSpecificParametersFromBeginResult(
+ @NonNull KeymasterArguments keymasterArgs) {
+ mIvHasBeenUsed = true;
+
+ // 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 (mIv == null) {
+ mIv = returnedIv;
+ } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
+ throw new ProviderException("IV in use differs from provided IV");
+ }
+ }
+
+ @Override
+ protected final int engineGetBlockSize() {
+ return BLOCK_SIZE_BYTES;
+ }
+
+ @Override
+ protected final byte[] engineGetIV() {
+ return ArrayUtils.cloneIfNotEmpty(mIv);
+ }
+
+ protected void setIv(byte[] iv) {
+ mIv = iv;
+ }
+
+ protected byte[] getIv() {
+ return mIv;
+ }
+
+ /**
+ * {@link KeyStoreCryptoOperationStreamer} which buffers all output until {@code doFinal} from
+ * which it returns all output in one go, provided {@code doFinal} succeeds.
+ */
+ private static class BufferAllOutputUntilDoFinalStreamer
+ implements KeyStoreCryptoOperationStreamer {
+
+ private final KeyStoreCryptoOperationStreamer mDelegate;
+ private ByteArrayOutputStream mBufferedOutput = new ByteArrayOutputStream();
+ private long mProducedOutputSizeBytes;
+
+ private BufferAllOutputUntilDoFinalStreamer(KeyStoreCryptoOperationStreamer delegate) {
+ mDelegate = delegate;
+ }
+
+ @Override
+ public byte[] update(byte[] input, int inputOffset, int inputLength)
+ throws KeyStoreException {
+ byte[] output = mDelegate.update(input, inputOffset, inputLength);
+ if (output != null) {
+ try {
+ mBufferedOutput.write(output);
+ } catch (IOException e) {
+ throw new ProviderException("Failed to buffer output", e);
+ }
+ }
+ return EmptyArray.BYTE;
+ }
+
+ @Override
+ public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
+ byte[] additionalEntropy) throws KeyStoreException {
+ byte[] output = mDelegate.doFinal(input, inputOffset, inputLength, additionalEntropy);
+ if (output != null) {
+ try {
+ mBufferedOutput.write(output);
+ } catch (IOException e) {
+ throw new ProviderException("Failed to buffer output", e);
+ }
+ }
+ byte[] result = mBufferedOutput.toByteArray();
+ mBufferedOutput.reset();
+ mProducedOutputSizeBytes += result.length;
+ return result;
+ }
+
+ @Override
+ public long getConsumedInputSizeBytes() {
+ return mDelegate.getConsumedInputSizeBytes();
+ }
+
+ @Override
+ public long getProducedOutputSizeBytes() {
+ return mProducedOutputSizeBytes;
+ }
+ }
+
+ /**
+ * Additional Authentication Data (AAD) stream via a KeyStore streaming operation. This stream
+ * sends AAD into the KeyStore.
+ */
+ private static class AdditionalAuthenticationDataStream implements Stream {
+
+ private final KeyStore mKeyStore;
+ private final IBinder mOperationToken;
+
+ private AdditionalAuthenticationDataStream(KeyStore keyStore, IBinder operationToken) {
+ mKeyStore = keyStore;
+ mOperationToken = operationToken;
+ }
+
+ @Override
+ public OperationResult update(byte[] input) {
+ KeymasterArguments keymasterArgs = new KeymasterArguments();
+ keymasterArgs.addBlob(KeymasterDefs.KM_TAG_ASSOCIATED_DATA, input);
+
+ // KeyStore does not reflect AAD in inputConsumed, but users of Stream rely on this
+ // field. We fix this discrepancy here. KeyStore.update contract is that all of AAD
+ // has been consumed if the method succeeds.
+ OperationResult result = mKeyStore.update(mOperationToken, keymasterArgs, null);
+ if (result.resultCode == KeyStore.NO_ERROR) {
+ result = new OperationResult(
+ result.resultCode,
+ result.token,
+ result.operationHandle,
+ input.length, // inputConsumed
+ result.output,
+ result.outParams);
+ }
+ return result;
+ }
+
+ @Override
+ public OperationResult finish(byte[] additionalEntropy) {
+ if ((additionalEntropy != null) && (additionalEntropy.length > 0)) {
+ throw new ProviderException("AAD stream does not support additional entropy");
+ }
+ return new OperationResult(
+ KeyStore.NO_ERROR,
+ mOperationToken,
+ 0, // operation handle -- nobody cares about this being returned from finish
+ 0, // inputConsumed
+ EmptyArray.BYTE, // output
+ new KeymasterArguments() // additional params returned by finish
+ );
+ }
+ }
+} \ No newline at end of file
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
index f37cf07..06b76fa 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
@@ -93,6 +93,9 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider {
putSymmetricCipherImpl("AES/CTR/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CTR$NoPadding");
+ putSymmetricCipherImpl("AES/GCM/NoPadding",
+ PACKAGE_NAME + ".AndroidKeyStoreAuthenticatedAESCipherSpi$GCM$NoPadding");
+
putAsymmetricCipherImpl("RSA/ECB/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$NoPadding");
put("Alg.Alias.Cipher.RSA/None/NoPadding", "RSA/ECB/NoPadding");
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
index 19375a2..fc53451 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
@@ -26,6 +26,8 @@ import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keymaster.OperationResult;
+import libcore.util.EmptyArray;
+
import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
@@ -78,6 +80,8 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
private IBinder mOperationToken;
private long mOperationHandle;
private KeyStoreCryptoOperationStreamer mMainDataStreamer;
+ private KeyStoreCryptoOperationStreamer mAdditionalAuthenticationDataStreamer;
+ private boolean mAdditionalAuthenticationDataStreamerClosed;
/**
* Encountered exception which could not be immediately thrown because it was encountered inside
@@ -189,6 +193,8 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
mOperationToken = null;
mOperationHandle = 0;
mMainDataStreamer = null;
+ mAdditionalAuthenticationDataStreamer = null;
+ mAdditionalAuthenticationDataStreamerClosed = false;
mCachedException = null;
}
@@ -209,6 +215,8 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
mOperationToken = null;
mOperationHandle = 0;
mMainDataStreamer = null;
+ mAdditionalAuthenticationDataStreamer = null;
+ mAdditionalAuthenticationDataStreamerClosed = false;
mCachedException = null;
}
@@ -273,6 +281,9 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
loadAlgorithmSpecificParametersFromBeginResult(opResult.outParams);
mMainDataStreamer = createMainDataStreamer(mKeyStore, opResult.token);
+ mAdditionalAuthenticationDataStreamer =
+ createAdditionalAuthenticationDataStreamer(mKeyStore, opResult.token);
+ mAdditionalAuthenticationDataStreamerClosed = false;
}
/**
@@ -289,6 +300,20 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
keyStore, operationToken));
}
+ /**
+ * Creates a streamer which sends Additional Authentication Data (AAD) into the KeyStore.
+ *
+ * <p>This implementation returns {@code null}.
+ *
+ * @returns stream or {@code null} if AAD is not supported by this cipher.
+ */
+ @Nullable
+ protected KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer(
+ @SuppressWarnings("unused") KeyStore keyStore,
+ @SuppressWarnings("unused") IBinder operationToken) {
+ return null;
+ }
+
@Override
protected final byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
if (mCachedException != null) {
@@ -307,6 +332,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
byte[] output;
try {
+ flushAAD();
output = mMainDataStreamer.update(input, inputOffset, inputLen);
} catch (KeyStoreException e) {
mCachedException = e;
@@ -320,6 +346,25 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
return output;
}
+ private void flushAAD() throws KeyStoreException {
+ if ((mAdditionalAuthenticationDataStreamer != null)
+ && (!mAdditionalAuthenticationDataStreamerClosed)) {
+ byte[] output;
+ try {
+ output = mAdditionalAuthenticationDataStreamer.doFinal(
+ EmptyArray.BYTE, 0, 0,
+ null // no additional entropy needed flushing AAD
+ );
+ } finally {
+ mAdditionalAuthenticationDataStreamerClosed = true;
+ }
+ if ((output != null) && (output.length > 0)) {
+ throw new ProviderException(
+ "AAD update unexpectedly returned data: " + output.length + " bytes");
+ }
+ }
+ }
+
@Override
protected final int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException {
@@ -344,12 +389,64 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
@Override
protected final void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) {
- super.engineUpdateAAD(input, inputOffset, inputLen);
+ if (mCachedException != null) {
+ return;
+ }
+
+ try {
+ ensureKeystoreOperationInitialized();
+ } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+ mCachedException = e;
+ return;
+ }
+
+ if (mAdditionalAuthenticationDataStreamerClosed) {
+ throw new IllegalStateException(
+ "AAD can only be provided before Cipher.update is invoked");
+ }
+
+ if (mAdditionalAuthenticationDataStreamer == null) {
+ throw new IllegalStateException("This cipher does not support AAD");
+ }
+
+ byte[] output;
+ try {
+ output = mAdditionalAuthenticationDataStreamer.update(input, inputOffset, inputLen);
+ } catch (KeyStoreException e) {
+ mCachedException = e;
+ return;
+ }
+
+ if ((output != null) && (output.length > 0)) {
+ throw new ProviderException("AAD update unexpectedly produced output: "
+ + output.length + " bytes");
+ }
}
@Override
protected final void engineUpdateAAD(ByteBuffer src) {
- super.engineUpdateAAD(src);
+ if (src == null) {
+ throw new IllegalArgumentException("src == null");
+ }
+ if (!src.hasRemaining()) {
+ return;
+ }
+
+ byte[] input;
+ int inputOffset;
+ int inputLen;
+ if (src.hasArray()) {
+ input = src.array();
+ inputOffset = src.arrayOffset() + src.position();
+ inputLen = src.remaining();
+ src.position(src.limit());
+ } else {
+ input = new byte[src.remaining()];
+ inputOffset = 0;
+ inputLen = input.length;
+ src.get(input);
+ }
+ super.engineUpdateAAD(input, inputOffset, inputLen);
}
@Override
@@ -368,7 +465,11 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
byte[] output;
try {
- output = mMainDataStreamer.doFinal(input, inputOffset, inputLen);
+ flushAAD();
+ byte[] additionalEntropy =
+ KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
+ mRng, getAdditionalEntropyAmountForFinish());
+ output = mMainDataStreamer.doFinal(input, inputOffset, inputLen, additionalEntropy);
} catch (KeyStoreException e) {
switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH:
@@ -612,6 +713,20 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
return mKeyStore;
}
+ protected final long getConsumedInputSizeBytes() {
+ if (mMainDataStreamer == null) {
+ throw new IllegalStateException("Not initialized");
+ }
+ return mMainDataStreamer.getConsumedInputSizeBytes();
+ }
+
+ protected final long getProducedOutputSizeBytes() {
+ if (mMainDataStreamer == null) {
+ throw new IllegalStateException("Not initialized");
+ }
+ return mMainDataStreamer.getProducedOutputSizeBytes();
+ }
+
// The methods below need to be implemented by subclasses.
/**
@@ -667,21 +782,37 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
/**
* Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's
- * {@code begin} operation.
+ * {@code begin} operation. This amount of entropy is typically what's consumed to generate
+ * random parameters, such as IV.
*
- * <p>For decryption, this should be {@code 0} because decryption should not be consuming any
- * entropy. For encryption, this value should match (or exceed) the amount of Shannon entropy of
- * the ciphertext produced by this cipher assuming the key, the plaintext, and all explicitly
- * provided parameters to {@code Cipher.init} are known. For example, for AES CBC encryption
- * with an explicitly provided IV this should be {@code 0}, whereas for the case where IV is
- * generated by the KeyStore's {@code begin} operation this should be {@code 16}. For RSA with
- * OAEP this should be the size of the OAEP hash output. For RSA with PKCS#1 padding this should
- * be the size of the padding string or could be raised (for simplicity) to the size of the
- * modulus.
+ * <p>For decryption, the return value should be {@code 0} because decryption should not be
+ * consuming any entropy. For encryption, the value combined with
+ * {@link #getAdditionalEntropyAmountForFinish()} should match (or exceed) the amount of Shannon
+ * entropy of the ciphertext produced by this cipher assuming the key, the plaintext, and all
+ * explicitly provided parameters to {@code Cipher.init} are known. For example, for AES CBC
+ * encryption with an explicitly provided IV the return value should be {@code 0}, whereas for
+ * the case where IV is generated by the KeyStore's {@code begin} operation it should be
+ * {@code 16}.
*/
protected abstract int getAdditionalEntropyAmountForBegin();
/**
+ * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's
+ * {@code finish} operation. This amount of entropy is typically what's consumed by encryption
+ * padding scheme.
+ *
+ * <p>For decryption, the return value should be {@code 0} because decryption should not be
+ * consuming any entropy. For encryption, the value combined with
+ * {@link #getAdditionalEntropyAmountForBegin()} should match (or exceed) the amount of Shannon
+ * entropy of the ciphertext produced by this cipher assuming the key, the plaintext, and all
+ * explicitly provided parameters to {@code Cipher.init} are known. For example, for RSA with
+ * OAEP the return value should be the size of the OAEP hash output. For RSA with PKCS#1 padding
+ * the return value should be the size of the padding string or could be raised (for simplicity)
+ * to the size of the modulus.
+ */
+ protected abstract int getAdditionalEntropyAmountForFinish();
+
+ /**
* Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation.
*
* @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreECDSASignatureSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreECDSASignatureSpi.java
index 335da07..d19a766 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreECDSASignatureSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreECDSASignatureSpi.java
@@ -117,7 +117,7 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature
}
@Override
- protected int getAdditionalEntropyAmountForBegin() {
- return (isSigning()) ? mGroupSizeBytes : 0;
+ protected int getAdditionalEntropyAmountForSign() {
+ return mGroupSizeBytes;
}
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreECPrivateKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreECPrivateKey.java
new file mode 100644
index 0000000..5dbcd68
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreECPrivateKey.java
@@ -0,0 +1,40 @@
+/*
+ * 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.PrivateKey;
+import java.security.interfaces.ECKey;
+import java.security.spec.ECParameterSpec;
+
+/**
+ * EC private key (instance of {@link PrivateKey} and {@link ECKey}) backed by keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreECPrivateKey extends AndroidKeyStorePrivateKey implements ECKey {
+ private final ECParameterSpec mParams;
+
+ public AndroidKeyStoreECPrivateKey(String alias, ECParameterSpec params) {
+ super(alias, KeyProperties.KEY_ALGORITHM_EC);
+ mParams = params;
+ }
+
+ @Override
+ public ECParameterSpec getParams() {
+ return mParams;
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java
index f31c06d..f7c184c 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java
@@ -232,7 +232,10 @@ public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreC
byte[] result;
try {
- result = mChunkedStreamer.doFinal(null, 0, 0);
+ result = mChunkedStreamer.doFinal(
+ null, 0, 0,
+ null // no additional entropy needed -- HMAC is deterministic
+ );
} catch (KeyStoreException e) {
throw new ProviderException("Keystore operation failed", e);
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreKey.java
index 1751aa5..e76802f 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKey.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKey.java
@@ -52,4 +52,42 @@ public class AndroidKeyStoreKey implements Key {
// This key does not export its key material
return null;
}
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mAlgorithm == null) ? 0 : mAlgorithm.hashCode());
+ result = prime * result + ((mAlias == null) ? 0 : mAlias.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ AndroidKeyStoreKey other = (AndroidKeyStoreKey) obj;
+ if (mAlgorithm == null) {
+ if (other.mAlgorithm != null) {
+ return false;
+ }
+ } else if (!mAlgorithm.equals(other.mAlgorithm)) {
+ return false;
+ }
+ if (mAlias == null) {
+ if (other.mAlias != null) {
+ return false;
+ }
+ } else if (!mAlias.equals(other.mAlias)) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 69155a8..af05578 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -20,7 +20,6 @@ import android.annotation.Nullable;
import android.security.Credentials;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore;
-import android.security.keymaster.ExportResult;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
@@ -44,29 +43,24 @@ import com.android.org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import com.android.org.bouncycastle.jce.X509Principal;
import com.android.org.bouncycastle.jce.provider.X509CertificateObject;
import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
-import com.android.org.conscrypt.OpenSSLEngine;
import libcore.util.EmptyArray;
import java.math.BigInteger;
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.ProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECGenParameterSpec;
-import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAKeyGenParameterSpec;
-import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@@ -110,8 +104,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
/* 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;
@@ -121,16 +113,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
private static final Map<String, Integer> SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE =
new HashMap<String, Integer>();
private static final List<String> SUPPORTED_EC_NIST_CURVE_NAMES = new ArrayList<String>();
+ private static final List<Integer> SUPPORTED_EC_NIST_CURVE_SIZES = new ArrayList<Integer>();
static {
- // Aliases for NIST P-192
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-192", 192);
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp192r1", 192);
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("prime192v1", 192);
-
// Aliases for NIST P-224
SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-224", 224);
SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp224r1", 224);
+
// Aliases for NIST P-256
SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-256", 256);
SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp256r1", 256);
@@ -146,7 +135,12 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
SUPPORTED_EC_NIST_CURVE_NAMES.addAll(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.keySet());
Collections.sort(SUPPORTED_EC_NIST_CURVE_NAMES);
+
+ SUPPORTED_EC_NIST_CURVE_SIZES.addAll(
+ new HashSet<Integer>(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.values()));
+ Collections.sort(SUPPORTED_EC_NIST_CURVE_SIZES);
}
+
private final int mOriginalKeymasterAlgorithm;
private KeyStore mKeyStore;
@@ -220,14 +214,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
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);
+ // Authorized to be used with any digest (including no digest).
+ specBuilder.setDigests(KeyProperties.DIGEST_NONE);
break;
case KeymasterDefs.KM_ALGORITHM_RSA:
specBuilder = new KeyGenParameterSpec.Builder(
@@ -236,19 +224,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
| 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);
+ // Authorized to be used with any digest (including no digest).
+ specBuilder.setDigests(KeyProperties.DIGEST_NONE);
specBuilder.setSignaturePaddings(
KeyProperties.SIGNATURE_PADDING_RSA_PKCS1);
+ // Authorized to be used with any padding (including no padding).
specBuilder.setEncryptionPaddings(
- KeyProperties.ENCRYPTION_PADDING_NONE,
- KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1);
+ KeyProperties.ENCRYPTION_PADDING_NONE);
// Disable randomized encryption requirement to support encryption
// padding NONE above.
specBuilder.setRandomizedEncryptionRequired(false);
@@ -431,24 +413,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
args.addInts(KeymasterDefs.KM_TAG_PADDING, mKeymasterSignaturePaddings);
args.addInts(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests);
- // TODO: Remove the digest and padding NONE workaround below once Android Keystore returns
- // keys which are backed by AndroidKeyStoreBCWorkaround provider instead of Conscrypt. The
- // workaround is needed because Conscrypt (via keystore-engine) uses old KeyStore API which
- // translates into digest NONE and padding NONE in the new API. keystore-engine cannot be
- // updated to pass in the correct padding and digest values because it uses
- // OpenSSL/BoringSSL engine which performs digesting and padding prior before invoking
- // KeyStore API.
- if (!com.android.internal.util.ArrayUtils.contains(
- mKeymasterDigests, KeymasterDefs.KM_DIGEST_NONE)) {
- args.addInt(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE);
- }
- if ((!com.android.internal.util.ArrayUtils.contains(
- mKeymasterSignaturePaddings, KeymasterDefs.KM_PAD_NONE))
- && (!com.android.internal.util.ArrayUtils.contains(
- mKeymasterEncryptionPaddings, KeymasterDefs.KM_PAD_NONE))) {
- args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- }
-
KeymasterUtils.addUserAuthArgs(args,
mSpec.isUserAuthenticationRequired(),
mSpec.getUserAuthenticationValidityDurationSeconds());
@@ -483,40 +447,23 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
"Failed to generate key pair", KeyStore.getKeyStoreException(errorCode));
}
- final PrivateKey privKey;
- final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
+ KeyPair result;
try {
- privKey = engine.getPrivateKeyById(privateKeyAlias);
- } catch (InvalidKeyException e) {
- throw new ProviderException("Failed to obtain generated private key", e);
- }
-
- ExportResult exportResult =
- mKeyStore.exportKey(
- privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null);
- if (exportResult == null) {
- throw new KeyStoreConnectException();
- } else if (exportResult.resultCode != KeyStore.NO_ERROR) {
- throw new ProviderException(
- "Failed to obtain X.509 form of generated public key",
- KeyStore.getKeyStoreException(exportResult.resultCode));
+ result = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
+ mKeyStore, privateKeyAlias);
+ } catch (UnrecoverableKeyException e) {
+ throw new ProviderException("Failed to load generated key pair from keystore", e);
}
- final byte[] pubKeyBytes = exportResult.exportData;
- final PublicKey pubKey;
- try {
- final KeyFactory keyFact = KeyFactory.getInstance(mJcaKeyAlgorithm);
- pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes));
- } catch (NoSuchAlgorithmException e) {
+ if (!mJcaKeyAlgorithm.equalsIgnoreCase(result.getPrivate().getAlgorithm())) {
throw new ProviderException(
- "Failed to obtain " + mJcaKeyAlgorithm + " KeyFactory", e);
- } catch (InvalidKeySpecException e) {
- throw new ProviderException("Invalid X.509 encoding of generated public key", e);
+ "Generated key pair algorithm does not match requested algorithm: "
+ + result.getPrivate().getAlgorithm() + " vs " + mJcaKeyAlgorithm);
}
final X509Certificate cert;
try {
- cert = generateSelfSignedCertificate(privKey, pubKey);
+ cert = generateSelfSignedCertificate(result.getPrivate(), result.getPublic());
} catch (Exception e) {
throw new ProviderException("Failed to generate self-signed certificate", e);
}
@@ -539,7 +486,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
KeyStore.getKeyStoreException(insertErrorCode));
}
- KeyPair result = new KeyPair(pubKey, privKey);
success = true;
return result;
} finally {
@@ -651,9 +597,9 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
throws InvalidAlgorithmParameterException {
switch (keymasterAlgorithm) {
case KeymasterDefs.KM_ALGORITHM_EC:
- if (keySize < EC_MIN_KEY_SIZE || keySize > EC_MAX_KEY_SIZE) {
- throw new InvalidAlgorithmParameterException("EC key size must be >= "
- + EC_MIN_KEY_SIZE + " and <= " + EC_MAX_KEY_SIZE);
+ if (!SUPPORTED_EC_NIST_CURVE_SIZES.contains(keySize)) {
+ throw new InvalidAlgorithmParameterException("Unsupported EC key size: "
+ + keySize + " bits. Supported: " + SUPPORTED_EC_NIST_CURVE_SIZES);
}
break;
case KeymasterDefs.KM_ALGORITHM_RSA:
@@ -677,7 +623,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
int keySizeBits,
KeyGenParameterSpec spec) {
// Constraints:
- // 1. Key must be authorized for signing.
+ // 1. Key must be authorized for signing without user authentication.
// 2. Signature digest must be one of key's authorized digests.
// 3. For RSA keys, the digest output size must not exceed modulus size minus space needed
// for RSA PKCS#1 signature padding (about 29 bytes: minimum 10 bytes of padding + 15--19
@@ -689,6 +635,10 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
// Key not authorized for signing
return null;
}
+ if (spec.isUserAuthenticationRequired()) {
+ // Key not authorized for use without user authentication
+ return null;
+ }
if (!spec.isDigestsSpecified()) {
// Key not authorized for any digests -- can't sign
return null;
@@ -744,6 +694,36 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
}
case KeymasterDefs.KM_ALGORITHM_RSA:
{
+ // Check whether this key is authorized for PKCS#1 signature padding.
+ // We use Bouncy Castle to generate self-signed RSA certificates. Bouncy Castle
+ // only supports RSA certificates signed using PKCS#1 padding scheme. The key needs
+ // to be authorized for PKCS#1 padding or padding NONE which means any padding.
+ boolean pkcs1SignaturePaddingSupported = false;
+ for (int keymasterPadding : KeyProperties.SignaturePadding.allToKeymaster(
+ spec.getSignaturePaddings())) {
+ if ((keymasterPadding == KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN)
+ || (keymasterPadding == KeymasterDefs.KM_PAD_NONE)) {
+ pkcs1SignaturePaddingSupported = true;
+ break;
+ }
+ }
+ if (!pkcs1SignaturePaddingSupported) {
+ // Keymaster doesn't distinguish between encryption padding NONE and signature
+ // padding NONE. In the Android Keystore API only encryption padding NONE is
+ // exposed.
+ for (int keymasterPadding : KeyProperties.EncryptionPadding.allToKeymaster(
+ spec.getEncryptionPaddings())) {
+ if (keymasterPadding == KeymasterDefs.KM_PAD_NONE) {
+ pkcs1SignaturePaddingSupported = true;
+ break;
+ }
+ }
+ }
+ if (!pkcs1SignaturePaddingSupported) {
+ // Key not authorized for PKCS#1 signature padding -- can't sign
+ return null;
+ }
+
Set<Integer> availableKeymasterDigests = getAvailableKeymasterSignatureDigests(
spec.getDigests(),
AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests());
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index cb270bb..967319a 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -16,11 +16,28 @@
package android.security.keystore;
+import android.annotation.NonNull;
import android.security.KeyStore;
+import android.security.keymaster.ExportResult;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterDefs;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
import java.security.Provider;
+import java.security.ProviderException;
+import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
+import java.security.UnrecoverableKeyException;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.List;
import javax.crypto.Cipher;
import javax.crypto.Mac;
@@ -146,4 +163,145 @@ public class AndroidKeyStoreProvider extends Provider {
}
return ((KeyStoreCryptoOperation) spi).getOperationHandle();
}
+
+ @NonNull
+ public static AndroidKeyStorePublicKey getAndroidKeyStorePublicKey(
+ @NonNull String alias,
+ @NonNull @KeyProperties.KeyAlgorithmEnum String keyAlgorithm,
+ @NonNull byte[] x509EncodedForm) {
+ PublicKey publicKey;
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
+ publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedForm));
+ } catch (NoSuchAlgorithmException e) {
+ throw new ProviderException(
+ "Failed to obtain " + keyAlgorithm + " KeyFactory", e);
+ } catch (InvalidKeySpecException e) {
+ throw new ProviderException("Invalid X.509 encoding of public key", e);
+ }
+ if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+ return new AndroidKeyStoreECPublicKey(alias, (ECPublicKey) publicKey);
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ return new AndroidKeyStoreRSAPublicKey(alias, (RSAPublicKey) publicKey);
+ } else {
+ throw new ProviderException("Unsupported Android Keystore public key algorithm: "
+ + keyAlgorithm);
+ }
+ }
+
+ @NonNull
+ public static AndroidKeyStorePrivateKey getAndroidKeyStorePrivateKey(
+ @NonNull AndroidKeyStorePublicKey publicKey) {
+ String keyAlgorithm = publicKey.getAlgorithm();
+ if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+ return new AndroidKeyStoreECPrivateKey(
+ publicKey.getAlias(), ((ECKey) publicKey).getParams());
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ return new AndroidKeyStoreRSAPrivateKey(
+ publicKey.getAlias(), ((RSAKey) publicKey).getModulus());
+ } else {
+ throw new ProviderException("Unsupported Android Keystore public key algorithm: "
+ + keyAlgorithm);
+ }
+ }
+
+ @NonNull
+ public static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore(
+ @NonNull KeyStore keyStore, @NonNull String privateKeyAlias)
+ throws UnrecoverableKeyException {
+ KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
+ int errorCode = keyStore.getKeyCharacteristics(
+ privateKeyAlias, null, null, keyCharacteristics);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw (UnrecoverableKeyException)
+ new UnrecoverableKeyException("Failed to obtain information about private key")
+ .initCause(KeyStore.getKeyStoreException(errorCode));
+ }
+ ExportResult exportResult = keyStore.exportKey(
+ privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null);
+ if (exportResult.resultCode != KeyStore.NO_ERROR) {
+ throw (UnrecoverableKeyException)
+ new UnrecoverableKeyException("Failed to obtain X.509 form of public key")
+ .initCause(KeyStore.getKeyStoreException(errorCode));
+ }
+ final byte[] x509EncodedPublicKey = exportResult.exportData;
+
+ int keymasterAlgorithm = keyCharacteristics.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1);
+ if (keymasterAlgorithm == -1) {
+ throw new UnrecoverableKeyException("Key algorithm unknown");
+ }
+
+ String jcaKeyAlgorithm;
+ try {
+ jcaKeyAlgorithm = KeyProperties.KeyAlgorithm.fromKeymasterAsymmetricKeyAlgorithm(
+ keymasterAlgorithm);
+ } catch (IllegalArgumentException e) {
+ throw (UnrecoverableKeyException)
+ new UnrecoverableKeyException("Failed to load private key")
+ .initCause(e);
+ }
+
+ return AndroidKeyStoreProvider.getAndroidKeyStorePublicKey(
+ privateKeyAlias, jcaKeyAlgorithm, x509EncodedPublicKey);
+ }
+
+ @NonNull
+ public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore(
+ @NonNull KeyStore keyStore, @NonNull String privateKeyAlias)
+ throws UnrecoverableKeyException {
+ AndroidKeyStorePublicKey publicKey =
+ loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias);
+ AndroidKeyStorePrivateKey privateKey =
+ AndroidKeyStoreProvider.getAndroidKeyStorePrivateKey(publicKey);
+ return new KeyPair(publicKey, privateKey);
+ }
+
+ @NonNull
+ public static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore(
+ @NonNull KeyStore keyStore, @NonNull String privateKeyAlias)
+ throws UnrecoverableKeyException {
+ KeyPair keyPair = loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias);
+ return (AndroidKeyStorePrivateKey) keyPair.getPrivate();
+ }
+
+ @NonNull
+ public static AndroidKeyStoreSecretKey loadAndroidKeyStoreSecretKeyFromKeystore(
+ @NonNull KeyStore keyStore, @NonNull String secretKeyAlias)
+ throws UnrecoverableKeyException {
+ KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
+ int errorCode = keyStore.getKeyCharacteristics(
+ secretKeyAlias, null, null, keyCharacteristics);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw (UnrecoverableKeyException)
+ new UnrecoverableKeyException("Failed to obtain information about key")
+ .initCause(KeyStore.getKeyStoreException(errorCode));
+ }
+
+ int keymasterAlgorithm = keyCharacteristics.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1);
+ if (keymasterAlgorithm == -1) {
+ throw new UnrecoverableKeyException("Key algorithm unknown");
+ }
+
+ List<Integer> 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(secretKeyAlias, keyAlgorithmString);
+ }
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java b/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java
index 8133d46..9fea30d 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java
@@ -17,6 +17,7 @@
package android.security.keystore;
import java.security.PublicKey;
+import java.util.Arrays;
/**
* {@link PublicKey} backed by Android Keystore.
@@ -41,4 +42,30 @@ public class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implements Publ
public byte[] getEncoded() {
return ArrayUtils.cloneIfNotEmpty(mEncoded);
}
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Arrays.hashCode(mEncoded);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ AndroidKeyStorePublicKey other = (AndroidKeyStorePublicKey) obj;
+ if (!Arrays.equals(mEncoded, other.mEncoded)) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java
index d33692a..38e216d 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java
@@ -99,6 +99,11 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase
}
@Override
+ protected final int getAdditionalEntropyAmountForFinish() {
+ return 0;
+ }
+
+ @Override
@NonNull
protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
KeyStore keyStore, IBinder operationToken) {
@@ -124,6 +129,7 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase
private final KeyStoreCryptoOperationStreamer mDelegate;
private final int mModulusSizeBytes;
private final ByteArrayOutputStream mInputBuffer = new ByteArrayOutputStream();
+ private long mConsumedInputSizeBytes;
private ZeroPaddingEncryptionStreamer(
KeyStoreCryptoOperationStreamer delegate,
@@ -137,14 +143,17 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase
throws KeyStoreException {
if (inputLength > 0) {
mInputBuffer.write(input, inputOffset, inputLength);
+ mConsumedInputSizeBytes += inputLength;
}
return EmptyArray.BYTE;
}
@Override
- public byte[] doFinal(byte[] input, int inputOffset, int inputLength)
+ public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
+ byte[] additionalEntropy)
throws KeyStoreException {
if (inputLength > 0) {
+ mConsumedInputSizeBytes += inputLength;
mInputBuffer.write(input, inputOffset, inputLength);
}
byte[] bufferedInput = mInputBuffer.toByteArray();
@@ -165,7 +174,17 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase
"Message size (" + bufferedInput.length + " bytes) must be smaller than"
+ " modulus (" + mModulusSizeBytes + " bytes)");
}
- return mDelegate.doFinal(paddedInput, 0, paddedInput.length);
+ return mDelegate.doFinal(paddedInput, 0, paddedInput.length, additionalEntropy);
+ }
+
+ @Override
+ public long getConsumedInputSizeBytes() {
+ return mConsumedInputSizeBytes;
+ }
+
+ @Override
+ public long getProducedOutputSizeBytes() {
+ return mDelegate.getProducedOutputSizeBytes();
}
}
}
@@ -207,6 +226,11 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase
@Override
protected final int getAdditionalEntropyAmountForBegin() {
+ return 0;
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForFinish() {
return (isEncrypting()) ? getModulusSizeBytes() : 0;
}
}
@@ -361,6 +385,11 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase
@Override
protected final int getAdditionalEntropyAmountForBegin() {
+ return 0;
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForFinish() {
return (isEncrypting()) ? mDigestOutputSizeBytes : 0;
}
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSAPrivateKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSAPrivateKey.java
new file mode 100644
index 0000000..179ffd8
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSAPrivateKey.java
@@ -0,0 +1,41 @@
+/*
+ * 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.math.BigInteger;
+import java.security.PrivateKey;
+import java.security.interfaces.RSAKey;
+
+/**
+ * RSA private key (instance of {@link PrivateKey} and {@link RSAKey}) backed by keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreRSAPrivateKey extends AndroidKeyStorePrivateKey implements RSAKey {
+
+ private final BigInteger mModulus;
+
+ public AndroidKeyStoreRSAPrivateKey(String alias, BigInteger modulus) {
+ super(alias, KeyProperties.KEY_ALGORITHM_RSA);
+ mModulus = modulus;
+ }
+
+ @Override
+ public BigInteger getModulus() {
+ return mModulus;
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSASignatureSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSASignatureSpi.java
index 898336d..954b71a 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreRSASignatureSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSASignatureSpi.java
@@ -36,7 +36,7 @@ abstract class AndroidKeyStoreRSASignatureSpi extends AndroidKeyStoreSignatureSp
}
@Override
- protected final int getAdditionalEntropyAmountForBegin() {
+ protected final int getAdditionalEntropyAmountForSign() {
// No entropy required for this deterministic signature scheme.
return 0;
}
@@ -92,8 +92,8 @@ abstract class AndroidKeyStoreRSASignatureSpi extends AndroidKeyStoreSignatureSp
}
@Override
- protected final int getAdditionalEntropyAmountForBegin() {
- return (isSigning()) ? SALT_LENGTH_BYTES : 0;
+ protected final int getAdditionalEntropyAmountForSign() {
+ return SALT_LENGTH_BYTES;
}
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSignatureSpiBase.java b/keystore/java/android/security/keystore/AndroidKeyStoreSignatureSpiBase.java
index 4c4062f..5cdcc41 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSignatureSpiBase.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSignatureSpiBase.java
@@ -25,7 +25,7 @@ import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keymaster.OperationResult;
-import com.android.org.conscrypt.util.EmptyArray;
+import libcore.util.EmptyArray;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
@@ -198,15 +198,14 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
KeymasterArguments keymasterInputArgs = new KeymasterArguments();
addAlgorithmSpecificParametersToBegin(keymasterInputArgs);
- byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
- appRandom, getAdditionalEntropyAmountForBegin());
OperationResult opResult = mKeyStore.begin(
mKey.getAlias(),
mSigning ? KeymasterDefs.KM_PURPOSE_SIGN : KeymasterDefs.KM_PURPOSE_VERIFY,
true, // permit aborting this operation if keystore runs out of resources
keymasterInputArgs,
- additionalEntropy);
+ null // no additional entropy for begin -- only finish might need some
+ );
if (opResult == null) {
throw new KeyStoreConnectException();
}
@@ -311,7 +310,11 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
byte[] signature;
try {
ensureKeystoreOperationInitialized();
- signature = mMessageStreamer.doFinal(EmptyArray.BYTE, 0, 0);
+
+ byte[] additionalEntropy =
+ KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
+ appRandom, getAdditionalEntropyAmountForSign());
+ signature = mMessageStreamer.doFinal(EmptyArray.BYTE, 0, 0, additionalEntropy);
} catch (InvalidKeyException | KeyStoreException e) {
throw new SignatureException(e);
}
@@ -388,15 +391,14 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
/**
* Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's
- * {@code begin} operation.
+ * {@code finish} operation when generating a signature.
*
- * <p>For signature verification, this should be {@code 0} because verification should not be
- * consuming any entropy. For signature generation, this value should match (or exceed) the
- * amount of Shannon entropy of the produced signature assuming the key and the message are
- * known. For example, for ECDSA signature this should be the size of {@code R}, whereas for the
- * RSA signature with PKCS#1 padding this should be {@code 0}.
+ * <p>This value should match (or exceed) the amount of Shannon entropy of the produced
+ * signature assuming the key and the message are known. For example, for ECDSA signature this
+ * should be the size of {@code R}, whereas for the RSA signature with PKCS#1 padding this
+ * should be {@code 0}.
*/
- protected abstract int getAdditionalEntropyAmountForBegin();
+ protected abstract int getAdditionalEntropyAmountForSign();
/**
* Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation.
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
index c03be63..3bd9d1d 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
@@ -16,9 +16,6 @@
package android.security.keystore;
-import com.android.org.conscrypt.OpenSSLEngine;
-import com.android.org.conscrypt.OpenSSLKeyHolder;
-
import libcore.util.EmptyArray;
import android.security.Credentials;
@@ -35,7 +32,6 @@ 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;
@@ -45,6 +41,7 @@ import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
+import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
@@ -59,7 +56,6 @@ 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;
@@ -92,59 +88,17 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
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;
- }
+ String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
+ return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
+ mKeyStore, privateKeyAlias);
} 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 != 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<Integer> 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);
+ String secretKeyAlias = Credentials.USER_SECRET_KEY + alias;
+ return AndroidKeyStoreProvider.loadAndroidKeyStoreSecretKeyFromKeystore(
+ mKeyStore, secretKeyAlias);
+ } else {
+ // Key not found
+ return null;
}
-
- return null;
}
@Override
@@ -188,22 +142,36 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
byte[] certificate = mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
if (certificate != null) {
- return toCertificate(certificate);
+ return wrapIntoKeyStoreCertificate(
+ Credentials.USER_PRIVATE_KEY + alias, toCertificate(certificate));
}
certificate = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
if (certificate != null) {
- return toCertificate(certificate);
+ return wrapIntoKeyStoreCertificate(
+ Credentials.USER_PRIVATE_KEY + alias, toCertificate(certificate));
}
return null;
}
+ /**
+ * Wraps the provided cerificate into {@link KeyStoreX509Certificate} so that the public key
+ * returned by the certificate contains information about the alias of the private key in
+ * keystore. This is needed so that Android Keystore crypto operations using public keys can
+ * find out which key alias to use. These operations cannot work without an alias.
+ */
+ private static KeyStoreX509Certificate wrapIntoKeyStoreCertificate(
+ String privateKeyAlias, X509Certificate certificate) {
+ return (certificate != null)
+ ? new KeyStoreX509Certificate(privateKeyAlias, certificate) : null;
+ }
+
private static X509Certificate toCertificate(byte[] bytes) {
try {
final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
- return (X509Certificate) certFactory
- .generateCertificate(new ByteArrayInputStream(bytes));
+ return (X509Certificate) certFactory.generateCertificate(
+ new ByteArrayInputStream(bytes));
} catch (CertificateException e) {
Log.w(NAME, "Couldn't parse certificate in keystore", e);
return null;
@@ -214,8 +182,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
private static Collection<X509Certificate> toCertificates(byte[] bytes) {
try {
final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
- return (Collection<X509Certificate>) certFactory
- .generateCertificates(new ByteArrayInputStream(bytes));
+ return (Collection<X509Certificate>) certFactory.generateCertificates(
+ new ByteArrayInputStream(bytes));
} catch (CertificateException e) {
Log.w(NAME, "Couldn't parse certificates in keystore", e);
return new ArrayList<X509Certificate>();
@@ -279,14 +247,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
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);
+ // Authorized to be used with any digest (including no digest).
+ specBuilder.setDigests(KeyProperties.DIGEST_NONE);
} else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
specBuilder =
new KeyProtection.Builder(
@@ -294,19 +256,13 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
| 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);
+ // Authorized to be used with any digest (including no digest).
+ specBuilder.setDigests(KeyProperties.DIGEST_NONE);
specBuilder.setSignaturePaddings(
KeyProperties.SIGNATURE_PADDING_RSA_PKCS1);
+ // Authorized to be used with any padding (including no padding).
specBuilder.setEncryptionPaddings(
- KeyProperties.ENCRYPTION_PADDING_NONE,
- KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1);
+ KeyProperties.ENCRYPTION_PADDING_NONE);
// Disable randomized encryption requirement to support encryption padding NONE
// above.
specBuilder.setRandomizedEncryptionRequired(false);
@@ -406,9 +362,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
}
final String pkeyAlias;
- if (key instanceof OpenSSLKeyHolder) {
- pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias();
- } else if (key instanceof AndroidKeyStorePrivateKey) {
+ if (key instanceof AndroidKeyStorePrivateKey) {
pkeyAlias = ((AndroidKeyStoreKey) key).getAlias();
} else {
pkeyAlias = null;
@@ -851,6 +805,19 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
if (cert == null) {
return null;
}
+ if (!"X.509".equalsIgnoreCase(cert.getType())) {
+ // Only X.509 certificates supported
+ return null;
+ }
+ byte[] targetCertBytes;
+ try {
+ targetCertBytes = cert.getEncoded();
+ } catch (CertificateEncodingException e) {
+ return null;
+ }
+ if (targetCertBytes == null) {
+ return null;
+ }
final Set<String> nonCaEntries = new HashSet<String>();
@@ -868,10 +835,9 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
continue;
}
- final Certificate c = toCertificate(certBytes);
nonCaEntries.add(alias);
- if (cert.equals(c)) {
+ if (Arrays.equals(certBytes, targetCertBytes)) {
return alias;
}
}
@@ -893,9 +859,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
continue;
}
- final Certificate c =
- toCertificate(mKeyStore.get(Credentials.CA_CERTIFICATE + alias));
- if (cert.equals(c)) {
+ if (Arrays.equals(certBytes, targetCertBytes)) {
return alias;
}
}
@@ -954,4 +918,25 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
}
}
+ /**
+ * {@link X509Certificate} which returns {@link AndroidKeyStorePublicKey} from
+ * {@link #getPublicKey()}. This is so that crypto operations on these public keys contain
+ * can find out which keystore private key entry to use. This is needed so that Android Keystore
+ * crypto operations using public keys can find out which key alias to use. These operations
+ * require an alias.
+ */
+ static class KeyStoreX509Certificate extends DelegatingX509Certificate {
+ private final String mPrivateKeyAlias;
+ KeyStoreX509Certificate(String privateKeyAlias, X509Certificate delegate) {
+ super(delegate);
+ mPrivateKeyAlias = privateKeyAlias;
+ }
+
+ @Override
+ public PublicKey getPublicKey() {
+ PublicKey original = super.getPublicKey();
+ return AndroidKeyStoreProvider.getAndroidKeyStorePublicKey(
+ mPrivateKeyAlias, original.getAlgorithm(), original.getEncoded());
+ }
+ }
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java
index 47cd1d1..6c53c6a 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.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.keystore;
import android.annotation.NonNull;
@@ -210,6 +226,11 @@ class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSp
}
@Override
+ protected final int getAdditionalEntropyAmountForFinish() {
+ return 0;
+ }
+
+ @Override
protected final void addAlgorithmSpecificParametersToBegin(
@NonNull KeymasterArguments keymasterArgs) {
if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) {
diff --git a/keystore/java/android/security/keystore/DelegatingX509Certificate.java b/keystore/java/android/security/keystore/DelegatingX509Certificate.java
new file mode 100644
index 0000000..03d202f
--- /dev/null
+++ b/keystore/java/android/security/keystore/DelegatingX509Certificate.java
@@ -0,0 +1,212 @@
+/*
+ * 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.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
+class DelegatingX509Certificate extends X509Certificate {
+ private final X509Certificate mDelegate;
+
+ DelegatingX509Certificate(X509Certificate delegate) {
+ mDelegate = delegate;
+ }
+
+ @Override
+ public Set<String> getCriticalExtensionOIDs() {
+ return mDelegate.getCriticalExtensionOIDs();
+ }
+
+ @Override
+ public byte[] getExtensionValue(String oid) {
+ return mDelegate.getExtensionValue(oid);
+ }
+
+ @Override
+ public Set<String> getNonCriticalExtensionOIDs() {
+ return mDelegate.getNonCriticalExtensionOIDs();
+ }
+
+ @Override
+ public boolean hasUnsupportedCriticalExtension() {
+ return mDelegate.hasUnsupportedCriticalExtension();
+ }
+
+ @Override
+ public void checkValidity() throws CertificateExpiredException,
+ CertificateNotYetValidException {
+ mDelegate.checkValidity();
+ }
+
+ @Override
+ public void checkValidity(Date date) throws CertificateExpiredException,
+ CertificateNotYetValidException {
+ mDelegate.checkValidity(date);
+ }
+
+ @Override
+ public int getBasicConstraints() {
+ return mDelegate.getBasicConstraints();
+ }
+
+ @Override
+ public Principal getIssuerDN() {
+ return mDelegate.getIssuerDN();
+ }
+
+ @Override
+ public boolean[] getIssuerUniqueID() {
+ return mDelegate.getIssuerUniqueID();
+ }
+
+ @Override
+ public boolean[] getKeyUsage() {
+ return mDelegate.getKeyUsage();
+ }
+
+ @Override
+ public Date getNotAfter() {
+ return mDelegate.getNotAfter();
+ }
+
+ @Override
+ public Date getNotBefore() {
+ return mDelegate.getNotBefore();
+ }
+
+ @Override
+ public BigInteger getSerialNumber() {
+ return mDelegate.getSerialNumber();
+ }
+
+ @Override
+ public String getSigAlgName() {
+ return mDelegate.getSigAlgName();
+ }
+
+ @Override
+ public String getSigAlgOID() {
+ return mDelegate.getSigAlgOID();
+ }
+
+ @Override
+ public byte[] getSigAlgParams() {
+ return mDelegate.getSigAlgParams();
+ }
+
+ @Override
+ public byte[] getSignature() {
+ return mDelegate.getSignature();
+ }
+
+ @Override
+ public Principal getSubjectDN() {
+ return mDelegate.getSubjectDN();
+ }
+
+ @Override
+ public boolean[] getSubjectUniqueID() {
+ return mDelegate.getSubjectUniqueID();
+ }
+
+ @Override
+ public byte[] getTBSCertificate() throws CertificateEncodingException {
+ return mDelegate.getTBSCertificate();
+ }
+
+ @Override
+ public int getVersion() {
+ return mDelegate.getVersion();
+ }
+
+ @Override
+ public byte[] getEncoded() throws CertificateEncodingException {
+ return mDelegate.getEncoded();
+ }
+
+ @Override
+ public PublicKey getPublicKey() {
+ return mDelegate.getPublicKey();
+ }
+
+ @Override
+ public String toString() {
+ return mDelegate.toString();
+ }
+
+ @Override
+ public void verify(PublicKey key)
+ throws CertificateException,
+ NoSuchAlgorithmException,
+ InvalidKeyException,
+ NoSuchProviderException,
+ SignatureException {
+ mDelegate.verify(key);
+ }
+
+ @Override
+ public void verify(PublicKey key, String sigProvider)
+ throws CertificateException,
+ NoSuchAlgorithmException,
+ InvalidKeyException,
+ NoSuchProviderException,
+ SignatureException {
+ mDelegate.verify(key, sigProvider);
+ }
+
+ @Override
+ public List<String> getExtendedKeyUsage() throws CertificateParsingException {
+ return mDelegate.getExtendedKeyUsage();
+ }
+
+ @Override
+ public Collection<List<?>> getIssuerAlternativeNames() throws CertificateParsingException {
+ return mDelegate.getIssuerAlternativeNames();
+ }
+
+ @Override
+ public X500Principal getIssuerX500Principal() {
+ return mDelegate.getIssuerX500Principal();
+ }
+
+ @Override
+ public Collection<List<?>> getSubjectAlternativeNames() throws CertificateParsingException {
+ return mDelegate.getSubjectAlternativeNames();
+ }
+
+ @Override
+ public X500Principal getSubjectX500Principal() {
+ return mDelegate.getSubjectX500Principal();
+ }
+}
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 19ff9c7..8d4bfcd 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -59,9 +59,19 @@ import javax.security.auth.x500.X500Principal;
* of the certificate can be customized in this spec. The self-signed certificate may be replaced at
* a later time by a certificate signed by a Certificate Authority (CA).
*
+ * <p>NOTE: If a private key is not authorized to sign the self-signed certificate, then the
+ * certificate will be created with an invalid signature which will not verify. Such a certificate
+ * is still useful because it provides access to the public key. To generate a valid
+ * signature for the certificate the key needs to be authorized for
+ * {@link KeyProperties#PURPOSE_SIGN}, a suitable digest or {@link KeyProperties#DIGEST_NONE}, and
+ * {@link KeyProperties#SIGNATURE_PADDING_RSA_PKCS1} or
+ * {@link KeyProperties#ENCRYPTION_PADDING_NONE}.
+ *
* <p>NOTE: The key material of the generated symmetric and private keys is not accessible. The key
* material of the public keys is accessible.
*
+ * <p>Instances of this class are immutable.
+ *
* <p><h3>Example: Asymmetric key pair</h3>
* The following example illustrates how to generate an EC key pair in the Android KeyStore system
* under alias {@code key1} authorized to be used only for signing using SHA-256, SHA-384,
@@ -71,11 +81,12 @@ import javax.security.auth.x500.X500Principal;
* KeyProperties.KEY_ALGORITHM_EC,
* "AndroidKeyStore");
* keyPairGenerator.initialize(
- * new KeyGenParameterSpec.Builder("key1",
+ * new KeyGenParameterSpec.Builder(
+ * "key1",
* KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
- * .setDigests(KeyProperties.DIGEST_SHA256
- * | KeyProperties.DIGEST_SHA384
- * | KeyProperties.DIGEST_SHA512)
+ * .setDigests(KeyProperties.DIGEST_SHA256,
+ * KeyProperties.DIGEST_SHA384,
+ * KeyProperties.DIGEST_SHA512)
* // Only permit this key to be used if the user authenticated
* // within the last five minutes.
* .setUserAuthenticationRequired(true)
@@ -92,16 +103,17 @@ import javax.security.auth.x500.X500Principal;
*
* <p><h3>Example: Symmetric key</h3>
* The following example illustrates how to generate an AES key in the Android KeyStore system under
- * alias {@code key2} authorized to be used only for encryption/decryption in CTR mode.
+ * alias {@code key2} authorized to be used only for encryption/decryption in CBC mode with PKCS#7
+ * padding.
* <pre> {@code
* KeyGenerator keyGenerator = KeyGenerator.getInstance(
- * KeyProperties.KEY_ALGORITHM_HMAC_SHA256,
+ * KeyProperties.KEY_ALGORITHM_AES,
* "AndroidKeyStore");
* keyGenerator.initialize(
* new KeyGenParameterSpec.Builder("key2",
* KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
- * .setBlockModes(KeyProperties.BLOCK_MODE_CTR)
- * .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ * .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
+ * .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
* .build());
* SecretKey key = keyGenerator.generateKey();
*
@@ -161,10 +173,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
int userAuthenticationValidityDurationSeconds) {
if (TextUtils.isEmpty(keyStoreAlias)) {
throw new IllegalArgumentException("keyStoreAlias must not be empty");
- } else if ((userAuthenticationValidityDurationSeconds < 0)
- && (userAuthenticationValidityDurationSeconds != -1)) {
- throw new IllegalArgumentException(
- "userAuthenticationValidityDurationSeconds must not be negative");
}
if (certificateSubject == null) {
@@ -189,11 +197,11 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
mSpec = spec;
mCertificateSubject = certificateSubject;
mCertificateSerialNumber = certificateSerialNumber;
- mCertificateNotBefore = certificateNotBefore;
- mCertificateNotAfter = certificateNotAfter;
- mKeyValidityStart = keyValidityStart;
- mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
- mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
+ mCertificateNotBefore = Utils.cloneIfNotNull(certificateNotBefore);
+ mCertificateNotAfter = Utils.cloneIfNotNull(certificateNotAfter);
+ mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart);
+ mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd);
+ mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd);
mPurposes = purposes;
mDigests = ArrayUtils.cloneIfNotEmpty(digests);
mEncryptionPaddings =
@@ -209,6 +217,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
* Returns the alias that will be used in the {@code java.security.KeyStore}
* in conjunction with the {@code AndroidKeyStore}.
*/
+ @NonNull
public String getKeystoreAlias() {
return mKeystoreAlias;
}
@@ -223,10 +232,10 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
}
/**
- * Returns the {@link AlgorithmParameterSpec} that will be used for creation
- * of the key pair.
+ * Returns the key algorithm-specific {@link AlgorithmParameterSpec} that will be used for
+ * creation of the key or {@code null} if algorithm-specific defaults should be used.
*/
- @NonNull
+ @Nullable
public AlgorithmParameterSpec getAlgorithmParameterSpec() {
return mSpec;
}
@@ -255,7 +264,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*/
@NonNull
public Date getCertificateNotBefore() {
- return mCertificateNotBefore;
+ return Utils.cloneIfNotNull(mCertificateNotBefore);
}
/**
@@ -264,7 +273,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*/
@NonNull
public Date getCertificateNotAfter() {
- return mCertificateNotAfter;
+ return Utils.cloneIfNotNull(mCertificateNotAfter);
}
/**
@@ -273,7 +282,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*/
@Nullable
public Date getKeyValidityStart() {
- return mKeyValidityStart;
+ return Utils.cloneIfNotNull(mKeyValidityStart);
}
/**
@@ -282,7 +291,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*/
@Nullable
public Date getKeyValidityForConsumptionEnd() {
- return mKeyValidityForConsumptionEnd;
+ return Utils.cloneIfNotNull(mKeyValidityForConsumptionEnd);
}
/**
@@ -291,7 +300,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*/
@Nullable
public Date getKeyValidityForOriginationEnd() {
- return mKeyValidityForOriginationEnd;
+ return Utils.cloneIfNotNull(mKeyValidityForOriginationEnd);
}
/**
@@ -403,7 +412,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
* restricted.
*
* @return duration in seconds or {@code -1} if authentication is required for every use of the
- * key.
+ * key.
*
* @see #isUserAuthenticationRequired()
*/
@@ -439,7 +448,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
* Creates a new instance of the {@code Builder}.
*
* @param keystoreAlias alias of the entry in which the generated key will appear in
- * Android KeyStore.
+ * Android KeyStore. Must not be empty.
* @param purposes set of purposes (e.g., encrypt, decrypt, sign) for which the key can be
* used. Attempts to use the key for any other purpose will be rejected.
*
@@ -449,14 +458,13 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
* invalid signature. This is OK if the certificate is only used for obtaining the
* public key from Android KeyStore.
*
- * <p><b>NOTE: The {@code purposes} parameter has currently no effect on asymmetric
- * key pairs.</b>
- *
* <p>See {@link KeyProperties}.{@code PURPOSE} flags.
*/
public Builder(@NonNull String keystoreAlias, @KeyProperties.PurposeEnum int purposes) {
if (keystoreAlias == null) {
throw new NullPointerException("keystoreAlias == null");
+ } else if (keystoreAlias.isEmpty()) {
+ throw new IllegalArgumentException("keystoreAlias must not be empty");
}
mKeystoreAlias = keystoreAlias;
mPurposes = purposes;
@@ -483,7 +491,11 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
/**
* Sets the algorithm-specific key generation parameters. For example, for RSA keys this may
- * be an instance of {@link java.security.spec.RSAKeyGenParameterSpec}.
+ * be an instance of {@link java.security.spec.RSAKeyGenParameterSpec} whereas for EC keys
+ * this may be an instance of {@link java.security.spec.ECGenParameterSpec}.
+ *
+ * <p>These key generation parameters must match other explicitly set parameters (if any),
+ * such as key size.
*/
public Builder setAlgorithmParameterSpec(@NonNull AlgorithmParameterSpec spec) {
if (spec == null) {
@@ -532,7 +544,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
if (date == null) {
throw new NullPointerException("date == null");
}
- mCertificateNotBefore = date;
+ mCertificateNotBefore = Utils.cloneIfNotNull(date);
return this;
}
@@ -547,7 +559,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
if (date == null) {
throw new NullPointerException("date == null");
}
- mCertificateNotAfter = date;
+ mCertificateNotAfter = Utils.cloneIfNotNull(date);
return this;
}
@@ -556,13 +568,11 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*
* <p>By default, the key is valid at any instant.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
- *
* @see #setKeyValidityEnd(Date)
*/
@NonNull
public Builder setKeyValidityStart(Date startDate) {
- mKeyValidityStart = startDate;
+ mKeyValidityStart = Utils.cloneIfNotNull(startDate);
return this;
}
@@ -571,8 +581,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*
* <p>By default, the key is valid at any instant.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
- *
* @see #setKeyValidityStart(Date)
* @see #setKeyValidityForConsumptionEnd(Date)
* @see #setKeyValidityForOriginationEnd(Date)
@@ -589,13 +597,11 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*
* <p>By default, the key is valid at any instant.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
- *
* @see #setKeyValidityForConsumptionEnd(Date)
*/
@NonNull
public Builder setKeyValidityForOriginationEnd(Date endDate) {
- mKeyValidityForOriginationEnd = endDate;
+ mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(endDate);
return this;
}
@@ -605,13 +611,11 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*
* <p>By default, the key is valid at any instant.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
- *
* @see #setKeyValidityForOriginationEnd(Date)
*/
@NonNull
public Builder setKeyValidityForConsumptionEnd(Date endDate) {
- mKeyValidityForConsumptionEnd = endDate;
+ mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(endDate);
return this;
}
@@ -622,11 +626,14 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*
* <p>This must be specified for keys which are used for signing/verification. For HMAC
* keys, the set of digests defaults to the digest associated with the key algorithm (e.g.,
- * {@code SHA-256} for key algorithm {@code HmacSHA256}
+ * {@code SHA-256} for key algorithm {@code HmacSHA256}).
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
+ * <p>For private keys used for TLS/SSL client or server authentication it is usually
+ * necessary to authorize the use of no digest ({@link KeyProperties#DIGEST_NONE}). This is
+ * because TLS/SSL stacks typically generate the necessary digest(s) themselves and then use
+ * a private key to sign it.
*
- * @see KeyProperties.Digest
+ * <p>See {@link KeyProperties}.{@code DIGEST} constants.
*/
@NonNull
public Builder setDigests(@KeyProperties.DigestEnum String... digests) {
@@ -642,7 +649,11 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*
* <p>This must be specified for keys which are used for encryption/decryption.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
+ * <p>For RSA private keys used by TLS/SSL servers to authenticate themselves to clients it
+ * is usually necessary to authorize the use of no/any padding
+ * ({@link KeyProperties#ENCRYPTION_PADDING_NONE}). This is because RSA decryption is
+ * required by some cipher suites, and some stacks request decryption using no padding
+ * whereas others request PKCS#1 padding.
*
* <p>See {@link KeyProperties}.{@code ENCRYPTION_PADDING} constants.
*/
@@ -660,8 +671,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*
* <p>This must be specified for RSA keys which are used for signing/verification.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
- *
* <p>See {@link KeyProperties}.{@code SIGNATURE_PADDING} constants.
*/
@NonNull
@@ -678,8 +687,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*
* <p>This must be specified for encryption/decryption keys.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
- *
* <p>See {@link KeyProperties}.{@code BLOCK_MODE} constants.
*/
@NonNull
@@ -723,8 +730,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
* <li>If you are using RSA encryption without padding, consider switching to encryption
* padding schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li>
* </ul>
- *
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
*/
@NonNull
public Builder setRandomizedEncryptionRequired(boolean required) {
@@ -748,8 +753,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
* <p>This restriction applies only to private key operations. Public key operations are not
* restricted.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
- *
* @see #setUserAuthenticationValidityDurationSeconds(int)
*/
@NonNull
@@ -764,8 +767,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*
* <p>By default, the user needs to authenticate for every use of the key.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
- *
* @param seconds duration in seconds or {@code -1} if the user needs to authenticate for
* every use of the key.
*
@@ -774,14 +775,15 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
@NonNull
public Builder setUserAuthenticationValidityDurationSeconds(
@IntRange(from = -1) int seconds) {
+ if (seconds < -1) {
+ throw new IllegalArgumentException("seconds must be -1 or larger");
+ }
mUserAuthenticationValidityDurationSeconds = seconds;
return this;
}
/**
* Builds an instance of {@code KeyGenParameterSpec}.
- *
- * @throws IllegalArgumentException if a required field is missing
*/
@NonNull
public KeyGenParameterSpec build() {
diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java
index e4f921e..03b4100 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -33,6 +33,8 @@ import javax.crypto.SecretKey;
* is authorized for (e.g., only in {@code CBC} mode, or signing only), whether the key should be
* encrypted at rest, the key's and validity start and end dates.
*
+ * <p>Instances of this class are immutable.
+ *
* <p><h3>Example: Symmetric Key</h3>
* The following example illustrates how to obtain a {@code KeyInfo} describing the provided Android
* Keystore {@link SecretKey}.
@@ -102,9 +104,9 @@ public class KeyInfo implements KeySpec {
mInsideSecureHardware = insideSecureHardware;
mOrigin = origin;
mKeySize = keySize;
- mKeyValidityStart = keyValidityStart;
- mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
- mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
+ mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart);
+ mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd);
+ mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd);
mPurposes = purposes;
mEncryptionPaddings =
ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings));
@@ -155,7 +157,7 @@ public class KeyInfo implements KeySpec {
*/
@Nullable
public Date getKeyValidityStart() {
- return mKeyValidityStart;
+ return Utils.cloneIfNotNull(mKeyValidityStart);
}
/**
@@ -165,7 +167,7 @@ public class KeyInfo implements KeySpec {
*/
@Nullable
public Date getKeyValidityForConsumptionEnd() {
- return mKeyValidityForConsumptionEnd;
+ return Utils.cloneIfNotNull(mKeyValidityForConsumptionEnd);
}
/**
@@ -175,7 +177,7 @@ public class KeyInfo implements KeySpec {
*/
@Nullable
public Date getKeyValidityForOriginationEnd() {
- return mKeyValidityForOriginationEnd;
+ return Utils.cloneIfNotNull(mKeyValidityForOriginationEnd);
}
/**
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 5af4181..f9fe176 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -368,6 +368,9 @@ public abstract class KeyProperties {
/**
* No encryption padding.
+ *
+ * <p><b>NOTE</b>: If a key is authorized to be used with no padding, then it can be used with
+ * any padding scheme, both for encryption and signing.
*/
public static final String ENCRYPTION_PADDING_NONE = "NoPadding";
@@ -514,6 +517,9 @@ public abstract class KeyProperties {
/**
* No digest: sign/authenticate the raw message.
+ *
+ * <p><b>NOTE</b>: If a key is authorized to be used with no digest, then it can be used with
+ * any digest.
*/
public static final String DIGEST_NONE = "NONE";
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index f52a193..1e0611c 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -47,6 +47,8 @@ import javax.crypto.Cipher;
*
* <p>NOTE: The key material of keys stored in the Android KeyStore is not accessible.
*
+ * <p>Instances of this class are immutable.
+ *
* <p><h3>Example: Symmetric Key</h3>
* The following example illustrates how to import an AES key into the Android KeyStore under alias
* {@code key1} authorized to be used only for encryption/decryption in CBC mode with PKCS#7
@@ -122,15 +124,9 @@ public final class KeyProtection implements ProtectionParameter {
boolean randomizedEncryptionRequired,
boolean userAuthenticationRequired,
int userAuthenticationValidityDurationSeconds) {
- if ((userAuthenticationValidityDurationSeconds < 0)
- && (userAuthenticationValidityDurationSeconds != -1)) {
- throw new IllegalArgumentException(
- "userAuthenticationValidityDurationSeconds must not be negative");
- }
-
- mKeyValidityStart = keyValidityStart;
- mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
- mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
+ mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart);
+ mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd);
+ mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd);
mPurposes = purposes;
mEncryptionPaddings =
ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings));
@@ -150,7 +146,7 @@ public final class KeyProtection implements ProtectionParameter {
*/
@Nullable
public Date getKeyValidityStart() {
- return mKeyValidityStart;
+ return Utils.cloneIfNotNull(mKeyValidityStart);
}
/**
@@ -160,7 +156,7 @@ public final class KeyProtection implements ProtectionParameter {
*/
@Nullable
public Date getKeyValidityForConsumptionEnd() {
- return mKeyValidityForConsumptionEnd;
+ return Utils.cloneIfNotNull(mKeyValidityForConsumptionEnd);
}
/**
@@ -170,7 +166,7 @@ public final class KeyProtection implements ProtectionParameter {
*/
@Nullable
public Date getKeyValidityForOriginationEnd() {
- return mKeyValidityForOriginationEnd;
+ return Utils.cloneIfNotNull(mKeyValidityForOriginationEnd);
}
/**
@@ -305,9 +301,6 @@ public final class KeyProtection implements ProtectionParameter {
* @param purposes set of purposes (e.g., encrypt, decrypt, sign) for which the key can be
* used. Attempts to use the key for any other purpose will be rejected.
*
- * <p><b>NOTE: The {@code purposes} parameter has currently no effect on asymmetric
- * key pairs.</b>
- *
* <p>See {@link KeyProperties}.{@code PURPOSE} flags.
*/
public Builder(@KeyProperties.PurposeEnum int purposes) {
@@ -319,13 +312,11 @@ public final class KeyProtection implements ProtectionParameter {
*
* <p>By default, the key is valid at any instant.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
- *
* @see #setKeyValidityEnd(Date)
*/
@NonNull
public Builder setKeyValidityStart(Date startDate) {
- mKeyValidityStart = startDate;
+ mKeyValidityStart = Utils.cloneIfNotNull(startDate);
return this;
}
@@ -334,8 +325,6 @@ public final class KeyProtection implements ProtectionParameter {
*
* <p>By default, the key is valid at any instant.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
- *
* @see #setKeyValidityStart(Date)
* @see #setKeyValidityForConsumptionEnd(Date)
* @see #setKeyValidityForOriginationEnd(Date)
@@ -352,13 +341,11 @@ public final class KeyProtection implements ProtectionParameter {
*
* <p>By default, the key is valid at any instant.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
- *
* @see #setKeyValidityForConsumptionEnd(Date)
*/
@NonNull
public Builder setKeyValidityForOriginationEnd(Date endDate) {
- mKeyValidityForOriginationEnd = endDate;
+ mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(endDate);
return this;
}
@@ -368,13 +355,11 @@ public final class KeyProtection implements ProtectionParameter {
*
* <p>By default, the key is valid at any instant.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
- *
* @see #setKeyValidityForOriginationEnd(Date)
*/
@NonNull
public Builder setKeyValidityForConsumptionEnd(Date endDate) {
- mKeyValidityForConsumptionEnd = endDate;
+ mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(endDate);
return this;
}
@@ -385,7 +370,11 @@ public final class KeyProtection implements ProtectionParameter {
*
* <p>This must be specified for keys which are used for encryption/decryption.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
+ * <p>For RSA private keys used by TLS/SSL servers to authenticate themselves to clients it
+ * is usually necessary to authorize the use of no/any padding
+ * ({@link KeyProperties#ENCRYPTION_PADDING_NONE}). This is because RSA decryption is
+ * required by some cipher suites, and some stacks request decryption using no padding
+ * whereas others request PKCS#1 padding.
*
* <p>See {@link KeyProperties}.{@code ENCRYPTION_PADDING} constants.
*/
@@ -403,8 +392,6 @@ public final class KeyProtection implements ProtectionParameter {
*
* <p>This must be specified for RSA keys which are used for signing/verification.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
- *
* <p>See {@link KeyProperties}.{@code SIGNATURE_PADDING} constants.
*/
@NonNull
@@ -423,7 +410,10 @@ public final class KeyProtection implements ProtectionParameter {
* {@link Key#getAlgorithm()}. For asymmetric signing keys the set of digest algorithms
* must be specified.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
+ * <p>For private keys used for TLS/SSL client or server authentication it is usually
+ * necessary to authorize the use of no digest ({@link KeyProperties#DIGEST_NONE}). This is
+ * because TLS/SSL stacks typically generate the necessary digest(s) themselves and then use
+ * a private key to sign it.
*
* <p>See {@link KeyProperties}.{@code DIGEST} constants.
*/
@@ -440,8 +430,6 @@ public final class KeyProtection implements ProtectionParameter {
*
* <p>This must be specified for encryption/decryption keys.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
- *
* <p>See {@link KeyProperties}.{@code BLOCK_MODE} constants.
*/
@NonNull
@@ -483,8 +471,6 @@ public final class KeyProtection implements ProtectionParameter {
* <li>If you are using RSA encryption without padding, consider switching to padding
* schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li>
* </ul>
- *
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
*/
@NonNull
public Builder setRandomizedEncryptionRequired(boolean required) {
@@ -505,8 +491,6 @@ public final class KeyProtection implements ProtectionParameter {
* <a href="{@docRoot}training/articles/keystore.html#UserAuthentication">More
* information</a>.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
- *
* @see #setUserAuthenticationValidityDurationSeconds(int)
*/
@NonNull
@@ -521,8 +505,6 @@ public final class KeyProtection implements ProtectionParameter {
*
* <p>By default, the user needs to authenticate for every use of the key.
*
- * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b>
- *
* @param seconds duration in seconds or {@code -1} if the user needs to authenticate for
* every use of the key.
*
@@ -531,6 +513,9 @@ public final class KeyProtection implements ProtectionParameter {
@NonNull
public Builder setUserAuthenticationValidityDurationSeconds(
@IntRange(from = -1) int seconds) {
+ if (seconds < -1) {
+ throw new IllegalArgumentException("seconds must be -1 or larger");
+ }
mUserAuthenticationValidityDurationSeconds = seconds;
return this;
}
diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java
index 47b4996..894d52a 100644
--- a/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java
+++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java
@@ -35,8 +35,8 @@ import java.io.IOException;
* 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.
+ * {@link #doFinal(byte[], int, int, byte[]) doFinal} operations which can be used to conveniently
+ * implement various JCA crypto primitives.
*
* <p>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
@@ -60,7 +60,7 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS
* Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't
* be reached.
*/
- OperationResult finish();
+ OperationResult finish(byte[] additionalEntropy);
}
// Binder buffer is about 1MB, but it's shared between all active transactions of the process.
@@ -73,6 +73,8 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS
private byte[] mBuffered = EmptyArray.BYTE;
private int mBufferedOffset;
private int mBufferedLength;
+ private long mConsumedInputSizeBytes;
+ private long mProducedOutputSizeBytes;
public KeyStoreCryptoOperationChunkedStreamer(Stream operation) {
this(operation, DEFAULT_MAX_CHUNK_SIZE);
@@ -119,6 +121,7 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS
// Update input array references to reflect that some of its bytes are now in mBuffered.
inputOffset += inputBytesInChunk;
inputLength -= inputBytesInChunk;
+ mConsumedInputSizeBytes += inputBytesInChunk;
OperationResult opResult = mKeyStoreStream.update(chunk);
if (opResult == null) {
@@ -167,9 +170,10 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS
}
} else {
// No more output will be produced in this loop
+ byte[] result;
if (bufferedOutput == null) {
// No previously buffered output
- return opResult.output;
+ result = opResult.output;
} else {
// There was some previously buffered output
try {
@@ -177,22 +181,27 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS
} catch (IOException e) {
throw new IllegalStateException("Failed to buffer output", e);
}
- return bufferedOutput.toByteArray();
+ result = bufferedOutput.toByteArray();
}
+ mProducedOutputSizeBytes += result.length;
+ return result;
}
}
}
+ byte[] result;
if (bufferedOutput == null) {
// No output produced
- return EmptyArray.BYTE;
+ result = EmptyArray.BYTE;
} else {
- return bufferedOutput.toByteArray();
+ result = bufferedOutput.toByteArray();
}
+ mProducedOutputSizeBytes += result.length;
+ return result;
}
@Override
- public byte[] doFinal(byte[] input, int inputOffset, int inputLength)
+ public byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] additionalEntropy)
throws KeyStoreException {
if (inputLength == 0) {
// No input provided -- simplify the rest of the code
@@ -204,20 +213,17 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS
byte[] output = update(input, inputOffset, inputLength);
output = ArrayUtils.concat(output, flush());
- OperationResult opResult = mKeyStoreStream.finish();
+ OperationResult opResult = mKeyStoreStream.finish(additionalEntropy);
if (opResult == null) {
throw new KeyStoreConnectException();
} else if (opResult.resultCode != KeyStore.NO_ERROR) {
throw KeyStore.getKeyStoreException(opResult.resultCode);
}
+ mProducedOutputSizeBytes += opResult.output.length;
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;
@@ -243,7 +249,19 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS
+ " . Provided: " + chunk.length + ", consumed: " + opResult.inputConsumed);
}
- return (opResult.output != null) ? opResult.output : EmptyArray.BYTE;
+ byte[] result = (opResult.output != null) ? opResult.output : EmptyArray.BYTE;
+ mProducedOutputSizeBytes += result.length;
+ return result;
+ }
+
+ @Override
+ public long getConsumedInputSizeBytes() {
+ return mConsumedInputSizeBytes;
+ }
+
+ @Override
+ public long getProducedOutputSizeBytes() {
+ return mProducedOutputSizeBytes;
}
/**
@@ -268,8 +286,8 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS
}
@Override
- public OperationResult finish() {
- return mKeyStore.finish(mOperationToken, null, null);
+ public OperationResult finish(byte[] additionalEntropy) {
+ return mKeyStore.finish(mOperationToken, null, null, additionalEntropy);
}
}
}
diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java
index 2fb8f20..897bd71 100644
--- a/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java
+++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java
@@ -28,12 +28,15 @@ import android.security.KeyStoreException;
* 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.
+ * {@link #doFinal(byte[], int, int, byte[]) doFinal} operations which can be used to conveniently
+ * implement various JCA crypto primitives.
*
* @hide
*/
interface KeyStoreCryptoOperationStreamer {
byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException;
- byte[] doFinal(byte[] input, int inputOffset, int inputLength) throws KeyStoreException;
+ byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] additionalEntropy)
+ throws KeyStoreException;
+ long getConsumedInputSizeBytes();
+ long getProducedOutputSizeBytes();
}
diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java
index 0639d49..4b37d90 100644
--- a/keystore/java/android/security/keystore/KeymasterUtils.java
+++ b/keystore/java/android/security/keystore/KeymasterUtils.java
@@ -101,13 +101,10 @@ public abstract class KeymasterUtils {
// 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();
+ // TODO: Restore USE_FINGERPRINT permission check in
+ // FingerprintManager.getAuthenticatorId once the ID is no longer needed here.
+ long fingerprintOnlySid =
+ (fingerprintManager != null) ? fingerprintManager.getAuthenticatorId() : 0;
if (fingerprintOnlySid == 0) {
throw new IllegalStateException(
"At least one fingerprint must be enrolled to create keys requiring user"
diff --git a/keystore/java/android/security/keystore/Utils.java b/keystore/java/android/security/keystore/Utils.java
new file mode 100644
index 0000000..9bec682
--- /dev/null
+++ b/keystore/java/android/security/keystore/Utils.java
@@ -0,0 +1,32 @@
+/*
+ * 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.util.Date;
+
+/**
+ * Assorted utility methods.
+ *
+ * @hide
+ */
+abstract class Utils {
+ private Utils() {}
+
+ static Date cloneIfNotNull(Date value) {
+ return (value != null) ? (Date) value.clone() : null;
+ }
+}
diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java
index 44fb826..0b60c62 100644
--- a/keystore/tests/src/android/security/KeyStoreTest.java
+++ b/keystore/tests/src/android/security/KeyStoreTest.java
@@ -20,7 +20,6 @@ import android.app.Activity;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
-import android.os.ServiceManager;
import android.security.keymaster.ExportResult;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
@@ -34,13 +33,9 @@ import android.test.suitebuilder.annotation.MediumTest;
import com.android.org.conscrypt.NativeConstants;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
-import java.util.Date;
import java.util.HashSet;
import java.security.spec.RSAKeyGenParameterSpec;
-import android.util.Log;
-import android.util.Base64;
-
/**
* Junit / Instrumentation test case for KeyStore class
*
diff --git a/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java b/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java
index 8488acd..e5c15c5 100644
--- a/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java
+++ b/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java
@@ -26,17 +26,21 @@ import android.test.AndroidTestCase;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.security.KeyPair;
+import java.security.KeyPairGenerator;
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.ECKey;
import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.text.SimpleDateFormat;
+import java.util.Arrays;
import java.util.Date;
import javax.security.auth.x500.X500Principal;
@@ -158,6 +162,26 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
}
public void testKeyPairGenerator_GenerateKeyPair_EC_Unencrypted_Success() throws Exception {
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
+ generator.initialize(new KeyGenParameterSpec.Builder(
+ TEST_ALIAS_1,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setCertificateSubject(TEST_DN_1)
+ .setCertificateSerialNumber(TEST_SERIAL_1)
+ .setCertificateNotBefore(NOW)
+ .setCertificateNotAfter(NOW_PLUS_10_YEARS)
+ .setDigests(KeyProperties.DIGEST_SHA256)
+ .build());
+
+ final KeyPair pair = generator.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_Legacy_GenerateKeyPair_EC_Unencrypted_Success()
+ throws Exception {
mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
.setAlias(TEST_ALIAS_1)
.setKeyType("EC")
@@ -328,19 +352,40 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
assertNotNull("The PrivateKey for the KeyPair should be not null", privKey);
assertEquals(keyType, privKey.getAlgorithm());
+ if ("EC".equalsIgnoreCase(keyType)) {
+ assertTrue("EC private key must be instanceof ECKey: " + privKey.getClass().getName(),
+ privKey instanceof ECKey);
+ assertEquals("Private and public key must have the same EC parameters",
+ ((ECKey) pubKey).getParams(), ((ECKey) privKey).getParams());
+ } else if ("RSA".equalsIgnoreCase(keyType)) {
+ assertTrue("RSA private key must be instance of RSAKey: "
+ + privKey.getClass().getName(),
+ privKey instanceof RSAKey);
+ assertEquals("Private and public key must have the same RSA modulus",
+ ((RSAKey) pubKey).getModulus(), ((RSAKey) privKey).getModulus());
+ }
+
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));
+ 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(
+ "Public key used to sign certificate should have the same algorithm as in KeyPair",
+ pubKey.getAlgorithm(), x509userCert.getPublicKey().getAlgorithm());
+
assertEquals("PublicKey used to sign certificate should match one returned in KeyPair",
- pubKey, x509userCert.getPublicKey());
+ pubKey,
+ AndroidKeyStoreProvider.getAndroidKeyStorePublicKey(
+ Credentials.USER_PRIVATE_KEY + alias,
+ x509userCert.getPublicKey().getAlgorithm(),
+ x509userCert.getPublicKey().getEncoded()));
assertEquals("The Subject DN should be the one passed into the params", dn,
x509userCert.getSubjectDN());
@@ -357,7 +402,10 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
assertDateEquals("The notAfter date should be the one passed into the params", end,
x509userCert.getNotAfter());
+ // Assert that the cert's signature verifies using the public key from generated KeyPair
x509userCert.verify(pubKey);
+ // Assert that the cert's signature verifies using the public key from the cert itself.
+ x509userCert.verify(x509userCert.getPublicKey());
final byte[] caCerts = mAndroidKeyStore.get(Credentials.CA_CERTIFICATE + alias);
assertNull("A list of CA certificates should not exist for the generated entry", caCerts);
@@ -368,6 +416,8 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
final byte[] pubKeyBytes = exportResult.exportData;
assertNotNull("The keystore should return the public key for the generated key",
pubKeyBytes);
+ assertTrue("Public key X.509 format should be as expected",
+ Arrays.equals(pubKey.getEncoded(), pubKeyBytes));
}
private static void assertDateEquals(String message, Date date1, Date date2) throws Exception {
diff --git a/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java b/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java
index 336fa40..c3b731b 100644
--- a/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java
+++ b/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java
@@ -19,38 +19,31 @@ 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.security.keymaster.ExportResult;
-import android.security.keymaster.KeymasterDefs;
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.KeyPair;
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.interfaces.ECKey;
+import java.security.interfaces.RSAKey;
import java.security.spec.PKCS8EncodedKeySpec;
-import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
@@ -1203,14 +1196,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, PrivateKey expectedKey,
Certificate expectedCert, Collection<Certificate> expectedChain) throws Exception {
- if (expectedKey instanceof ECPrivateKey) {
+ if (expectedKey instanceof ECKey) {
assertEquals("Returned PrivateKey should be what we inserted",
- ((ECPrivateKey) expectedKey).getParams().getCurve(),
- ((ECPublicKey) keyEntry.getCertificate().getPublicKey()).getParams().getCurve());
- } else if (expectedKey instanceof RSAPrivateKey) {
+ ((ECKey) expectedKey).getParams().getCurve(),
+ ((ECKey) keyEntry.getCertificate().getPublicKey()).getParams().getCurve());
+ } else if (expectedKey instanceof RSAKey) {
assertEquals("Returned PrivateKey should be what we inserted",
- ((RSAPrivateKey) expectedKey).getModulus(),
- ((RSAPrivateKey) keyEntry.getPrivateKey()).getModulus());
+ ((RSAKey) expectedKey).getModulus(),
+ ((RSAKey) keyEntry.getPrivateKey()).getModulus());
}
assertEquals("Returned Certificate should be what we inserted", expectedCert,
@@ -1263,15 +1256,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
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;
+ assertTrue("Should be a PrivateKey", key instanceof PrivateKey);
+ assertTrue("Should be a RSAKey", key instanceof RSAKey);
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());
+ ((RSAKey) expectedKey).getModulus(), ((RSAKey) key).getModulus());
}
public void testKeyStore_GetKey_NoPassword_Unencrypted_Success() throws Exception {
@@ -1287,15 +1279,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
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;
+ assertTrue("Should be a PrivateKey", key instanceof PrivateKey);
+ assertTrue("Should be a RSAKey", key instanceof RSAKey);
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());
+ ((RSAKey) expectedKey).getModulus(), ((RSAKey) key).getModulus());
}
public void testKeyStore_GetKey_Certificate_Encrypted_Failure() throws Exception {
@@ -1926,31 +1917,11 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
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);
- }
-
- ExportResult exportResult =
- keyStore.exportKey(privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null);
- assertEquals(KeyStore.NO_ERROR, exportResult.resultCode);
- final byte[] pubKeyBytes = exportResult.exportData;
-
- 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);
- }
+ KeyPair keyPair = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
+ keyStore, privateKeyAlias);
final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
- certGen.setPublicKey(pubKey);
+ certGen.setPublicKey(keyPair.getPublic());
certGen.setSerialNumber(serialNumber);
certGen.setSubjectDN(subjectDN);
certGen.setIssuerDN(subjectDN);
@@ -1958,7 +1929,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
certGen.setNotAfter(notAfter);
certGen.setSignatureAlgorithm("sha1WithRSA");
- final X509Certificate cert = certGen.generate(privKey);
+ final X509Certificate cert = certGen.generate(keyPair.getPrivate());
return cert;
}