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/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java442
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java53
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java116
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java30
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java13
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreSpi.java5
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java16
-rw-r--r--keystore/java/android/security/keystore/KeyGenParameterSpec.java193
-rw-r--r--keystore/java/android/security/keystore/KeyInfo.java34
-rw-r--r--keystore/java/android/security/keystore/KeyPermanentlyInvalidatedException.java13
-rw-r--r--keystore/java/android/security/keystore/KeyProperties.java2
-rw-r--r--keystore/java/android/security/keystore/KeyProtection.java140
-rw-r--r--keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java36
-rw-r--r--keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java2
-rw-r--r--keystore/java/android/security/keystore/Utils.java32
16 files changed, 952 insertions, 447 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/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..156f45f 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");
@@ -129,52 +132,52 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider {
putSignatureImpl("MD5withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$MD5WithPKCS1Padding");
- put("Alg.Alias.Signature.MD5WithRSAEncryption", "MD5WithRSA");
- put("Alg.Alias.Signature.MD5/RSA", "MD5WithRSA");
- put("Alg.Alias.Signature.1.2.840.113549.1.1.4", "MD5WithRSA");
- put("Alg.Alias.Signature.1.2.840.113549.2.5with1.2.840.113549.1.1.1", "MD5WithRSA");
+ put("Alg.Alias.Signature.MD5WithRSAEncryption", "MD5withRSA");
+ put("Alg.Alias.Signature.MD5/RSA", "MD5withRSA");
+ put("Alg.Alias.Signature.1.2.840.113549.1.1.4", "MD5withRSA");
+ put("Alg.Alias.Signature.1.2.840.113549.2.5with1.2.840.113549.1.1.1", "MD5withRSA");
putSignatureImpl("SHA1withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA1WithPKCS1Padding");
- put("Alg.Alias.Signature.SHA1WithRSAEncryption", "SHA1WithRSA");
- put("Alg.Alias.Signature.SHA1/RSA", "SHA1WithRSA");
- put("Alg.Alias.Signature.SHA-1/RSA", "SHA1WithRSA");
- put("Alg.Alias.Signature.1.2.840.113549.1.1.5", "SHA1WithRSA");
- put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.1", "SHA1WithRSA");
- put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.5", "SHA1WithRSA");
- put("Alg.Alias.Signature.1.3.14.3.2.29", "SHA1WithRSA");
+ put("Alg.Alias.Signature.SHA1WithRSAEncryption", "SHA1withRSA");
+ put("Alg.Alias.Signature.SHA1/RSA", "SHA1withRSA");
+ put("Alg.Alias.Signature.SHA-1/RSA", "SHA1withRSA");
+ put("Alg.Alias.Signature.1.2.840.113549.1.1.5", "SHA1withRSA");
+ put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.1", "SHA1withRSA");
+ put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.5", "SHA1withRSA");
+ put("Alg.Alias.Signature.1.3.14.3.2.29", "SHA1withRSA");
putSignatureImpl("SHA224withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA224WithPKCS1Padding");
- put("Alg.Alias.Signature.SHA224WithRSAEncryption", "SHA224WithRSA");
- put("Alg.Alias.Signature.1.2.840.113549.1.1.11", "SHA224WithRSA");
+ put("Alg.Alias.Signature.SHA224WithRSAEncryption", "SHA224withRSA");
+ put("Alg.Alias.Signature.1.2.840.113549.1.1.11", "SHA224withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.4with1.2.840.113549.1.1.1",
- "SHA224WithRSA");
+ "SHA224withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.4with1.2.840.113549.1.1.11",
- "SHA224WithRSA");
+ "SHA224withRSA");
putSignatureImpl("SHA256withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA256WithPKCS1Padding");
- put("Alg.Alias.Signature.SHA256WithRSAEncryption", "SHA256WithRSA");
- put("Alg.Alias.Signature.1.2.840.113549.1.1.11", "SHA256WithRSA");
+ put("Alg.Alias.Signature.SHA256WithRSAEncryption", "SHA256withRSA");
+ put("Alg.Alias.Signature.1.2.840.113549.1.1.11", "SHA256withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.113549.1.1.1",
- "SHA256WithRSA");
+ "SHA256withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.113549.1.1.11",
- "SHA256WithRSA");
+ "SHA256withRSA");
putSignatureImpl("SHA384withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA384WithPKCS1Padding");
- put("Alg.Alias.Signature.SHA384WithRSAEncryption", "SHA384WithRSA");
- put("Alg.Alias.Signature.1.2.840.113549.1.1.12", "SHA384WithRSA");
+ put("Alg.Alias.Signature.SHA384WithRSAEncryption", "SHA384withRSA");
+ put("Alg.Alias.Signature.1.2.840.113549.1.1.12", "SHA384withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.2with1.2.840.113549.1.1.1",
- "SHA384WithRSA");
+ "SHA384withRSA");
putSignatureImpl("SHA512withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA512WithPKCS1Padding");
- put("Alg.Alias.Signature.SHA512WithRSAEncryption", "SHA512WithRSA");
- put("Alg.Alias.Signature.1.2.840.113549.1.1.13", "SHA512WithRSA");
+ put("Alg.Alias.Signature.SHA512WithRSAEncryption", "SHA512withRSA");
+ put("Alg.Alias.Signature.1.2.840.113549.1.1.13", "SHA512withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.3with1.2.840.113549.1.1.1",
- "SHA512WithRSA");
+ "SHA512withRSA");
putSignatureImpl("SHA1withRSA/PSS",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA1WithPSSPadding");
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
index d2d5850..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,6 +465,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
byte[] output;
try {
+ flushAAD();
byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, getAdditionalEntropyAmountForFinish());
@@ -615,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.
/**
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index b93424d..2055cdb 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -104,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;
@@ -115,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);
@@ -140,6 +135,10 @@ 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;
@@ -227,9 +226,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
| KeyProperties.PURPOSE_VERIFY);
// 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).
+ // Authorized to be used with any encryption and signature padding
+ // scheme (including no padding).
specBuilder.setEncryptionPaddings(
KeyProperties.ENCRYPTION_PADDING_NONE);
// Disable randomized encryption requirement to support encryption
@@ -598,9 +596,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:
@@ -624,7 +622,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
@@ -636,6 +634,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;
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java
index 6abdf19..38e216d 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java
@@ -129,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,
@@ -142,6 +143,7 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase
throws KeyStoreException {
if (inputLength > 0) {
mInputBuffer.write(input, inputOffset, inputLength);
+ mConsumedInputSizeBytes += inputLength;
}
return EmptyArray.BYTE;
}
@@ -151,6 +153,7 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase
byte[] additionalEntropy)
throws KeyStoreException {
if (inputLength > 0) {
+ mConsumedInputSizeBytes += inputLength;
mInputBuffer.write(input, inputOffset, inputLength);
}
byte[] bufferedInput = mInputBuffer.toByteArray();
@@ -173,6 +176,16 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase
}
return mDelegate.doFinal(paddedInput, 0, paddedInput.length, additionalEntropy);
}
+
+ @Override
+ public long getConsumedInputSizeBytes() {
+ return mConsumedInputSizeBytes;
+ }
+
+ @Override
+ public long getProducedOutputSizeBytes() {
+ return mDelegate.getProducedOutputSizeBytes();
+ }
}
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
index 3bd9d1d..5fb589e 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
@@ -258,9 +258,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
| KeyProperties.PURPOSE_VERIFY);
// 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).
+ // Authorized to be used with any encryption and signature padding scheme (including no
+ // padding).
specBuilder.setEncryptionPaddings(
KeyProperties.ENCRYPTION_PADDING_NONE);
// Disable randomized encryption requirement to support encryption padding NONE
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java
index 76804a9..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;
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 47aab74..1732db9 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -19,16 +19,20 @@ package android.security.keystore;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.KeyguardManager;
+import android.hardware.fingerprint.FingerprintManager;
import android.text.TextUtils;
import java.math.BigInteger;
import java.security.KeyPairGenerator;
+import java.security.Signature;
import java.security.cert.Certificate;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Date;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
import javax.security.auth.x500.X500Principal;
/**
@@ -59,9 +63,24 @@ 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 all of the following:
+ * <ul>
+ * <li>{@link KeyProperties#PURPOSE_SIGN},</li>
+ * <li>operation without requiring the user to be authenticated (see
+ * {@link Builder#setUserAuthenticationRequired(boolean)}),</li>
+ * <li>suitable digest or {@link KeyProperties#DIGEST_NONE},</li>
+ * <li>(RSA keys only) padding scheme {@link KeyProperties#SIGNATURE_PADDING_RSA_PKCS1} or
+ * {@link KeyProperties#ENCRYPTION_PADDING_NONE}.</li>
+ * </ul>
+ *
* <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 +90,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,15 +112,16 @@ 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 GCM mode with no
+ * 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)
+ * .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
* .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
* .build());
* SecretKey key = keyGenerator.generateKey();
@@ -161,10 +182,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 +206,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 +226,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 +241,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 +273,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*/
@NonNull
public Date getCertificateNotBefore() {
- return mCertificateNotBefore;
+ return Utils.cloneIfNotNull(mCertificateNotBefore);
}
/**
@@ -264,7 +282,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*/
@NonNull
public Date getCertificateNotAfter() {
- return mCertificateNotAfter;
+ return Utils.cloneIfNotNull(mCertificateNotAfter);
}
/**
@@ -273,7 +291,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*/
@Nullable
public Date getKeyValidityStart() {
- return mKeyValidityStart;
+ return Utils.cloneIfNotNull(mKeyValidityStart);
}
/**
@@ -282,7 +300,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*/
@Nullable
public Date getKeyValidityForConsumptionEnd() {
- return mKeyValidityForConsumptionEnd;
+ return Utils.cloneIfNotNull(mKeyValidityForConsumptionEnd);
}
/**
@@ -291,7 +309,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*/
@Nullable
public Date getKeyValidityForOriginationEnd() {
- return mKeyValidityForOriginationEnd;
+ return Utils.cloneIfNotNull(mKeyValidityForOriginationEnd);
}
/**
@@ -359,7 +377,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
}
/**
- * Gets the set of block modes (e.g., {@code CBC}, {@code CTR}) with which the key can be used
+ * Gets the set of block modes (e.g., {@code GCM}, {@code CBC}) with which the key can be used
* when encrypting/decrypting. Attempts to use the key with any other block modes will be
* rejected.
*
@@ -384,28 +402,32 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
}
/**
- * Returns {@code true} if user authentication is required for this key to be used.
+ * Returns {@code true} if the key is authorized to be used only if the user has been
+ * authenticated.
*
- * <p>This restriction applies only to private key operations. Public key operations are not
- * restricted.
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
*
* @see #getUserAuthenticationValidityDurationSeconds()
+ * @see Builder#setUserAuthenticationRequired(boolean)
*/
public boolean isUserAuthenticationRequired() {
return mUserAuthenticationRequired;
}
/**
- * Gets the duration of time (seconds) for which this key can be used after the user is
- * successfully authenticated. This has effect only if user authentication is required.
+ * Gets the duration of time (seconds) for which this key is authorized to be used after the
+ * user is successfully authenticated. This has effect only if user authentication is required
+ * (see {@link #isUserAuthenticationRequired()}).
*
- * <p>This restriction applies only to private key operations. Public key operations are not
- * restricted.
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
*
* @return duration in seconds or {@code -1} if authentication is required for every use of the
* key.
*
* @see #isUserAuthenticationRequired()
+ * @see Builder#setUserAuthenticationValidityDurationSeconds(int)
*/
public int getUserAuthenticationValidityDurationSeconds() {
return mUserAuthenticationValidityDurationSeconds;
@@ -439,7 +461,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.
*
@@ -454,6 +476,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
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;
@@ -480,7 +504,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) {
@@ -529,7 +557,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
if (date == null) {
throw new NullPointerException("date == null");
}
- mCertificateNotBefore = date;
+ mCertificateNotBefore = Utils.cloneIfNotNull(date);
return this;
}
@@ -544,7 +572,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
if (date == null) {
throw new NullPointerException("date == null");
}
- mCertificateNotAfter = date;
+ mCertificateNotAfter = Utils.cloneIfNotNull(date);
return this;
}
@@ -557,7 +585,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*/
@NonNull
public Builder setKeyValidityStart(Date startDate) {
- mKeyValidityStart = startDate;
+ mKeyValidityStart = Utils.cloneIfNotNull(startDate);
return this;
}
@@ -586,7 +614,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*/
@NonNull
public Builder setKeyValidityForOriginationEnd(Date endDate) {
- mKeyValidityForOriginationEnd = endDate;
+ mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(endDate);
return this;
}
@@ -600,7 +628,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
*/
@NonNull
public Builder setKeyValidityForConsumptionEnd(Date endDate) {
- mKeyValidityForConsumptionEnd = endDate;
+ mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(endDate);
return this;
}
@@ -666,11 +694,11 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
}
/**
- * Sets the set of block modes (e.g., {@code CBC}, {@code CTR}, {@code ECB}) with which the
- * key can be used when encrypting/decrypting. Attempts to use the key with any other block
- * modes will be rejected.
+ * Sets the set of block modes (e.g., {@code GCM}, {@code CBC}) with which the key can be
+ * used when encrypting/decrypting. Attempts to use the key with any other block modes will
+ * be rejected.
*
- * <p>This must be specified for encryption/decryption keys.
+ * <p>This must be specified for symmetric encryption/decryption keys.
*
* <p>See {@link KeyProperties}.{@code BLOCK_MODE} constants.
*/
@@ -696,7 +724,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
* <li>encryption/decryption transformation which do not offer {@code IND-CPA}, such as
* {@code ECB} with a symmetric encryption algorithm, or RSA encryption/decryption without
* padding, are prohibited;</li>
- * <li>in block modes which use an IV, such as {@code CBC}, {@code CTR}, and {@code GCM},
+ * <li>in block modes which use an IV, such as {@code GCM}, {@code CBC}, and {@code CTR},
* caller-provided IVs are rejected when encrypting, to ensure that only random IVs are
* used.</li>
* </ul>
@@ -723,22 +751,38 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
}
/**
- * Sets whether user authentication is required to use this key.
+ * Sets whether this key is authorized to be used only if the user has been authenticated.
*
- * <p>By default, the key can be used without user authentication.
+ * <p>By default, the key is authorized to be used regardless of whether the user has been
+ * authenticated.
*
- * <p>When user authentication is required, the user authorizes the use of the key by
- * authenticating to this Android device using a subset of their secure lock screen
- * credentials. Different authentication methods are used depending on whether the every
- * use of the key must be authenticated (as specified by
- * {@link #setUserAuthenticationValidityDurationSeconds(int)}).
+ * <p>When user authentication is required:
+ * <ul>
+ * <li>The key can only be generated if secure lock screen is set up (see
+ * {@link KeyguardManager#isDeviceSecure()}). Additionally, if the key requires that user
+ * authentication takes place for every use of the key (see
+ * {@link #setUserAuthenticationValidityDurationSeconds(int)}), at least one fingerprint
+ * must be enrolled (see {@link FingerprintManager#hasEnrolledFingerprints()}).</li>
+ * <li>The use of the key must be authorized by the user by authenticating to this Android
+ * device using a subset of their secure lock screen credentials such as
+ * password/PIN/pattern or fingerprint.
* <a href="{@docRoot}training/articles/keystore.html#UserAuthentication">More
* information</a>.
+ * <li>The key will become <em>irreversibly invalidated</em> once the secure lock screen is
+ * disabled (reconfigured to None, Swipe or other mode which does not authenticate the user)
+ * or when the secure lock screen is forcibly reset (e.g., by a Device Administrator).
+ * Additionally, if the key requires that user authentication takes place for every use of
+ * the key, it is also irreversibly invalidated once a new fingerprint is enrolled or once\
+ * no more fingerprints are enrolled. Attempts to initialize cryptographic operations using
+ * such keys will throw {@link KeyPermanentlyInvalidatedException}.</li>
+ * </ul>
*
- * <p>This restriction applies only to private key operations. Public key operations are not
- * restricted.
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
*
* @see #setUserAuthenticationValidityDurationSeconds(int)
+ * @see KeyguardManager#isDeviceSecure()
+ * @see FingerprintManager#hasEnrolledFingerprints()
*/
@NonNull
public Builder setUserAuthenticationRequired(boolean required) {
@@ -747,27 +791,52 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
}
/**
- * Sets the duration of time (seconds) for which this key can be used after the user is
- * successfully authenticated. This has effect only if user authentication is required.
- *
- * <p>By default, the user needs to authenticate for every use of the key.
- *
- * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for
- * every use of the key.
+ * Sets the duration of time (seconds) for which this key is authorized to be used after the
+ * user is successfully authenticated. This has effect if the key requires user
+ * authentication for its use (see {@link #setUserAuthenticationRequired(boolean)}).
+ *
+ * <p>By default, if user authentication is required, it must take place for every use of
+ * the key.
+ *
+ * <p>Cryptographic operations involving keys which require user authentication to take
+ * place for every operation can only use fingerprint authentication. This is achieved by
+ * initializing a cryptographic operation ({@link Signature}, {@link Cipher}, {@link Mac})
+ * with the key, wrapping it into a {@link FingerprintManager.CryptoObject}, invoking
+ * {@code FingerprintManager.authenticate} with {@code CryptoObject}, and proceeding with
+ * the cryptographic operation only if the authentication flow succeeds.
+ *
+ * <p>Cryptographic operations involving keys which are authorized to be used for a duration
+ * of time after a successful user authentication event can only use secure lock screen
+ * authentication. These cryptographic operations will throw
+ * {@link UserNotAuthenticatedException} during initialization if the user needs to be
+ * authenticated to proceed. This situation can be resolved by the user unlocking the secure
+ * lock screen of the Android or by going through the confirm credential flow initiated by
+ * {@link KeyguardManager#createConfirmDeviceCredentialIntent(CharSequence, CharSequence)}.
+ * Once resolved, initializing a new cryptographic operation using this key (or any other
+ * key which is authorized to be used for a fixed duration of time after user
+ * authentication) should succeed provided the user authentication flow completed
+ * successfully.
+ *
+ * @param seconds duration in seconds or {@code -1} if user authentication must take place
+ * for every use of the key.
*
* @see #setUserAuthenticationRequired(boolean)
+ * @see FingerprintManager
+ * @see FingerprintManager.CryptoObject
+ * @see KeyguardManager
*/
@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..785ec15 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -30,9 +30,11 @@ import javax.crypto.SecretKey;
* Keystore system</a>. This class describes whether the key material is available in
* plaintext outside of secure hardware, whether user authentication is required for using the key
* and whether this requirement is enforced by secure hardware, the key's origin, what uses the key
- * is authorized for (e.g., only in {@code CBC} mode, or signing only), whether the key should be
+ * is authorized for (e.g., only in {@code GCM} 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);
}
/**
@@ -189,7 +191,7 @@ public class KeyInfo implements KeySpec {
}
/**
- * Gets the set of block modes (e.g., {@code CBC}, {@code CTR}) with which the key can be used
+ * Gets the set of block modes (e.g., {@code GCM}, {@code CBC}) with which the key can be used
* when encrypting/decrypting. Attempts to use the key with any other block modes will be
* rejected.
*
@@ -236,17 +238,27 @@ public class KeyInfo implements KeySpec {
}
/**
- * Returns {@code true} if user authentication is required for this key to be used.
+ * Returns {@code true} if the key is authorized to be used only if the user has been
+ * authenticated.
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
*
* @see #getUserAuthenticationValidityDurationSeconds()
+ * @see KeyGenParameterSpec.Builder#setUserAuthenticationRequired(boolean)
+ * @see KeyProtection.Builder#setUserAuthenticationRequired(boolean)
*/
public boolean isUserAuthenticationRequired() {
return mUserAuthenticationRequired;
}
/**
- * Gets the duration of time (seconds) for which this key can be used after the user is
- * successfully authenticated. This has effect only if user authentication is required.
+ * Gets the duration of time (seconds) for which this key is authorized to be used after the
+ * user is successfully authenticated. This has effect only if user authentication is required
+ * (see {@link #isUserAuthenticationRequired()}).
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
*
* @return duration in seconds or {@code -1} if authentication is required for every use of the
* key.
diff --git a/keystore/java/android/security/keystore/KeyPermanentlyInvalidatedException.java b/keystore/java/android/security/keystore/KeyPermanentlyInvalidatedException.java
index e320c9c..9e82fc0 100644
--- a/keystore/java/android/security/keystore/KeyPermanentlyInvalidatedException.java
+++ b/keystore/java/android/security/keystore/KeyPermanentlyInvalidatedException.java
@@ -21,12 +21,13 @@ import java.security.InvalidKeyException;
/**
* Indicates that the key can no longer be used because it has been permanently invalidated.
*
- * <p>This can currently occur only for keys that require user authentication. Such keys are
- * permanently invalidated once the secure lock screen is disabled (i.e., reconfigured to None,
- * Swipe or other mode which does not authenticate the user) or when the secure lock screen is
- * forcibly reset (e.g., by Device Admin). Additionally, keys configured to require user
- * authentication for every use of the key are also permanently invalidated once a new fingerprint
- * is enrolled or once no more fingerprints are enrolled.
+ * <p>This only occurs for keys which are authorized to be used only if the user has been
+ * authenticated. Such keys are permanently and irreversibly invalidated once the secure lock screen
+ * is disabled (i.e., reconfigured to None, Swipe or other mode which does not authenticate the
+ * user) or when the secure lock screen is forcibly reset (e.g., by Device Admin). Additionally,
+ * keys configured to require user authentication to take place for every of the keys, are also
+ * permanently invalidated once a new fingerprint is enrolled or once no more fingerprints are
+ * enrolled.
*/
public class KeyPermanentlyInvalidatedException extends InvalidKeyException {
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 403e814..f9fe176 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -370,7 +370,7 @@ 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.
+ * any padding scheme, both for encryption and signing.
*/
public static final String ENCRYPTION_PADDING_NONE = "NoPadding";
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index 432fc12..b7a2a0b 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -19,19 +19,23 @@ package android.security.keystore;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.KeyguardManager;
+import android.hardware.fingerprint.FingerprintManager;
import java.security.Key;
+import java.security.Signature;
import java.security.KeyStore.ProtectionParameter;
import java.security.cert.Certificate;
import java.util.Date;
import javax.crypto.Cipher;
+import javax.crypto.Mac;
/**
* Specification of how a key or key pair is secured when imported into the
* <a href="{@docRoot}training/articles/keystore.html">Android KeyStore facility</a>. This class
* specifies parameters such as whether user authentication is required for using the key, what uses
- * the key is authorized for (e.g., only in {@code CTR} mode, or only for signing -- decryption not
+ * the key is authorized for (e.g., only in {@code GCM} mode, or only for signing -- decryption not
* permitted), the key's and validity start and end dates.
*
* <p>To import a key or key pair into the Android KeyStore, create an instance of this class using
@@ -47,10 +51,12 @@ 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
- * padding. The key must export its key material via {@link Key#getEncoded()} in {@code RAW} format.
+ * {@code key1} authorized to be used only for encryption/decryption in GCM mode with no padding.
+ * The key must export its key material via {@link Key#getEncoded()} in {@code RAW} format.
* <pre> {@code
* SecretKey key = ...; // AES key
*
@@ -60,8 +66,8 @@ import javax.crypto.Cipher;
* "key1",
* new KeyStore.SecretKeyEntry(key),
* new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
- * .setBlockMode(KeyProperties.BLOCK_MODE_CBC)
- * .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
+ * .setBlockMode(KeyProperties.BLOCK_MODE_GCM)
+ * .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
* .build());
* // Key imported, obtain a reference to it.
* SecretKey keyStoreKey = (SecretKey) keyStore.getKey("key1", null);
@@ -122,15 +128,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 +150,7 @@ public final class KeyProtection implements ProtectionParameter {
*/
@Nullable
public Date getKeyValidityStart() {
- return mKeyValidityStart;
+ return Utils.cloneIfNotNull(mKeyValidityStart);
}
/**
@@ -160,7 +160,7 @@ public final class KeyProtection implements ProtectionParameter {
*/
@Nullable
public Date getKeyValidityForConsumptionEnd() {
- return mKeyValidityForConsumptionEnd;
+ return Utils.cloneIfNotNull(mKeyValidityForConsumptionEnd);
}
/**
@@ -170,7 +170,7 @@ public final class KeyProtection implements ProtectionParameter {
*/
@Nullable
public Date getKeyValidityForOriginationEnd() {
- return mKeyValidityForOriginationEnd;
+ return Utils.cloneIfNotNull(mKeyValidityForOriginationEnd);
}
/**
@@ -236,7 +236,7 @@ public final class KeyProtection implements ProtectionParameter {
}
/**
- * Gets the set of block modes (e.g., {@code CBC}, {@code CTR}) with which the key can be used
+ * Gets the set of block modes (e.g., {@code GCM}, {@code CBC}) with which the key can be used
* when encrypting/decrypting. Attempts to use the key with any other block modes will be
* rejected.
*
@@ -261,22 +261,32 @@ public final class KeyProtection implements ProtectionParameter {
}
/**
- * Returns {@code true} if user authentication is required for this key to be used.
+ * Returns {@code true} if the key is authorized to be used only if the user has been
+ * authenticated.
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
*
* @see #getUserAuthenticationValidityDurationSeconds()
+ * @see Builder#setUserAuthenticationRequired(boolean)
*/
public boolean isUserAuthenticationRequired() {
return mUserAuthenticationRequired;
}
/**
- * Gets the duration of time (seconds) for which this key can be used after the user is
- * successfully authenticated. This has effect only if user authentication is required.
+ * Gets the duration of time (seconds) for which this key is authorized to be used after the
+ * user is successfully authenticated. This has effect only if user authentication is required
+ * (see {@link #isUserAuthenticationRequired()}).
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
*
* @return duration in seconds or {@code -1} if authentication is required for every use of the
* key.
*
* @see #isUserAuthenticationRequired()
+ * @see Builder#setUserAuthenticationValidityDurationSeconds(int)
*/
public int getUserAuthenticationValidityDurationSeconds() {
return mUserAuthenticationValidityDurationSeconds;
@@ -320,7 +330,7 @@ public final class KeyProtection implements ProtectionParameter {
*/
@NonNull
public Builder setKeyValidityStart(Date startDate) {
- mKeyValidityStart = startDate;
+ mKeyValidityStart = Utils.cloneIfNotNull(startDate);
return this;
}
@@ -349,7 +359,7 @@ public final class KeyProtection implements ProtectionParameter {
*/
@NonNull
public Builder setKeyValidityForOriginationEnd(Date endDate) {
- mKeyValidityForOriginationEnd = endDate;
+ mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(endDate);
return this;
}
@@ -363,7 +373,7 @@ public final class KeyProtection implements ProtectionParameter {
*/
@NonNull
public Builder setKeyValidityForConsumptionEnd(Date endDate) {
- mKeyValidityForConsumptionEnd = endDate;
+ mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(endDate);
return this;
}
@@ -428,11 +438,11 @@ public final class KeyProtection implements ProtectionParameter {
}
/**
- * Sets the set of block modes (e.g., {@code CBC}, {@code CTR}, {@code ECB}) with which the
- * key can be used when encrypting/decrypting. Attempts to use the key with any other block
- * modes will be rejected.
+ * Sets the set of block modes (e.g., {@code GCM}, {@code CBC}) with which the key can be
+ * used when encrypting/decrypting. Attempts to use the key with any other block modes will
+ * be rejected.
*
- * <p>This must be specified for encryption/decryption keys.
+ * <p>This must be specified for symmetric encryption/decryption keys.
*
* <p>See {@link KeyProperties}.{@code BLOCK_MODE} constants.
*/
@@ -457,8 +467,8 @@ public final class KeyProtection implements ProtectionParameter {
* <ul>
* <li>transformation which do not offer {@code IND-CPA}, such as symmetric ciphers using
* {@code ECB} mode or RSA encryption without padding, are prohibited;</li>
- * <li>in transformations which use an IV, such as symmetric ciphers in {@code CBC},
- * {@code CTR}, and {@code GCM} block modes, caller-provided IVs are rejected when
+ * <li>in transformations which use an IV, such as symmetric ciphers in {@code GCM},
+ * {@code CBC}, and {@code CTR} block modes, caller-provided IVs are rejected when
* encrypting, to ensure that only random IVs are used.</li>
*
* <p>Before disabling this requirement, consider the following approaches instead:
@@ -483,19 +493,38 @@ public final class KeyProtection implements ProtectionParameter {
}
/**
- * Sets whether user authentication is required to use this key.
+ * Sets whether this key is authorized to be used only if the user has been authenticated.
*
- * <p>By default, the key can be used without user authentication.
+ * <p>By default, the key is authorized to be used regardless of whether the user has been
+ * authenticated.
*
- * <p>When user authentication is required, the user authorizes the use of the key by
- * authenticating to this Android device using a subset of their secure lock screen
- * credentials. Different authentication methods are used depending on whether the every
- * use of the key must be authenticated (as specified by
- * {@link #setUserAuthenticationValidityDurationSeconds(int)}).
+ * <p>When user authentication is required:
+ * <ul>
+ * <li>The key can only be import if secure lock screen is set up (see
+ * {@link KeyguardManager#isDeviceSecure()}). Additionally, if the key requires that user
+ * authentication takes place for every use of the key (see
+ * {@link #setUserAuthenticationValidityDurationSeconds(int)}), at least one fingerprint
+ * must be enrolled (see {@link FingerprintManager#hasEnrolledFingerprints()}).</li>
+ * <li>The use of the key must be authorized by the user by authenticating to this Android
+ * device using a subset of their secure lock screen credentials such as
+ * password/PIN/pattern or fingerprint.
* <a href="{@docRoot}training/articles/keystore.html#UserAuthentication">More
* information</a>.
+ * <li>The key will become <em>irreversibly invalidated</em> once the secure lock screen is
+ * disabled (reconfigured to None, Swipe or other mode which does not authenticate the user)
+ * or when the secure lock screen is forcibly reset (e.g., by a Device Administrator).
+ * Additionally, if the key requires that user authentication takes place for every use of
+ * the key, it is also irreversibly invalidated once a new fingerprint is enrolled or once\
+ * no more fingerprints are enrolled. Attempts to initialize cryptographic operations using
+ * such keys will throw {@link KeyPermanentlyInvalidatedException}.</li>
+ * </ul>
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
*
* @see #setUserAuthenticationValidityDurationSeconds(int)
+ * @see KeyguardManager#isDeviceSecure()
+ * @see FingerprintManager#hasEnrolledFingerprints()
*/
@NonNull
public Builder setUserAuthenticationRequired(boolean required) {
@@ -504,19 +533,46 @@ public final class KeyProtection implements ProtectionParameter {
}
/**
- * Sets the duration of time (seconds) for which this key can be used after the user is
- * successfully authenticated. This has effect only if user authentication is required.
+ * Sets the duration of time (seconds) for which this key is authorized to be used after the
+ * user is successfully authenticated. This has effect if the key requires user
+ * authentication for its use (see {@link #setUserAuthenticationRequired(boolean)}).
+ *
+ * <p>By default, if user authentication is required, it must take place for every use of
+ * the key.
+ *
+ * <p>Cryptographic operations involving keys which require user authentication to take
+ * place for every operation can only use fingerprint authentication. This is achieved by
+ * initializing a cryptographic operation ({@link Signature}, {@link Cipher}, {@link Mac})
+ * with the key, wrapping it into a {@link FingerprintManager.CryptoObject}, invoking
+ * {@code FingerprintManager.authenticate} with {@code CryptoObject}, and proceeding with
+ * the cryptographic operation only if the authentication flow succeeds.
*
- * <p>By default, the user needs to authenticate for every use of the key.
+ * <p>Cryptographic operations involving keys which are authorized to be used for a duration
+ * of time after a successful user authentication event can only use secure lock screen
+ * authentication. These cryptographic operations will throw
+ * {@link UserNotAuthenticatedException} during initialization if the user needs to be
+ * authenticated to proceed. This situation can be resolved by the user unlocking the secure
+ * lock screen of the Android or by going through the confirm credential flow initiated by
+ * {@link KeyguardManager#createConfirmDeviceCredentialIntent(CharSequence, CharSequence)}.
+ * Once resolved, initializing a new cryptographic operation using this key (or any other
+ * key which is authorized to be used for a fixed duration of time after user
+ * authentication) should succeed provided the user authentication flow completed
+ * successfully.
*
- * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for
- * every use of the key.
+ * @param seconds duration in seconds or {@code -1} if user authentication must take place
+ * for every use of the key.
*
* @see #setUserAuthenticationRequired(boolean)
+ * @see FingerprintManager
+ * @see FingerprintManager.CryptoObject
+ * @see KeyguardManager
*/
@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 9957e79..894d52a 100644
--- a/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java
+++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java
@@ -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,18 +181,23 @@ 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
@@ -210,14 +219,11 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS
} 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;
}
/**
diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java
index 1c6de2d..897bd71 100644
--- a/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java
+++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java
@@ -37,4 +37,6 @@ interface KeyStoreCryptoOperationStreamer {
byte[] update(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/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;
+ }
+}