From f88d3a4b27a7eea41c4c06dcfd73d8202d0bb81c Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Mon, 19 Oct 2020 12:28:22 -0700 Subject: Keystore 2.0 SPI: Duplicate Keystore SPI to android.security.keystore2 package This patch copies the relevant portion of the Keystore SPI to the new package name android.security.keystore2. The purpose of this is to illustrate the evolution from the existing Keystore SPI to the Keystore 2.0 SPI while keeping the existing Keystore SPI intact. Reviewers are advised to check the equivalence of this code to the corresponding files in android/security/keystore (<-- no 2 here). Subsequent patches can them be reviewed as evolution towards the new SPI rather than completely new code. Test: None. When the evolution is complete, Keystore CTS tests can be used to check for regressions. Bug: 159476414 Change-Id: I21a01a679e789868ce820b5f73221e616a456a61 --- .../keystore2/AndroidKeyStore3DESCipherSpi.java | 304 ++++++ .../AndroidKeyStoreAuthenticatedAESCipherSpi.java | 454 ++++++++ .../AndroidKeyStoreBCWorkaroundProvider.java | 273 +++++ .../keystore2/AndroidKeyStoreCipherSpiBase.java | 923 ++++++++++++++++ .../AndroidKeyStoreECDSASignatureSpi.java | 203 ++++ .../keystore2/AndroidKeyStoreECPrivateKey.java | 42 + .../keystore2/AndroidKeyStoreECPublicKey.java | 59 ++ .../security/keystore2/AndroidKeyStoreHmacSpi.java | 268 +++++ .../security/keystore2/AndroidKeyStoreKey.java | 103 ++ .../keystore2/AndroidKeyStoreKeyFactorySpi.java | 156 +++ .../keystore2/AndroidKeyStoreKeyGeneratorSpi.java | 354 +++++++ .../AndroidKeyStoreKeyPairGeneratorSpi.java | 933 ++++++++++++++++ .../AndroidKeyStoreLoadStoreParameter.java | 38 + .../keystore2/AndroidKeyStorePrivateKey.java | 31 + .../keystore2/AndroidKeyStoreProvider.java | 428 ++++++++ .../keystore2/AndroidKeyStorePublicKey.java | 73 ++ .../keystore2/AndroidKeyStoreRSACipherSpi.java | 517 +++++++++ .../keystore2/AndroidKeyStoreRSAPrivateKey.java | 43 + .../keystore2/AndroidKeyStoreRSAPublicKey.java | 57 + .../keystore2/AndroidKeyStoreRSASignatureSpi.java | 166 +++ .../keystore2/AndroidKeyStoreSecretKey.java | 31 + .../AndroidKeyStoreSecretKeyFactorySpi.java | 249 +++++ .../keystore2/AndroidKeyStoreSignatureSpiBase.java | 434 ++++++++ .../security/keystore2/AndroidKeyStoreSpi.java | 1113 ++++++++++++++++++++ ...AndroidKeyStoreUnauthenticatedAESCipherSpi.java | 326 ++++++ .../keystore2/DelegatingX509Certificate.java | 212 ++++ .../KeyStoreCryptoOperationChunkedStreamer.java | 227 ++++ .../keystore2/KeyStoreCryptoOperationStreamer.java | 42 + .../keystore2/KeyStoreCryptoOperationUtils.java | 119 +++ 29 files changed, 8178 insertions(+) create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreECPrivateKey.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreKey.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStorePrivateKey.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreRSAPrivateKey.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreSecretKey.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java create mode 100644 keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java create mode 100644 keystore/java/android/security/keystore2/DelegatingX509Certificate.java create mode 100644 keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java create mode 100644 keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java create mode 100644 keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java diff --git a/keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java new file mode 100644 index 000000000000..275dcef8a78c --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2018 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.keystore2; + +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.ArrayUtils; +import android.security.keystore.KeyProperties; + +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.IvParameterSpec; + +/** + * Base class for Android Keystore 3DES {@link CipherSpi} implementations. + * + * @hide + */ +public class AndroidKeyStore3DESCipherSpi extends AndroidKeyStoreCipherSpiBase { + + private static final int BLOCK_SIZE_BYTES = 8; + + private final int mKeymasterBlockMode; + private final int mKeymasterPadding; + /** Whether this transformation requires an IV. */ + private final boolean mIvRequired; + + private byte[] mIv; + + /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */ + private boolean mIvHasBeenUsed; + + AndroidKeyStore3DESCipherSpi( + int keymasterBlockMode, + int keymasterPadding, + boolean ivRequired) { + mKeymasterBlockMode = keymasterBlockMode; + mKeymasterPadding = keymasterPadding; + mIvRequired = ivRequired; + } + + abstract static class ECB extends AndroidKeyStore3DESCipherSpi { + protected ECB(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false); + } + + public static class NoPadding extends + AndroidKeyStore3DESCipherSpi.ECB { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + } + + public static class PKCS7Padding extends + AndroidKeyStore3DESCipherSpi.ECB { + public PKCS7Padding() { + super(KeymasterDefs.KM_PAD_PKCS7); + } + } + } + + abstract static class CBC extends AndroidKeyStore3DESCipherSpi { + protected CBC(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true); + } + + public static class NoPadding extends + AndroidKeyStore3DESCipherSpi.CBC { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + } + + public static class PKCS7Padding extends + AndroidKeyStore3DESCipherSpi.CBC { + public PKCS7Padding() { + super(KeymasterDefs.KM_PAD_PKCS7); + } + } + } + + @Override + protected void initKey(int i, Key key) throws InvalidKeyException { + if (!(key instanceof AndroidKeyStoreSecretKey)) { + throw new InvalidKeyException( + "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null")); + } + if (!KeyProperties.KEY_ALGORITHM_3DES.equalsIgnoreCase(key.getAlgorithm())) { + throw new InvalidKeyException( + "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " + + KeyProperties.KEY_ALGORITHM_3DES + " supported"); + } + setKey((AndroidKeyStoreSecretKey) key); + } + + @Override + protected int engineGetBlockSize() { + return BLOCK_SIZE_BYTES; + } + + @Override + protected int engineGetOutputSize(int inputLen) { + return inputLen + 3 * BLOCK_SIZE_BYTES; + } + + @Override + protected final byte[] engineGetIV() { + return ArrayUtils.cloneIfNotEmpty(mIv); + } + + @Override + protected AlgorithmParameters engineGetParameters() { + if (!mIvRequired) { + return null; + } + if ((mIv != null) && (mIv.length > 0)) { + try { + AlgorithmParameters params = AlgorithmParameters.getInstance("DESede"); + params.init(new IvParameterSpec(mIv)); + return params; + } catch (NoSuchAlgorithmException e) { + throw new ProviderException( + "Failed to obtain 3DES AlgorithmParameters", e); + } catch (InvalidParameterSpecException e) { + throw new ProviderException( + "Failed to initialize 3DES AlgorithmParameters with an IV", + e); + } + } + return null; + } + + @Override + protected void initAlgorithmSpecificParameters() throws InvalidKeyException { + if (!mIvRequired) { + return; + } + + // IV is used + if (!isEncrypting()) { + throw new InvalidKeyException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + } + + @Override + protected void initAlgorithmSpecificParameters(AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + if (!mIvRequired) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException( + "IvParameterSpec must be provided when decrypting"); + } + return; + } + if (!(params instanceof IvParameterSpec)) { + throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported"); + } + mIv = ((IvParameterSpec) params).getIV(); + if (mIv == null) { + throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec"); + } + } + + @Override + protected void initAlgorithmSpecificParameters(AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + if (!mIvRequired) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + return; + } + + if (!"DESede".equalsIgnoreCase(params.getAlgorithm())) { + throw new InvalidAlgorithmParameterException( + "Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm() + + ". Supported: DESede"); + } + + IvParameterSpec ivSpec; + try { + ivSpec = params.getParameterSpec(IvParameterSpec.class); + } catch (InvalidParameterSpecException e) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ", but not found in parameters: " + params, e); + } + mIv = null; + return; + } + mIv = ivSpec.getIV(); + if (mIv == null) { + throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters"); + } + } + + @Override + protected final int getAdditionalEntropyAmountForBegin() { + if ((mIvRequired) && (mIv == null) && (isEncrypting())) { + // IV will need to be generated + return BLOCK_SIZE_BYTES; + } + + return 0; + } + + @Override + protected int getAdditionalEntropyAmountForFinish() { + return 0; + } + + @Override + protected void addAlgorithmSpecificParametersToBegin(KeymasterArguments keymasterArgs) { + if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) { + // IV is being reused for encryption: this violates security best practices. + throw new IllegalStateException( + "IV has already been used. Reusing IV in encryption mode violates security best" + + " practices."); + } + + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_3DES); + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode); + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding); + if ((mIvRequired) && (mIv != null)) { + keymasterArgs.addBytes(KeymasterDefs.KM_TAG_NONCE, mIv); + } + } + + @Override + protected void loadAlgorithmSpecificParametersFromBeginResult( + KeymasterArguments keymasterArgs) { + mIvHasBeenUsed = true; + + // NOTE: Keymaster doesn't always return an IV, even if it's used. + byte[] returnedIv = keymasterArgs.getBytes(KeymasterDefs.KM_TAG_NONCE, null); + if ((returnedIv != null) && (returnedIv.length == 0)) { + returnedIv = null; + } + + if (mIvRequired) { + if (mIv == null) { + mIv = returnedIv; + } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) { + throw new ProviderException("IV in use differs from provided IV"); + } + } else { + if (returnedIv != null) { + throw new ProviderException( + "IV in use despite IV not being used by this transformation"); + } + } + } + + @Override + protected final void resetAll() { + mIv = null; + mIvHasBeenUsed = false; + super.resetAll(); + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java new file mode 100644 index 000000000000..43381c0078d9 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java @@ -0,0 +1,454 @@ +/* + * 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.keystore2; + +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.ArrayUtils; +import android.security.keystore.KeyProperties; +import android.security.keystore2.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 { + 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; + } + + if (!"GCM".equalsIgnoreCase(params.getAlgorithm())) { + throw new InvalidAlgorithmParameterException( + "Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm() + + ". Supported: GCM"); + } + + 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), 0); + 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 AndroidKeyStoreAuthenticatedAESCipherSpi.BufferAllOutputUntilDoFinalStreamer(streamer); + } + } + + @NonNull + @Override + protected final KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer( + KeyStore keyStore, IBinder operationToken) { + return new KeyStoreCryptoOperationChunkedStreamer( + new AndroidKeyStoreAuthenticatedAESCipherSpi.AdditionalAuthenticationDataStream(keyStore, operationToken), 0); + } + + @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.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mTagLengthBits); + } + + protected final int getTagLengthBits() { + return mTagLengthBits; + } + + public static final class NoPadding extends + AndroidKeyStoreAuthenticatedAESCipherSpi.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.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES); + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode); + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding); + if (mIv != null) { + keymasterArgs.addBytes(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.getBytes(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[] signature, byte[] additionalEntropy) throws KeyStoreException { + byte[] output = mDelegate.doFinal(input, inputOffset, inputLength, signature, + 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.addBytes(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[] input, byte[] signature, 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/keystore2/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java new file mode 100644 index 000000000000..5b6971007077 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java @@ -0,0 +1,273 @@ +/* + * 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.keystore2; + +import java.security.Provider; + +/** + * {@link Provider} of JCA crypto operations operating on Android KeyStore keys. + * + *

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

Once Bouncy Castle provider is fixed, this provider can be merged into the + * {@code AndroidKeyStoreProvider}. + * + * @hide + */ +class AndroidKeyStoreBCWorkaroundProvider extends Provider { + + // IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these + // classes when this provider is instantiated and installed early on during each app's + // initialization process. + + private static final String PACKAGE_NAME = "android.security.keystore"; + private static final String KEYSTORE_SECRET_KEY_CLASS_NAME = + PACKAGE_NAME + ".AndroidKeyStoreSecretKey"; + private static final String KEYSTORE_PRIVATE_KEY_CLASS_NAME = + PACKAGE_NAME + ".AndroidKeyStorePrivateKey"; + private static final String KEYSTORE_PUBLIC_KEY_CLASS_NAME = + PACKAGE_NAME + ".AndroidKeyStorePublicKey"; + + private static final String DESEDE_SYSTEM_PROPERTY = "ro.hardware.keystore_desede"; + + AndroidKeyStoreBCWorkaroundProvider() { + super("AndroidKeyStoreBCWorkaround", + 1.0, + "Android KeyStore security provider to work around Bouncy Castle"); + + // --------------------- javax.crypto.Mac + putMacImpl("HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA1"); + put("Alg.Alias.Mac.1.2.840.113549.2.7", "HmacSHA1"); + put("Alg.Alias.Mac.HMAC-SHA1", "HmacSHA1"); + put("Alg.Alias.Mac.HMAC/SHA1", "HmacSHA1"); + + putMacImpl("HmacSHA224", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA224"); + put("Alg.Alias.Mac.1.2.840.113549.2.9", "HmacSHA224"); + put("Alg.Alias.Mac.HMAC-SHA224", "HmacSHA224"); + put("Alg.Alias.Mac.HMAC/SHA224", "HmacSHA224"); + + putMacImpl("HmacSHA256", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA256"); + put("Alg.Alias.Mac.1.2.840.113549.2.9", "HmacSHA256"); + put("Alg.Alias.Mac.HMAC-SHA256", "HmacSHA256"); + put("Alg.Alias.Mac.HMAC/SHA256", "HmacSHA256"); + + putMacImpl("HmacSHA384", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA384"); + put("Alg.Alias.Mac.1.2.840.113549.2.10", "HmacSHA384"); + put("Alg.Alias.Mac.HMAC-SHA384", "HmacSHA384"); + put("Alg.Alias.Mac.HMAC/SHA384", "HmacSHA384"); + + putMacImpl("HmacSHA512", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA512"); + put("Alg.Alias.Mac.1.2.840.113549.2.11", "HmacSHA512"); + put("Alg.Alias.Mac.HMAC-SHA512", "HmacSHA512"); + put("Alg.Alias.Mac.HMAC/SHA512", "HmacSHA512"); + + // --------------------- javax.crypto.Cipher + putSymmetricCipherImpl("AES/ECB/NoPadding", + PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$ECB$NoPadding"); + putSymmetricCipherImpl("AES/ECB/PKCS7Padding", + PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$ECB$PKCS7Padding"); + + putSymmetricCipherImpl("AES/CBC/NoPadding", + PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CBC$NoPadding"); + putSymmetricCipherImpl("AES/CBC/PKCS7Padding", + PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CBC$PKCS7Padding"); + + putSymmetricCipherImpl("AES/CTR/NoPadding", + PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CTR$NoPadding"); + + if ("true".equals(android.os.SystemProperties.get(DESEDE_SYSTEM_PROPERTY))) { + putSymmetricCipherImpl("DESede/CBC/NoPadding", + PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$CBC$NoPadding"); + putSymmetricCipherImpl("DESede/CBC/PKCS7Padding", + PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$CBC$PKCS7Padding"); + + putSymmetricCipherImpl("DESede/ECB/NoPadding", + PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$ECB$NoPadding"); + putSymmetricCipherImpl("DESede/ECB/PKCS7Padding", + PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$ECB$PKCS7Padding"); + } + + 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"); + putAsymmetricCipherImpl("RSA/ECB/PKCS1Padding", + PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$PKCS1Padding"); + put("Alg.Alias.Cipher.RSA/None/PKCS1Padding", "RSA/ECB/PKCS1Padding"); + putAsymmetricCipherImpl("RSA/ECB/OAEPPadding", + PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA1AndMGF1Padding"); + put("Alg.Alias.Cipher.RSA/None/OAEPPadding", "RSA/ECB/OAEPPadding"); + putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-1AndMGF1Padding", + PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA1AndMGF1Padding"); + put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-1AndMGF1Padding", + "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"); + putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-224AndMGF1Padding", + PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA224AndMGF1Padding"); + put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-224AndMGF1Padding", + "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); + putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-256AndMGF1Padding", + PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA256AndMGF1Padding"); + put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-256AndMGF1Padding", + "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); + putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-384AndMGF1Padding", + PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA384AndMGF1Padding"); + put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-384AndMGF1Padding", + "RSA/ECB/OAEPWithSHA-384AndMGF1Padding"); + putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-512AndMGF1Padding", + PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA512AndMGF1Padding"); + put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-512AndMGF1Padding", + "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"); + + // --------------------- java.security.Signature + putSignatureImpl("NONEwithRSA", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$NONEWithPKCS1Padding"); + + 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"); + + 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"); + + 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.2.16.840.1.101.3.4.2.4with1.2.840.113549.1.1.1", + "SHA224withRSA"); + put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.4with1.2.840.113549.1.1.11", + "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.2.16.840.1.101.3.4.2.1with1.2.840.113549.1.1.1", + "SHA256withRSA"); + put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.113549.1.1.11", + "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.2.16.840.1.101.3.4.2.2with1.2.840.113549.1.1.1", + "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.2.16.840.1.101.3.4.2.3with1.2.840.113549.1.1.1", + "SHA512withRSA"); + + putSignatureImpl("SHA1withRSA/PSS", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA1WithPSSPadding"); + putSignatureImpl("SHA224withRSA/PSS", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA224WithPSSPadding"); + putSignatureImpl("SHA256withRSA/PSS", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA256WithPSSPadding"); + putSignatureImpl("SHA384withRSA/PSS", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA384WithPSSPadding"); + putSignatureImpl("SHA512withRSA/PSS", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA512WithPSSPadding"); + + putSignatureImpl("NONEwithECDSA", + PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$NONE"); + + putSignatureImpl("SHA1withECDSA", PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA1"); + put("Alg.Alias.Signature.ECDSA", "SHA1withECDSA"); + put("Alg.Alias.Signature.ECDSAwithSHA1", "SHA1withECDSA"); + // iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA1(1) + put("Alg.Alias.Signature.1.2.840.10045.4.1", "SHA1withECDSA"); + put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.10045.2.1", "SHA1withECDSA"); + + // iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA2(3) + putSignatureImpl("SHA224withECDSA", + PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA224"); + // ecdsa-with-SHA224(1) + put("Alg.Alias.Signature.1.2.840.10045.4.3.1", "SHA224withECDSA"); + put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.4with1.2.840.10045.2.1", "SHA224withECDSA"); + + // iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA2(3) + putSignatureImpl("SHA256withECDSA", + PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA256"); + // ecdsa-with-SHA256(2) + put("Alg.Alias.Signature.1.2.840.10045.4.3.2", "SHA256withECDSA"); + put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.10045.2.1", "SHA256withECDSA"); + + putSignatureImpl("SHA384withECDSA", + PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA384"); + // ecdsa-with-SHA384(3) + put("Alg.Alias.Signature.1.2.840.10045.4.3.3", "SHA384withECDSA"); + put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.2with1.2.840.10045.2.1", "SHA384withECDSA"); + + putSignatureImpl("SHA512withECDSA", + PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA512"); + // ecdsa-with-SHA512(4) + put("Alg.Alias.Signature.1.2.840.10045.4.3.4", "SHA512withECDSA"); + put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.3with1.2.840.10045.2.1", "SHA512withECDSA"); + } + + private void putMacImpl(String algorithm, String implClass) { + put("Mac." + algorithm, implClass); + put("Mac." + algorithm + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME); + } + + private void putSymmetricCipherImpl(String transformation, String implClass) { + put("Cipher." + transformation, implClass); + put("Cipher." + transformation + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME); + } + + private void putAsymmetricCipherImpl(String transformation, String implClass) { + put("Cipher." + transformation, implClass); + put("Cipher." + transformation + " SupportedKeyClasses", + KEYSTORE_PRIVATE_KEY_CLASS_NAME + "|" + KEYSTORE_PUBLIC_KEY_CLASS_NAME); + } + + private void putSignatureImpl(String algorithm, String implClass) { + put("Signature." + algorithm, implClass); + put("Signature." + algorithm + " SupportedKeyClasses", + KEYSTORE_PRIVATE_KEY_CLASS_NAME + "|" + KEYSTORE_PUBLIC_KEY_CLASS_NAME); + } + + public static String[] getSupportedEcdsaSignatureDigests() { + return new String[] {"NONE", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"}; + } + + public static String[] getSupportedRsaSignatureWithPkcs1PaddingDigests() { + return new String[] {"NONE", "MD5", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"}; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java new file mode 100644 index 000000000000..94b5c4d23afe --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java @@ -0,0 +1,923 @@ +/* + * 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.keystore2; + +import android.annotation.CallSuper; +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.KeyStoreConnectException; +import android.security.keystore.KeyStoreCryptoOperation; + +import libcore.util.EmptyArray; + +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.InvalidParameterException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +import javax.crypto.AEADBadTagException; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.CipherSpi; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.SecretKeySpec; + +/** + * Base class for {@link CipherSpi} implementations of Android KeyStore backed ciphers. + * + * @hide + */ +abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStoreCryptoOperation { + private final KeyStore mKeyStore; + + // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after + // doFinal finishes. + private boolean mEncrypting; + private int mKeymasterPurposeOverride = -1; + private AndroidKeyStoreKey mKey; + private SecureRandom mRng; + + /** + * Token referencing this operation inside keystore service. It is initialized by + * {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and on some error + * conditions in between. + */ + 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 + * a method that does not throw checked exception. This exception will be thrown from + * {@code engineDoFinal}. Once such an exception is encountered, {@code engineUpdate} and + * {@code engineDoFinal} start ignoring input data. + */ + private Exception mCachedException; + + AndroidKeyStoreCipherSpiBase() { + mKeyStore = KeyStore.getInstance(); + } + + @Override + protected final void engineInit(int opmode, Key key, SecureRandom random) + throws InvalidKeyException { + resetAll(); + + boolean success = false; + try { + init(opmode, key, random); + initAlgorithmSpecificParameters(); + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidKeyException(e); + } + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + @Override + protected final void engineInit(int opmode, Key key, AlgorithmParameters params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + init(opmode, key, random); + initAlgorithmSpecificParameters(params); + ensureKeystoreOperationInitialized(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + @Override + protected final void engineInit(int opmode, Key key, AlgorithmParameterSpec params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + init(opmode, key, random); + initAlgorithmSpecificParameters(params); + ensureKeystoreOperationInitialized(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + switch (opmode) { + case Cipher.ENCRYPT_MODE: + case Cipher.WRAP_MODE: + mEncrypting = true; + break; + case Cipher.DECRYPT_MODE: + case Cipher.UNWRAP_MODE: + mEncrypting = false; + break; + default: + throw new InvalidParameterException("Unsupported opmode: " + opmode); + } + initKey(opmode, key); + if (mKey == null) { + throw new ProviderException("initKey did not initialize the key"); + } + mRng = random; + } + + /** + * Resets this cipher to its pristine pre-init state. This must be equivalent to obtaining a new + * cipher instance. + * + *

Subclasses storing additional state should override this method, reset the additional + * state, and then chain to superclass. + */ + @CallSuper + protected void resetAll() { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mKeyStore.abort(operationToken); + } + mEncrypting = false; + mKeymasterPurposeOverride = -1; + mKey = null; + mRng = null; + mOperationToken = null; + mOperationHandle = 0; + mMainDataStreamer = null; + mAdditionalAuthenticationDataStreamer = null; + mAdditionalAuthenticationDataStreamerClosed = false; + mCachedException = null; + } + + /** + * Resets this cipher while preserving the initialized state. This must be equivalent to + * rolling back the cipher's state to just after the most recent {@code engineInit} completed + * successfully. + * + *

Subclasses storing additional post-init state should override this method, reset the + * additional state, and then chain to superclass. + */ + @CallSuper + protected void resetWhilePreservingInitState() { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mKeyStore.abort(operationToken); + } + mOperationToken = null; + mOperationHandle = 0; + mMainDataStreamer = null; + mAdditionalAuthenticationDataStreamer = null; + mAdditionalAuthenticationDataStreamerClosed = false; + mCachedException = null; + } + + private void ensureKeystoreOperationInitialized() throws InvalidKeyException, + InvalidAlgorithmParameterException { + if (mMainDataStreamer != null) { + return; + } + if (mCachedException != null) { + return; + } + if (mKey == null) { + throw new IllegalStateException("Not initialized"); + } + + KeymasterArguments keymasterInputArgs = new KeymasterArguments(); + addAlgorithmSpecificParametersToBegin(keymasterInputArgs); + byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, getAdditionalEntropyAmountForBegin()); + + int purpose; + if (mKeymasterPurposeOverride != -1) { + purpose = mKeymasterPurposeOverride; + } else { + purpose = mEncrypting + ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT; + } + OperationResult opResult = mKeyStore.begin( + mKey.getAlias(), + purpose, + true, // permit aborting this operation if keystore runs out of resources + keymasterInputArgs, + additionalEntropy, + mKey.getUid()); + if (opResult == null) { + throw new KeyStoreConnectException(); + } + + // Store operation token and handle regardless of the error code returned by KeyStore to + // ensure that the operation gets aborted immediately if the code below throws an exception. + mOperationToken = opResult.token; + mOperationHandle = opResult.operationHandle; + + // If necessary, throw an exception due to KeyStore operation having failed. + GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit( + mKeyStore, mKey, opResult.resultCode); + if (e != null) { + if (e instanceof InvalidKeyException) { + throw (InvalidKeyException) e; + } else if (e instanceof InvalidAlgorithmParameterException) { + throw (InvalidAlgorithmParameterException) e; + } else { + throw new ProviderException("Unexpected exception type", e); + } + } + + if (mOperationToken == null) { + throw new ProviderException("Keystore returned null operation token"); + } + if (mOperationHandle == 0) { + throw new ProviderException("Keystore returned invalid operation handle"); + } + + loadAlgorithmSpecificParametersFromBeginResult(opResult.outParams); + mMainDataStreamer = createMainDataStreamer(mKeyStore, opResult.token); + mAdditionalAuthenticationDataStreamer = + createAdditionalAuthenticationDataStreamer(mKeyStore, opResult.token); + mAdditionalAuthenticationDataStreamerClosed = false; + } + + /** + * Creates a streamer which sends plaintext/ciphertext into the provided KeyStore and receives + * the corresponding ciphertext/plaintext from the KeyStore. + * + *

This implementation returns a working streamer. + */ + @NonNull + protected KeyStoreCryptoOperationStreamer createMainDataStreamer( + KeyStore keyStore, IBinder operationToken) { + return new KeyStoreCryptoOperationChunkedStreamer( + new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( + keyStore, operationToken), 0); + } + + /** + * Creates a streamer which sends Additional Authentication Data (AAD) into the KeyStore. + * + *

This implementation returns {@code null}. + * + * @return 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) { + return null; + } + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + mCachedException = e; + return null; + } + + if (inputLen == 0) { + return null; + } + + byte[] output; + try { + flushAAD(); + output = mMainDataStreamer.update(input, inputOffset, inputLen); + } catch (KeyStoreException e) { + mCachedException = e; + return null; + } + + if (output.length == 0) { + return null; + } + + return output; + } + + private void flushAAD() throws KeyStoreException { + if ((mAdditionalAuthenticationDataStreamer != null) + && (!mAdditionalAuthenticationDataStreamerClosed)) { + byte[] output; + try { + output = mAdditionalAuthenticationDataStreamer.doFinal( + EmptyArray.BYTE, 0, 0, + null, // no signature + 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 { + byte[] outputCopy = engineUpdate(input, inputOffset, inputLen); + if (outputCopy == null) { + return 0; + } + int outputAvailable = output.length - outputOffset; + if (outputCopy.length > outputAvailable) { + throw new ShortBufferException("Output buffer too short. Produced: " + + outputCopy.length + ", available: " + outputAvailable); + } + System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); + return outputCopy.length; + } + + @Override + protected final int engineUpdate(ByteBuffer input, ByteBuffer output) + throws ShortBufferException { + if (input == null) { + throw new NullPointerException("input == null"); + } + if (output == null) { + throw new NullPointerException("output == null"); + } + + int inputSize = input.remaining(); + byte[] outputArray; + if (input.hasArray()) { + outputArray = + engineUpdate( + input.array(), input.arrayOffset() + input.position(), inputSize); + input.position(input.position() + inputSize); + } else { + byte[] inputArray = new byte[inputSize]; + input.get(inputArray); + outputArray = engineUpdate(inputArray, 0, inputSize); + } + + int outputSize = (outputArray != null) ? outputArray.length : 0; + if (outputSize > 0) { + int outputBufferAvailable = output.remaining(); + try { + output.put(outputArray); + } catch (BufferOverflowException e) { + throw new ShortBufferException( + "Output buffer too small. Produced: " + outputSize + ", available: " + + outputBufferAvailable); + } + } + return outputSize; + } + + @Override + protected final void engineUpdateAAD(byte[] input, int inputOffset, int 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) { + 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); + } + engineUpdateAAD(input, inputOffset, inputLen); + } + + @Override + protected final byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) + throws IllegalBlockSizeException, BadPaddingException { + if (mCachedException != null) { + throw (IllegalBlockSizeException) + new IllegalBlockSizeException().initCause(mCachedException); + } + + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); + } + + byte[] output; + try { + flushAAD(); + byte[] additionalEntropy = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, getAdditionalEntropyAmountForFinish()); + output = mMainDataStreamer.doFinal( + input, inputOffset, inputLen, + null, // no signature involved + additionalEntropy); + } catch (KeyStoreException e) { + switch (e.getErrorCode()) { + case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH: + throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); + case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT: + throw (BadPaddingException) new BadPaddingException().initCause(e); + case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED: + throw (AEADBadTagException) new AEADBadTagException().initCause(e); + default: + throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); + } + } + + resetWhilePreservingInitState(); + return output; + } + + @Override + protected final int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, + int outputOffset) throws ShortBufferException, IllegalBlockSizeException, + BadPaddingException { + byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen); + if (outputCopy == null) { + return 0; + } + int outputAvailable = output.length - outputOffset; + if (outputCopy.length > outputAvailable) { + throw new ShortBufferException("Output buffer too short. Produced: " + + outputCopy.length + ", available: " + outputAvailable); + } + System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); + return outputCopy.length; + } + + @Override + protected final int engineDoFinal(ByteBuffer input, ByteBuffer output) + throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + if (input == null) { + throw new NullPointerException("input == null"); + } + if (output == null) { + throw new NullPointerException("output == null"); + } + + int inputSize = input.remaining(); + byte[] outputArray; + if (input.hasArray()) { + outputArray = + engineDoFinal( + input.array(), input.arrayOffset() + input.position(), inputSize); + input.position(input.position() + inputSize); + } else { + byte[] inputArray = new byte[inputSize]; + input.get(inputArray); + outputArray = engineDoFinal(inputArray, 0, inputSize); + } + + int outputSize = (outputArray != null) ? outputArray.length : 0; + if (outputSize > 0) { + int outputBufferAvailable = output.remaining(); + try { + output.put(outputArray); + } catch (BufferOverflowException e) { + throw new ShortBufferException( + "Output buffer too small. Produced: " + outputSize + ", available: " + + outputBufferAvailable); + } + } + return outputSize; + } + + @Override + protected final byte[] engineWrap(Key key) + throws IllegalBlockSizeException, InvalidKeyException { + if (mKey == null) { + throw new IllegalStateException("Not initilized"); + } + + if (!isEncrypting()) { + throw new IllegalStateException( + "Cipher must be initialized in Cipher.WRAP_MODE to wrap keys"); + } + + if (key == null) { + throw new NullPointerException("key == null"); + } + byte[] encoded = null; + if (key instanceof SecretKey) { + if ("RAW".equalsIgnoreCase(key.getFormat())) { + encoded = key.getEncoded(); + } + if (encoded == null) { + try { + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(key.getAlgorithm()); + SecretKeySpec spec = + (SecretKeySpec) keyFactory.getKeySpec( + (SecretKey) key, SecretKeySpec.class); + encoded = spec.getEncoded(); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to wrap key because it does not export its key material", + e); + } + } + } else if (key instanceof PrivateKey) { + if ("PKCS8".equalsIgnoreCase(key.getFormat())) { + encoded = key.getEncoded(); + } + if (encoded == null) { + try { + KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm()); + PKCS8EncodedKeySpec spec = + keyFactory.getKeySpec(key, PKCS8EncodedKeySpec.class); + encoded = spec.getEncoded(); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to wrap key because it does not export its key material", + e); + } + } + } else if (key instanceof PublicKey) { + if ("X.509".equalsIgnoreCase(key.getFormat())) { + encoded = key.getEncoded(); + } + if (encoded == null) { + try { + KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm()); + X509EncodedKeySpec spec = + keyFactory.getKeySpec(key, X509EncodedKeySpec.class); + encoded = spec.getEncoded(); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to wrap key because it does not export its key material", + e); + } + } + } else { + throw new InvalidKeyException("Unsupported key type: " + key.getClass().getName()); + } + + if (encoded == null) { + throw new InvalidKeyException( + "Failed to wrap key because it does not export its key material"); + } + + try { + return engineDoFinal(encoded, 0, encoded.length); + } catch (BadPaddingException e) { + throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); + } + } + + @Override + protected final Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, + int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException { + if (mKey == null) { + throw new IllegalStateException("Not initilized"); + } + + if (isEncrypting()) { + throw new IllegalStateException( + "Cipher must be initialized in Cipher.WRAP_MODE to wrap keys"); + } + + if (wrappedKey == null) { + throw new NullPointerException("wrappedKey == null"); + } + + byte[] encoded; + try { + encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length); + } catch (IllegalBlockSizeException | BadPaddingException e) { + throw new InvalidKeyException("Failed to unwrap key", e); + } + + switch (wrappedKeyType) { + case Cipher.SECRET_KEY: + { + return new SecretKeySpec(encoded, wrappedKeyAlgorithm); + // break; + } + case Cipher.PRIVATE_KEY: + { + KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm); + try { + return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded)); + } catch (InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to create private key from its PKCS#8 encoded form", e); + } + // break; + } + case Cipher.PUBLIC_KEY: + { + KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm); + try { + return keyFactory.generatePublic(new X509EncodedKeySpec(encoded)); + } catch (InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to create public key from its X.509 encoded form", e); + } + // break; + } + default: + throw new InvalidParameterException( + "Unsupported wrappedKeyType: " + wrappedKeyType); + } + } + + @Override + protected final void engineSetMode(String mode) throws NoSuchAlgorithmException { + // This should never be invoked because all algorithms registered with the AndroidKeyStore + // provide explicitly specify block mode. + throw new UnsupportedOperationException(); + } + + @Override + protected final void engineSetPadding(String arg0) throws NoSuchPaddingException { + // This should never be invoked because all algorithms registered with the AndroidKeyStore + // provide explicitly specify padding mode. + throw new UnsupportedOperationException(); + } + + @Override + protected final int engineGetKeySize(Key key) throws InvalidKeyException { + throw new UnsupportedOperationException(); + } + + @CallSuper + @Override + public void finalize() throws Throwable { + try { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mKeyStore.abort(operationToken); + } + } finally { + super.finalize(); + } + } + + @Override + public final long getOperationHandle() { + return mOperationHandle; + } + + protected final void setKey(@NonNull AndroidKeyStoreKey key) { + mKey = key; + } + + /** + * Overrides the default purpose/type of the crypto operation. + */ + protected final void setKeymasterPurposeOverride(int keymasterPurpose) { + mKeymasterPurposeOverride = keymasterPurpose; + } + + protected final int getKeymasterPurposeOverride() { + return mKeymasterPurposeOverride; + } + + /** + * Returns {@code true} if this cipher is initialized for encryption, {@code false} if this + * cipher is initialized for decryption. + */ + protected final boolean isEncrypting() { + return mEncrypting; + } + + @NonNull + protected final KeyStore getKeyStore() { + 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(); + } + + static String opmodeToString(int opmode) { + switch (opmode) { + case Cipher.ENCRYPT_MODE: + return "ENCRYPT_MODE"; + case Cipher.DECRYPT_MODE: + return "DECRYPT_MODE"; + case Cipher.WRAP_MODE: + return "WRAP_MODE"; + case Cipher.UNWRAP_MODE: + return "UNWRAP_MODE"; + default: + return String.valueOf(opmode); + } + } + + // The methods below need to be implemented by subclasses. + + /** + * Initializes this cipher with the provided key. + * + * @throws InvalidKeyException if the {@code key} is not suitable for this cipher in the + * specified {@code opmode}. + * + * @see #setKey(AndroidKeyStoreKey) + */ + protected abstract void initKey(int opmode, @Nullable Key key) throws InvalidKeyException; + + /** + * Returns algorithm-specific parameters used by this cipher or {@code null} if no + * algorithm-specific parameters are used. + */ + @Nullable + @Override + protected abstract AlgorithmParameters engineGetParameters(); + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters when no additional + * initialization parameters were provided. + * + * @throws InvalidKeyException if this cipher cannot be configured based purely on the provided + * key and needs additional parameters to be provided to {@code Cipher.init}. + */ + protected abstract void initAlgorithmSpecificParameters() throws InvalidKeyException; + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional + * parameters were provided. + * + * @param params additional algorithm parameters or {@code null} if not specified. + * + * @throws InvalidAlgorithmParameterException if there is insufficient information to configure + * this cipher or if the provided parameters are not suitable for this cipher. + */ + protected abstract void initAlgorithmSpecificParameters( + @Nullable AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException; + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional + * parameters were provided. + * + * @param params additional algorithm parameters or {@code null} if not specified. + * + * @throws InvalidAlgorithmParameterException if there is insufficient information to configure + * this cipher or if the provided parameters are not suitable for this cipher. + */ + protected abstract void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params) + throws InvalidAlgorithmParameterException; + + /** + * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's + * {@code begin} operation. This amount of entropy is typically what's consumed to generate + * random parameters, such as IV. + * + *

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. + * + *

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 + * parameters. + */ + protected abstract void addAlgorithmSpecificParametersToBegin( + @NonNull KeymasterArguments keymasterArgs); + + /** + * Invoked to obtain algorithm-specific parameters from the result of the KeyStore's + * {@code begin} operation. + * + *

Some parameters, such as IV, are not required to be provided to {@code Cipher.init}. Such + * parameters, if not provided, must be generated by KeyStore and returned to the user of + * {@code Cipher} and potentially reused after {@code doFinal}. + * + * @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin} + * operation. + */ + protected abstract void loadAlgorithmSpecificParametersFromBeginResult( + @NonNull KeymasterArguments keymasterArgs); +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java new file mode 100644 index 000000000000..95e4c52591e0 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java @@ -0,0 +1,203 @@ +/* + * 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.keystore2; + +import android.annotation.NonNull; +import android.os.IBinder; +import android.security.KeyStore; +import android.security.KeyStoreException; +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyProperties; + +import libcore.util.EmptyArray; + +import java.io.ByteArrayOutputStream; +import java.security.InvalidKeyException; +import java.security.SignatureSpi; + +/** + * Base class for {@link SignatureSpi} providing Android KeyStore backed ECDSA signatures. + * + * @hide + */ +abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignatureSpiBase { + + public final static class NONE extends AndroidKeyStoreECDSASignatureSpi { + public NONE() { + super(KeymasterDefs.KM_DIGEST_NONE); + } + + @Override + protected KeyStoreCryptoOperationStreamer createMainDataStreamer(KeyStore keyStore, + IBinder operationToken) { + return new TruncateToFieldSizeMessageStreamer( + super.createMainDataStreamer(keyStore, operationToken), + getGroupSizeBits()); + } + + /** + * Streamer which buffers all input, then truncates it to field size, and then sends it into + * KeyStore via the provided delegate streamer. + */ + private static class TruncateToFieldSizeMessageStreamer + implements KeyStoreCryptoOperationStreamer { + + private final KeyStoreCryptoOperationStreamer mDelegate; + private final int mGroupSizeBits; + private final ByteArrayOutputStream mInputBuffer = new ByteArrayOutputStream(); + private long mConsumedInputSizeBytes; + + private TruncateToFieldSizeMessageStreamer( + KeyStoreCryptoOperationStreamer delegate, + int groupSizeBits) { + mDelegate = delegate; + mGroupSizeBits = groupSizeBits; + } + + @Override + public byte[] update(byte[] input, int inputOffset, int inputLength) + 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, byte[] signature, + byte[] additionalEntropy) throws KeyStoreException { + if (inputLength > 0) { + mConsumedInputSizeBytes += inputLength; + mInputBuffer.write(input, inputOffset, inputLength); + } + + byte[] bufferedInput = mInputBuffer.toByteArray(); + mInputBuffer.reset(); + // Truncate input at field size (bytes) + return mDelegate.doFinal(bufferedInput, + 0, + Math.min(bufferedInput.length, ((mGroupSizeBits + 7) / 8)), + signature, additionalEntropy); + } + + @Override + public long getConsumedInputSizeBytes() { + return mConsumedInputSizeBytes; + } + + @Override + public long getProducedOutputSizeBytes() { + return mDelegate.getProducedOutputSizeBytes(); + } + } + } + + public final static class SHA1 extends AndroidKeyStoreECDSASignatureSpi { + public SHA1() { + super(KeymasterDefs.KM_DIGEST_SHA1); + } + } + + public final static class SHA224 extends AndroidKeyStoreECDSASignatureSpi { + public SHA224() { + super(KeymasterDefs.KM_DIGEST_SHA_2_224); + } + } + + public final static class SHA256 extends AndroidKeyStoreECDSASignatureSpi { + public SHA256() { + super(KeymasterDefs.KM_DIGEST_SHA_2_256); + } + } + + public final static class SHA384 extends AndroidKeyStoreECDSASignatureSpi { + public SHA384() { + super(KeymasterDefs.KM_DIGEST_SHA_2_384); + } + } + + public final static class SHA512 extends AndroidKeyStoreECDSASignatureSpi { + public SHA512() { + super(KeymasterDefs.KM_DIGEST_SHA_2_512); + } + } + + private final int mKeymasterDigest; + + private int mGroupSizeBits = -1; + + AndroidKeyStoreECDSASignatureSpi(int keymasterDigest) { + mKeymasterDigest = keymasterDigest; + } + + @Override + protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException { + if (!KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(key.getAlgorithm())) { + throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm() + + ". Only" + KeyProperties.KEY_ALGORITHM_EC + " supported"); + } + + KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); + int errorCode = getKeyStore().getKeyCharacteristics( + key.getAlias(), null, null, key.getUid(), keyCharacteristics); + if (errorCode != KeyStore.NO_ERROR) { + throw getKeyStore().getInvalidKeyException(key.getAlias(), key.getUid(), errorCode); + } + long keySizeBits = keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, -1); + if (keySizeBits == -1) { + throw new InvalidKeyException("Size of key not known"); + } else if (keySizeBits > Integer.MAX_VALUE) { + throw new InvalidKeyException("Key too large: " + keySizeBits + " bits"); + } + mGroupSizeBits = (int) keySizeBits; + + super.initKey(key); + } + + @Override + protected final void resetAll() { + mGroupSizeBits = -1; + super.resetAll(); + } + + @Override + protected final void resetWhilePreservingInitState() { + super.resetWhilePreservingInitState(); + } + + @Override + protected final void addAlgorithmSpecificParametersToBegin( + @NonNull KeymasterArguments keymasterArgs) { + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_EC); + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); + } + + @Override + protected final int getAdditionalEntropyAmountForSign() { + return (mGroupSizeBits + 7) / 8; + } + + protected final int getGroupSizeBits() { + if (mGroupSizeBits == -1) { + throw new IllegalStateException("Not initialized"); + } + return mGroupSizeBits; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECPrivateKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECPrivateKey.java new file mode 100644 index 000000000000..0ef75cd30c37 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECPrivateKey.java @@ -0,0 +1,42 @@ +/* + * 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.keystore2; + +import android.security.keystore.KeyProperties; + +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, int uid, ECParameterSpec params) { + super(alias, uid, KeyProperties.KEY_ALGORITHM_EC); + mParams = params; + } + + @Override + public ECParameterSpec getParams() { + return mParams; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java new file mode 100644 index 000000000000..bc1b0eeb9b19 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java @@ -0,0 +1,59 @@ +/* + * 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.keystore2; + +import android.security.keystore.KeyProperties; + +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; + +/** + * {@link ECPublicKey} backed by keystore. + * + * @hide + */ +public class AndroidKeyStoreECPublicKey extends AndroidKeyStorePublicKey implements ECPublicKey { + + private final ECParameterSpec mParams; + private final ECPoint mW; + + public AndroidKeyStoreECPublicKey(String alias, int uid, byte[] x509EncodedForm, ECParameterSpec params, + ECPoint w) { + super(alias, uid, KeyProperties.KEY_ALGORITHM_EC, x509EncodedForm); + mParams = params; + mW = w; + } + + public AndroidKeyStoreECPublicKey(String alias, int uid, ECPublicKey info) { + this(alias, uid, info.getEncoded(), info.getParams(), info.getW()); + if (!"X.509".equalsIgnoreCase(info.getFormat())) { + throw new IllegalArgumentException( + "Unsupported key export format: " + info.getFormat()); + } + } + + @Override + public ECParameterSpec getParams() { + return mParams; + } + + @Override + public ECPoint getW() { + return mW; + } +} \ No newline at end of file diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java new file mode 100644 index 000000000000..f7155b09750c --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java @@ -0,0 +1,268 @@ +/* + * 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.keystore2; + +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.KeyStoreConnectException; +import android.security.keystore.KeyStoreCryptoOperation; +import android.security.keystore.KeymasterUtils; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.ProviderException; +import java.security.spec.AlgorithmParameterSpec; + +import javax.crypto.MacSpi; + +/** + * {@link MacSpi} which provides HMAC implementations backed by Android KeyStore. + * + * @hide + */ +public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation { + + public static class HmacSHA1 extends AndroidKeyStoreHmacSpi { + public HmacSHA1() { + super(KeymasterDefs.KM_DIGEST_SHA1); + } + } + + public static class HmacSHA224 extends AndroidKeyStoreHmacSpi { + public HmacSHA224() { + super(KeymasterDefs.KM_DIGEST_SHA_2_224); + } + } + + public static class HmacSHA256 extends AndroidKeyStoreHmacSpi { + public HmacSHA256() { + super(KeymasterDefs.KM_DIGEST_SHA_2_256); + } + } + + public static class HmacSHA384 extends AndroidKeyStoreHmacSpi { + public HmacSHA384() { + super(KeymasterDefs.KM_DIGEST_SHA_2_384); + } + } + + public static class HmacSHA512 extends AndroidKeyStoreHmacSpi { + public HmacSHA512() { + super(KeymasterDefs.KM_DIGEST_SHA_2_512); + } + } + + private final KeyStore mKeyStore = KeyStore.getInstance(); + private final int mKeymasterDigest; + private final int mMacSizeBits; + + // Fields below are populated by engineInit and should be preserved after engineDoFinal. + private AndroidKeyStoreSecretKey mKey; + + // Fields below are reset when engineDoFinal succeeds. + private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer; + private IBinder mOperationToken; + private long mOperationHandle; + + protected AndroidKeyStoreHmacSpi(int keymasterDigest) { + mKeymasterDigest = keymasterDigest; + mMacSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest); + } + + @Override + protected int engineGetMacLength() { + return (mMacSizeBits + 7) / 8; + } + + @Override + protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, + InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + init(key, params); + ensureKeystoreOperationInitialized(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + private void init(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, + InvalidAlgorithmParameterException { + if (key == null) { + throw new InvalidKeyException("key == null"); + } else if (!(key instanceof AndroidKeyStoreSecretKey)) { + throw new InvalidKeyException( + "Only Android KeyStore secret keys supported. Key: " + key); + } + mKey = (AndroidKeyStoreSecretKey) key; + + if (params != null) { + throw new InvalidAlgorithmParameterException( + "Unsupported algorithm parameters: " + params); + } + + } + + private void resetAll() { + mKey = null; + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mKeyStore.abort(operationToken); + } + mOperationToken = null; + mOperationHandle = 0; + mChunkedStreamer = null; + } + + private void resetWhilePreservingInitState() { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mKeyStore.abort(operationToken); + } + mOperationToken = null; + mOperationHandle = 0; + mChunkedStreamer = null; + } + + @Override + protected void engineReset() { + resetWhilePreservingInitState(); + } + + private void ensureKeystoreOperationInitialized() throws InvalidKeyException { + if (mChunkedStreamer != null) { + return; + } + if (mKey == null) { + throw new IllegalStateException("Not initialized"); + } + + KeymasterArguments keymasterArgs = new KeymasterArguments(); + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC); + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); + keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mMacSizeBits); + + OperationResult opResult = mKeyStore.begin( + mKey.getAlias(), + KeymasterDefs.KM_PURPOSE_SIGN, + true, + keymasterArgs, + null, // no additional entropy needed for HMAC because it's deterministic + mKey.getUid()); + + if (opResult == null) { + throw new KeyStoreConnectException(); + } + + // Store operation token and handle regardless of the error code returned by KeyStore to + // ensure that the operation gets aborted immediately if the code below throws an exception. + mOperationToken = opResult.token; + mOperationHandle = opResult.operationHandle; + + // If necessary, throw an exception due to KeyStore operation having failed. + InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit( + mKeyStore, mKey, opResult.resultCode); + if (e != null) { + throw e; + } + + if (mOperationToken == null) { + throw new ProviderException("Keystore returned null operation token"); + } + if (mOperationHandle == 0) { + throw new ProviderException("Keystore returned invalid operation handle"); + } + + mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer( + new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( + mKeyStore, mOperationToken)); + } + + @Override + protected void engineUpdate(byte input) { + engineUpdate(new byte[] {input}, 0, 1); + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException e) { + throw new ProviderException("Failed to reinitialize MAC", e); + } + + byte[] output; + try { + output = mChunkedStreamer.update(input, offset, len); + } catch (KeyStoreException e) { + throw new ProviderException("Keystore operation failed", e); + } + if ((output != null) && (output.length != 0)) { + throw new ProviderException("Update operation unexpectedly produced output"); + } + } + + @Override + protected byte[] engineDoFinal() { + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException e) { + throw new ProviderException("Failed to reinitialize MAC", e); + } + + byte[] result; + try { + result = mChunkedStreamer.doFinal( + null, 0, 0, + null, // no signature provided -- this invocation will generate one + null // no additional entropy needed -- HMAC is deterministic + ); + } catch (KeyStoreException e) { + throw new ProviderException("Keystore operation failed", e); + } + + resetWhilePreservingInitState(); + return result; + } + + @Override + public void finalize() throws Throwable { + try { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mKeyStore.abort(operationToken); + } + } finally { + super.finalize(); + } + } + + @Override + public long getOperationHandle() { + return mOperationHandle; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java new file mode 100644 index 000000000000..e2a3d61d4cfc --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java @@ -0,0 +1,103 @@ +/* + * 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.keystore2; + +import java.security.Key; + +/** + * {@link Key} backed by Android Keystore. + * + * @hide + */ +public class AndroidKeyStoreKey implements Key { + private final String mAlias; + private final int mUid; + private final String mAlgorithm; + + public AndroidKeyStoreKey(String alias, int uid, String algorithm) { + mAlias = alias; + mUid = uid; + mAlgorithm = algorithm; + } + + String getAlias() { + return mAlias; + } + + int getUid() { + return mUid; + } + + @Override + public String getAlgorithm() { + return mAlgorithm; + } + + @Override + public String getFormat() { + // This key does not export its key material + return null; + } + + @Override + public byte[] getEncoded() { + // This key does not export its key material + return null; + } + + @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()); + result = prime * result + mUid; + 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; + } + if (mUid != other.mUid) { + return false; + } + return true; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java new file mode 100644 index 000000000000..607bcae6570d --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java @@ -0,0 +1,156 @@ +/* + * 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.keystore2; + +import android.security.Credentials; +import android.security.KeyStore; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyInfo; + +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactorySpi; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.security.spec.X509EncodedKeySpec; + +/** + * {@link KeyFactorySpi} backed by Android KeyStore. + * + * @hide + */ +public class AndroidKeyStoreKeyFactorySpi extends KeyFactorySpi { + + private final KeyStore mKeyStore = KeyStore.getInstance(); + + @Override + protected T engineGetKeySpec(Key key, Class keySpecClass) + throws InvalidKeySpecException { + if (key == null) { + throw new InvalidKeySpecException("key == null"); + } else if ((!(key instanceof AndroidKeyStorePrivateKey)) + && (!(key instanceof AndroidKeyStorePublicKey))) { + throw new InvalidKeySpecException( + "Unsupported key type: " + key.getClass().getName() + + ". This KeyFactory supports only Android Keystore asymmetric keys"); + } + + // key is an Android Keystore private or public key + + if (keySpecClass == null) { + throw new InvalidKeySpecException("keySpecClass == null"); + } else if (KeyInfo.class.equals(keySpecClass)) { + if (!(key instanceof AndroidKeyStorePrivateKey)) { + throw new InvalidKeySpecException( + "Unsupported key type: " + key.getClass().getName() + + ". KeyInfo can be obtained only for Android Keystore private keys"); + } + AndroidKeyStorePrivateKey + keystorePrivateKey = (AndroidKeyStorePrivateKey) key; + String keyAliasInKeystore = keystorePrivateKey.getAlias(); + String entryAlias; + if (keyAliasInKeystore.startsWith(Credentials.USER_PRIVATE_KEY)) { + entryAlias = keyAliasInKeystore.substring(Credentials.USER_PRIVATE_KEY.length()); + } else { + throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore); + } + @SuppressWarnings("unchecked") + T result = (T) AndroidKeyStoreSecretKeyFactorySpi.getKeyInfo( + mKeyStore, entryAlias, keyAliasInKeystore, keystorePrivateKey.getUid()); + return result; + } else if (X509EncodedKeySpec.class.equals(keySpecClass)) { + if (!(key instanceof AndroidKeyStorePublicKey)) { + throw new InvalidKeySpecException( + "Unsupported key type: " + key.getClass().getName() + + ". X509EncodedKeySpec can be obtained only for Android Keystore public" + + " keys"); + } + @SuppressWarnings("unchecked") + T result = (T) new X509EncodedKeySpec(((AndroidKeyStorePublicKey) key).getEncoded()); + return result; + } else if (PKCS8EncodedKeySpec.class.equals(keySpecClass)) { + if (key instanceof AndroidKeyStorePrivateKey) { + throw new InvalidKeySpecException( + "Key material export of Android Keystore private keys is not supported"); + } else { + throw new InvalidKeySpecException( + "Cannot export key material of public key in PKCS#8 format." + + " Only X.509 format (X509EncodedKeySpec) supported for public keys."); + } + } else if (RSAPublicKeySpec.class.equals(keySpecClass)) { + if (key instanceof AndroidKeyStoreRSAPublicKey) { + AndroidKeyStoreRSAPublicKey + rsaKey = (AndroidKeyStoreRSAPublicKey) key; + @SuppressWarnings("unchecked") + T result = + (T) new RSAPublicKeySpec(rsaKey.getModulus(), rsaKey.getPublicExponent()); + return result; + } else { + throw new InvalidKeySpecException( + "Obtaining RSAPublicKeySpec not supported for " + key.getAlgorithm() + " " + + ((key instanceof AndroidKeyStorePrivateKey) ? "private" : "public") + + " key"); + } + } else if (ECPublicKeySpec.class.equals(keySpecClass)) { + if (key instanceof AndroidKeyStoreECPublicKey) { + AndroidKeyStoreECPublicKey + ecKey = (AndroidKeyStoreECPublicKey) key; + @SuppressWarnings("unchecked") + T result = (T) new ECPublicKeySpec(ecKey.getW(), ecKey.getParams()); + return result; + } else { + throw new InvalidKeySpecException( + "Obtaining ECPublicKeySpec not supported for " + key.getAlgorithm() + " " + + ((key instanceof AndroidKeyStorePrivateKey) ? "private" : "public") + + " key"); + } + } else { + throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName()); + } + } + + @Override + protected PrivateKey engineGeneratePrivate(KeySpec spec) throws InvalidKeySpecException { + throw new InvalidKeySpecException( + "To generate a key pair in Android Keystore, use KeyPairGenerator initialized with" + + " " + KeyGenParameterSpec.class.getName()); + } + + @Override + protected PublicKey engineGeneratePublic(KeySpec spec) throws InvalidKeySpecException { + throw new InvalidKeySpecException( + "To generate a key pair in Android Keystore, use KeyPairGenerator initialized with" + + " " + KeyGenParameterSpec.class.getName()); + } + + @Override + protected Key engineTranslateKey(Key key) throws InvalidKeyException { + if (key == null) { + throw new InvalidKeyException("key == null"); + } else if ((!(key instanceof AndroidKeyStorePrivateKey)) + && (!(key instanceof AndroidKeyStorePublicKey))) { + throw new InvalidKeyException( + "To import a key into Android Keystore, use KeyStore.setEntry"); + } + return key; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java new file mode 100644 index 000000000000..7d18be553122 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java @@ -0,0 +1,354 @@ +/* + * 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.keystore2; + +import android.security.Credentials; +import android.security.KeyStore; +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; +import android.security.keystore.KeymasterUtils; +import android.security.keystore.StrongBoxUnavailableException; + +import libcore.util.EmptyArray; + +import java.security.InvalidAlgorithmParameterException; +import java.security.ProviderException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; + +import javax.crypto.KeyGeneratorSpi; +import javax.crypto.SecretKey; + +/** + * {@link KeyGeneratorSpi} backed by Android KeyStore. + * + * @hide + */ +public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { + + public static class AES extends AndroidKeyStoreKeyGeneratorSpi { + public AES() { + super(KeymasterDefs.KM_ALGORITHM_AES, 128); + } + + @Override + protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + super.engineInit(params, random); + if ((mKeySizeBits != 128) && (mKeySizeBits != 192) && (mKeySizeBits != 256)) { + throw new InvalidAlgorithmParameterException( + "Unsupported key size: " + mKeySizeBits + + ". Supported: 128, 192, 256."); + } + } + } + + public static class DESede extends AndroidKeyStoreKeyGeneratorSpi { + public DESede() { + super(KeymasterDefs.KM_ALGORITHM_3DES, 168); + } + } + + protected static abstract class HmacBase extends AndroidKeyStoreKeyGeneratorSpi { + protected HmacBase(int keymasterDigest) { + super(KeymasterDefs.KM_ALGORITHM_HMAC, + keymasterDigest, + KeymasterUtils.getDigestOutputSizeBits(keymasterDigest)); + } + } + + public static class HmacSHA1 extends HmacBase { + public HmacSHA1() { + super(KeymasterDefs.KM_DIGEST_SHA1); + } + } + + public static class HmacSHA224 extends HmacBase { + public HmacSHA224() { + super(KeymasterDefs.KM_DIGEST_SHA_2_224); + } + } + + public static class HmacSHA256 extends HmacBase { + public HmacSHA256() { + super(KeymasterDefs.KM_DIGEST_SHA_2_256); + } + } + + public static class HmacSHA384 extends HmacBase { + public HmacSHA384() { + super(KeymasterDefs.KM_DIGEST_SHA_2_384); + } + } + + public static class HmacSHA512 extends HmacBase { + public HmacSHA512() { + super(KeymasterDefs.KM_DIGEST_SHA_2_512); + } + } + + private final KeyStore mKeyStore = KeyStore.getInstance(); + private final int mKeymasterAlgorithm; + private final int mKeymasterDigest; + private final int mDefaultKeySizeBits; + + private KeyGenParameterSpec mSpec; + private SecureRandom mRng; + + protected int mKeySizeBits; + private int[] mKeymasterPurposes; + private int[] mKeymasterBlockModes; + private int[] mKeymasterPaddings; + private int[] mKeymasterDigests; + + protected AndroidKeyStoreKeyGeneratorSpi( + int keymasterAlgorithm, + int defaultKeySizeBits) { + this(keymasterAlgorithm, -1, defaultKeySizeBits); + } + + protected AndroidKeyStoreKeyGeneratorSpi( + int keymasterAlgorithm, + int keymasterDigest, + int defaultKeySizeBits) { + mKeymasterAlgorithm = keymasterAlgorithm; + mKeymasterDigest = keymasterDigest; + mDefaultKeySizeBits = defaultKeySizeBits; + if (mDefaultKeySizeBits <= 0) { + throw new IllegalArgumentException("Default key size must be positive"); + } + + if ((mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) && (mKeymasterDigest == -1)) { + throw new IllegalArgumentException( + "Digest algorithm must be specified for HMAC key"); + } + } + + @Override + protected void engineInit(SecureRandom random) { + throw new UnsupportedOperationException("Cannot initialize without a " + + KeyGenParameterSpec.class.getName() + " parameter"); + } + + @Override + protected void engineInit(int keySize, SecureRandom random) { + throw new UnsupportedOperationException("Cannot initialize without a " + + KeyGenParameterSpec.class.getName() + " parameter"); + } + + @Override + protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + if ((params == null) || (!(params instanceof KeyGenParameterSpec))) { + throw new InvalidAlgorithmParameterException("Cannot initialize without a " + + KeyGenParameterSpec.class.getName() + " parameter"); + } + KeyGenParameterSpec spec = (KeyGenParameterSpec) params; + if (spec.getKeystoreAlias() == null) { + throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided"); + } + + mRng = random; + mSpec = spec; + + mKeySizeBits = (spec.getKeySize() != -1) ? spec.getKeySize() : mDefaultKeySizeBits; + if (mKeySizeBits <= 0) { + throw new InvalidAlgorithmParameterException( + "Key size must be positive: " + mKeySizeBits); + } else if ((mKeySizeBits % 8) != 0) { + throw new InvalidAlgorithmParameterException( + "Key size must be a multiple of 8: " + mKeySizeBits); + } + + try { + mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes()); + mKeymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster( + spec.getEncryptionPaddings()); + if (spec.getSignaturePaddings().length > 0) { + throw new InvalidAlgorithmParameterException( + "Signature paddings not supported for symmetric key algorithms"); + } + mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()); + if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) + && (spec.isRandomizedEncryptionRequired())) { + for (int keymasterBlockMode : mKeymasterBlockModes) { + if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto( + keymasterBlockMode)) { + throw new InvalidAlgorithmParameterException( + "Randomized encryption (IND-CPA) required but may be violated" + + " by block mode: " + + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode) + + ". See " + KeyGenParameterSpec.class.getName() + + " documentation."); + } + } + } + if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_3DES) { + if (mKeySizeBits != 168) { + throw new InvalidAlgorithmParameterException( + "3DES key size must be 168 bits."); + } + } + if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { + if (mKeySizeBits < 64 || mKeySizeBits > 512) { + throw new InvalidAlgorithmParameterException( + "HMAC key sizes must be within 64-512 bits, inclusive."); + } + + // JCA HMAC key algorithm implies a digest (e.g., HmacSHA256 key algorithm + // implies SHA-256 digest). Because keymaster HMAC key is authorized only for + // one digest, we don't let algorithm parameter spec override the digest implied + // by the key. If the spec specifies digests at all, it must specify only one + // digest, the only implied by key algorithm. + mKeymasterDigests = new int[] {mKeymasterDigest}; + if (spec.isDigestsSpecified()) { + // Digest(s) explicitly specified in the spec. Check that the list + // consists of exactly one digest, the one implied by key algorithm. + int[] keymasterDigestsFromSpec = + KeyProperties.Digest.allToKeymaster(spec.getDigests()); + if ((keymasterDigestsFromSpec.length != 1) + || (keymasterDigestsFromSpec[0] != mKeymasterDigest)) { + throw new InvalidAlgorithmParameterException( + "Unsupported digests specification: " + + Arrays.asList(spec.getDigests()) + ". Only " + + KeyProperties.Digest.fromKeymaster(mKeymasterDigest) + + " supported for this HMAC key algorithm"); + } + } + } else { + // Key algorithm does not imply a digest. + if (spec.isDigestsSpecified()) { + mKeymasterDigests = KeyProperties.Digest.allToKeymaster(spec.getDigests()); + } else { + mKeymasterDigests = EmptyArray.INT; + } + } + + // Check that user authentication related parameters are acceptable. This method + // will throw an IllegalStateException if there are issues (e.g., secure lock screen + // not set up). + KeymasterUtils.addUserAuthArgs(new KeymasterArguments(), spec); + } catch (IllegalStateException | IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException(e); + } + + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + private void resetAll() { + mSpec = null; + mRng = null; + mKeySizeBits = -1; + mKeymasterPurposes = null; + mKeymasterPaddings = null; + mKeymasterBlockModes = null; + } + + @Override + protected SecretKey engineGenerateKey() { + KeyGenParameterSpec spec = mSpec; + if (spec == null) { + throw new IllegalStateException("Not initialized"); + } + + KeymasterArguments args = new KeymasterArguments(); + args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits); + args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); + args.addEnums(KeymasterDefs.KM_TAG_PURPOSE, mKeymasterPurposes); + args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockModes); + args.addEnums(KeymasterDefs.KM_TAG_PADDING, mKeymasterPaddings); + args.addEnums(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests); + KeymasterUtils.addUserAuthArgs(args, spec); + KeymasterUtils.addMinMacLengthAuthorizationIfNecessary( + args, + mKeymasterAlgorithm, + mKeymasterBlockModes, + mKeymasterDigests); + args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart()); + args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + spec.getKeyValidityForOriginationEnd()); + args.addDateIfNotNull(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + spec.getKeyValidityForConsumptionEnd()); + + if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) + && (!spec.isRandomizedEncryptionRequired())) { + // Permit caller-provided IV when encrypting with this key + args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); + } + + byte[] additionalEntropy = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, (mKeySizeBits + 7) / 8); + int flags = 0; + if (spec.isStrongBoxBacked()) { + flags |= KeyStore.FLAG_STRONGBOX; + } + if (spec.isCriticalToDeviceEncryption()) { + flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION; + } + String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + spec.getKeystoreAlias(); + KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); + boolean success = false; + try { + Credentials.deleteAllTypesForAlias(mKeyStore, spec.getKeystoreAlias(), spec.getUid()); + int errorCode = mKeyStore.generateKey( + keyAliasInKeystore, + args, + additionalEntropy, + spec.getUid(), + flags, + resultingKeyCharacteristics); + if (errorCode != KeyStore.NO_ERROR) { + if (errorCode == KeyStore.HARDWARE_TYPE_UNAVAILABLE) { + throw new StrongBoxUnavailableException("Failed to generate key"); + } else { + throw new ProviderException( + "Keystore operation failed", KeyStore.getKeyStoreException(errorCode)); + } + } + @KeyProperties.KeyAlgorithmEnum String keyAlgorithmJCA; + try { + keyAlgorithmJCA = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm( + mKeymasterAlgorithm, mKeymasterDigest); + } catch (IllegalArgumentException e) { + throw new ProviderException("Failed to obtain JCA secret key algorithm name", e); + } + SecretKey result = new AndroidKeyStoreSecretKey( + keyAliasInKeystore, spec.getUid(), keyAlgorithmJCA); + success = true; + return result; + } finally { + if (!success) { + Credentials.deleteAllTypesForAlias( + mKeyStore, spec.getKeystoreAlias(), spec.getUid()); + } + } + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java new file mode 100644 index 000000000000..ff40c1586212 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -0,0 +1,933 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore2; + +import android.annotation.Nullable; +import android.os.Build; +import android.security.Credentials; +import android.security.KeyPairGeneratorSpec; +import android.security.KeyStore; +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterCertificateChain; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.security.keystore.KeymasterUtils; +import android.security.keystore.SecureKeyImportUnavailableException; +import android.security.keystore.StrongBoxUnavailableException; + +import com.android.org.bouncycastle.asn1.ASN1EncodableVector; +import com.android.org.bouncycastle.asn1.ASN1InputStream; +import com.android.org.bouncycastle.asn1.ASN1Integer; +import com.android.org.bouncycastle.asn1.ASN1ObjectIdentifier; +import com.android.org.bouncycastle.asn1.DERBitString; +import com.android.org.bouncycastle.asn1.DERNull; +import com.android.org.bouncycastle.asn1.DERSequence; +import com.android.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import com.android.org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import com.android.org.bouncycastle.asn1.x509.Certificate; +import com.android.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import com.android.org.bouncycastle.asn1.x509.TBSCertificate; +import com.android.org.bouncycastle.asn1.x509.Time; +import com.android.org.bouncycastle.asn1.x509.V3TBSCertificateGenerator; +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 libcore.util.EmptyArray; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyPairGeneratorSpi; +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.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.RSAKeyGenParameterSpec; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * Provides a way to create instances of a KeyPair which will be placed in the + * Android keystore service usable only by the application that called it. This + * can be used in conjunction with + * {@link java.security.KeyStore#getInstance(String)} using the + * {@code "AndroidKeyStore"} type. + *

+ * This class can not be directly instantiated and must instead be used via the + * {@link KeyPairGenerator#getInstance(String) + * KeyPairGenerator.getInstance("AndroidKeyStore")} API. + * + * @hide + */ +public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGeneratorSpi { + + public static class RSA extends AndroidKeyStoreKeyPairGeneratorSpi { + public RSA() { + super(KeymasterDefs.KM_ALGORITHM_RSA); + } + } + + public static class EC extends AndroidKeyStoreKeyPairGeneratorSpi { + public EC() { + super(KeymasterDefs.KM_ALGORITHM_EC); + } + } + + /* + * These must be kept in sync with system/security/keystore/defaults.h + */ + + /* EC */ + private static final int EC_DEFAULT_KEY_SIZE = 256; + + /* RSA */ + private static final int RSA_DEFAULT_KEY_SIZE = 2048; + private static final int RSA_MIN_KEY_SIZE = 512; + private static final int RSA_MAX_KEY_SIZE = 8192; + + private static final Map SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE = + new HashMap(); + private static final List SUPPORTED_EC_NIST_CURVE_NAMES = new ArrayList(); + private static final List SUPPORTED_EC_NIST_CURVE_SIZES = new ArrayList(); + static { + // 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); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("prime256v1", 256); + + // Aliases for NIST P-384 + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-384", 384); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp384r1", 384); + + // Aliases for NIST P-521 + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-521", 521); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp521r1", 521); + + 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(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.values())); + Collections.sort(SUPPORTED_EC_NIST_CURVE_SIZES); + } + + private final int mOriginalKeymasterAlgorithm; + + private KeyStore mKeyStore; + + private KeyGenParameterSpec mSpec; + + private String mEntryAlias; + private int mEntryUid; + private boolean mEncryptionAtRestRequired; + private @KeyProperties.KeyAlgorithmEnum String mJcaKeyAlgorithm; + private int mKeymasterAlgorithm = -1; + private int mKeySizeBits; + private SecureRandom mRng; + + private int[] mKeymasterPurposes; + private int[] mKeymasterBlockModes; + private int[] mKeymasterEncryptionPaddings; + private int[] mKeymasterSignaturePaddings; + private int[] mKeymasterDigests; + + private BigInteger mRSAPublicExponent; + + protected AndroidKeyStoreKeyPairGeneratorSpi(int keymasterAlgorithm) { + mOriginalKeymasterAlgorithm = keymasterAlgorithm; + } + + @SuppressWarnings("deprecation") + @Override + public void initialize(int keysize, SecureRandom random) { + throw new IllegalArgumentException( + KeyGenParameterSpec.class.getName() + " or " + KeyPairGeneratorSpec.class.getName() + + " required to initialize this KeyPairGenerator"); + } + + @SuppressWarnings("deprecation") + @Override + public void initialize(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + if (params == null) { + throw new InvalidAlgorithmParameterException( + "Must supply params of type " + KeyGenParameterSpec.class.getName() + + " or " + KeyPairGeneratorSpec.class.getName()); + } + + KeyGenParameterSpec spec; + boolean encryptionAtRestRequired = false; + int keymasterAlgorithm = mOriginalKeymasterAlgorithm; + if (params instanceof KeyGenParameterSpec) { + spec = (KeyGenParameterSpec) params; + } else if (params instanceof KeyPairGeneratorSpec) { + // Legacy/deprecated spec + KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params; + try { + KeyGenParameterSpec.Builder specBuilder; + String specKeyAlgorithm = legacySpec.getKeyType(); + if (specKeyAlgorithm != null) { + // Spec overrides the generator's default key algorithm + try { + keymasterAlgorithm = + KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm( + specKeyAlgorithm); + } catch (IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException( + "Invalid key type in parameters", e); + } + } + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + specBuilder = new KeyGenParameterSpec.Builder( + legacySpec.getKeystoreAlias(), + KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + // Authorized to be used with any digest (including no digest). + // MD5 was never offered for Android Keystore for ECDSA. + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + break; + case KeymasterDefs.KM_ALGORITHM_RSA: + specBuilder = new KeyGenParameterSpec.Builder( + legacySpec.getKeystoreAlias(), + KeyProperties.PURPOSE_ENCRYPT + | KeyProperties.PURPOSE_DECRYPT + | KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + // Authorized to be used with any digest (including no digest). + 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 encryption and signature padding + // schemes (including no padding). + specBuilder.setEncryptionPaddings( + KeyProperties.ENCRYPTION_PADDING_NONE, + KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1, + KeyProperties.ENCRYPTION_PADDING_RSA_OAEP); + specBuilder.setSignaturePaddings( + KeyProperties.SIGNATURE_PADDING_RSA_PKCS1, + KeyProperties.SIGNATURE_PADDING_RSA_PSS); + // Disable randomized encryption requirement to support encryption + // padding NONE above. + specBuilder.setRandomizedEncryptionRequired(false); + break; + default: + throw new ProviderException( + "Unsupported algorithm: " + mKeymasterAlgorithm); + } + + if (legacySpec.getKeySize() != -1) { + specBuilder.setKeySize(legacySpec.getKeySize()); + } + if (legacySpec.getAlgorithmParameterSpec() != null) { + specBuilder.setAlgorithmParameterSpec( + legacySpec.getAlgorithmParameterSpec()); + } + specBuilder.setCertificateSubject(legacySpec.getSubjectDN()); + specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber()); + specBuilder.setCertificateNotBefore(legacySpec.getStartDate()); + specBuilder.setCertificateNotAfter(legacySpec.getEndDate()); + encryptionAtRestRequired = legacySpec.isEncryptionRequired(); + specBuilder.setUserAuthenticationRequired(false); + + spec = specBuilder.build(); + } catch (NullPointerException | IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException(e); + } + } else { + throw new InvalidAlgorithmParameterException( + "Unsupported params class: " + params.getClass().getName() + + ". Supported: " + KeyGenParameterSpec.class.getName() + + ", " + KeyPairGeneratorSpec.class.getName()); + } + + mEntryAlias = spec.getKeystoreAlias(); + mEntryUid = spec.getUid(); + mSpec = spec; + mKeymasterAlgorithm = keymasterAlgorithm; + mEncryptionAtRestRequired = encryptionAtRestRequired; + mKeySizeBits = spec.getKeySize(); + initAlgorithmSpecificParameters(); + if (mKeySizeBits == -1) { + mKeySizeBits = getDefaultKeySize(keymasterAlgorithm); + } + checkValidKeySize(keymasterAlgorithm, mKeySizeBits, mSpec.isStrongBoxBacked()); + + if (spec.getKeystoreAlias() == null) { + throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided"); + } + + String jcaKeyAlgorithm; + try { + jcaKeyAlgorithm = KeyProperties.KeyAlgorithm.fromKeymasterAsymmetricKeyAlgorithm( + keymasterAlgorithm); + mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes()); + mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()); + mKeymasterEncryptionPaddings = KeyProperties.EncryptionPadding.allToKeymaster( + spec.getEncryptionPaddings()); + if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) + && (spec.isRandomizedEncryptionRequired())) { + for (int keymasterPadding : mKeymasterEncryptionPaddings) { + if (!KeymasterUtils + .isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto( + keymasterPadding)) { + throw new InvalidAlgorithmParameterException( + "Randomized encryption (IND-CPA) required but may be violated" + + " by padding scheme: " + + KeyProperties.EncryptionPadding.fromKeymaster( + keymasterPadding) + + ". See " + KeyGenParameterSpec.class.getName() + + " documentation."); + } + } + } + mKeymasterSignaturePaddings = KeyProperties.SignaturePadding.allToKeymaster( + spec.getSignaturePaddings()); + if (spec.isDigestsSpecified()) { + mKeymasterDigests = KeyProperties.Digest.allToKeymaster(spec.getDigests()); + } else { + mKeymasterDigests = EmptyArray.INT; + } + + // Check that user authentication related parameters are acceptable. This method + // will throw an IllegalStateException if there are issues (e.g., secure lock screen + // not set up). + KeymasterUtils.addUserAuthArgs(new KeymasterArguments(), mSpec); + } catch (IllegalArgumentException | IllegalStateException e) { + throw new InvalidAlgorithmParameterException(e); + } + + mJcaKeyAlgorithm = jcaKeyAlgorithm; + mRng = random; + mKeyStore = KeyStore.getInstance(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + private void resetAll() { + mEntryAlias = null; + mEntryUid = KeyStore.UID_SELF; + mJcaKeyAlgorithm = null; + mKeymasterAlgorithm = -1; + mKeymasterPurposes = null; + mKeymasterBlockModes = null; + mKeymasterEncryptionPaddings = null; + mKeymasterSignaturePaddings = null; + mKeymasterDigests = null; + mKeySizeBits = 0; + mSpec = null; + mRSAPublicExponent = null; + mEncryptionAtRestRequired = false; + mRng = null; + mKeyStore = null; + } + + private void initAlgorithmSpecificParameters() throws InvalidAlgorithmParameterException { + AlgorithmParameterSpec algSpecificSpec = mSpec.getAlgorithmParameterSpec(); + switch (mKeymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_RSA: + { + BigInteger publicExponent = null; + if (algSpecificSpec instanceof RSAKeyGenParameterSpec) { + RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) algSpecificSpec; + if (mKeySizeBits == -1) { + mKeySizeBits = rsaSpec.getKeysize(); + } else if (mKeySizeBits != rsaSpec.getKeysize()) { + throw new InvalidAlgorithmParameterException("RSA key size must match " + + " between " + mSpec + " and " + algSpecificSpec + + ": " + mKeySizeBits + " vs " + rsaSpec.getKeysize()); + } + publicExponent = rsaSpec.getPublicExponent(); + } else if (algSpecificSpec != null) { + throw new InvalidAlgorithmParameterException( + "RSA may only use RSAKeyGenParameterSpec"); + } + if (publicExponent == null) { + publicExponent = RSAKeyGenParameterSpec.F4; + } + if (publicExponent.compareTo(BigInteger.ZERO) < 1) { + throw new InvalidAlgorithmParameterException( + "RSA public exponent must be positive: " + publicExponent); + } + if (publicExponent.compareTo(KeymasterArguments.UINT64_MAX_VALUE) > 0) { + throw new InvalidAlgorithmParameterException( + "Unsupported RSA public exponent: " + publicExponent + + ". Maximum supported value: " + KeymasterArguments.UINT64_MAX_VALUE); + } + mRSAPublicExponent = publicExponent; + break; + } + case KeymasterDefs.KM_ALGORITHM_EC: + if (algSpecificSpec instanceof ECGenParameterSpec) { + ECGenParameterSpec ecSpec = (ECGenParameterSpec) algSpecificSpec; + String curveName = ecSpec.getName(); + Integer ecSpecKeySizeBits = SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.get( + curveName.toLowerCase(Locale.US)); + if (ecSpecKeySizeBits == null) { + throw new InvalidAlgorithmParameterException( + "Unsupported EC curve name: " + curveName + + ". Supported: " + SUPPORTED_EC_NIST_CURVE_NAMES); + } + if (mKeySizeBits == -1) { + mKeySizeBits = ecSpecKeySizeBits; + } else if (mKeySizeBits != ecSpecKeySizeBits) { + throw new InvalidAlgorithmParameterException("EC key size must match " + + " between " + mSpec + " and " + algSpecificSpec + + ": " + mKeySizeBits + " vs " + ecSpecKeySizeBits); + } + } else if (algSpecificSpec != null) { + throw new InvalidAlgorithmParameterException( + "EC may only use ECGenParameterSpec"); + } + break; + default: + throw new ProviderException("Unsupported algorithm: " + mKeymasterAlgorithm); + } + } + + @Override + public KeyPair generateKeyPair() { + if (mKeyStore == null || mSpec == null) { + throw new IllegalStateException("Not initialized"); + } + + int flags = (mEncryptionAtRestRequired) ? KeyStore.FLAG_ENCRYPTED : 0; + if (((flags & KeyStore.FLAG_ENCRYPTED) != 0) + && (mKeyStore.state() != KeyStore.State.UNLOCKED)) { + throw new IllegalStateException( + "Encryption at rest using secure lock screen credential requested for key pair" + + ", but the user has not yet entered the credential"); + } + + if (mSpec.isStrongBoxBacked()) { + flags |= KeyStore.FLAG_STRONGBOX; + } + if (mSpec.isCriticalToDeviceEncryption()) { + flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION; + } + + byte[] additionalEntropy = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, (mKeySizeBits + 7) / 8); + + Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid); + final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + mEntryAlias; + boolean success = false; + try { + generateKeystoreKeyPair( + privateKeyAlias, constructKeyGenerationArguments(), additionalEntropy, flags); + KeyPair keyPair = loadKeystoreKeyPair(privateKeyAlias); + + storeCertificateChain(flags, createCertificateChain(privateKeyAlias, keyPair)); + + success = true; + return keyPair; + } catch (ProviderException e) { + if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) { + throw new SecureKeyImportUnavailableException(e); + } else { + throw e; + } + } finally { + if (!success) { + Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid); + } + } + } + + private Iterable createCertificateChain(final String privateKeyAlias, KeyPair keyPair) + throws ProviderException { + byte[] challenge = mSpec.getAttestationChallenge(); + if (challenge != null) { + KeymasterArguments args = new KeymasterArguments(); + args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, challenge); + + if (mSpec.isDevicePropertiesAttestationIncluded()) { + args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND, + Build.BRAND.getBytes(StandardCharsets.UTF_8)); + args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE, + Build.DEVICE.getBytes(StandardCharsets.UTF_8)); + args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT, + Build.PRODUCT.getBytes(StandardCharsets.UTF_8)); + args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER, + Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8)); + args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MODEL, + Build.MODEL.getBytes(StandardCharsets.UTF_8)); + } + + return getAttestationChain(privateKeyAlias, keyPair, args); + } + + // Very short certificate chain in the non-attestation case. + return Collections.singleton(generateSelfSignedCertificateBytes(keyPair)); + } + + private void generateKeystoreKeyPair(final String privateKeyAlias, KeymasterArguments args, + byte[] additionalEntropy, final int flags) throws ProviderException { + KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); + int errorCode = mKeyStore.generateKey(privateKeyAlias, args, additionalEntropy, + mEntryUid, flags, resultingKeyCharacteristics); + if (errorCode != KeyStore.NO_ERROR) { + if (errorCode == KeyStore.HARDWARE_TYPE_UNAVAILABLE) { + throw new StrongBoxUnavailableException("Failed to generate key pair"); + } else { + throw new ProviderException( + "Failed to generate key pair", KeyStore.getKeyStoreException(errorCode)); + } + } + } + + private KeyPair loadKeystoreKeyPair(final String privateKeyAlias) throws ProviderException { + try { + KeyPair result = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore( + mKeyStore, privateKeyAlias, mEntryUid); + if (!mJcaKeyAlgorithm.equalsIgnoreCase(result.getPrivate().getAlgorithm())) { + throw new ProviderException( + "Generated key pair algorithm does not match requested algorithm: " + + result.getPrivate().getAlgorithm() + " vs " + mJcaKeyAlgorithm); + } + return result; + } catch (UnrecoverableKeyException | KeyPermanentlyInvalidatedException e) { + throw new ProviderException("Failed to load generated key pair from keystore", e); + } + } + + private KeymasterArguments constructKeyGenerationArguments() { + KeymasterArguments args = new KeymasterArguments(); + args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits); + args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); + args.addEnums(KeymasterDefs.KM_TAG_PURPOSE, mKeymasterPurposes); + args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockModes); + args.addEnums(KeymasterDefs.KM_TAG_PADDING, mKeymasterEncryptionPaddings); + args.addEnums(KeymasterDefs.KM_TAG_PADDING, mKeymasterSignaturePaddings); + args.addEnums(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests); + + KeymasterUtils.addUserAuthArgs(args, mSpec); + args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, mSpec.getKeyValidityStart()); + args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + mSpec.getKeyValidityForOriginationEnd()); + args.addDateIfNotNull(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + mSpec.getKeyValidityForConsumptionEnd()); + addAlgorithmSpecificParameters(args); + + if (mSpec.isUniqueIdIncluded()) + args.addBoolean(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID); + + return args; + } + + private void storeCertificateChain(final int flags, Iterable iterable) + throws ProviderException { + Iterator iter = iterable.iterator(); + storeCertificate( + Credentials.USER_CERTIFICATE, iter.next(), flags, "Failed to store certificate"); + + if (!iter.hasNext()) { + return; + } + + ByteArrayOutputStream certificateConcatenationStream = new ByteArrayOutputStream(); + while (iter.hasNext()) { + byte[] data = iter.next(); + certificateConcatenationStream.write(data, 0, data.length); + } + + storeCertificate(Credentials.CA_CERTIFICATE, certificateConcatenationStream.toByteArray(), + flags, "Failed to store attestation CA certificate"); + } + + private void storeCertificate(String prefix, byte[] certificateBytes, final int flags, + String failureMessage) throws ProviderException { + int insertErrorCode = mKeyStore.insert( + prefix + mEntryAlias, + certificateBytes, + mEntryUid, + flags); + if (insertErrorCode != KeyStore.NO_ERROR) { + throw new ProviderException(failureMessage, + KeyStore.getKeyStoreException(insertErrorCode)); + } + } + + private byte[] generateSelfSignedCertificateBytes(KeyPair keyPair) throws ProviderException { + try { + return generateSelfSignedCertificate(keyPair.getPrivate(), keyPair.getPublic()) + .getEncoded(); + } catch (IOException | CertificateParsingException e) { + throw new ProviderException("Failed to generate self-signed certificate", e); + } catch (CertificateEncodingException e) { + throw new ProviderException( + "Failed to obtain encoded form of self-signed certificate", e); + } + } + + private Iterable getAttestationChain(String privateKeyAlias, + KeyPair keyPair, KeymasterArguments args) + throws ProviderException { + final KeymasterCertificateChain outChain = new KeymasterCertificateChain(); + final int errorCode; + if (mSpec.isDevicePropertiesAttestationIncluded() + && mSpec.getAttestationChallenge() == null) { + throw new ProviderException("An attestation challenge must be provided when requesting " + + "device properties attestation."); + } + errorCode = mKeyStore.attestKey(privateKeyAlias, args, outChain); + if (errorCode != KeyStore.NO_ERROR) { + throw new ProviderException("Failed to generate attestation certificate chain", + KeyStore.getKeyStoreException(errorCode)); + } + Collection chain = outChain.getCertificates(); + if (chain.size() < 2) { + throw new ProviderException("Attestation certificate chain contained " + + chain.size() + " entries. At least two are required."); + } + return chain; + } + + private void addAlgorithmSpecificParameters(KeymasterArguments keymasterArgs) { + switch (mKeymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_RSA: + keymasterArgs.addUnsignedLong( + KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, mRSAPublicExponent); + break; + case KeymasterDefs.KM_ALGORITHM_EC: + break; + default: + throw new ProviderException("Unsupported algorithm: " + mKeymasterAlgorithm); + } + } + + private X509Certificate generateSelfSignedCertificate(PrivateKey privateKey, + PublicKey publicKey) throws CertificateParsingException, IOException { + String signatureAlgorithm = + getCertificateSignatureAlgorithm(mKeymasterAlgorithm, mKeySizeBits, mSpec); + if (signatureAlgorithm == null) { + // Key cannot be used to sign a certificate + return generateSelfSignedCertificateWithFakeSignature(publicKey); + } else { + // Key can be used to sign a certificate + try { + return generateSelfSignedCertificateWithValidSignature( + privateKey, publicKey, signatureAlgorithm); + } catch (Exception e) { + // Failed to generate the self-signed certificate with valid signature. Fall back + // to generating a self-signed certificate with a fake signature. This is done for + // all exception types because we prefer key pair generation to succeed and end up + // producing a self-signed certificate with an invalid signature to key pair + // generation failing. + return generateSelfSignedCertificateWithFakeSignature(publicKey); + } + } + } + + @SuppressWarnings("deprecation") + private X509Certificate generateSelfSignedCertificateWithValidSignature( + PrivateKey privateKey, PublicKey publicKey, String signatureAlgorithm) throws Exception { + final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); + certGen.setPublicKey(publicKey); + certGen.setSerialNumber(mSpec.getCertificateSerialNumber()); + certGen.setSubjectDN(mSpec.getCertificateSubject()); + certGen.setIssuerDN(mSpec.getCertificateSubject()); + certGen.setNotBefore(mSpec.getCertificateNotBefore()); + certGen.setNotAfter(mSpec.getCertificateNotAfter()); + certGen.setSignatureAlgorithm(signatureAlgorithm); + return certGen.generate(privateKey); + } + + @SuppressWarnings("deprecation") + private X509Certificate generateSelfSignedCertificateWithFakeSignature( + PublicKey publicKey) throws IOException, CertificateParsingException { + V3TBSCertificateGenerator tbsGenerator = new V3TBSCertificateGenerator(); + ASN1ObjectIdentifier sigAlgOid; + AlgorithmIdentifier sigAlgId; + byte[] signature; + switch (mKeymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + sigAlgOid = X9ObjectIdentifiers.ecdsa_with_SHA256; + sigAlgId = new AlgorithmIdentifier(sigAlgOid); + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(new ASN1Integer(BigInteger.valueOf(0))); + v.add(new ASN1Integer(BigInteger.valueOf(0))); + signature = new DERSequence().getEncoded(); + break; + case KeymasterDefs.KM_ALGORITHM_RSA: + sigAlgOid = PKCSObjectIdentifiers.sha256WithRSAEncryption; + sigAlgId = new AlgorithmIdentifier(sigAlgOid, DERNull.INSTANCE); + signature = new byte[1]; + break; + default: + throw new ProviderException("Unsupported key algorithm: " + mKeymasterAlgorithm); + } + + try (ASN1InputStream publicKeyInfoIn = new ASN1InputStream(publicKey.getEncoded())) { + tbsGenerator.setSubjectPublicKeyInfo( + SubjectPublicKeyInfo.getInstance(publicKeyInfoIn.readObject())); + } + tbsGenerator.setSerialNumber(new ASN1Integer(mSpec.getCertificateSerialNumber())); + X509Principal subject = + new X509Principal(mSpec.getCertificateSubject().getEncoded()); + tbsGenerator.setSubject(subject); + tbsGenerator.setIssuer(subject); + tbsGenerator.setStartDate(new Time(mSpec.getCertificateNotBefore())); + tbsGenerator.setEndDate(new Time(mSpec.getCertificateNotAfter())); + tbsGenerator.setSignature(sigAlgId); + TBSCertificate tbsCertificate = tbsGenerator.generateTBSCertificate(); + + ASN1EncodableVector result = new ASN1EncodableVector(); + result.add(tbsCertificate); + result.add(sigAlgId); + result.add(new DERBitString(signature)); + return new X509CertificateObject(Certificate.getInstance(new DERSequence(result))); + } + + private static int getDefaultKeySize(int keymasterAlgorithm) { + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + return EC_DEFAULT_KEY_SIZE; + case KeymasterDefs.KM_ALGORITHM_RSA: + return RSA_DEFAULT_KEY_SIZE; + default: + throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm); + } + } + + private static void checkValidKeySize( + int keymasterAlgorithm, + int keySize, + boolean isStrongBoxBacked) + throws InvalidAlgorithmParameterException { + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + if (isStrongBoxBacked && keySize != 256) { + throw new InvalidAlgorithmParameterException( + "Unsupported StrongBox EC key size: " + + keySize + " bits. Supported: 256"); + } + 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: + if (keySize < RSA_MIN_KEY_SIZE || keySize > RSA_MAX_KEY_SIZE) { + throw new InvalidAlgorithmParameterException("RSA key size must be >= " + + RSA_MIN_KEY_SIZE + " and <= " + RSA_MAX_KEY_SIZE); + } + break; + default: + throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm); + } + } + + /** + * Returns the {@code Signature} algorithm to be used for signing a certificate using the + * specified key or {@code null} if the key cannot be used for signing a certificate. + */ + @Nullable + private static String getCertificateSignatureAlgorithm( + int keymasterAlgorithm, + int keySizeBits, + KeyGenParameterSpec spec) { + // Constraints: + // 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 overhead + // of RSA PKCS#1 signature padding scheme (about 30 bytes). + // 4. For EC keys, the there is no point in using a digest whose output size is longer than + // key/field size because the digest will be truncated to that size. + + if ((spec.getPurposes() & KeyProperties.PURPOSE_SIGN) == 0) { + // 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; + } + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + { + Set availableKeymasterDigests = getAvailableKeymasterSignatureDigests( + spec.getDigests(), + AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests()); + + int bestKeymasterDigest = -1; + int bestDigestOutputSizeBits = -1; + for (int keymasterDigest : availableKeymasterDigests) { + int outputSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest); + if (outputSizeBits == keySizeBits) { + // Perfect match -- use this digest + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + break; + } + // Not a perfect match -- check against the best digest so far + if (bestKeymasterDigest == -1) { + // First digest tested -- definitely the best so far + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } else { + // Prefer output size to be as close to key size as possible, with output + // sizes larger than key size preferred to those smaller than key size. + if (bestDigestOutputSizeBits < keySizeBits) { + // Output size of the best digest so far is smaller than key size. + // Anything larger is a win. + if (outputSizeBits > bestDigestOutputSizeBits) { + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } + } else { + // Output size of the best digest so far is larger than key size. + // Anything smaller is a win, as long as it's not smaller than key size. + if ((outputSizeBits < bestDigestOutputSizeBits) + && (outputSizeBits >= keySizeBits)) { + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } + } + } + } + if (bestKeymasterDigest == -1) { + return null; + } + return KeyProperties.Digest.fromKeymasterToSignatureAlgorithmDigest( + bestKeymasterDigest) + "WithECDSA"; + } + 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 = + com.android.internal.util.ArrayUtils.contains( + KeyProperties.SignaturePadding.allToKeymaster( + spec.getSignaturePaddings()), + KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN); + if (!pkcs1SignaturePaddingSupported) { + // Key not authorized for PKCS#1 signature padding -- can't sign + return null; + } + + Set availableKeymasterDigests = getAvailableKeymasterSignatureDigests( + spec.getDigests(), + AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests()); + + // The amount of space available for the digest is less than modulus size by about + // 30 bytes because padding must be at least 11 bytes long (00 || 01 || PS || 00, + // where PS must be at least 8 bytes long), and then there's also the 15--19 bytes + // overhead (depending the on chosen digest) for encoding digest OID and digest + // value in DER. + int maxDigestOutputSizeBits = keySizeBits - 30 * 8; + int bestKeymasterDigest = -1; + int bestDigestOutputSizeBits = -1; + for (int keymasterDigest : availableKeymasterDigests) { + int outputSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest); + if (outputSizeBits > maxDigestOutputSizeBits) { + // Digest too long (signature generation will fail) -- skip + continue; + } + if (bestKeymasterDigest == -1) { + // First digest tested -- definitely the best so far + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } else { + // The longer the better + if (outputSizeBits > bestDigestOutputSizeBits) { + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } + } + } + if (bestKeymasterDigest == -1) { + return null; + } + return KeyProperties.Digest.fromKeymasterToSignatureAlgorithmDigest( + bestKeymasterDigest) + "WithRSA"; + } + default: + throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm); + } + } + + private static Set getAvailableKeymasterSignatureDigests( + @KeyProperties.DigestEnum String[] authorizedKeyDigests, + @KeyProperties.DigestEnum String[] supportedSignatureDigests) { + Set authorizedKeymasterKeyDigests = new HashSet(); + for (int keymasterDigest : KeyProperties.Digest.allToKeymaster(authorizedKeyDigests)) { + authorizedKeymasterKeyDigests.add(keymasterDigest); + } + Set supportedKeymasterSignatureDigests = new HashSet(); + for (int keymasterDigest + : KeyProperties.Digest.allToKeymaster(supportedSignatureDigests)) { + supportedKeymasterSignatureDigests.add(keymasterDigest); + } + Set result = new HashSet(supportedKeymasterSignatureDigests); + result.retainAll(authorizedKeymasterKeyDigests); + return result; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java b/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java new file mode 100644 index 000000000000..38db36020fb7 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java @@ -0,0 +1,38 @@ +/* + * 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.keystore2; + +import java.security.KeyStore; +import java.security.KeyStore.ProtectionParameter; + +class AndroidKeyStoreLoadStoreParameter implements KeyStore.LoadStoreParameter { + + private final int mUid; + + AndroidKeyStoreLoadStoreParameter(int uid) { + mUid = uid; + } + + @Override + public ProtectionParameter getProtectionParameter() { + return null; + } + + int getUid() { + return mUid; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStorePrivateKey.java b/keystore/java/android/security/keystore2/AndroidKeyStorePrivateKey.java new file mode 100644 index 000000000000..f071fe89fe37 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStorePrivateKey.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore2; + +import java.security.PrivateKey; + +/** + * {@link PrivateKey} backed by Android Keystore. + * + * @hide + */ +public class AndroidKeyStorePrivateKey extends AndroidKeyStoreKey implements PrivateKey { + + public AndroidKeyStorePrivateKey(String alias, int uid, String algorithm) { + super(alias, uid, algorithm); + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java new file mode 100644 index 000000000000..b759733a7573 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore2; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.security.KeyStore; +import android.security.keymaster.ExportResult; +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.security.keystore.KeyProperties; +import android.security.keystore.KeyStoreCryptoOperation; + +import java.io.IOException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +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.cert.CertificateException; +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; + +/** + * A provider focused on providing JCA interfaces for the Android KeyStore. + * + * @hide + */ +@SystemApi +public class AndroidKeyStoreProvider extends Provider { + private static final String PROVIDER_NAME = "AndroidKeyStore"; + + // IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these + // classes when this provider is instantiated and installed early on during each app's + // initialization process. + // + // Crypto operations operating on the AndroidKeyStore keys must not be offered by this provider. + // Instead, they need to be offered by AndroidKeyStoreBCWorkaroundProvider. See its Javadoc + // for details. + + private static final String PACKAGE_NAME = "android.security.keystore"; + + private static final String DESEDE_SYSTEM_PROPERTY = + "ro.hardware.keystore_desede"; + + /** @hide **/ + public AndroidKeyStoreProvider() { + super(PROVIDER_NAME, 1.0, "Android KeyStore security provider"); + + boolean supports3DES = "true".equals(android.os.SystemProperties.get(DESEDE_SYSTEM_PROPERTY)); + + // java.security.KeyStore + put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStoreSpi"); + + // java.security.KeyPairGenerator + put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC"); + put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA"); + + // java.security.KeyFactory + putKeyFactoryImpl("EC"); + putKeyFactoryImpl("RSA"); + + // javax.crypto.KeyGenerator + put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES"); + put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA1"); + put("KeyGenerator.HmacSHA224", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA224"); + put("KeyGenerator.HmacSHA256", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA256"); + put("KeyGenerator.HmacSHA384", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA384"); + put("KeyGenerator.HmacSHA512", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA512"); + + if (supports3DES) { + put("KeyGenerator.DESede", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$DESede"); + } + + // java.security.SecretKeyFactory + putSecretKeyFactoryImpl("AES"); + if (supports3DES) { + putSecretKeyFactoryImpl("DESede"); + } + putSecretKeyFactoryImpl("HmacSHA1"); + putSecretKeyFactoryImpl("HmacSHA224"); + putSecretKeyFactoryImpl("HmacSHA256"); + putSecretKeyFactoryImpl("HmacSHA384"); + putSecretKeyFactoryImpl("HmacSHA512"); + } + + /** + * Installs a new instance of this provider (and the + * {@link AndroidKeyStoreBCWorkaroundProvider}). + * @hide + */ + public static void install() { + Provider[] providers = Security.getProviders(); + int bcProviderIndex = -1; + for (int i = 0; i < providers.length; i++) { + Provider provider = providers[i]; + if ("BC".equals(provider.getName())) { + bcProviderIndex = i; + break; + } + } + + Security.addProvider(new AndroidKeyStoreProvider()); + Provider workaroundProvider = new AndroidKeyStoreBCWorkaroundProvider(); + if (bcProviderIndex != -1) { + // Bouncy Castle provider found -- install the workaround provider above it. + // insertProviderAt uses 1-based positions. + Security.insertProviderAt(workaroundProvider, bcProviderIndex + 1); + } else { + // Bouncy Castle provider not found -- install the workaround provider at lowest + // priority. + Security.addProvider(workaroundProvider); + } + } + + private void putSecretKeyFactoryImpl(String algorithm) { + put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreSecretKeyFactorySpi"); + } + + private void putKeyFactoryImpl(String algorithm) { + put("KeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreKeyFactorySpi"); + } + + /** + * Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto + * primitive. + * + *

The following primitives are supported: {@link Cipher} and {@link Mac}. + * + * @return KeyStore operation handle or {@code 0} if the provided primitive's KeyStore operation + * is not in progress. + * + * @throws IllegalArgumentException if the provided primitive is not supported or is not backed + * by AndroidKeyStore provider. + * @throws IllegalStateException if the provided primitive is not initialized. + * @hide + */ + @UnsupportedAppUsage + public static long getKeyStoreOperationHandle(Object cryptoPrimitive) { + if (cryptoPrimitive == null) { + throw new NullPointerException(); + } + Object spi; + if (cryptoPrimitive instanceof Signature) { + spi = ((Signature) cryptoPrimitive).getCurrentSpi(); + } else if (cryptoPrimitive instanceof Mac) { + spi = ((Mac) cryptoPrimitive).getCurrentSpi(); + } else if (cryptoPrimitive instanceof Cipher) { + spi = ((Cipher) cryptoPrimitive).getCurrentSpi(); + } else { + throw new IllegalArgumentException("Unsupported crypto primitive: " + cryptoPrimitive + + ". Supported: Signature, Mac, Cipher"); + } + if (spi == null) { + throw new IllegalStateException("Crypto primitive not initialized"); + } else if (!(spi instanceof KeyStoreCryptoOperation)) { + throw new IllegalArgumentException( + "Crypto primitive not backed by AndroidKeyStore provider: " + cryptoPrimitive + + ", spi: " + spi); + } + return ((KeyStoreCryptoOperation) spi).getOperationHandle(); + } + + /** @hide **/ + @NonNull + public static AndroidKeyStorePublicKey getAndroidKeyStorePublicKey( + @NonNull String alias, + int uid, + @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, uid, (ECPublicKey) publicKey); + } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { + return new AndroidKeyStoreRSAPublicKey(alias, uid, (RSAPublicKey) publicKey); + } else { + throw new ProviderException("Unsupported Android Keystore public key algorithm: " + + keyAlgorithm); + } + } + + @NonNull + private static AndroidKeyStorePrivateKey getAndroidKeyStorePrivateKey( + @NonNull AndroidKeyStorePublicKey publicKey) { + String keyAlgorithm = publicKey.getAlgorithm(); + if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { + return new AndroidKeyStoreECPrivateKey( + publicKey.getAlias(), publicKey.getUid(), ((ECKey) publicKey).getParams()); + } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { + return new AndroidKeyStoreRSAPrivateKey( + publicKey.getAlias(), publicKey.getUid(), ((RSAKey) publicKey).getModulus()); + } else { + throw new ProviderException("Unsupported Android Keystore public key algorithm: " + + keyAlgorithm); + } + } + + @NonNull + private static KeyCharacteristics getKeyCharacteristics(@NonNull KeyStore keyStore, + @NonNull String alias, int uid) + throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { + KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); + int errorCode = keyStore.getKeyCharacteristics( + alias, null, null, uid, keyCharacteristics); + if (errorCode == KeyStore.KEY_PERMANENTLY_INVALIDATED) { + throw (KeyPermanentlyInvalidatedException) + new KeyPermanentlyInvalidatedException( + "User changed or deleted their auth credentials", + KeyStore.getKeyStoreException(errorCode)); + } + if (errorCode != KeyStore.NO_ERROR) { + throw (UnrecoverableKeyException) + new UnrecoverableKeyException("Failed to obtain information about key") + .initCause(KeyStore.getKeyStoreException(errorCode)); + } + return keyCharacteristics; + } + + @NonNull + private static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore( + @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid, + KeyCharacteristics keyCharacteristics) + throws UnrecoverableKeyException { + ExportResult exportResult = keyStore.exportKey( + privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null, uid); + if (exportResult.resultCode != KeyStore.NO_ERROR) { + throw (UnrecoverableKeyException) + new UnrecoverableKeyException("Failed to obtain X.509 form of public key") + .initCause(KeyStore.getKeyStoreException(exportResult.resultCode)); + } + final byte[] x509EncodedPublicKey = exportResult.exportData; + + Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM); + if (keymasterAlgorithm == null) { + 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, uid, jcaKeyAlgorithm, x509EncodedPublicKey); + } + + /** @hide **/ + @NonNull + public static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore( + @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid) + throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { + return loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias, uid, + getKeyCharacteristics(keyStore, privateKeyAlias, uid)); + } + + @NonNull + private static KeyPair loadAndroidKeyStoreKeyPairFromKeystore( + @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid, + @NonNull KeyCharacteristics keyCharacteristics) + throws UnrecoverableKeyException { + AndroidKeyStorePublicKey publicKey = + loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias, uid, + keyCharacteristics); + AndroidKeyStorePrivateKey privateKey = + AndroidKeyStoreProvider.getAndroidKeyStorePrivateKey(publicKey); + return new KeyPair(publicKey, privateKey); + } + + /** @hide **/ + @NonNull + public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore( + @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid) + throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { + return loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias, uid, + getKeyCharacteristics(keyStore, privateKeyAlias, uid)); + } + + @NonNull + private static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore( + @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid, + @NonNull KeyCharacteristics keyCharacteristics) + throws UnrecoverableKeyException { + KeyPair keyPair = loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias, uid, + keyCharacteristics); + return (AndroidKeyStorePrivateKey) keyPair.getPrivate(); + } + + /** @hide **/ + @NonNull + public static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore( + @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid) + throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { + return loadAndroidKeyStorePrivateKeyFromKeystore(keyStore, privateKeyAlias, uid, + getKeyCharacteristics(keyStore, privateKeyAlias, uid)); + } + + @NonNull + private static AndroidKeyStoreSecretKey loadAndroidKeyStoreSecretKeyFromKeystore( + @NonNull String secretKeyAlias, int uid, @NonNull KeyCharacteristics keyCharacteristics) + throws UnrecoverableKeyException { + Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM); + if (keymasterAlgorithm == null) { + throw new UnrecoverableKeyException("Key algorithm unknown"); + } + + List keymasterDigests = keyCharacteristics.getEnums(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, uid, keyAlgorithmString); + } + + /** @hide **/ + @NonNull + public static AndroidKeyStoreKey loadAndroidKeyStoreKeyFromKeystore( + @NonNull KeyStore keyStore, @NonNull String userKeyAlias, int uid) + throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { + KeyCharacteristics keyCharacteristics = getKeyCharacteristics(keyStore, userKeyAlias, uid); + + Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM); + if (keymasterAlgorithm == null) { + throw new UnrecoverableKeyException("Key algorithm unknown"); + } + + if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC || + keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES || + keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_3DES) { + return loadAndroidKeyStoreSecretKeyFromKeystore(userKeyAlias, uid, + keyCharacteristics); + } else if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_RSA || + keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) { + return loadAndroidKeyStorePrivateKeyFromKeystore(keyStore, userKeyAlias, uid, + keyCharacteristics); + } else { + throw new UnrecoverableKeyException("Key algorithm unknown"); + } + } + + /** + * Returns an {@code AndroidKeyStore} {@link java.security.KeyStore}} of the specified UID. + * The {@code KeyStore} contains keys and certificates owned by that UID. Such cross-UID + * access is permitted to a few system UIDs and only to a few other UIDs (e.g., Wi-Fi, VPN) + * all of which are system. + * + *

Note: the returned {@code KeyStore} is already initialized/loaded. Thus, there is + * no need to invoke {@code load} on it. + * + * @param uid Uid for which the keystore provider is requested. + * @throws KeyStoreException if a KeyStoreSpi implementation for the specified type is not + * available from the specified provider. + * @throws NoSuchProviderException If the specified provider is not registered in the security + * provider list. + * @hide + */ + @SystemApi + @NonNull + public static java.security.KeyStore getKeyStoreForUid(int uid) + throws KeyStoreException, NoSuchProviderException { + java.security.KeyStore result = + java.security.KeyStore.getInstance("AndroidKeyStore", PROVIDER_NAME); + try { + result.load(new AndroidKeyStoreLoadStoreParameter(uid)); + } catch (NoSuchAlgorithmException | CertificateException | IOException e) { + throw new KeyStoreException( + "Failed to load AndroidKeyStore KeyStore for UID " + uid, e); + } + return result; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java new file mode 100644 index 000000000000..a030efb64ff6 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java @@ -0,0 +1,73 @@ +/* + * 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.keystore2; + +import android.security.keystore.ArrayUtils; + +import java.security.PublicKey; +import java.util.Arrays; + +/** + * {@link PublicKey} backed by Android Keystore. + * + * @hide + */ +public class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implements PublicKey { + + private final byte[] mEncoded; + + public AndroidKeyStorePublicKey(String alias, int uid, String algorithm, byte[] x509EncodedForm) { + super(alias, uid, algorithm); + mEncoded = ArrayUtils.cloneIfNotEmpty(x509EncodedForm); + } + + @Override + public String getFormat() { + return "X.509"; + } + + @Override + 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/keystore2/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java new file mode 100644 index 000000000000..c9c0b0de3463 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java @@ -0,0 +1,517 @@ +/* + * 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.keystore2; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.security.KeyStore; +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyProperties; +import android.security.keystore.KeymasterUtils; + +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.security.spec.MGF1ParameterSpec; + +import javax.crypto.Cipher; +import javax.crypto.CipherSpi; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; + +/** + * Base class for {@link CipherSpi} providing Android KeyStore backed RSA encryption/decryption. + * + * @hide + */ +abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase { + + /** + * Raw RSA cipher without any padding. + */ + public static final class NoPadding extends AndroidKeyStoreRSACipherSpi { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + + @Override + protected boolean adjustConfigForEncryptingWithPrivateKey() { + // RSA encryption with no padding using private key is a way to implement raw RSA + // signatures which JCA does not expose via Signature. We thus have to support this. + setKeymasterPurposeOverride(KeymasterDefs.KM_PURPOSE_SIGN); + return true; + } + + @Override + protected void initAlgorithmSpecificParameters() throws InvalidKeyException {} + + @Override + protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + if (params != null) { + throw new InvalidAlgorithmParameterException( + "Unexpected parameters: " + params + ". No parameters supported"); + } + } + + @Override + protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + + if (params != null) { + throw new InvalidAlgorithmParameterException( + "Unexpected parameters: " + params + ". No parameters supported"); + } + } + + @Override + protected AlgorithmParameters engineGetParameters() { + return null; + } + + @Override + protected final int getAdditionalEntropyAmountForBegin() { + return 0; + } + + @Override + protected final int getAdditionalEntropyAmountForFinish() { + return 0; + } + } + + /** + * RSA cipher with PKCS#1 v1.5 encryption padding. + */ + public static final class PKCS1Padding extends AndroidKeyStoreRSACipherSpi { + public PKCS1Padding() { + super(KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT); + } + + @Override + protected boolean adjustConfigForEncryptingWithPrivateKey() { + // RSA encryption with PCKS#1 padding using private key is a way to implement RSA + // signatures with PKCS#1 padding. We have to support this for legacy reasons. + setKeymasterPurposeOverride(KeymasterDefs.KM_PURPOSE_SIGN); + setKeymasterPaddingOverride(KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN); + return true; + } + + @Override + protected void initAlgorithmSpecificParameters() throws InvalidKeyException {} + + @Override + protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + if (params != null) { + throw new InvalidAlgorithmParameterException( + "Unexpected parameters: " + params + ". No parameters supported"); + } + } + + @Override + protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + + if (params != null) { + throw new InvalidAlgorithmParameterException( + "Unexpected parameters: " + params + ". No parameters supported"); + } + } + + @Override + protected AlgorithmParameters engineGetParameters() { + return null; + } + + @Override + protected final int getAdditionalEntropyAmountForBegin() { + return 0; + } + + @Override + protected final int getAdditionalEntropyAmountForFinish() { + return (isEncrypting()) ? getModulusSizeBytes() : 0; + } + } + + /** + * RSA cipher with OAEP encryption padding. Only SHA-1 based MGF1 is supported as MGF. + */ + abstract static class OAEPWithMGF1Padding extends AndroidKeyStoreRSACipherSpi { + + private static final String MGF_ALGORITGM_MGF1 = "MGF1"; + + private int mKeymasterDigest = -1; + private int mDigestOutputSizeBytes; + + OAEPWithMGF1Padding(int keymasterDigest) { + super(KeymasterDefs.KM_PAD_RSA_OAEP); + mKeymasterDigest = keymasterDigest; + mDigestOutputSizeBytes = + (KeymasterUtils.getDigestOutputSizeBits(keymasterDigest) + 7) / 8; + } + + @Override + protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {} + + @Override + protected final void initAlgorithmSpecificParameters( + @Nullable AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException { + if (params == null) { + return; + } + + if (!(params instanceof OAEPParameterSpec)) { + throw new InvalidAlgorithmParameterException( + "Unsupported parameter spec: " + params + + ". Only OAEPParameterSpec supported"); + } + OAEPParameterSpec spec = (OAEPParameterSpec) params; + if (!MGF_ALGORITGM_MGF1.equalsIgnoreCase(spec.getMGFAlgorithm())) { + throw new InvalidAlgorithmParameterException( + "Unsupported MGF: " + spec.getMGFAlgorithm() + + ". Only " + MGF_ALGORITGM_MGF1 + " supported"); + } + String jcaDigest = spec.getDigestAlgorithm(); + int keymasterDigest; + try { + keymasterDigest = KeyProperties.Digest.toKeymaster(jcaDigest); + } catch (IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException( + "Unsupported digest: " + jcaDigest, e); + } + switch (keymasterDigest) { + case KeymasterDefs.KM_DIGEST_SHA1: + case KeymasterDefs.KM_DIGEST_SHA_2_224: + case KeymasterDefs.KM_DIGEST_SHA_2_256: + case KeymasterDefs.KM_DIGEST_SHA_2_384: + case KeymasterDefs.KM_DIGEST_SHA_2_512: + // Permitted. + break; + default: + throw new InvalidAlgorithmParameterException( + "Unsupported digest: " + jcaDigest); + } + AlgorithmParameterSpec mgfParams = spec.getMGFParameters(); + if (mgfParams == null) { + throw new InvalidAlgorithmParameterException("MGF parameters must be provided"); + } + // Check whether MGF parameters match the OAEPParameterSpec + if (!(mgfParams instanceof MGF1ParameterSpec)) { + throw new InvalidAlgorithmParameterException("Unsupported MGF parameters" + + ": " + mgfParams + ". Only MGF1ParameterSpec supported"); + } + MGF1ParameterSpec mgfSpec = (MGF1ParameterSpec) mgfParams; + String mgf1JcaDigest = mgfSpec.getDigestAlgorithm(); + if (!KeyProperties.DIGEST_SHA1.equalsIgnoreCase(mgf1JcaDigest)) { + throw new InvalidAlgorithmParameterException( + "Unsupported MGF1 digest: " + mgf1JcaDigest + + ". Only " + KeyProperties.DIGEST_SHA1 + " supported"); + } + PSource pSource = spec.getPSource(); + if (!(pSource instanceof PSource.PSpecified)) { + throw new InvalidAlgorithmParameterException( + "Unsupported source of encoding input P: " + pSource + + ". Only pSpecifiedEmpty (PSource.PSpecified.DEFAULT) supported"); + } + PSource.PSpecified pSourceSpecified = (PSource.PSpecified) pSource; + byte[] pSourceValue = pSourceSpecified.getValue(); + if ((pSourceValue != null) && (pSourceValue.length > 0)) { + throw new InvalidAlgorithmParameterException( + "Unsupported source of encoding input P: " + pSource + + ". Only pSpecifiedEmpty (PSource.PSpecified.DEFAULT) supported"); + } + mKeymasterDigest = keymasterDigest; + mDigestOutputSizeBytes = + (KeymasterUtils.getDigestOutputSizeBits(keymasterDigest) + 7) / 8; + } + + @Override + protected final void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + if (params == null) { + return; + } + + OAEPParameterSpec spec; + try { + spec = params.getParameterSpec(OAEPParameterSpec.class); + } catch (InvalidParameterSpecException e) { + throw new InvalidAlgorithmParameterException("OAEP parameters required" + + ", but not found in parameters: " + params, e); + } + if (spec == null) { + throw new InvalidAlgorithmParameterException("OAEP parameters required" + + ", but not provided in parameters: " + params); + } + initAlgorithmSpecificParameters(spec); + } + + @Override + protected final AlgorithmParameters engineGetParameters() { + OAEPParameterSpec spec = + new OAEPParameterSpec( + KeyProperties.Digest.fromKeymaster(mKeymasterDigest), + MGF_ALGORITGM_MGF1, + MGF1ParameterSpec.SHA1, + PSource.PSpecified.DEFAULT); + try { + AlgorithmParameters params = AlgorithmParameters.getInstance("OAEP"); + params.init(spec); + return params; + } catch (NoSuchAlgorithmException e) { + throw new ProviderException( + "Failed to obtain OAEP AlgorithmParameters", e); + } catch (InvalidParameterSpecException e) { + throw new ProviderException( + "Failed to initialize OAEP AlgorithmParameters with an IV", + e); + } + } + + @Override + protected final void addAlgorithmSpecificParametersToBegin( + KeymasterArguments keymasterArgs) { + super.addAlgorithmSpecificParametersToBegin(keymasterArgs); + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); + } + + @Override + protected final void loadAlgorithmSpecificParametersFromBeginResult( + @NonNull KeymasterArguments keymasterArgs) { + super.loadAlgorithmSpecificParametersFromBeginResult(keymasterArgs); + } + + @Override + protected final int getAdditionalEntropyAmountForBegin() { + return 0; + } + + @Override + protected final int getAdditionalEntropyAmountForFinish() { + return (isEncrypting()) ? mDigestOutputSizeBytes : 0; + } + } + + public static class OAEPWithSHA1AndMGF1Padding extends OAEPWithMGF1Padding { + public OAEPWithSHA1AndMGF1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA1); + } + } + + public static class OAEPWithSHA224AndMGF1Padding extends OAEPWithMGF1Padding { + public OAEPWithSHA224AndMGF1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_224); + } + } + + public static class OAEPWithSHA256AndMGF1Padding extends OAEPWithMGF1Padding { + public OAEPWithSHA256AndMGF1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_256); + } + } + + public static class OAEPWithSHA384AndMGF1Padding extends OAEPWithMGF1Padding { + public OAEPWithSHA384AndMGF1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_384); + } + } + + public static class OAEPWithSHA512AndMGF1Padding extends OAEPWithMGF1Padding { + public OAEPWithSHA512AndMGF1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_512); + } + } + + private final int mKeymasterPadding; + private int mKeymasterPaddingOverride; + + private int mModulusSizeBytes = -1; + + AndroidKeyStoreRSACipherSpi(int keymasterPadding) { + mKeymasterPadding = keymasterPadding; + } + + @Override + protected final void initKey(int opmode, Key key) throws InvalidKeyException { + if (key == null) { + throw new InvalidKeyException("Unsupported key: null"); + } + if (!KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(key.getAlgorithm())) { + throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm() + + ". Only " + KeyProperties.KEY_ALGORITHM_RSA + " supported"); + } + AndroidKeyStoreKey keystoreKey; + if (key instanceof AndroidKeyStorePrivateKey) { + keystoreKey = (AndroidKeyStoreKey) key; + } else if (key instanceof AndroidKeyStorePublicKey) { + keystoreKey = (AndroidKeyStoreKey) key; + } else { + throw new InvalidKeyException("Unsupported key type: " + key); + } + + if (keystoreKey instanceof PrivateKey) { + // Private key + switch (opmode) { + case Cipher.DECRYPT_MODE: + case Cipher.UNWRAP_MODE: + // Permitted + break; + case Cipher.ENCRYPT_MODE: + case Cipher.WRAP_MODE: + if (!adjustConfigForEncryptingWithPrivateKey()) { + throw new InvalidKeyException( + "RSA private keys cannot be used with " + opmodeToString(opmode) + + " and padding " + + KeyProperties.EncryptionPadding.fromKeymaster(mKeymasterPadding) + + ". Only RSA public keys supported for this mode"); + } + break; + default: + throw new InvalidKeyException( + "RSA private keys cannot be used with opmode: " + opmode); + } + } else { + // Public key + switch (opmode) { + case Cipher.ENCRYPT_MODE: + case Cipher.WRAP_MODE: + // Permitted + break; + case Cipher.DECRYPT_MODE: + case Cipher.UNWRAP_MODE: + throw new InvalidKeyException( + "RSA public keys cannot be used with " + opmodeToString(opmode) + + " and padding " + + KeyProperties.EncryptionPadding.fromKeymaster(mKeymasterPadding) + + ". Only RSA private keys supported for this opmode."); + // break; + default: + throw new InvalidKeyException( + "RSA public keys cannot be used with " + opmodeToString(opmode)); + } + } + + KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); + int errorCode = getKeyStore().getKeyCharacteristics( + keystoreKey.getAlias(), null, null, keystoreKey.getUid(), keyCharacteristics); + if (errorCode != KeyStore.NO_ERROR) { + throw getKeyStore().getInvalidKeyException( + keystoreKey.getAlias(), keystoreKey.getUid(), errorCode); + } + long keySizeBits = keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, -1); + if (keySizeBits == -1) { + throw new InvalidKeyException("Size of key not known"); + } else if (keySizeBits > Integer.MAX_VALUE) { + throw new InvalidKeyException("Key too large: " + keySizeBits + " bits"); + } + mModulusSizeBytes = (int) ((keySizeBits + 7) / 8); + + setKey(keystoreKey); + } + + /** + * Adjusts the configuration of this cipher for encrypting using the private key. + * + *

The default implementation does nothing and refuses to adjust the configuration. + * + * @return {@code true} if the configuration has been adjusted, {@code false} if encrypting + * using private key is not permitted for this cipher. + */ + protected boolean adjustConfigForEncryptingWithPrivateKey() { + return false; + } + + @Override + protected final void resetAll() { + mModulusSizeBytes = -1; + mKeymasterPaddingOverride = -1; + super.resetAll(); + } + + @Override + protected final void resetWhilePreservingInitState() { + super.resetWhilePreservingInitState(); + } + + @Override + protected void addAlgorithmSpecificParametersToBegin( + @NonNull KeymasterArguments keymasterArgs) { + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA); + int keymasterPadding = getKeymasterPaddingOverride(); + if (keymasterPadding == -1) { + keymasterPadding = mKeymasterPadding; + } + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, keymasterPadding); + int purposeOverride = getKeymasterPurposeOverride(); + if ((purposeOverride != -1) + && ((purposeOverride == KeymasterDefs.KM_PURPOSE_SIGN) + || (purposeOverride == KeymasterDefs.KM_PURPOSE_VERIFY))) { + // Keymaster sign/verify requires digest to be specified. For raw sign/verify it's NONE. + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE); + } + } + + @Override + protected void loadAlgorithmSpecificParametersFromBeginResult( + @NonNull KeymasterArguments keymasterArgs) { + } + + @Override + protected final int engineGetBlockSize() { + // Not a block cipher, according to the RI + return 0; + } + + @Override + protected final byte[] engineGetIV() { + // IV never used + return null; + } + + @Override + protected final int engineGetOutputSize(int inputLen) { + return getModulusSizeBytes(); + } + + protected final int getModulusSizeBytes() { + if (mModulusSizeBytes == -1) { + throw new IllegalStateException("Not initialized"); + } + return mModulusSizeBytes; + } + + /** + * Overrides the default padding of the crypto operation. + */ + protected final void setKeymasterPaddingOverride(int keymasterPadding) { + mKeymasterPaddingOverride = keymasterPadding; + } + + protected final int getKeymasterPaddingOverride() { + return mKeymasterPaddingOverride; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPrivateKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPrivateKey.java new file mode 100644 index 000000000000..4c1231b674c0 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPrivateKey.java @@ -0,0 +1,43 @@ +/* + * 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.keystore2; + +import android.security.keystore.KeyProperties; + +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, int uid, BigInteger modulus) { + super(alias, uid, KeyProperties.KEY_ALGORITHM_RSA); + mModulus = modulus; + } + + @Override + public BigInteger getModulus() { + return mModulus; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java new file mode 100644 index 000000000000..7a59abb38719 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java @@ -0,0 +1,57 @@ +/* + * 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.keystore2; + +import android.security.keystore.KeyProperties; + +import java.math.BigInteger; +import java.security.interfaces.RSAPublicKey; + +/** + * {@link RSAPublicKey} backed by Android Keystore. + * + * @hide + */ +public class AndroidKeyStoreRSAPublicKey extends AndroidKeyStorePublicKey implements RSAPublicKey { + private final BigInteger mModulus; + private final BigInteger mPublicExponent; + + public AndroidKeyStoreRSAPublicKey(String alias, int uid, byte[] x509EncodedForm, BigInteger modulus, + BigInteger publicExponent) { + super(alias, uid, KeyProperties.KEY_ALGORITHM_RSA, x509EncodedForm); + mModulus = modulus; + mPublicExponent = publicExponent; + } + + public AndroidKeyStoreRSAPublicKey(String alias, int uid, RSAPublicKey info) { + this(alias, uid, info.getEncoded(), info.getModulus(), info.getPublicExponent()); + if (!"X.509".equalsIgnoreCase(info.getFormat())) { + throw new IllegalArgumentException( + "Unsupported key export format: " + info.getFormat()); + } + } + + @Override + public BigInteger getModulus() { + return mModulus; + } + + @Override + public BigInteger getPublicExponent() { + return mPublicExponent; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java new file mode 100644 index 000000000000..6b2c098810e4 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java @@ -0,0 +1,166 @@ +/* + * 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.keystore2; + +import android.annotation.NonNull; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyProperties; + +import java.security.InvalidKeyException; +import java.security.SignatureSpi; + +/** + * Base class for {@link SignatureSpi} providing Android KeyStore backed RSA signatures. + * + * @hide + */ +abstract class AndroidKeyStoreRSASignatureSpi extends + AndroidKeyStoreSignatureSpiBase { + + abstract static class PKCS1Padding extends AndroidKeyStoreRSASignatureSpi { + PKCS1Padding(int keymasterDigest) { + super(keymasterDigest, KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN); + } + + @Override + protected final int getAdditionalEntropyAmountForSign() { + // No entropy required for this deterministic signature scheme. + return 0; + } + } + + public static final class NONEWithPKCS1Padding extends PKCS1Padding { + public NONEWithPKCS1Padding() { + super(KeymasterDefs.KM_DIGEST_NONE); + } + } + + public static final class MD5WithPKCS1Padding extends PKCS1Padding { + public MD5WithPKCS1Padding() { + super(KeymasterDefs.KM_DIGEST_MD5); + } + } + + public static final class SHA1WithPKCS1Padding extends PKCS1Padding { + public SHA1WithPKCS1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA1); + } + } + + public static final class SHA224WithPKCS1Padding extends PKCS1Padding { + public SHA224WithPKCS1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_224); + } + } + + public static final class SHA256WithPKCS1Padding extends PKCS1Padding { + public SHA256WithPKCS1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_256); + } + } + + public static final class SHA384WithPKCS1Padding extends PKCS1Padding { + public SHA384WithPKCS1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_384); + } + } + + public static final class SHA512WithPKCS1Padding extends PKCS1Padding { + public SHA512WithPKCS1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_512); + } + } + + abstract static class PSSPadding extends AndroidKeyStoreRSASignatureSpi { + private static final int SALT_LENGTH_BYTES = 20; + + PSSPadding(int keymasterDigest) { + super(keymasterDigest, KeymasterDefs.KM_PAD_RSA_PSS); + } + + @Override + protected final int getAdditionalEntropyAmountForSign() { + return SALT_LENGTH_BYTES; + } + } + + public static final class SHA1WithPSSPadding extends PSSPadding { + public SHA1WithPSSPadding() { + super(KeymasterDefs.KM_DIGEST_SHA1); + } + } + + public static final class SHA224WithPSSPadding extends PSSPadding { + public SHA224WithPSSPadding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_224); + } + } + + public static final class SHA256WithPSSPadding extends PSSPadding { + public SHA256WithPSSPadding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_256); + } + } + + public static final class SHA384WithPSSPadding extends PSSPadding { + public SHA384WithPSSPadding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_384); + } + } + + public static final class SHA512WithPSSPadding extends PSSPadding { + public SHA512WithPSSPadding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_512); + } + } + + private final int mKeymasterDigest; + private final int mKeymasterPadding; + + AndroidKeyStoreRSASignatureSpi(int keymasterDigest, int keymasterPadding) { + mKeymasterDigest = keymasterDigest; + mKeymasterPadding = keymasterPadding; + } + + @Override + protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException { + if (!KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(key.getAlgorithm())) { + throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm() + + ". Only" + KeyProperties.KEY_ALGORITHM_RSA + " supported"); + } + super.initKey(key); + } + + @Override + protected final void resetAll() { + super.resetAll(); + } + + @Override + protected final void resetWhilePreservingInitState() { + super.resetWhilePreservingInitState(); + } + + @Override + protected final void addAlgorithmSpecificParametersToBegin( + @NonNull KeymasterArguments keymasterArgs) { + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA); + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding); + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKey.java new file mode 100644 index 000000000000..8adf27a6189a --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKey.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore2; + +import javax.crypto.SecretKey; + +/** + * {@link SecretKey} backed by Android Keystore. + * + * @hide + */ +public class AndroidKeyStoreSecretKey extends AndroidKeyStoreKey implements SecretKey { + + public AndroidKeyStoreSecretKey(String alias, int uid, String algorithm) { + super(alias, uid, algorithm); + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java new file mode 100644 index 000000000000..c2a8e10c1e51 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java @@ -0,0 +1,249 @@ +/* + * 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.keystore2; + +import android.security.Credentials; +import android.security.GateKeeper; +import android.security.KeyStore; +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyInfo; + +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.ProviderException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactorySpi; +import javax.crypto.spec.SecretKeySpec; + +/** + * {@link SecretKeyFactorySpi} backed by Android Keystore. + * + * @hide + */ +public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { + + private final KeyStore mKeyStore = KeyStore.getInstance(); + + @Override + protected KeySpec engineGetKeySpec(SecretKey key, + @SuppressWarnings("rawtypes") Class keySpecClass) throws InvalidKeySpecException { + if (keySpecClass == null) { + throw new InvalidKeySpecException("keySpecClass == null"); + } + if (!(key instanceof AndroidKeyStoreSecretKey)) { + throw new InvalidKeySpecException("Only Android KeyStore secret keys supported: " + + ((key != null) ? key.getClass().getName() : "null")); + } + if (SecretKeySpec.class.isAssignableFrom(keySpecClass)) { + throw new InvalidKeySpecException( + "Key material export of Android KeyStore keys is not supported"); + } + if (!KeyInfo.class.equals(keySpecClass)) { + throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName()); + } + AndroidKeyStoreKey keystoreKey = (AndroidKeyStoreKey) key; + String keyAliasInKeystore = keystoreKey.getAlias(); + String entryAlias; + if (keyAliasInKeystore.startsWith(Credentials.USER_PRIVATE_KEY)) { + entryAlias = keyAliasInKeystore.substring(Credentials.USER_PRIVATE_KEY.length()); + } else if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)){ + // key has legacy prefix + entryAlias = keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length()); + } else { + throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore); + } + + return getKeyInfo(mKeyStore, entryAlias, keyAliasInKeystore, keystoreKey.getUid()); + } + + static KeyInfo getKeyInfo(KeyStore keyStore, String entryAlias, String keyAliasInKeystore, + int keyUid) { + KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); + int errorCode = keyStore.getKeyCharacteristics( + keyAliasInKeystore, null, null, keyUid, keyCharacteristics); + if (errorCode != KeyStore.NO_ERROR) { + throw new ProviderException("Failed to obtain information about key." + + " Keystore error: " + errorCode); + } + + boolean insideSecureHardware; + @KeyProperties.OriginEnum int origin; + int keySize; + @KeyProperties.PurposeEnum int purposes; + String[] encryptionPaddings; + String[] signaturePaddings; + @KeyProperties.DigestEnum String[] digests; + @KeyProperties.BlockModeEnum String[] blockModes; + int keymasterSwEnforcedUserAuthenticators; + int keymasterHwEnforcedUserAuthenticators; + List keymasterSecureUserIds; + try { + if (keyCharacteristics.hwEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) { + insideSecureHardware = true; + origin = KeyProperties.Origin.fromKeymaster( + keyCharacteristics.hwEnforced.getEnum(KeymasterDefs.KM_TAG_ORIGIN, -1)); + } else if (keyCharacteristics.swEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) { + insideSecureHardware = false; + origin = KeyProperties.Origin.fromKeymaster( + keyCharacteristics.swEnforced.getEnum(KeymasterDefs.KM_TAG_ORIGIN, -1)); + } else { + throw new ProviderException("Key origin not available"); + } + long keySizeUnsigned = + keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, -1); + if (keySizeUnsigned == -1) { + throw new ProviderException("Key size not available"); + } else if (keySizeUnsigned > Integer.MAX_VALUE) { + throw new ProviderException("Key too large: " + keySizeUnsigned + " bits"); + } + keySize = (int) keySizeUnsigned; + purposes = KeyProperties.Purpose.allFromKeymaster( + keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_PURPOSE)); + + List encryptionPaddingsList = new ArrayList(); + List signaturePaddingsList = new ArrayList(); + // Keymaster stores both types of paddings in the same array -- we split it into two. + for (int keymasterPadding : keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_PADDING)) { + try { + @KeyProperties.EncryptionPaddingEnum String jcaPadding = + KeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding); + encryptionPaddingsList.add(jcaPadding); + } catch (IllegalArgumentException e) { + try { + @KeyProperties.SignaturePaddingEnum String padding = + KeyProperties.SignaturePadding.fromKeymaster(keymasterPadding); + signaturePaddingsList.add(padding); + } catch (IllegalArgumentException e2) { + throw new ProviderException( + "Unsupported encryption padding: " + keymasterPadding); + } + } + + } + encryptionPaddings = + encryptionPaddingsList.toArray(new String[encryptionPaddingsList.size()]); + signaturePaddings = + signaturePaddingsList.toArray(new String[signaturePaddingsList.size()]); + + digests = KeyProperties.Digest.allFromKeymaster( + keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_DIGEST)); + blockModes = KeyProperties.BlockMode.allFromKeymaster( + keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_BLOCK_MODE)); + keymasterSwEnforcedUserAuthenticators = + keyCharacteristics.swEnforced.getEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); + keymasterHwEnforcedUserAuthenticators = + keyCharacteristics.hwEnforced.getEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); + keymasterSecureUserIds = + keyCharacteristics.getUnsignedLongs(KeymasterDefs.KM_TAG_USER_SECURE_ID); + } catch (IllegalArgumentException e) { + throw new ProviderException("Unsupported key characteristic", e); + } + + Date keyValidityStart = keyCharacteristics.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME); + Date keyValidityForOriginationEnd = + keyCharacteristics.getDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME); + Date keyValidityForConsumptionEnd = + keyCharacteristics.getDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME); + boolean userAuthenticationRequired = + !keyCharacteristics.getBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); + long userAuthenticationValidityDurationSeconds = + keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT, 0); + if (userAuthenticationValidityDurationSeconds > Integer.MAX_VALUE) { + throw new ProviderException("User authentication timeout validity too long: " + + userAuthenticationValidityDurationSeconds + " seconds"); + } + boolean userAuthenticationRequirementEnforcedBySecureHardware = (userAuthenticationRequired) + && (keymasterHwEnforcedUserAuthenticators != 0) + && (keymasterSwEnforcedUserAuthenticators == 0); + boolean userAuthenticationValidWhileOnBody = + keyCharacteristics.hwEnforced.getBoolean(KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY); + boolean trustedUserPresenceRequired = + keyCharacteristics.hwEnforced.getBoolean( + KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED); + + boolean invalidatedByBiometricEnrollment = false; + if (keymasterSwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_BIOMETRIC + || keymasterHwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_BIOMETRIC) { + // Fingerprint-only key; will be invalidated if the root SID isn't in the SID list. + invalidatedByBiometricEnrollment = keymasterSecureUserIds != null + && !keymasterSecureUserIds.isEmpty() + && !keymasterSecureUserIds.contains(getGateKeeperSecureUserId()); + } + + boolean userConfirmationRequired = keyCharacteristics.hwEnforced.getBoolean(KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED); + + return new KeyInfo(entryAlias, + insideSecureHardware, + origin, + keySize, + keyValidityStart, + keyValidityForOriginationEnd, + keyValidityForConsumptionEnd, + purposes, + encryptionPaddings, + signaturePaddings, + digests, + blockModes, + userAuthenticationRequired, + (int) userAuthenticationValidityDurationSeconds, + keymasterHwEnforcedUserAuthenticators, + userAuthenticationRequirementEnforcedBySecureHardware, + userAuthenticationValidWhileOnBody, + trustedUserPresenceRequired, + invalidatedByBiometricEnrollment, + userConfirmationRequired, + // Keystore 1.0 does not tell us the exact security level of the key + // so we assume TEE if the key is in secure hardware. + insideSecureHardware ? KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT + : KeyProperties.SecurityLevelEnum.SOFTWARE); + } + + private static BigInteger getGateKeeperSecureUserId() throws ProviderException { + try { + return BigInteger.valueOf(GateKeeper.getSecureUserId()); + } catch (IllegalStateException e) { + throw new ProviderException("Failed to get GateKeeper secure user ID", e); + } + } + + @Override + protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException { + throw new InvalidKeySpecException( + "To generate secret key in Android Keystore, use KeyGenerator initialized with " + + KeyGenParameterSpec.class.getName()); + } + + @Override + protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException { + if (key == null) { + throw new InvalidKeyException("key == null"); + } else if (!(key instanceof AndroidKeyStoreSecretKey)) { + throw new InvalidKeyException( + "To import a secret key into Android Keystore, use KeyStore.setEntry"); + } + + return key; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java new file mode 100644 index 000000000000..23818a784f89 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java @@ -0,0 +1,434 @@ +/* + * 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.keystore2; + +import android.annotation.CallSuper; +import android.annotation.NonNull; +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.ArrayUtils; +import android.security.keystore.KeyStoreConnectException; +import android.security.keystore.KeyStoreCryptoOperation; + +import libcore.util.EmptyArray; + +import java.nio.ByteBuffer; +import java.security.InvalidKeyException; +import java.security.InvalidParameterException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.SignatureException; +import java.security.SignatureSpi; + +/** + * Base class for {@link SignatureSpi} implementations of Android KeyStore backed ciphers. + * + * @hide + */ +abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi + implements KeyStoreCryptoOperation { + private final KeyStore mKeyStore; + + // Fields below are populated by SignatureSpi.engineInitSign/engineInitVerify and KeyStore.begin + // and should be preserved after SignatureSpi.engineSign/engineVerify finishes. + private boolean mSigning; + private AndroidKeyStoreKey mKey; + + /** + * Token referencing this operation inside keystore service. It is initialized by + * {@code engineInitSign}/{@code engineInitVerify} and is invalidated when + * {@code engineSign}/{@code engineVerify} succeeds and on some error conditions in between. + */ + private IBinder mOperationToken; + private long mOperationHandle; + private KeyStoreCryptoOperationStreamer mMessageStreamer; + + /** + * Encountered exception which could not be immediately thrown because it was encountered inside + * a method that does not throw checked exception. This exception will be thrown from + * {@code engineSign} or {@code engineVerify}. Once such an exception is encountered, + * {@code engineUpdate} starts ignoring input data. + */ + private Exception mCachedException; + + AndroidKeyStoreSignatureSpiBase() { + mKeyStore = KeyStore.getInstance(); + } + + @Override + protected final void engineInitSign(PrivateKey key) throws InvalidKeyException { + engineInitSign(key, null); + } + + @Override + protected final void engineInitSign(PrivateKey privateKey, SecureRandom random) + throws InvalidKeyException { + resetAll(); + + boolean success = false; + try { + if (privateKey == null) { + throw new InvalidKeyException("Unsupported key: null"); + } + AndroidKeyStoreKey keystoreKey; + if (privateKey instanceof AndroidKeyStorePrivateKey) { + keystoreKey = (AndroidKeyStoreKey) privateKey; + } else { + throw new InvalidKeyException("Unsupported private key type: " + privateKey); + } + mSigning = true; + initKey(keystoreKey); + appRandom = random; + ensureKeystoreOperationInitialized(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + @Override + protected final void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { + resetAll(); + + boolean success = false; + try { + if (publicKey == null) { + throw new InvalidKeyException("Unsupported key: null"); + } + AndroidKeyStoreKey keystoreKey; + if (publicKey instanceof AndroidKeyStorePublicKey) { + keystoreKey = (AndroidKeyStorePublicKey) publicKey; + } else { + throw new InvalidKeyException("Unsupported public key type: " + publicKey); + } + mSigning = false; + initKey(keystoreKey); + appRandom = null; + ensureKeystoreOperationInitialized(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + /** + * Configures this signature instance to use the provided key. + * + * @throws InvalidKeyException if the {@code key} is not suitable. + */ + @CallSuper + protected void initKey(AndroidKeyStoreKey key) throws InvalidKeyException { + mKey = key; + } + + /** + * Resets this cipher to its pristine pre-init state. This must be equivalent to obtaining a new + * cipher instance. + * + *

Subclasses storing additional state should override this method, reset the additional + * state, and then chain to superclass. + */ + @CallSuper + protected void resetAll() { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mOperationToken = null; + mKeyStore.abort(operationToken); + } + mSigning = false; + mKey = null; + appRandom = null; + mOperationToken = null; + mOperationHandle = 0; + mMessageStreamer = null; + mCachedException = null; + } + + /** + * Resets this cipher while preserving the initialized state. This must be equivalent to + * rolling back the cipher's state to just after the most recent {@code engineInit} completed + * successfully. + * + *

Subclasses storing additional post-init state should override this method, reset the + * additional state, and then chain to superclass. + */ + @CallSuper + protected void resetWhilePreservingInitState() { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mOperationToken = null; + mKeyStore.abort(operationToken); + } + mOperationHandle = 0; + mMessageStreamer = null; + mCachedException = null; + } + + private void ensureKeystoreOperationInitialized() throws InvalidKeyException { + if (mMessageStreamer != null) { + return; + } + if (mCachedException != null) { + return; + } + if (mKey == null) { + throw new IllegalStateException("Not initialized"); + } + + KeymasterArguments keymasterInputArgs = new KeymasterArguments(); + addAlgorithmSpecificParametersToBegin(keymasterInputArgs); + + 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, + null, // no additional entropy for begin -- only finish might need some + mKey.getUid()); + if (opResult == null) { + throw new KeyStoreConnectException(); + } + + // Store operation token and handle regardless of the error code returned by KeyStore to + // ensure that the operation gets aborted immediately if the code below throws an exception. + mOperationToken = opResult.token; + mOperationHandle = opResult.operationHandle; + + // If necessary, throw an exception due to KeyStore operation having failed. + InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit( + mKeyStore, mKey, opResult.resultCode); + if (e != null) { + throw e; + } + + if (mOperationToken == null) { + throw new ProviderException("Keystore returned null operation token"); + } + if (mOperationHandle == 0) { + throw new ProviderException("Keystore returned invalid operation handle"); + } + + mMessageStreamer = createMainDataStreamer(mKeyStore, opResult.token); + } + + /** + * Creates a streamer which sends the message to be signed/verified into the provided KeyStore + * + *

This implementation returns a working streamer. + */ + @NonNull + protected KeyStoreCryptoOperationStreamer createMainDataStreamer( + KeyStore keyStore, IBinder operationToken) { + return new KeyStoreCryptoOperationChunkedStreamer( + new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( + keyStore, operationToken)); + } + + @Override + public final long getOperationHandle() { + return mOperationHandle; + } + + @Override + protected final void engineUpdate(byte[] b, int off, int len) throws SignatureException { + if (mCachedException != null) { + throw new SignatureException(mCachedException); + } + + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException e) { + throw new SignatureException(e); + } + + if (len == 0) { + return; + } + + byte[] output; + try { + output = mMessageStreamer.update(b, off, len); + } catch (KeyStoreException e) { + throw new SignatureException(e); + } + + if (output.length != 0) { + throw new ProviderException( + "Update operation unexpectedly produced output: " + output.length + " bytes"); + } + } + + @Override + protected final void engineUpdate(byte b) throws SignatureException { + engineUpdate(new byte[] {b}, 0, 1); + } + + @Override + protected final void engineUpdate(ByteBuffer input) { + byte[] b; + int off; + int len = input.remaining(); + if (input.hasArray()) { + b = input.array(); + off = input.arrayOffset() + input.position(); + input.position(input.limit()); + } else { + b = new byte[len]; + off = 0; + input.get(b); + } + + try { + engineUpdate(b, off, len); + } catch (SignatureException e) { + mCachedException = e; + } + } + + @Override + protected final int engineSign(byte[] out, int outOffset, int outLen) + throws SignatureException { + return super.engineSign(out, outOffset, outLen); + } + + @Override + protected final byte[] engineSign() throws SignatureException { + if (mCachedException != null) { + throw new SignatureException(mCachedException); + } + + byte[] signature; + try { + ensureKeystoreOperationInitialized(); + + byte[] additionalEntropy = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + appRandom, getAdditionalEntropyAmountForSign()); + signature = mMessageStreamer.doFinal( + EmptyArray.BYTE, 0, 0, + null, // no signature provided -- it'll be generated by this invocation + additionalEntropy); + } catch (InvalidKeyException | KeyStoreException e) { + throw new SignatureException(e); + } + + resetWhilePreservingInitState(); + return signature; + } + + @Override + protected final boolean engineVerify(byte[] signature) throws SignatureException { + if (mCachedException != null) { + throw new SignatureException(mCachedException); + } + + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException e) { + throw new SignatureException(e); + } + + boolean verified; + try { + byte[] output = mMessageStreamer.doFinal( + EmptyArray.BYTE, 0, 0, + signature, + null // no additional entropy needed -- verification is deterministic + ); + if (output.length != 0) { + throw new ProviderException( + "Signature verification unexpected produced output: " + output.length + + " bytes"); + } + verified = true; + } catch (KeyStoreException e) { + switch (e.getErrorCode()) { + case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED: + verified = false; + break; + default: + throw new SignatureException(e); + } + } + + resetWhilePreservingInitState(); + return verified; + } + + @Override + protected final boolean engineVerify(byte[] sigBytes, int offset, int len) + throws SignatureException { + return engineVerify(ArrayUtils.subarray(sigBytes, offset, len)); + } + + @Deprecated + @Override + protected final Object engineGetParameter(String param) throws InvalidParameterException { + throw new InvalidParameterException(); + } + + @Deprecated + @Override + protected final void engineSetParameter(String param, Object value) + throws InvalidParameterException { + throw new InvalidParameterException(); + } + + protected final KeyStore getKeyStore() { + return mKeyStore; + } + + /** + * Returns {@code true} if this signature is initialized for signing, {@code false} if this + * signature is initialized for verification. + */ + protected final boolean isSigning() { + return mSigning; + } + + // The methods below need to be implemented by subclasses. + + /** + * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's + * {@code finish} operation when generating a signature. + * + *

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 getAdditionalEntropyAmountForSign(); + + /** + * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation. + * + * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific + * parameters. + */ + protected abstract void addAlgorithmSpecificParametersToBegin( + @NonNull KeymasterArguments keymasterArgs); +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java new file mode 100644 index 000000000000..96cfe411e73a --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java @@ -0,0 +1,1113 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore2; + +import android.security.Credentials; +import android.security.GateKeeper; +import android.security.KeyStore; +import android.security.KeyStoreParameter; +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.security.keystore.KeyProtection; +import android.security.keystore.KeymasterUtils; +import android.security.keystore.SecureKeyImportUnavailableException; +import android.security.keystore.WrappedKeyEntry; +import android.util.Log; + +import libcore.util.EmptyArray; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.Key; +import java.security.KeyStore.Entry; +import java.security.KeyStore.LoadStoreParameter; +import java.security.KeyStore.PrivateKeyEntry; +import java.security.KeyStore.ProtectionParameter; +import java.security.KeyStore.SecretKeyEntry; +import java.security.KeyStoreException; +import java.security.KeyStoreSpi; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import javax.crypto.SecretKey; + +/** + * A java.security.KeyStore interface for the Android KeyStore. An instance of + * it can be created via the {@link java.security.KeyStore#getInstance(String) + * KeyStore.getInstance("AndroidKeyStore")} interface. This returns a + * java.security.KeyStore backed by this "AndroidKeyStore" implementation. + *

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

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

+ * TrustedCertificateEntry will just have a Credentials.CA_CERTIFICATE entry + * with a single certificate. + * + * @hide + */ +public class AndroidKeyStoreSpi extends KeyStoreSpi { + public static final String NAME = "AndroidKeyStore"; + + private KeyStore mKeyStore; + private int mUid = KeyStore.UID_SELF; + + @Override + public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, + UnrecoverableKeyException { + String userKeyAlias = Credentials.USER_PRIVATE_KEY + alias; + AndroidKeyStoreKey key; + if (!mKeyStore.contains(userKeyAlias, mUid)) { + // try legacy prefix for backward compatibility + userKeyAlias = Credentials.USER_SECRET_KEY + alias; + if (!mKeyStore.contains(userKeyAlias, mUid)) return null; + } + try { + key = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(mKeyStore, + userKeyAlias, + mUid); + } catch (KeyPermanentlyInvalidatedException e) { + throw new UnrecoverableKeyException(e.getMessage()); + } + return key; + } + + @Override + public Certificate[] engineGetCertificateChain(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + + final X509Certificate leaf = (X509Certificate) engineGetCertificate(alias); + if (leaf == null) { + return null; + } + + final Certificate[] caList; + + // Suppress the key not found warning for this call. It seems that this error is exclusively + // being thrown when there is a self signed certificate chain, so when the keystore service + // attempts to query for the CA details, it obviously fails to find them and returns a + // key not found exception. This is WAI, and throwing a stack trace here can be very + // misleading since the trace is not clear. + final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias, + mUid, + true /* suppressKeyNotFoundWarning */); + if (caBytes != null) { + final Collection caChain = toCertificates(caBytes); + + caList = new Certificate[caChain.size() + 1]; + + final Iterator it = caChain.iterator(); + int i = 1; + while (it.hasNext()) { + caList[i++] = it.next(); + } + } else { + caList = new Certificate[1]; + } + + caList[0] = leaf; + + return caList; + } + + @Override + public Certificate engineGetCertificate(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + + byte[] encodedCert = mKeyStore.get(Credentials.USER_CERTIFICATE + alias, mUid); + if (encodedCert != null) { + return getCertificateForPrivateKeyEntry(alias, encodedCert); + } + + encodedCert = mKeyStore.get(Credentials.CA_CERTIFICATE + alias, mUid); + if (encodedCert != null) { + return getCertificateForTrustedCertificateEntry(encodedCert); + } + + // This entry/alias does not contain a certificate. + return null; + } + + private Certificate getCertificateForTrustedCertificateEntry(byte[] encodedCert) { + // For this certificate there shouldn't be a private key in this KeyStore entry. Thus, + // there's no need to wrap this certificate as opposed to the certificate associated with + // a private key entry. + return toCertificate(encodedCert); + } + + private Certificate getCertificateForPrivateKeyEntry(String alias, byte[] encodedCert) { + // All crypto algorithms offered by Android Keystore for its private keys must also + // be offered for the corresponding public keys stored in the Android Keystore. The + // complication is that the underlying keystore service operates only on full key pairs, + // rather than just public keys or private keys. As a result, Android Keystore-backed + // crypto can only be offered for public keys for which keystore contains the + // corresponding private key. This is not the case for certificate-only entries (e.g., + // trusted certificates). + // + // getCertificate().getPublicKey() is the only way to obtain the public key + // corresponding to the private key stored in the KeyStore. Thus, we need to make sure + // that the returned public key points to the underlying key pair / private key + // when available. + + X509Certificate cert = toCertificate(encodedCert); + if (cert == null) { + // Failed to parse the certificate. + return null; + } + + String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; + if (mKeyStore.contains(privateKeyAlias, mUid)) { + // As expected, keystore contains the private key corresponding to this public key. Wrap + // the certificate so that its getPublicKey method returns an Android Keystore + // PublicKey. This key will delegate crypto operations involving this public key to + // Android Keystore when higher-priority providers do not offer these crypto + // operations for this key. + return wrapIntoKeyStoreCertificate(privateKeyAlias, mUid, cert); + } else { + // This KeyStore entry/alias is supposed to contain the private key corresponding to + // the public key in this certificate, but it does not for some reason. It's probably a + // bug. Let other providers handle crypto operations involving the public key returned + // by this certificate's getPublicKey. + return cert; + } + } + + /** + * 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, int uid, X509Certificate certificate) { + return (certificate != null) + ? new KeyStoreX509Certificate(privateKeyAlias, uid, certificate) : null; + } + + private static X509Certificate toCertificate(byte[] bytes) { + try { + final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + return (X509Certificate) certFactory.generateCertificate( + new ByteArrayInputStream(bytes)); + } catch (CertificateException e) { + Log.w(NAME, "Couldn't parse certificate in keystore", e); + return null; + } + } + + @SuppressWarnings("unchecked") + private static Collection toCertificates(byte[] bytes) { + try { + final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + return (Collection) certFactory.generateCertificates( + new ByteArrayInputStream(bytes)); + } catch (CertificateException e) { + Log.w(NAME, "Couldn't parse certificates in keystore", e); + return new ArrayList(); + } + } + + private Date getModificationDate(String alias) { + final long epochMillis = mKeyStore.getmtime(alias, mUid); + if (epochMillis == -1L) { + return null; + } + + return new Date(epochMillis); + } + + @Override + public Date engineGetCreationDate(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + + Date d = getModificationDate(Credentials.USER_PRIVATE_KEY + alias); + if (d != null) { + return d; + } + + d = getModificationDate(Credentials.USER_SECRET_KEY + alias); + if (d != null) { + return d; + } + + d = getModificationDate(Credentials.USER_CERTIFICATE + alias); + if (d != null) { + return d; + } + + return getModificationDate(Credentials.CA_CERTIFICATE + alias); + } + + @Override + public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) + throws KeyStoreException { + if ((password != null) && (password.length > 0)) { + throw new KeyStoreException("entries cannot be protected with passwords"); + } + + if (key instanceof PrivateKey) { + setPrivateKeyEntry(alias, (PrivateKey) key, chain, null); + } else if (key instanceof SecretKey) { + setSecretKeyEntry(alias, (SecretKey) key, null); + } else { + throw new KeyStoreException("Only PrivateKey and SecretKey are supported"); + } + } + + private static KeyProtection getLegacyKeyProtectionParameter(PrivateKey key) + throws KeyStoreException { + String keyAlgorithm = key.getAlgorithm(); + KeyProtection.Builder specBuilder; + if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { + specBuilder = + new KeyProtection.Builder( + KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY); + // Authorized to be used with any digest (including no digest). + // MD5 was never offered for Android Keystore for ECDSA. + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { + specBuilder = + new KeyProtection.Builder( + KeyProperties.PURPOSE_ENCRYPT + | KeyProperties.PURPOSE_DECRYPT + | KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + // Authorized to be used with any digest (including no digest). + 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 encryption and signature padding + // schemes (including no padding). + specBuilder.setEncryptionPaddings( + KeyProperties.ENCRYPTION_PADDING_NONE, + KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1, + KeyProperties.ENCRYPTION_PADDING_RSA_OAEP); + specBuilder.setSignaturePaddings( + KeyProperties.SIGNATURE_PADDING_RSA_PKCS1, + KeyProperties.SIGNATURE_PADDING_RSA_PSS); + // Disable randomized encryption requirement to support encryption + // padding NONE above. + specBuilder.setRandomizedEncryptionRequired(false); + } else { + throw new KeyStoreException("Unsupported key algorithm: " + keyAlgorithm); + } + specBuilder.setUserAuthenticationRequired(false); + + return specBuilder.build(); + } + + private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain, + ProtectionParameter param) throws KeyStoreException { + int flags = 0; + KeyProtection spec; + if (param == null) { + spec = getLegacyKeyProtectionParameter(key); + } else if (param instanceof KeyStoreParameter) { + spec = getLegacyKeyProtectionParameter(key); + KeyStoreParameter legacySpec = (KeyStoreParameter) param; + if (legacySpec.isEncryptionRequired()) { + flags = KeyStore.FLAG_ENCRYPTED; + } + } else if (param instanceof KeyProtection) { + spec = (KeyProtection) param; + if (spec.isCriticalToDeviceEncryption()) { + flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION; + } + + if (spec.isStrongBoxBacked()) { + flags |= KeyStore.FLAG_STRONGBOX; + } + } else { + throw new KeyStoreException( + "Unsupported protection parameter class:" + param.getClass().getName() + + ". Supported: " + KeyProtection.class.getName() + ", " + + KeyStoreParameter.class.getName()); + } + + // Make sure the chain exists since this is a PrivateKey + if ((chain == null) || (chain.length == 0)) { + throw new KeyStoreException("Must supply at least one Certificate with PrivateKey"); + } + + // Do chain type checking. + X509Certificate[] x509chain = new X509Certificate[chain.length]; + for (int i = 0; i < chain.length; i++) { + if (!"X.509".equals(chain[i].getType())) { + throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #" + + i); + } + + if (!(chain[i] instanceof X509Certificate)) { + throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #" + + i); + } + + x509chain[i] = (X509Certificate) chain[i]; + } + + final byte[] userCertBytes; + try { + userCertBytes = x509chain[0].getEncoded(); + } catch (CertificateEncodingException e) { + throw new KeyStoreException("Failed to encode certificate #0", e); + } + + /* + * If we have a chain, store it in the CA certificate slot for this + * alias as concatenated DER-encoded certificates. These can be + * deserialized by {@link CertificateFactory#generateCertificates}. + */ + final byte[] chainBytes; + if (chain.length > 1) { + /* + * The chain is passed in as {user_cert, ca_cert_1, ca_cert_2, ...} + * so we only need the certificates starting at index 1. + */ + final byte[][] certsBytes = new byte[x509chain.length - 1][]; + int totalCertLength = 0; + for (int i = 0; i < certsBytes.length; i++) { + try { + certsBytes[i] = x509chain[i + 1].getEncoded(); + totalCertLength += certsBytes[i].length; + } catch (CertificateEncodingException e) { + throw new KeyStoreException("Failed to encode certificate #" + i, e); + } + } + + /* + * Serialize this into one byte array so we can later call + * CertificateFactory#generateCertificates to recover them. + */ + chainBytes = new byte[totalCertLength]; + int outputOffset = 0; + for (int i = 0; i < certsBytes.length; i++) { + final int certLength = certsBytes[i].length; + System.arraycopy(certsBytes[i], 0, chainBytes, outputOffset, certLength); + outputOffset += certLength; + certsBytes[i] = null; + } + } else { + chainBytes = null; + } + + final String pkeyAlias; + if (key instanceof AndroidKeyStorePrivateKey) { + pkeyAlias = ((AndroidKeyStoreKey) key).getAlias(); + } else { + pkeyAlias = null; + } + + byte[] pkcs8EncodedPrivateKeyBytes; + KeymasterArguments importArgs; + final boolean shouldReplacePrivateKey; + if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) { + final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length()); + if (!alias.equals(keySubalias)) { + throw new KeyStoreException("Can only replace keys with same alias: " + alias + + " != " + keySubalias); + } + shouldReplacePrivateKey = false; + importArgs = null; + pkcs8EncodedPrivateKeyBytes = null; + } else { + shouldReplacePrivateKey = true; + // Make sure the PrivateKey format is the one we support. + final String keyFormat = key.getFormat(); + if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) { + throw new KeyStoreException( + "Unsupported private key export format: " + keyFormat + + ". Only private keys which export their key material in PKCS#8 format are" + + " supported."); + } + + // Make sure we can actually encode the key. + pkcs8EncodedPrivateKeyBytes = key.getEncoded(); + if (pkcs8EncodedPrivateKeyBytes == null) { + throw new KeyStoreException("Private key did not export any key material"); + } + + importArgs = new KeymasterArguments(); + try { + importArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, + KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm( + key.getAlgorithm())); + @KeyProperties.PurposeEnum int purposes = spec.getPurposes(); + importArgs.addEnums(KeymasterDefs.KM_TAG_PURPOSE, + KeyProperties.Purpose.allToKeymaster(purposes)); + if (spec.isDigestsSpecified()) { + importArgs.addEnums(KeymasterDefs.KM_TAG_DIGEST, + KeyProperties.Digest.allToKeymaster(spec.getDigests())); + } + + importArgs.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, + KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes())); + int[] keymasterEncryptionPaddings = + KeyProperties.EncryptionPadding.allToKeymaster( + spec.getEncryptionPaddings()); + if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) + && (spec.isRandomizedEncryptionRequired())) { + for (int keymasterPadding : keymasterEncryptionPaddings) { + if (!KeymasterUtils + .isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto( + keymasterPadding)) { + throw new KeyStoreException( + "Randomized encryption (IND-CPA) required but is violated by" + + " encryption padding mode: " + + KeyProperties.EncryptionPadding.fromKeymaster( + keymasterPadding) + + ". See KeyProtection documentation."); + } + } + } + importArgs.addEnums(KeymasterDefs.KM_TAG_PADDING, keymasterEncryptionPaddings); + importArgs.addEnums(KeymasterDefs.KM_TAG_PADDING, + KeyProperties.SignaturePadding.allToKeymaster(spec.getSignaturePaddings())); + KeymasterUtils.addUserAuthArgs(importArgs, spec); + importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, + spec.getKeyValidityStart()); + importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + spec.getKeyValidityForOriginationEnd()); + importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + spec.getKeyValidityForConsumptionEnd()); + } catch (IllegalArgumentException | IllegalStateException e) { + throw new KeyStoreException(e); + } + } + + + boolean success = false; + try { + // Store the private key, if necessary + if (shouldReplacePrivateKey) { + // Delete the stored private key and any related entries before importing the + // provided key + Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid); + KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); + int errorCode = mKeyStore.importKey( + Credentials.USER_PRIVATE_KEY + alias, + importArgs, + KeymasterDefs.KM_KEY_FORMAT_PKCS8, + pkcs8EncodedPrivateKeyBytes, + mUid, + flags, + resultingKeyCharacteristics); + if (errorCode != KeyStore.NO_ERROR) { + throw new KeyStoreException("Failed to store private key", + KeyStore.getKeyStoreException(errorCode)); + } + } else { + // Keep the stored private key around -- delete all other entry types + Credentials.deleteCertificateTypesForAlias(mKeyStore, alias, mUid); + Credentials.deleteLegacyKeyForAlias(mKeyStore, alias, mUid); + } + + // Store the leaf certificate + int errorCode = mKeyStore.insert(Credentials.USER_CERTIFICATE + alias, userCertBytes, + mUid, flags); + if (errorCode != KeyStore.NO_ERROR) { + throw new KeyStoreException("Failed to store certificate #0", + KeyStore.getKeyStoreException(errorCode)); + } + + // Store the certificate chain + errorCode = mKeyStore.insert(Credentials.CA_CERTIFICATE + alias, chainBytes, + mUid, flags); + if (errorCode != KeyStore.NO_ERROR) { + throw new KeyStoreException("Failed to store certificate chain", + KeyStore.getKeyStoreException(errorCode)); + } + success = true; + } finally { + if (!success) { + if (shouldReplacePrivateKey) { + Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid); + } else { + Credentials.deleteCertificateTypesForAlias(mKeyStore, alias, mUid); + Credentials.deleteLegacyKeyForAlias(mKeyStore, alias, mUid); + } + } + } + } + + private void setSecretKeyEntry(String entryAlias, SecretKey key, + ProtectionParameter param) + throws KeyStoreException { + if ((param != null) && (!(param instanceof KeyProtection))) { + throw new KeyStoreException( + "Unsupported protection parameter class: " + param.getClass().getName() + + ". Supported: " + KeyProtection.class.getName()); + } + KeyProtection params = (KeyProtection) param; + + if (key instanceof AndroidKeyStoreSecretKey) { + // KeyStore-backed secret key. It cannot be duplicated into another entry and cannot + // overwrite its own entry. + String keyAliasInKeystore = ((AndroidKeyStoreSecretKey) key).getAlias(); + if (keyAliasInKeystore == null) { + throw new KeyStoreException("KeyStore-backed secret key does not have an alias"); + } + String keyAliasPrefix = Credentials.USER_PRIVATE_KEY; + if (!keyAliasInKeystore.startsWith(keyAliasPrefix)) { + // try legacy prefix + keyAliasPrefix = Credentials.USER_SECRET_KEY; + if (!keyAliasInKeystore.startsWith(keyAliasPrefix)) { + throw new KeyStoreException("KeyStore-backed secret key has invalid alias: " + + keyAliasInKeystore); + } + } + String keyEntryAlias = + keyAliasInKeystore.substring(keyAliasPrefix.length()); + if (!entryAlias.equals(keyEntryAlias)) { + throw new KeyStoreException("Can only replace KeyStore-backed keys with same" + + " alias: " + entryAlias + " != " + keyEntryAlias); + } + // This is the entry where this key is already stored. No need to do anything. + if (params != null) { + throw new KeyStoreException("Modifying KeyStore-backed key using protection" + + " parameters not supported"); + } + return; + } + + if (params == null) { + throw new KeyStoreException( + "Protection parameters must be specified when importing a symmetric key"); + } + + // Not a KeyStore-backed secret key -- import its key material into keystore. + String keyExportFormat = key.getFormat(); + if (keyExportFormat == null) { + throw new KeyStoreException( + "Only secret keys that export their key material are supported"); + } else if (!"RAW".equals(keyExportFormat)) { + throw new KeyStoreException( + "Unsupported secret key material export format: " + keyExportFormat); + } + byte[] keyMaterial = key.getEncoded(); + if (keyMaterial == null) { + throw new KeyStoreException("Key did not export its key material despite supporting" + + " RAW format export"); + } + + KeymasterArguments args = new KeymasterArguments(); + try { + int keymasterAlgorithm = + KeyProperties.KeyAlgorithm.toKeymasterSecretKeyAlgorithm(key.getAlgorithm()); + args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, keymasterAlgorithm); + + int[] keymasterDigests; + if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { + // JCA HMAC key algorithm implies a digest (e.g., HmacSHA256 key algorithm + // implies SHA-256 digest). Because keymaster HMAC key is authorized only for one + // digest, we don't let import parameters override the digest implied by the key. + // If the parameters specify digests at all, they must specify only one digest, the + // only implied by key algorithm. + int keymasterImpliedDigest = + KeyProperties.KeyAlgorithm.toKeymasterDigest(key.getAlgorithm()); + if (keymasterImpliedDigest == -1) { + throw new ProviderException( + "HMAC key algorithm digest unknown for key algorithm " + + key.getAlgorithm()); + } + keymasterDigests = new int[] {keymasterImpliedDigest}; + if (params.isDigestsSpecified()) { + // Digest(s) explicitly specified in params -- check that the list consists of + // exactly one digest, the one implied by key algorithm. + int[] keymasterDigestsFromParams = + KeyProperties.Digest.allToKeymaster(params.getDigests()); + if ((keymasterDigestsFromParams.length != 1) + || (keymasterDigestsFromParams[0] != keymasterImpliedDigest)) { + throw new KeyStoreException( + "Unsupported digests specification: " + + Arrays.asList(params.getDigests()) + ". Only " + + KeyProperties.Digest.fromKeymaster(keymasterImpliedDigest) + + " supported for HMAC key algorithm " + key.getAlgorithm()); + } + } + } else { + // Key algorithm does not imply a digest. + if (params.isDigestsSpecified()) { + keymasterDigests = KeyProperties.Digest.allToKeymaster(params.getDigests()); + } else { + keymasterDigests = EmptyArray.INT; + } + } + args.addEnums(KeymasterDefs.KM_TAG_DIGEST, keymasterDigests); + + @KeyProperties.PurposeEnum int purposes = params.getPurposes(); + int[] keymasterBlockModes = + KeyProperties.BlockMode.allToKeymaster(params.getBlockModes()); + if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) + && (params.isRandomizedEncryptionRequired())) { + for (int keymasterBlockMode : keymasterBlockModes) { + if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto( + keymasterBlockMode)) { + throw new KeyStoreException( + "Randomized encryption (IND-CPA) required but may be violated by" + + " block mode: " + + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode) + + ". See KeyProtection documentation."); + } + } + } + args.addEnums(KeymasterDefs.KM_TAG_PURPOSE, + KeyProperties.Purpose.allToKeymaster(purposes)); + args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes); + if (params.getSignaturePaddings().length > 0) { + throw new KeyStoreException("Signature paddings not supported for symmetric keys"); + } + int[] keymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster( + params.getEncryptionPaddings()); + args.addEnums(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings); + KeymasterUtils.addUserAuthArgs(args, params); + KeymasterUtils.addMinMacLengthAuthorizationIfNecessary( + args, + keymasterAlgorithm, + keymasterBlockModes, + keymasterDigests); + args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, + params.getKeyValidityStart()); + args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + params.getKeyValidityForOriginationEnd()); + args.addDateIfNotNull(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + params.getKeyValidityForConsumptionEnd()); + + if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) + && (!params.isRandomizedEncryptionRequired())) { + // Permit caller-provided IV when encrypting with this key + args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); + } + } catch (IllegalArgumentException | IllegalStateException e) { + throw new KeyStoreException(e); + } + int flags = 0; + if (params.isCriticalToDeviceEncryption()) { + flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION; + } + if (params.isStrongBoxBacked()) { + flags |= KeyStore.FLAG_STRONGBOX; + } + + Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias, mUid); + String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + entryAlias; + int errorCode = mKeyStore.importKey( + keyAliasInKeystore, + args, + KeymasterDefs.KM_KEY_FORMAT_RAW, + keyMaterial, + mUid, + flags, + new KeyCharacteristics()); + if (errorCode != KeyStore.NO_ERROR) { + throw new KeyStoreException("Failed to import secret key. Keystore error code: " + + errorCode); + } + } + + private void setWrappedKeyEntry(String alias, WrappedKeyEntry entry, + ProtectionParameter param) throws KeyStoreException { + if (param != null) { + throw new KeyStoreException("Protection parameters are specified inside wrapped keys"); + } + + byte[] maskingKey = new byte[32]; + + + KeymasterArguments args = new KeymasterArguments(); + String[] parts = entry.getTransformation().split("/"); + + String algorithm = parts[0]; + if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) { + args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA); + } else if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) { + args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA); + } + + if (parts.length > 1) { + String mode = parts[1]; + if (KeyProperties.BLOCK_MODE_ECB.equalsIgnoreCase(mode)) { + args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB); + } else if (KeyProperties.BLOCK_MODE_CBC.equalsIgnoreCase(mode)) { + args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CBC); + } else if (KeyProperties.BLOCK_MODE_CTR.equalsIgnoreCase(mode)) { + args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CTR); + } else if (KeyProperties.BLOCK_MODE_GCM.equalsIgnoreCase(mode)) { + args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_GCM); + } + } + + if (parts.length > 2) { + String padding = parts[2]; + if (KeyProperties.ENCRYPTION_PADDING_NONE.equalsIgnoreCase(padding)) { + // Noop + } else if (KeyProperties.ENCRYPTION_PADDING_PKCS7.equalsIgnoreCase(padding)) { + args.addEnums(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_PKCS7); + } else if (KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1.equalsIgnoreCase(padding)) { + args.addEnums(KeymasterDefs.KM_TAG_PADDING, + KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT); + } else if (KeyProperties.ENCRYPTION_PADDING_RSA_OAEP.equalsIgnoreCase(padding)) { + args.addEnums(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_RSA_OAEP); + } + } + + KeyGenParameterSpec spec = (KeyGenParameterSpec) entry.getAlgorithmParameterSpec(); + if (spec.isDigestsSpecified()) { + String digest = spec.getDigests()[0]; + if (KeyProperties.DIGEST_NONE.equalsIgnoreCase(digest)) { + // Noop + } else if (KeyProperties.DIGEST_MD5.equalsIgnoreCase(digest)) { + args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_MD5); + } else if (KeyProperties.DIGEST_SHA1.equalsIgnoreCase(digest)) { + args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA1); + } else if (KeyProperties.DIGEST_SHA224.equalsIgnoreCase(digest)) { + args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_224); + } else if (KeyProperties.DIGEST_SHA256.equalsIgnoreCase(digest)) { + args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_256); + } else if (KeyProperties.DIGEST_SHA384.equalsIgnoreCase(digest)) { + args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_384); + } else if (KeyProperties.DIGEST_SHA512.equalsIgnoreCase(digest)) { + args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_512); + } + } + + int errorCode = mKeyStore.importWrappedKey( + Credentials.USER_PRIVATE_KEY + alias, + entry.getWrappedKeyBytes(), + Credentials.USER_PRIVATE_KEY + entry.getWrappingKeyAlias(), + maskingKey, + args, + GateKeeper.getSecureUserId(), + 0, // FIXME fingerprint id? + mUid, + new KeyCharacteristics()); + if (errorCode == KeymasterDefs.KM_ERROR_UNIMPLEMENTED) { + throw new SecureKeyImportUnavailableException("Could not import wrapped key"); + } else if (errorCode != KeyStore.NO_ERROR) { + throw new KeyStoreException("Failed to import wrapped key. Keystore error code: " + + errorCode); + } + } + + @Override + public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain) + throws KeyStoreException { + throw new KeyStoreException("Operation not supported because key encoding is unknown"); + } + + @Override + public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException { + if (isKeyEntry(alias)) { + throw new KeyStoreException("Entry exists and is not a trusted certificate"); + } + + // We can't set something to null. + if (cert == null) { + throw new NullPointerException("cert == null"); + } + + final byte[] encoded; + try { + encoded = cert.getEncoded(); + } catch (CertificateEncodingException e) { + throw new KeyStoreException(e); + } + + if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded, mUid, KeyStore.FLAG_NONE)) { + throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?"); + } + } + + @Override + public void engineDeleteEntry(String alias) throws KeyStoreException { + if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid)) { + throw new KeyStoreException("Failed to delete entry: " + alias); + } + } + + private Set getUniqueAliases() { + final String[] rawAliases = mKeyStore.list("", mUid); + if (rawAliases == null) { + return new HashSet(); + } + + final Set aliases = new HashSet(rawAliases.length); + for (String alias : rawAliases) { + final int idx = alias.indexOf('_'); + if ((idx == -1) || (alias.length() <= idx)) { + Log.e(NAME, "invalid alias: " + alias); + continue; + } + + aliases.add(new String(alias.substring(idx + 1))); + } + + return aliases; + } + + @Override + public Enumeration engineAliases() { + return Collections.enumeration(getUniqueAliases()); + } + + @Override + public boolean engineContainsAlias(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + + return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias, mUid) + || mKeyStore.contains(Credentials.USER_SECRET_KEY + alias, mUid) + || mKeyStore.contains(Credentials.USER_CERTIFICATE + alias, mUid) + || mKeyStore.contains(Credentials.CA_CERTIFICATE + alias, mUid); + } + + @Override + public int engineSize() { + return getUniqueAliases().size(); + } + + @Override + public boolean engineIsKeyEntry(String alias) { + return isKeyEntry(alias); + } + + private boolean isKeyEntry(String alias) { + return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias, mUid) || + mKeyStore.contains(Credentials.USER_SECRET_KEY + alias, mUid); + } + + + private boolean isCertificateEntry(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + + return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias, mUid); + } + + @Override + public boolean engineIsCertificateEntry(String alias) { + return !isKeyEntry(alias) && isCertificateEntry(alias); + } + + @Override + public String engineGetCertificateAlias(Certificate cert) { + 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 nonCaEntries = new HashSet(); + + /* + * First scan the PrivateKeyEntry types. The KeyStoreSpi documentation + * says to only compare the first certificate in the chain which is + * equivalent to the USER_CERTIFICATE prefix for the Android keystore + * convention. + */ + final String[] certAliases = mKeyStore.list(Credentials.USER_CERTIFICATE, mUid); + if (certAliases != null) { + for (String alias : certAliases) { + final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias, mUid); + if (certBytes == null) { + continue; + } + + nonCaEntries.add(alias); + + if (Arrays.equals(certBytes, targetCertBytes)) { + return alias; + } + } + } + + /* + * Look at all the TrustedCertificateEntry types. Skip all the + * PrivateKeyEntry we looked at above. + */ + final String[] caAliases = mKeyStore.list(Credentials.CA_CERTIFICATE, mUid); + if (certAliases != null) { + for (String alias : caAliases) { + if (nonCaEntries.contains(alias)) { + continue; + } + + final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias, mUid); + if (certBytes == null) { + continue; + } + + if (Arrays.equals(certBytes, targetCertBytes)) { + return alias; + } + } + } + + return null; + } + + @Override + public void engineStore(OutputStream stream, char[] password) throws IOException, + NoSuchAlgorithmException, CertificateException { + throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream"); + } + + @Override + public void engineLoad(InputStream stream, char[] password) throws IOException, + NoSuchAlgorithmException, CertificateException { + if (stream != null) { + throw new IllegalArgumentException("InputStream not supported"); + } + + if (password != null) { + throw new IllegalArgumentException("password not supported"); + } + + // Unfortunate name collision. + mKeyStore = KeyStore.getInstance(); + mUid = KeyStore.UID_SELF; + } + + @Override + public void engineLoad(LoadStoreParameter param) throws IOException, + NoSuchAlgorithmException, CertificateException { + int uid = KeyStore.UID_SELF; + if (param != null) { + if (param instanceof AndroidKeyStoreLoadStoreParameter) { + uid = ((AndroidKeyStoreLoadStoreParameter) param).getUid(); + } else { + throw new IllegalArgumentException( + "Unsupported param type: " + param.getClass()); + } + } + mKeyStore = KeyStore.getInstance(); + mUid = uid; + } + + @Override + public void engineSetEntry(String alias, Entry entry, ProtectionParameter param) + throws KeyStoreException { + if (entry == null) { + throw new KeyStoreException("entry == null"); + } + + Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid); + + if (entry instanceof java.security.KeyStore.TrustedCertificateEntry) { + java.security.KeyStore.TrustedCertificateEntry trE = + (java.security.KeyStore.TrustedCertificateEntry) entry; + engineSetCertificateEntry(alias, trE.getTrustedCertificate()); + return; + } + + if (entry instanceof PrivateKeyEntry) { + PrivateKeyEntry prE = (PrivateKeyEntry) entry; + setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(), param); + } else if (entry instanceof SecretKeyEntry) { + SecretKeyEntry secE = (SecretKeyEntry) entry; + setSecretKeyEntry(alias, secE.getSecretKey(), param); + } else if (entry instanceof WrappedKeyEntry) { + WrappedKeyEntry wke = (WrappedKeyEntry) entry; + setWrappedKeyEntry(alias, wke, param); + } else { + throw new KeyStoreException( + "Entry must be a PrivateKeyEntry, SecretKeyEntry or TrustedCertificateEntry" + + "; was " + entry); + } + } + + /** + * {@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; + private final int mPrivateKeyUid; + KeyStoreX509Certificate(String privateKeyAlias, int privateKeyUid, + X509Certificate delegate) { + super(delegate); + mPrivateKeyAlias = privateKeyAlias; + mPrivateKeyUid = privateKeyUid; + } + + @Override + public PublicKey getPublicKey() { + PublicKey original = super.getPublicKey(); + return AndroidKeyStoreProvider.getAndroidKeyStorePublicKey( + mPrivateKeyAlias, mPrivateKeyUid, + original.getAlgorithm(), original.getEncoded()); + } + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java new file mode 100644 index 000000000000..65678a37b938 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java @@ -0,0 +1,326 @@ +/* + * 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.keystore2; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.ArrayUtils; +import android.security.keystore.KeyProperties; + +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.IvParameterSpec; + +/** + * Base class for Android Keystore unauthenticated AES {@link CipherSpi} implementations. + * + * @hide + */ +class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase { + + abstract static class ECB extends AndroidKeyStoreUnauthenticatedAESCipherSpi { + protected ECB(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false); + } + + public static class NoPadding extends + AndroidKeyStoreUnauthenticatedAESCipherSpi.ECB { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + } + + public static class PKCS7Padding extends + AndroidKeyStoreUnauthenticatedAESCipherSpi.ECB { + public PKCS7Padding() { + super(KeymasterDefs.KM_PAD_PKCS7); + } + } + } + + abstract static class CBC extends AndroidKeyStoreUnauthenticatedAESCipherSpi { + protected CBC(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true); + } + + public static class NoPadding extends + AndroidKeyStoreUnauthenticatedAESCipherSpi.CBC { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + } + + public static class PKCS7Padding extends + AndroidKeyStoreUnauthenticatedAESCipherSpi.CBC { + public PKCS7Padding() { + super(KeymasterDefs.KM_PAD_PKCS7); + } + } + } + + abstract static class CTR extends AndroidKeyStoreUnauthenticatedAESCipherSpi { + protected CTR(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_CTR, keymasterPadding, true); + } + + public static class NoPadding extends + AndroidKeyStoreUnauthenticatedAESCipherSpi.CTR { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + } + } + + private static final int BLOCK_SIZE_BYTES = 16; + + private final int mKeymasterBlockMode; + private final int mKeymasterPadding; + /** Whether this transformation requires an IV. */ + private final boolean mIvRequired; + + private byte[] mIv; + + /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */ + private boolean mIvHasBeenUsed; + + AndroidKeyStoreUnauthenticatedAESCipherSpi( + int keymasterBlockMode, + int keymasterPadding, + boolean ivRequired) { + mKeymasterBlockMode = keymasterBlockMode; + mKeymasterPadding = keymasterPadding; + mIvRequired = ivRequired; + } + + @Override + protected final void resetAll() { + mIv = null; + mIvHasBeenUsed = false; + super.resetAll(); + } + + @Override + protected final void resetWhilePreservingInitState() { + super.resetWhilePreservingInitState(); + } + + @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 final void initAlgorithmSpecificParameters() throws InvalidKeyException { + if (!mIvRequired) { + return; + } + + // IV is used + 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 { + if (!mIvRequired) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException( + "IvParameterSpec must be provided when decrypting"); + } + return; + } + if (!(params instanceof IvParameterSpec)) { + throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported"); + } + mIv = ((IvParameterSpec) params).getIV(); + if (mIv == null) { + throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec"); + } + } + + @Override + protected final void initAlgorithmSpecificParameters(AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + if (!mIvRequired) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + return; + } + + if (!"AES".equalsIgnoreCase(params.getAlgorithm())) { + throw new InvalidAlgorithmParameterException( + "Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm() + + ". Supported: AES"); + } + + IvParameterSpec ivSpec; + try { + ivSpec = params.getParameterSpec(IvParameterSpec.class); + } catch (InvalidParameterSpecException e) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ", but not found in parameters: " + params, e); + } + mIv = null; + return; + } + mIv = ivSpec.getIV(); + if (mIv == null) { + throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters"); + } + } + + @Override + protected final int getAdditionalEntropyAmountForBegin() { + if ((mIvRequired) && (mIv == null) && (isEncrypting())) { + // IV will need to be generated + return BLOCK_SIZE_BYTES; + } + + return 0; + } + + @Override + protected final int getAdditionalEntropyAmountForFinish() { + return 0; + } + + @Override + protected final void addAlgorithmSpecificParametersToBegin( + @NonNull KeymasterArguments keymasterArgs) { + if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) { + // IV is being reused for encryption: this violates security best practices. + throw new IllegalStateException( + "IV has already been used. Reusing IV in encryption mode violates security best" + + " practices."); + } + + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES); + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode); + keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding); + if ((mIvRequired) && (mIv != null)) { + keymasterArgs.addBytes(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.getBytes(KeymasterDefs.KM_TAG_NONCE, null); + if ((returnedIv != null) && (returnedIv.length == 0)) { + returnedIv = null; + } + + if (mIvRequired) { + if (mIv == null) { + mIv = returnedIv; + } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) { + throw new ProviderException("IV in use differs from provided IV"); + } + } else { + if (returnedIv != null) { + throw new ProviderException( + "IV in use despite IV not being used by this transformation"); + } + } + } + + @Override + protected final int engineGetBlockSize() { + return BLOCK_SIZE_BYTES; + } + + @Override + protected final int engineGetOutputSize(int inputLen) { + return inputLen + 3 * BLOCK_SIZE_BYTES; + } + + @Override + protected final byte[] engineGetIV() { + return ArrayUtils.cloneIfNotEmpty(mIv); + } + + @Nullable + @Override + protected final AlgorithmParameters engineGetParameters() { + if (!mIvRequired) { + return null; + } + if ((mIv != null) && (mIv.length > 0)) { + try { + AlgorithmParameters params = AlgorithmParameters.getInstance("AES"); + params.init(new IvParameterSpec(mIv)); + return params; + } catch (NoSuchAlgorithmException e) { + throw new ProviderException( + "Failed to obtain AES AlgorithmParameters", e); + } catch (InvalidParameterSpecException e) { + throw new ProviderException( + "Failed to initialize AES AlgorithmParameters with an IV", + e); + } + } + return null; + } +} diff --git a/keystore/java/android/security/keystore2/DelegatingX509Certificate.java b/keystore/java/android/security/keystore2/DelegatingX509Certificate.java new file mode 100644 index 000000000000..9dfee8ce098c --- /dev/null +++ b/keystore/java/android/security/keystore2/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.keystore2; + +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 getCriticalExtensionOIDs() { + return mDelegate.getCriticalExtensionOIDs(); + } + + @Override + public byte[] getExtensionValue(String oid) { + return mDelegate.getExtensionValue(oid); + } + + @Override + public Set 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 getExtendedKeyUsage() throws CertificateParsingException { + return mDelegate.getExtendedKeyUsage(); + } + + @Override + public Collection> getIssuerAlternativeNames() throws CertificateParsingException { + return mDelegate.getIssuerAlternativeNames(); + } + + @Override + public X500Principal getIssuerX500Principal() { + return mDelegate.getIssuerX500Principal(); + } + + @Override + public Collection> getSubjectAlternativeNames() throws CertificateParsingException { + return mDelegate.getSubjectAlternativeNames(); + } + + @Override + public X500Principal getSubjectX500Principal() { + return mDelegate.getSubjectX500Principal(); + } +} diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java new file mode 100644 index 000000000000..3bf9da080f30 --- /dev/null +++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java @@ -0,0 +1,227 @@ +/* + * 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.keystore2; + +import android.os.IBinder; +import android.security.KeyStore; +import android.security.KeyStoreException; +import android.security.keymaster.KeymasterDefs; +import android.security.keymaster.OperationResult; +import android.security.keystore.ArrayUtils; +import android.security.keystore.KeyStoreConnectException; + +import libcore.util.EmptyArray; + +/** + * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's + * {@code update} and {@code finish} operations. + * + *

The helper abstracts away issues that need to be solved in most code that uses KeyStore's + * update and finish operations. Firstly, KeyStore's update operation can consume only a limited + * amount of data in one go because the operations are marshalled via Binder. Secondly, the update + * operation may consume less data than provided, in which case the caller has to buffer the + * remainder for next time. Thirdly, when the input is smaller than a threshold, skipping update + * and passing input data directly to final improves performance. This threshold is configurable; + * using a threshold <= 1 causes the helper act eagerly, which may be required for some types of + * operations (e.g. ciphers). + * + *

The helper exposes {@link #update(byte[], int, int) update} and + * {@link #doFinal(byte[], int, int, byte[], byte[]) doFinal} operations which can be used to + * conveniently implement various JCA crypto primitives. + * + *

Bidirectional chunked streaming of data via a KeyStore crypto operation is abstracted away as + * a {@link Stream} to avoid having this class deal with operation tokens and occasional additional + * parameters to {@code update} and {@code final} operations. + * + * @hide + */ +class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationStreamer { + + /** + * Bidirectional chunked data stream over a KeyStore crypto operation. + */ + interface Stream { + /** + * Returns the result of the KeyStore {@code update} operation or null if keystore couldn't + * be reached. + */ + OperationResult update(byte[] input); + + /** + * Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't + * be reached. + */ + OperationResult finish(byte[] input, byte[] siganture, byte[] additionalEntropy); + } + + // Binder buffer is about 1MB, but it's shared between all active transactions of the process. + // Thus, it's safer to use a much smaller upper bound. + private static final int DEFAULT_CHUNK_SIZE_MAX = 64 * 1024; + // The chunk buffer will be sent to update until its size under this threshold. + // This threshold should be <= the max input allowed for finish. + // Setting this threshold <= 1 will effectivley disable buffering between updates. + private static final int DEFAULT_CHUNK_SIZE_THRESHOLD = 2 * 1024; + + private final Stream mKeyStoreStream; + private final int mChunkSizeMax; + private final int mChunkSizeThreshold; + private final byte[] mChunk; + private int mChunkLength = 0; + private long mConsumedInputSizeBytes; + private long mProducedOutputSizeBytes; + + KeyStoreCryptoOperationChunkedStreamer(Stream operation) { + this(operation, DEFAULT_CHUNK_SIZE_THRESHOLD, DEFAULT_CHUNK_SIZE_MAX); + } + + KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold) { + this(operation, chunkSizeThreshold, DEFAULT_CHUNK_SIZE_MAX); + } + + KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold, + int chunkSizeMax) { + mKeyStoreStream = operation; + mChunkSizeMax = chunkSizeMax; + if (chunkSizeThreshold <= 0) { + mChunkSizeThreshold = 1; + } else if (chunkSizeThreshold > chunkSizeMax) { + mChunkSizeThreshold = chunkSizeMax; + } else { + mChunkSizeThreshold = chunkSizeThreshold; + } + mChunk = new byte[mChunkSizeMax]; + } + + public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException { + if (inputLength == 0 || input == null) { + // No input provided + return EmptyArray.BYTE; + } + if (inputLength < 0 || inputOffset < 0 || (inputOffset + inputLength) > input.length) { + throw new KeyStoreException(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR, + "Input offset and length out of bounds of input array"); + } + + byte[] output = EmptyArray.BYTE; + + while (inputLength > 0 || mChunkLength >= mChunkSizeThreshold) { + int inputConsumed = ArrayUtils.copy(input, inputOffset, mChunk, mChunkLength, + inputLength); + inputLength -= inputConsumed; + inputOffset += inputConsumed; + mChunkLength += inputConsumed; + mConsumedInputSizeBytes += inputConsumed; + + if (mChunkLength > mChunkSizeMax) { + throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH, + "Chunk size exceeded max chunk size. Max: " + mChunkSizeMax + + " Actual: " + mChunkLength); + } + + if (mChunkLength >= mChunkSizeThreshold) { + OperationResult opResult = mKeyStoreStream.update( + ArrayUtils.subarray(mChunk, 0, mChunkLength)); + + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeyStore.getKeyStoreException(opResult.resultCode); + } + if (opResult.inputConsumed <= 0) { + throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH, + "Keystore consumed 0 of " + mChunkLength + " bytes provided."); + } else if (opResult.inputConsumed > mChunkLength) { + throw new KeyStoreException(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR, + "Keystore consumed more input than provided. Provided: " + + mChunkLength + ", consumed: " + opResult.inputConsumed); + } + mChunkLength -= opResult.inputConsumed; + + if (mChunkLength > 0) { + // Partialy consumed, shift chunk contents + ArrayUtils.copy(mChunk, opResult.inputConsumed, mChunk, 0, mChunkLength); + } + + if ((opResult.output != null) && (opResult.output.length > 0)) { + // Output was produced + mProducedOutputSizeBytes += opResult.output.length; + output = ArrayUtils.concat(output, opResult.output); + } + } + } + return output; + } + + public byte[] doFinal(byte[] input, int inputOffset, int inputLength, + byte[] signature, byte[] additionalEntropy) throws KeyStoreException { + byte[] output = update(input, inputOffset, inputLength); + byte[] finalChunk = ArrayUtils.subarray(mChunk, 0, mChunkLength); + OperationResult opResult = mKeyStoreStream.finish(finalChunk, signature, additionalEntropy); + + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeyStore.getKeyStoreException(opResult.resultCode); + } + // If no error, assume all input consumed + mConsumedInputSizeBytes += finalChunk.length; + + if ((opResult.output != null) && (opResult.output.length > 0)) { + mProducedOutputSizeBytes += opResult.output.length; + output = ArrayUtils.concat(output, opResult.output); + } + + return output; + } + + @Override + public long getConsumedInputSizeBytes() { + return mConsumedInputSizeBytes; + } + + @Override + public long getProducedOutputSizeBytes() { + return mProducedOutputSizeBytes; + } + + /** + * Main data stream via a KeyStore streaming operation. + * + *

For example, for an encryption operation, this is the stream through which plaintext is + * provided and ciphertext is obtained. + */ + public static class MainDataStream implements Stream { + + private final KeyStore mKeyStore; + private final IBinder mOperationToken; + + public MainDataStream(KeyStore keyStore, IBinder operationToken) { + mKeyStore = keyStore; + mOperationToken = operationToken; + } + + @Override + public OperationResult update(byte[] input) { + return mKeyStore.update(mOperationToken, null, input); + } + + @Override + public OperationResult finish(byte[] input, byte[] signature, byte[] additionalEntropy) { + return mKeyStore.finish(mOperationToken, null, input, signature, additionalEntropy); + } + } +} diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java new file mode 100644 index 000000000000..fec3bbc3460a --- /dev/null +++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java @@ -0,0 +1,42 @@ +/* + * 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.keystore2; + +import android.security.KeyStore; +import android.security.KeyStoreException; + +/** + * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's + * {@code update} and {@code finish} operations. + * + *

The helper abstracts away to issues that need to be solved in most code that uses KeyStore's + * update and finish operations. Firstly, KeyStore's update operation can consume only a limited + * amount of data in one go because the operations are marshalled via Binder. Secondly, the update + * operation may consume less data than provided, in which case the caller has to buffer the + * remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and + * {@link #doFinal(byte[], int, int, byte[], 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, byte[] signature, + byte[] additionalEntropy) throws KeyStoreException; + long getConsumedInputSizeBytes(); + long getProducedOutputSizeBytes(); +} diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java new file mode 100644 index 000000000000..36590efa82b6 --- /dev/null +++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java @@ -0,0 +1,119 @@ +/* + * 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.keystore2; + +import android.security.KeyStore; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.UserNotAuthenticatedException; + +import libcore.util.EmptyArray; + +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.SecureRandom; + +/** + * Assorted utility methods for implementing crypto operations on top of KeyStore. + * + * @hide + */ +abstract class KeyStoreCryptoOperationUtils { + + private static volatile SecureRandom sRng; + + private KeyStoreCryptoOperationUtils() {} + + /** + * Returns the {@link InvalidKeyException} to be thrown by the {@code init} method of + * the crypto operation in response to {@code KeyStore.begin} operation or {@code null} if + * the {@code init} method should succeed. + */ + static InvalidKeyException getInvalidKeyExceptionForInit( + KeyStore keyStore, AndroidKeyStoreKey key, int beginOpResultCode) { + if (beginOpResultCode == KeyStore.NO_ERROR) { + return null; + } + + // An error occurred. However, some errors should not lead to init throwing an exception. + // See below. + InvalidKeyException e = + keyStore.getInvalidKeyException(key.getAlias(), key.getUid(), beginOpResultCode); + switch (beginOpResultCode) { + case KeyStore.OP_AUTH_NEEDED: + // Operation needs to be authorized by authenticating the user. Don't throw an + // exception is such authentication is possible for this key + // (UserNotAuthenticatedException). An example of when it's not possible is where + // the key is permanently invalidated (KeyPermanentlyInvalidatedException). + if (e instanceof UserNotAuthenticatedException) { + return null; + } + break; + } + return e; + } + + /** + * Returns the exception to be thrown by the {@code Cipher.init} method of the crypto operation + * in response to {@code KeyStore.begin} operation or {@code null} if the {@code init} method + * should succeed. + */ + public static GeneralSecurityException getExceptionForCipherInit( + KeyStore keyStore, AndroidKeyStoreKey key, int beginOpResultCode) { + if (beginOpResultCode == KeyStore.NO_ERROR) { + return null; + } + + // Cipher-specific cases + switch (beginOpResultCode) { + case KeymasterDefs.KM_ERROR_INVALID_NONCE: + return new InvalidAlgorithmParameterException("Invalid IV"); + case KeymasterDefs.KM_ERROR_CALLER_NONCE_PROHIBITED: + return new InvalidAlgorithmParameterException("Caller-provided IV not permitted"); + } + + // General cases + return getInvalidKeyExceptionForInit(keyStore, key, beginOpResultCode); + } + + /** + * Returns the requested number of random bytes to mix into keystore/keymaster RNG. + * + * @param rng RNG from which to obtain the random bytes or {@code null} for the platform-default + * RNG. + */ + static byte[] getRandomBytesToMixIntoKeystoreRng(SecureRandom rng, int sizeBytes) { + if (sizeBytes <= 0) { + return EmptyArray.BYTE; + } + if (rng == null) { + rng = getRng(); + } + byte[] result = new byte[sizeBytes]; + rng.nextBytes(result); + return result; + } + + private static SecureRandom getRng() { + // IMPLEMENTATION NOTE: It's OK to share a SecureRandom instance because SecureRandom is + // required to be thread-safe. + if (sRng == null) { + sRng = new SecureRandom(); + } + return sRng; + } +} -- cgit v1.2.3-59-g8ed1b From ebd964a0862c130a30cde29a2e3f8d96bfe6f5f3 Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Mon, 5 Oct 2020 13:29:14 -0700 Subject: Keystore 2.0: Shim around the basic functionality of Keystore 2.0 This patch adds a shim around the Keystore 2.0 AIDL spec. The new shim is modularized like the AIDL spec into the base Keystore module Keystore2, the security level specific interface KeystoreSecurityLevel, and the operation specific interface KeystoreOperation. Other system maintenance specific interfaces have yet to be added. Bug: 159476414 Bug: 171305684 Test: None Change-Id: I070f73739e4b37ce10568939ac666e40b14a52a8 --- api/current.txt | 6 + core/api/current.txt | 6 + .../android/security/CheckedRemoteRequest.java | 33 +++ keystore/java/android/security/KeyStore2.java | 277 +++++++++++++++++++++ .../java/android/security/KeyStoreOperation.java | 141 +++++++++++ .../android/security/KeyStoreSecurityLevel.java | 217 ++++++++++++++++ .../security/keystore/BackendBusyException.java | 52 ++++ 7 files changed, 732 insertions(+) create mode 100644 keystore/java/android/security/CheckedRemoteRequest.java create mode 100644 keystore/java/android/security/KeyStore2.java create mode 100644 keystore/java/android/security/KeyStoreOperation.java create mode 100644 keystore/java/android/security/KeyStoreSecurityLevel.java create mode 100644 keystore/java/android/security/keystore/BackendBusyException.java diff --git a/api/current.txt b/api/current.txt index 69f76451a5c4..6632db3d7acf 100644 --- a/api/current.txt +++ b/api/current.txt @@ -42742,6 +42742,12 @@ package android.security.identity { package android.security.keystore { + public class BackendBusyException extends java.security.ProviderException { + ctor public BackendBusyException(); + ctor public BackendBusyException(@NonNull String); + ctor public BackendBusyException(@NonNull String, @NonNull Throwable); + } + public class KeyExpiredException extends java.security.InvalidKeyException { ctor public KeyExpiredException(); ctor public KeyExpiredException(String); diff --git a/core/api/current.txt b/core/api/current.txt index 055f909077f7..02652b2c103c 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -40910,6 +40910,12 @@ package android.security.identity { package android.security.keystore { + public class BackendBusyException extends java.security.ProviderException { + ctor public BackendBusyException(); + ctor public BackendBusyException(@NonNull String); + ctor public BackendBusyException(@NonNull String, @NonNull Throwable); + } + public class KeyExpiredException extends java.security.InvalidKeyException { ctor public KeyExpiredException(); ctor public KeyExpiredException(String); diff --git a/keystore/java/android/security/CheckedRemoteRequest.java b/keystore/java/android/security/CheckedRemoteRequest.java new file mode 100644 index 000000000000..b6e7c1fa61b9 --- /dev/null +++ b/keystore/java/android/security/CheckedRemoteRequest.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import android.os.RemoteException; + +/** + * This is a Producer of {@code R} that is expected to throw a {@link RemoteException}. + * + * It is used by Keystore2 service wrappers to handle and convert {@link RemoteException} + * and {@link android.os.ServiceSpecificException} into {@link KeyStoreException}. + * + * @hide + * @param + */ +@FunctionalInterface +interface CheckedRemoteRequest { + R execute() throws RemoteException; +} diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java new file mode 100644 index 000000000000..92d87aa0fed6 --- /dev/null +++ b/keystore/java/android/security/KeyStore2.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import android.annotation.NonNull; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; +import android.os.Build; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceSpecificException; +import android.system.keystore2.IKeystoreService; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyEntryResponse; +import android.system.keystore2.ResponseCode; +import android.util.Log; + +import java.util.Calendar; + +/** + * @hide This should not be made public in its present form because it + * assumes that private and secret key bytes are available and would + * preclude the use of hardware crypto. + */ +public class KeyStore2 { + private static final String TAG = "KeyStore"; + + private static final int RECOVERY_GRACE_PERIOD_MS = 50; + + /** + * Keystore operation creation may fail + * + * Keystore used to work under the assumption that the creation of cryptographic operations + * always succeeds. However, the KeyMint backend has only a limited number of operation slots. + * In order to keep up the appearance of "infinite" operation slots, the Keystore daemon + * would prune least recently used operations if there is no available operation slot. + * As a result, good operations could be terminated prematurely. + * + * This opens AndroidKeystore up to denial-of-service and unintended livelock situations. + * E.g.: if multiple apps wake up at the same time, e.g., due to power management optimizations, + * and attempt to perform crypto operations, they start terminating each others operations + * without making any progress. + * + * To break out of livelocks and to discourage DoS attempts we have changed the pruning + * strategy such that it prefers clients that use few operation slots and only briefly. + * As a result we can, almost, guarantee that single operations that don't linger inactive + * for more than 5 seconds will conclude unhampered by the pruning strategy. "Almost", + * because there are operations related to file system encryption that can prune even + * these operations, but those are extremely rare. + * + * As a side effect of this new pruning strategy operation creation can now fail if the + * client has a lower pruning power than all of the existing operations. + * + * Pruning strategy + * + * To find a suitable candidate we compute the malus for the caller and each existing + * operation. The malus is the inverse of the pruning power (caller) or pruning + * resistance (existing operation). For the caller to be able to prune an operation it must + * find an operation with a malus higher than its own. + * + * For more detail on the pruning strategy consult the implementation at + * https://android.googlesource.com/platform/system/security/+/refs/heads/master/keystore2/src/operation.rs + * + * For older SDK version, KeyStore2 will poll the Keystore daemon for a free operation + * slot. So to applications, targeting earlier SDK versions, it will still look like cipher and + * signature object initialization always succeeds, however, it may take longer to get an + * operation. + * + * All SDK version benefit from fairer operation slot scheduling and a better chance to + * successfully conclude an operation. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + static final long KEYSTORE_OPERATION_CREATION_MAY_FAIL = 169897160L; + + // Never use mBinder directly, use KeyStore2.getService() instead or better yet + // handleRemoteExceptionWithRetry which retries connecting to Keystore once in case + // of a remote exception. + private IKeystoreService mBinder; + + + @FunctionalInterface + interface CheckedRemoteRequest { + R execute(IKeystoreService service) throws RemoteException; + } + + private R handleRemoteExceptionWithRetry(@NonNull CheckedRemoteRequest request) + throws KeyStoreException { + IKeystoreService service = getService(false /* retryLookup */); + boolean firstTry = true; + while (true) { + try { + return request.execute(service); + } catch (ServiceSpecificException e) { + Log.e(TAG, "KeyStore exception", e); + throw new KeyStoreException(e.errorCode, ""); + } catch (RemoteException e) { + if (firstTry) { + Log.w(TAG, "Looks like we may have lost connection to the Keystore " + + "daemon."); + Log.w(TAG, "Retrying after giving Keystore " + + RECOVERY_GRACE_PERIOD_MS + "ms to recover."); + interruptedPreservingSleep(RECOVERY_GRACE_PERIOD_MS); + service = getService(true /* retry Lookup */); + firstTry = false; + } else { + Log.e(TAG, "Cannot connect to Keystore daemon.", e); + throw new KeyStoreException(ResponseCode.SYSTEM_ERROR, ""); + } + } + } + } + + + private KeyStore2() { + mBinder = null; + } + + public static KeyStore2 getInstance() { + return new KeyStore2(); + } + + private synchronized IKeystoreService getService(boolean retryLookup) { + if (mBinder == null || retryLookup) { + mBinder = IKeystoreService.Stub.asInterface(ServiceManager + .getService("android.system.keystore2")); + } + return mBinder; + } + + void delete(KeyDescriptor descriptor) throws KeyStoreException { + handleRemoteExceptionWithRetry((service) -> { + service.deleteKey(descriptor); + return 0; + }); + } + + /** + * List all entries in the keystore for in the given namespace. + */ + public KeyDescriptor[] list(int domain, long namespace) throws KeyStoreException { + return handleRemoteExceptionWithRetry((service) -> service.listEntries(domain, namespace)); + } + + /** + * Create a grant that allows the grantee identified by {@code granteeUid} to use + * the key specified by {@code descriptor} withint the restrictions given by + * {@code accessVectore}. + * @see IKeystoreService#grant(KeyDescriptor, int, int) for more details. + * @param descriptor + * @param granteeUid + * @param accessVector + * @return + * @throws KeyStoreException + * @hide + */ + public KeyDescriptor grant(KeyDescriptor descriptor, int granteeUid, int accessVector) + throws KeyStoreException { + return handleRemoteExceptionWithRetry( + (service) -> service.grant(descriptor, granteeUid, accessVector) + ); + } + + /** + * Destroys a grant. + * @see IKeystoreService#ungrant(KeyDescriptor, int) for more details. + * @param descriptor + * @param granteeUid + * @throws KeyStoreException + * @hide + */ + public void ungrant(KeyDescriptor descriptor, int granteeUid) + throws KeyStoreException { + handleRemoteExceptionWithRetry((service) -> { + service.ungrant(descriptor, granteeUid); + return 0; + }); + } + + /** + * Retrieves a key entry from the keystore backend. + * @see IKeystoreService#getKeyEntry(KeyDescriptor) for more details. + * @param descriptor + * @return + * @throws KeyStoreException + * @hide + */ + public KeyEntryResponse getKeyEntry(@NonNull KeyDescriptor descriptor) + throws KeyStoreException { + return handleRemoteExceptionWithRetry((service) -> service.getKeyEntry(descriptor)); + } + + /** + * Get the security level specific keystore interface from the keystore daemon. + * @see IKeystoreService#getSecurityLevel(int) for more details. + * @param securityLevel + * @return + * @throws KeyStoreException + * @hide + */ + public KeyStoreSecurityLevel getSecurityLevel(int securityLevel) + throws KeyStoreException { + return handleRemoteExceptionWithRetry((service) -> + new KeyStoreSecurityLevel( + service.getSecurityLevel(securityLevel) + ) + ); + } + + /** + * Update the subcomponents of a key entry designated by the key descriptor. + * @see IKeystoreService#updateSubcomponent(KeyDescriptor, byte[], byte[]) for more details. + * @param key + * @param publicCert + * @param publicCertChain + * @throws KeyStoreException + * @hide + */ + public void updateSubcomponents(@NonNull KeyDescriptor key, byte[] publicCert, + byte[] publicCertChain) throws KeyStoreException { + handleRemoteExceptionWithRetry((service) -> { + service.updateSubcomponent(key, publicCert, publicCertChain); + return 0; + }); + } + + /** + * Delete the key designed by the key descriptor. + * @see IKeystoreService#deleteKey(KeyDescriptor) for more details. + * @param descriptor + * @throws KeyStoreException + * @hide + */ + public void deleteKey(@NonNull KeyDescriptor descriptor) + throws KeyStoreException { + handleRemoteExceptionWithRetry((service) -> { + service.deleteKey(descriptor); + return 0; + }); + } + + protected static void interruptedPreservingSleep(long millis) { + boolean wasInterrupted = false; + Calendar calendar = Calendar.getInstance(); + long target = calendar.getTimeInMillis() + millis; + while (true) { + try { + Thread.sleep(target - calendar.getTimeInMillis()); + break; + } catch (InterruptedException e) { + wasInterrupted = true; + } catch (IllegalArgumentException e) { + // This means that the argument to sleep was negative. + // So we are done sleeping. + break; + } + } + if (wasInterrupted) { + Thread.currentThread().interrupt(); + } + } + +} diff --git a/keystore/java/android/security/KeyStoreOperation.java b/keystore/java/android/security/KeyStoreOperation.java new file mode 100644 index 000000000000..9af15a5f4a16 --- /dev/null +++ b/keystore/java/android/security/KeyStoreOperation.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import android.annotation.NonNull; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.security.keymaster.KeymasterDefs; +import android.system.keystore2.IKeystoreOperation; +import android.system.keystore2.KeyParameter; +import android.system.keystore2.ResponseCode; +import android.util.Log; + +/** + * @hide + */ +public class KeyStoreOperation { + static final String TAG = "KeyStoreOperation"; + private final IKeystoreOperation mOperation; + private final Long mChallenge; + private final KeyParameter[] mParameters; + + public KeyStoreOperation( + @NonNull IKeystoreOperation operation, + Long challenge, + KeyParameter[] parameters + ) { + this.mOperation = operation; + this.mChallenge = challenge; + this.mParameters = parameters; + } + + /** + * Gets the challenge associated with this operation. + * @return null if the operation does not required authorization. A 64bit operation + * challenge otherwise. + */ + public Long getChallenge() { + return mChallenge; + } + + /** + * Gets the parameters associated with this operation. + * @return + */ + public KeyParameter[] getParameters() { + return mParameters; + } + + private R handleExceptions(@NonNull CheckedRemoteRequest request) + throws KeyStoreException { + try { + return request.execute(); + } catch (ServiceSpecificException e) { + switch(e.errorCode) { + case ResponseCode.OPERATION_BUSY: { + throw new IllegalThreadStateException( + "Cannot update the same operation concurrently." + ); + } + default: + // TODO Human readable string. Use something like KeyStore.getKeyStoreException + throw new KeyStoreException(e.errorCode, ""); + } + } catch (RemoteException e) { + // Log exception and report invalid operation handle. + // This should prompt the caller drop the reference to this operation and retry. + Log.e( + TAG, + "Remote exception while advancing a KeyStoreOperation.", + e + ); + throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_OPERATION_HANDLE, ""); + } + } + + /** + * Updates the Keystore operation represented by this object with more associated data. + * @see IKeystoreOperation#updateAad(byte[]) for more details. + * @param input + * @throws KeyStoreException + */ + public void updateAad(@NonNull byte[] input) throws KeyStoreException { + handleExceptions(() -> { + mOperation.updateAad(input); + return 0; + }); + } + + /** + * Updates the Keystore operation represented by this object. + * @see IKeystoreOperation#update(byte[]) for more details. + * @param input + * @return + * @throws KeyStoreException + * @hide + */ + public byte[] update(@NonNull byte[] input) throws KeyStoreException { + return handleExceptions(() -> mOperation.update(input)); + } + + /** + * Finalizes the Keystore operation represented by this object. + * @see IKeystoreOperation#finish(byte[], byte[]) for more details. + * @param input + * @param signature + * @return + * @throws KeyStoreException + * @hide + */ + public byte[] finish(byte[] input, byte[] signature) throws KeyStoreException { + return handleExceptions(() -> mOperation.finish(input, signature)); + } + + /** + * Aborts the Keystore operation represented by this object. + * @see IKeystoreOperation#abort() for more details. + * @throws KeyStoreException + * @hide + */ + public void abort() throws KeyStoreException { + handleExceptions(() -> { + mOperation.abort(); + return 0; + }); + } +} diff --git a/keystore/java/android/security/KeyStoreSecurityLevel.java b/keystore/java/android/security/KeyStoreSecurityLevel.java new file mode 100644 index 000000000000..9d3b62278ba0 --- /dev/null +++ b/keystore/java/android/security/KeyStoreSecurityLevel.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import android.annotation.NonNull; +import android.app.compat.CompatChanges; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.security.keystore.BackendBusyException; +import android.security.keystore.KeyStoreConnectException; +import android.system.keystore2.AuthenticatorSpec; +import android.system.keystore2.CreateOperationResponse; +import android.system.keystore2.IKeystoreSecurityLevel; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyMetadata; +import android.system.keystore2.KeyParameter; +import android.system.keystore2.ResponseCode; +import android.util.Log; + +import java.util.Calendar; +import java.util.Collection; + +/** + * This is a shim around the security level specific interface of Keystore 2.0. Services with + * this interface are instantiated per KeyMint backend, each having there own security level. + * Thus this object representation of a security level. + * @hide + */ +public class KeyStoreSecurityLevel { + private static final String TAG = "KeyStoreSecurityLevel"; + private final IKeystoreSecurityLevel mSecurityLevel; + + public KeyStoreSecurityLevel(IKeystoreSecurityLevel securityLevel) { + this.mSecurityLevel = securityLevel; + } + + private R handleExceptions(CheckedRemoteRequest request) throws KeyStoreException { + try { + return request.execute(); + } catch (ServiceSpecificException e) { + throw new KeyStoreException(e.errorCode, ""); + } catch (RemoteException e) { + // Log exception and report invalid operation handle. + // This should prompt the caller drop the reference to this operation and retry. + Log.e(TAG, "Could not connect to Keystore.", e); + throw new KeyStoreException(ResponseCode.SYSTEM_ERROR, ""); + } + } + + /** + * Creates a new keystore operation. + * @see IKeystoreSecurityLevel#createOperation(KeyDescriptor, KeyParameter[], boolean) for more + * details. + * @param keyDescriptor + * @param args + * @return + * @throws KeyStoreException + * @hide + */ + public KeyStoreOperation createOperation(@NonNull KeyDescriptor keyDescriptor, + Collection args) throws KeyStoreException { + while (true) { + try { + CreateOperationResponse createOperationResponse = + mSecurityLevel.createOperation( + keyDescriptor, + args.toArray(new KeyParameter[args.size()]), + false /* forced */ + ); + Long challenge = null; + if (createOperationResponse.operationChallenge != null) { + challenge = createOperationResponse.operationChallenge.challenge; + } + KeyParameter[] parameters = null; + if (createOperationResponse.parameters != null) { + parameters = createOperationResponse.parameters.keyParameter; + } + return new KeyStoreOperation( + createOperationResponse.iOperation, + challenge, + parameters); + } catch (ServiceSpecificException e) { + switch (e.errorCode) { + case ResponseCode.BACKEND_BUSY: { + if (CompatChanges.isChangeEnabled( + KeyStore2.KEYSTORE_OPERATION_CREATION_MAY_FAIL)) { + // Starting with Android S we inform the caller about the + // backend being busy. + throw new BackendBusyException(); + } else { + // Before Android S operation creation must always succeed. So we + // just have to retry. We do so with a randomized back-off between + // 50 and 250ms. + // It is a little awkward that we cannot break out of this loop + // by interrupting this thread. But that is the expected behavior. + // There is some comfort in the fact that interrupting a thread + // also does not unblock a thread waiting for a binder transaction. + interruptedPreservingSleep((long) (Math.random() * 200 + 50)); + } + break; + } + default: + throw new KeyStoreException(e.errorCode, ""); + } + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + throw new KeyStoreConnectException(); + } + } + } + + /** + * Generates a new key in Keystore. + * @see IKeystoreSecurityLevel#generateKey(KeyDescriptor, KeyDescriptor, KeyParameter[], int, + * byte[]) for more details. + * @param descriptor + * @param attestationKey + * @param args + * @param flags + * @param entropy + * @return + * @throws KeyStoreException + * @hide + */ + public KeyMetadata generateKey(@NonNull KeyDescriptor descriptor, KeyDescriptor attestationKey, + Collection args, int flags, byte[] entropy) + throws KeyStoreException { + return handleExceptions(() -> mSecurityLevel.generateKey( + descriptor, attestationKey, args.toArray(new KeyParameter[args.size()]), + flags, entropy)); + } + + /** + * Imports a key into Keystore. + * @see IKeystoreSecurityLevel#importKey(KeyDescriptor, KeyDescriptor, KeyParameter[], int, + * byte[]) for more details. + * @param descriptor + * @param attestationKey + * @param args + * @param flags + * @param keyData + * @return + * @throws KeyStoreException + * @hide + */ + public KeyMetadata importKey(KeyDescriptor descriptor, KeyDescriptor attestationKey, + Collection args, int flags, byte[] keyData) + throws KeyStoreException { + return handleExceptions(() -> mSecurityLevel.importKey(descriptor, attestationKey, + args.toArray(new KeyParameter[args.size()]), flags, keyData)); + } + + /** + * Imports a wrapped key into Keystore. + * @see IKeystoreSecurityLevel#importWrappedKey(KeyDescriptor, KeyDescriptor, byte[], + * KeyParameter[], AuthenticatorSpec[]) for more details. + * @param wrappedKeyDescriptor + * @param wrappingKeyDescriptor + * @param wrappedKey + * @param maskingKey + * @param args + * @param authenticatorSpecs + * @return + * @throws KeyStoreException + * @hide + */ + public KeyMetadata importWrappedKey(@NonNull KeyDescriptor wrappedKeyDescriptor, + @NonNull KeyDescriptor wrappingKeyDescriptor, + @NonNull byte[] wrappedKey, byte[] maskingKey, + Collection args, @NonNull AuthenticatorSpec[] authenticatorSpecs) + throws KeyStoreException { + KeyDescriptor keyDescriptor = new KeyDescriptor(); + keyDescriptor.alias = wrappedKeyDescriptor.alias; + keyDescriptor.nspace = wrappedKeyDescriptor.nspace; + keyDescriptor.blob = wrappedKey; + keyDescriptor.domain = wrappedKeyDescriptor.domain; + + return handleExceptions(() -> mSecurityLevel.importWrappedKey(wrappedKeyDescriptor, + wrappingKeyDescriptor, maskingKey, + args.toArray(new KeyParameter[args.size()]), authenticatorSpecs)); + } + + protected static void interruptedPreservingSleep(long millis) { + boolean wasInterrupted = false; + Calendar calendar = Calendar.getInstance(); + long target = calendar.getTimeInMillis() + millis; + while (true) { + try { + Thread.sleep(target - calendar.getTimeInMillis()); + break; + } catch (InterruptedException e) { + wasInterrupted = true; + } catch (IllegalArgumentException e) { + // This means that the argument to sleep was negative. + // So we are done sleeping. + break; + } + } + if (wasInterrupted) { + Thread.currentThread().interrupt(); + } + } +} diff --git a/keystore/java/android/security/keystore/BackendBusyException.java b/keystore/java/android/security/keystore/BackendBusyException.java new file mode 100644 index 000000000000..1a88469d7e54 --- /dev/null +++ b/keystore/java/android/security/keystore/BackendBusyException.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 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 java.security.ProviderException; + +/** + * Indicates a transient error that prevented a key operation from being created. + * Callers should try again with a back-off period of 10-30 milliseconds. + */ +public class BackendBusyException extends ProviderException { + + /** + * Constructs a new {@code BackendBusyException} without detail message and cause. + */ + public BackendBusyException() { + super("The keystore backend has no operation slots available. Retry later."); + } + + /** + * Constructs a new {@code BackendBusyException} with the provided detail message and + * no cause. + */ + public BackendBusyException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@code BackendBusyException} with the provided detail message and + * cause. + */ + public BackendBusyException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } + +} -- cgit v1.2.3-59-g8ed1b From e5795a90db1e1b9d0fcc932ee9a864e926817468 Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Fri, 9 Oct 2020 14:19:56 -0700 Subject: Keystore 2.0 SPI: KeyStoreKeys adopt Keystore 2.0 KeyStoreKeys can now be constructed from key entry metadata and key descriptors as defined by the new Keystore AIDL spec. AndroidKeystorePublicKey can now create the private key proxy. KeyStoreKeys also cache the key characteristic, which should drastically reduce the frequency by which the SPI has to call into the Keystore 2.0 daemon. Test: None Bug: 159476414 Change-Id: Ia0a7841582621897760be49d39dd5442b70b3aa0 --- .../keystore2/AndroidKeyStoreECPrivateKey.java | 12 ++- .../keystore2/AndroidKeyStoreECPublicKey.java | 27 +++++-- .../security/keystore2/AndroidKeyStoreKey.java | 86 ++++++++++++++++------ .../keystore2/AndroidKeyStorePrivateKey.java | 12 ++- .../keystore2/AndroidKeyStorePublicKey.java | 36 +++++---- .../keystore2/AndroidKeyStoreRSAPrivateKey.java | 12 ++- .../keystore2/AndroidKeyStoreRSAPublicKey.java | 24 ++++-- .../keystore2/AndroidKeyStoreSecretKey.java | 11 ++- 8 files changed, 163 insertions(+), 57 deletions(-) diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECPrivateKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECPrivateKey.java index 0ef75cd30c37..35effde6234b 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreECPrivateKey.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECPrivateKey.java @@ -16,7 +16,11 @@ package android.security.keystore2; +import android.annotation.NonNull; +import android.security.KeyStoreSecurityLevel; import android.security.keystore.KeyProperties; +import android.system.keystore2.Authorization; +import android.system.keystore2.KeyDescriptor; import java.security.PrivateKey; import java.security.interfaces.ECKey; @@ -30,8 +34,12 @@ import java.security.spec.ECParameterSpec; public class AndroidKeyStoreECPrivateKey extends AndroidKeyStorePrivateKey implements ECKey { private final ECParameterSpec mParams; - public AndroidKeyStoreECPrivateKey(String alias, int uid, ECParameterSpec params) { - super(alias, uid, KeyProperties.KEY_ALGORITHM_EC); + public AndroidKeyStoreECPrivateKey(@NonNull KeyDescriptor descriptor, + long keyId, + Authorization[] authorizations, + @NonNull KeyStoreSecurityLevel securityLevel, + @NonNull ECParameterSpec params) { + super(descriptor, keyId, authorizations, KeyProperties.KEY_ALGORITHM_EC, securityLevel); mParams = params; } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java index bc1b0eeb9b19..6ddaa704afa8 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java @@ -16,7 +16,11 @@ package android.security.keystore2; +import android.annotation.NonNull; +import android.security.KeyStoreSecurityLevel; import android.security.keystore.KeyProperties; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyMetadata; import java.security.interfaces.ECPublicKey; import java.security.spec.ECParameterSpec; @@ -32,21 +36,32 @@ public class AndroidKeyStoreECPublicKey extends AndroidKeyStorePublicKey impleme private final ECParameterSpec mParams; private final ECPoint mW; - public AndroidKeyStoreECPublicKey(String alias, int uid, byte[] x509EncodedForm, ECParameterSpec params, - ECPoint w) { - super(alias, uid, KeyProperties.KEY_ALGORITHM_EC, x509EncodedForm); + public AndroidKeyStoreECPublicKey(@NonNull KeyDescriptor descriptor, + @NonNull KeyMetadata metadata, + @NonNull KeyStoreSecurityLevel securityLevel, + @NonNull ECParameterSpec params, @NonNull ECPoint w) { + super(descriptor, metadata, KeyProperties.KEY_ALGORITHM_EC, securityLevel); mParams = params; mW = w; } - public AndroidKeyStoreECPublicKey(String alias, int uid, ECPublicKey info) { - this(alias, uid, info.getEncoded(), info.getParams(), info.getW()); + public AndroidKeyStoreECPublicKey(@NonNull KeyDescriptor descriptor, + @NonNull KeyMetadata metadata, + @NonNull KeyStoreSecurityLevel securityLevel, @NonNull ECPublicKey info) { + this(descriptor, metadata, securityLevel, info.getParams(), info.getW()); if (!"X.509".equalsIgnoreCase(info.getFormat())) { throw new IllegalArgumentException( "Unsupported key export format: " + info.getFormat()); } } + @Override + public AndroidKeyStorePrivateKey getPrivateKey() { + return new AndroidKeyStoreECPrivateKey( + getUserKeyDescriptor(), getKeyIdDescriptor().nspace, getAuthorizations(), + getSecurityLevel(), mParams); + } + @Override public ECParameterSpec getParams() { return mParams; @@ -56,4 +71,4 @@ public class AndroidKeyStoreECPublicKey extends AndroidKeyStorePublicKey impleme public ECPoint getW() { return mW; } -} \ No newline at end of file +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java index e2a3d61d4cfc..32650aeda1b1 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java @@ -16,6 +16,13 @@ package android.security.keystore2; +import android.annotation.NonNull; +import android.security.KeyStoreSecurityLevel; +import android.system.keystore2.Authorization; +import android.system.keystore2.Domain; +import android.system.keystore2.KeyDescriptor; +import android.util.Log; + import java.security.Key; /** @@ -24,24 +31,56 @@ import java.security.Key; * @hide */ public class AndroidKeyStoreKey implements Key { - private final String mAlias; - private final int mUid; + // This is the original KeyDescriptor by which the key was loaded from + // with alias and domain. + private final KeyDescriptor mDescriptor; + // The key id can be used make certain manipulations to the keystore database + // assuring that the manipulation is made to the exact key that was loaded + // from the database. Alias based manipulations can not assure this, because + // aliases can be rebound to other keys at any time. + private final long mKeyId; + private final Authorization[] mAuthorizations; + // TODO extract algorithm string from metadata. private final String mAlgorithm; - public AndroidKeyStoreKey(String alias, int uid, String algorithm) { - mAlias = alias; - mUid = uid; + // This is the security level interface, that this key is associated with. + // We do not include this member in comparisons. + private final KeyStoreSecurityLevel mSecurityLevel; + + AndroidKeyStoreKey(@NonNull KeyDescriptor descriptor, + long keyId, + @NonNull Authorization[] authorizations, + @NonNull String algorithm, + @NonNull KeyStoreSecurityLevel securityLevel) { + mDescriptor = descriptor; + mKeyId = keyId; + mAuthorizations = authorizations; mAlgorithm = algorithm; + mSecurityLevel = securityLevel; + } + + KeyDescriptor getUserKeyDescriptor() { + return mDescriptor; + } + + KeyDescriptor getKeyIdDescriptor() { + KeyDescriptor descriptor = new KeyDescriptor(); + descriptor.nspace = mKeyId; + descriptor.domain = Domain.KEY_ID; + descriptor.alias = null; + descriptor.blob = null; + return descriptor; } - String getAlias() { - return mAlias; + Authorization[] getAuthorizations() { + return mAuthorizations; } - int getUid() { - return mUid; + KeyStoreSecurityLevel getSecurityLevel() { + return mSecurityLevel; } + @Override public String getAlgorithm() { return mAlgorithm; @@ -63,9 +102,12 @@ public class AndroidKeyStoreKey implements Key { public int hashCode() { final int prime = 31; int result = 1; + + result = prime * result + ((mDescriptor == null) ? 0 : mDescriptor.hashCode()); + result = prime * result + (int) (mKeyId >>> 32); + result = prime * result + (int) (mKeyId & 0xffffffff); + result = prime * result + ((mAuthorizations == null) ? 0 : mAuthorizations.hashCode()); result = prime * result + ((mAlgorithm == null) ? 0 : mAlgorithm.hashCode()); - result = prime * result + ((mAlias == null) ? 0 : mAlias.hashCode()); - result = prime * result + mUid; return result; } @@ -81,21 +123,17 @@ public class AndroidKeyStoreKey implements Key { return false; } AndroidKeyStoreKey other = (AndroidKeyStoreKey) obj; - if (mAlgorithm == null) { - if (other.mAlgorithm != null) { - return false; - } - } else if (!mAlgorithm.equals(other.mAlgorithm)) { + if (mKeyId != other.mKeyId) { return false; } - if (mAlias == null) { - if (other.mAlias != null) { - return false; - } - } else if (!mAlias.equals(other.mAlias)) { - return false; - } - if (mUid != other.mUid) { + + // If the key ids are equal and the class matches all the other fields cannot differ + // unless we have a bug. + if (!mAlgorithm.equals(other.mAlgorithm) + || !mAuthorizations.equals(other.mAuthorizations) + || !mDescriptor.equals(other.mDescriptor)) { + Log.e("AndroidKeyStoreKey", "Bug: key ids are identical, but key metadata" + + "differs."); return false; } return true; diff --git a/keystore/java/android/security/keystore2/AndroidKeyStorePrivateKey.java b/keystore/java/android/security/keystore2/AndroidKeyStorePrivateKey.java index f071fe89fe37..8b331ee3b880 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStorePrivateKey.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStorePrivateKey.java @@ -16,6 +16,11 @@ package android.security.keystore2; +import android.annotation.NonNull; +import android.security.KeyStoreSecurityLevel; +import android.system.keystore2.Authorization; +import android.system.keystore2.KeyDescriptor; + import java.security.PrivateKey; /** @@ -25,7 +30,10 @@ import java.security.PrivateKey; */ public class AndroidKeyStorePrivateKey extends AndroidKeyStoreKey implements PrivateKey { - public AndroidKeyStorePrivateKey(String alias, int uid, String algorithm) { - super(alias, uid, algorithm); + public AndroidKeyStorePrivateKey(@NonNull KeyDescriptor descriptor, + long keyId, @NonNull Authorization[] authorizations, @NonNull String algorithm, + @NonNull KeyStoreSecurityLevel securityLevel) { + super(descriptor, keyId, authorizations, algorithm, securityLevel); } + } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java index a030efb64ff6..49dd77e3a3db 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java @@ -16,25 +16,33 @@ package android.security.keystore2; +import android.annotation.NonNull; +import android.security.KeyStoreSecurityLevel; import android.security.keystore.ArrayUtils; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyMetadata; import java.security.PublicKey; -import java.util.Arrays; /** * {@link PublicKey} backed by Android Keystore. * * @hide */ -public class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implements PublicKey { +public abstract class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implements PublicKey { + private final byte[] mCertificate; + private final byte[] mCertificateChain; - private final byte[] mEncoded; - - public AndroidKeyStorePublicKey(String alias, int uid, String algorithm, byte[] x509EncodedForm) { - super(alias, uid, algorithm); - mEncoded = ArrayUtils.cloneIfNotEmpty(x509EncodedForm); + public AndroidKeyStorePublicKey(@NonNull KeyDescriptor descriptor, + @NonNull KeyMetadata metadata, @NonNull String algorithm, + @NonNull KeyStoreSecurityLevel securityLevel) { + super(descriptor, metadata.key.nspace, metadata.authorizations, algorithm, securityLevel); + mCertificate = metadata.certificate; + mCertificateChain = metadata.certificateChain; } + abstract AndroidKeyStorePrivateKey getPrivateKey(); + @Override public String getFormat() { return "X.509"; @@ -42,14 +50,18 @@ public class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implements Publ @Override public byte[] getEncoded() { - return ArrayUtils.cloneIfNotEmpty(mEncoded); + return ArrayUtils.cloneIfNotEmpty(mCertificate); } @Override public int hashCode() { final int prime = 31; - int result = super.hashCode(); - result = prime * result + Arrays.hashCode(mEncoded); + int result = 1; + + result = prime * result + super.hashCode(); + result = prime * result + ((mCertificate == null) ? 0 : mCertificate.hashCode()); + result = prime * result + ((mCertificateChain == null) ? 0 : mCertificateChain.hashCode()); + return result; } @@ -64,10 +76,6 @@ public class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implements Publ 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/keystore2/AndroidKeyStoreRSAPrivateKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPrivateKey.java index 4c1231b674c0..ef0d3bc4d46a 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPrivateKey.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPrivateKey.java @@ -16,7 +16,11 @@ package android.security.keystore2; +import android.annotation.NonNull; +import android.security.KeyStoreSecurityLevel; import android.security.keystore.KeyProperties; +import android.system.keystore2.Authorization; +import android.system.keystore2.KeyDescriptor; import java.math.BigInteger; import java.security.PrivateKey; @@ -31,8 +35,12 @@ public class AndroidKeyStoreRSAPrivateKey extends AndroidKeyStorePrivateKey impl private final BigInteger mModulus; - public AndroidKeyStoreRSAPrivateKey(String alias, int uid, BigInteger modulus) { - super(alias, uid, KeyProperties.KEY_ALGORITHM_RSA); + + public AndroidKeyStoreRSAPrivateKey(@NonNull KeyDescriptor descriptor, + long keyId, + @NonNull Authorization[] authorizations, + @NonNull KeyStoreSecurityLevel securityLevel, @NonNull BigInteger modulus) { + super(descriptor, keyId, authorizations, KeyProperties.KEY_ALGORITHM_RSA, securityLevel); mModulus = modulus; } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java index 7a59abb38719..b578ea9baa06 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java @@ -16,7 +16,11 @@ package android.security.keystore2; +import android.annotation.NonNull; +import android.security.KeyStoreSecurityLevel; import android.security.keystore.KeyProperties; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyMetadata; import java.math.BigInteger; import java.security.interfaces.RSAPublicKey; @@ -30,21 +34,31 @@ public class AndroidKeyStoreRSAPublicKey extends AndroidKeyStorePublicKey implem private final BigInteger mModulus; private final BigInteger mPublicExponent; - public AndroidKeyStoreRSAPublicKey(String alias, int uid, byte[] x509EncodedForm, BigInteger modulus, - BigInteger publicExponent) { - super(alias, uid, KeyProperties.KEY_ALGORITHM_RSA, x509EncodedForm); + public AndroidKeyStoreRSAPublicKey(@NonNull KeyDescriptor descriptor, + @NonNull KeyMetadata metadata, + @NonNull KeyStoreSecurityLevel securityLevel, @NonNull BigInteger modulus, + @NonNull BigInteger publicExponent) { + super(descriptor, metadata, KeyProperties.KEY_ALGORITHM_RSA, securityLevel); mModulus = modulus; mPublicExponent = publicExponent; } - public AndroidKeyStoreRSAPublicKey(String alias, int uid, RSAPublicKey info) { - this(alias, uid, info.getEncoded(), info.getModulus(), info.getPublicExponent()); + public AndroidKeyStoreRSAPublicKey(@NonNull KeyDescriptor descriptor, + @NonNull KeyMetadata metadata, + @NonNull KeyStoreSecurityLevel securityLevel, @NonNull RSAPublicKey info) { + this(descriptor, metadata, securityLevel, info.getModulus(), info.getPublicExponent()); if (!"X.509".equalsIgnoreCase(info.getFormat())) { throw new IllegalArgumentException( "Unsupported key export format: " + info.getFormat()); } } + @Override + public AndroidKeyStorePrivateKey getPrivateKey() { + return new AndroidKeyStoreRSAPrivateKey(getUserKeyDescriptor(), getKeyIdDescriptor().nspace, + getAuthorizations(), getSecurityLevel(), mModulus); + } + @Override public BigInteger getModulus() { return mModulus; diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKey.java index 8adf27a6189a..4e459137e875 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKey.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKey.java @@ -16,6 +16,11 @@ package android.security.keystore2; +import android.annotation.NonNull; +import android.security.KeyStoreSecurityLevel; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyMetadata; + import javax.crypto.SecretKey; /** @@ -25,7 +30,9 @@ import javax.crypto.SecretKey; */ public class AndroidKeyStoreSecretKey extends AndroidKeyStoreKey implements SecretKey { - public AndroidKeyStoreSecretKey(String alias, int uid, String algorithm) { - super(alias, uid, algorithm); + public AndroidKeyStoreSecretKey(@NonNull KeyDescriptor descriptor, + @NonNull KeyMetadata metadata, @NonNull String algorithm, + @NonNull KeyStoreSecurityLevel securityLevel) { + super(descriptor, metadata.key.nspace, metadata.authorizations, algorithm, securityLevel); } } -- cgit v1.2.3-59-g8ed1b From 27ee56c021a41d6ae94537c8083b26a92aee0d62 Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Fri, 9 Oct 2020 14:22:59 -0700 Subject: Keystore 2.0 SPI: KeyStoreCryptoOperationUtils Keystore 2.0 does no longer report an error code if an operation requires user authorization. Instead this is indicated by sending us an operation challenge. In that case we have to check if the authorization can possibly succeed. We changed the utility class by adding a predicate function that checks exactly that, and we handle other errors separately instead of having one exception handling path that does all. Test: None Bug: 159476414 Change-Id: I9a373cf8f0a0b181df54c26fe314d71b6835bb97 --- .../keystore2/KeyStoreCryptoOperationUtils.java | 112 ++++++++++++++++----- 1 file changed, 85 insertions(+), 27 deletions(-) diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java index 36590efa82b6..4b48bb749133 100644 --- a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java +++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java @@ -16,9 +16,18 @@ package android.security.keystore2; +import android.app.ActivityThread; +import android.hardware.biometrics.BiometricManager; +import android.security.GateKeeper; import android.security.KeyStore; +import android.security.KeyStoreException; import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyExpiredException; +import android.security.keystore.KeyNotYetValidException; +import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.UserNotAuthenticatedException; +import android.system.keystore2.Authorization; +import android.system.keystore2.ResponseCode; import libcore.util.EmptyArray; @@ -26,6 +35,8 @@ import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.List; /** * Assorted utility methods for implementing crypto operations on top of KeyStore. @@ -38,33 +49,80 @@ abstract class KeyStoreCryptoOperationUtils { private KeyStoreCryptoOperationUtils() {} - /** - * Returns the {@link InvalidKeyException} to be thrown by the {@code init} method of - * the crypto operation in response to {@code KeyStore.begin} operation or {@code null} if - * the {@code init} method should succeed. - */ - static InvalidKeyException getInvalidKeyExceptionForInit( - KeyStore keyStore, AndroidKeyStoreKey key, int beginOpResultCode) { - if (beginOpResultCode == KeyStore.NO_ERROR) { - return null; + + public static boolean canUserAuthorizationSucceed(AndroidKeyStoreKey key) { + List keySids = new ArrayList(); + for (Authorization p : key.getAuthorizations()) { + switch(p.keyParameter.tag) { + case KeymasterDefs.KM_TAG_USER_SECURE_ID: + keySids.add(p.keyParameter.longInteger); + break; + default: + break; + } + } + if (keySids.isEmpty()) { + // Key is not bound to any SIDs -- no amount of authentication will help here. + return false; + } + long rootSid = GateKeeper.getSecureUserId(); + if ((rootSid != 0) && (keySids.contains(rootSid))) { + // One of the key's SIDs is the current root SID -- user can be authenticated + // against that SID. + return true; } - // An error occurred. However, some errors should not lead to init throwing an exception. - // See below. - InvalidKeyException e = - keyStore.getInvalidKeyException(key.getAlias(), key.getUid(), beginOpResultCode); - switch (beginOpResultCode) { - case KeyStore.OP_AUTH_NEEDED: - // Operation needs to be authorized by authenticating the user. Don't throw an - // exception is such authentication is possible for this key - // (UserNotAuthenticatedException). An example of when it's not possible is where - // the key is permanently invalidated (KeyPermanentlyInvalidatedException). - if (e instanceof UserNotAuthenticatedException) { - return null; - } + long[] biometricSids = ActivityThread + .currentApplication() + .getSystemService(BiometricManager.class) + .getAuthenticatorIds(); + + // The key must contain every biometric SID. This is because the current API surface + // treats all biometrics (capable of keystore integration) equally. e.g. if the + // device has multiple keystore-capable sensors, and one of the sensor's SIDs + // changed, 1) there is no way for a developer to specify authentication with a + // specific sensor (the one that hasn't changed), and 2) currently the only + // signal to developers is the UserNotAuthenticatedException, which doesn't + // indicate a specific sensor. + boolean canUnlockViaBiometrics = true; + for (long sid : biometricSids) { + if (!keySids.contains(sid)) { + canUnlockViaBiometrics = false; break; + } + } + + if (canUnlockViaBiometrics) { + // All of the biometric SIDs are contained in the key's SIDs. + return true; + } + + // None of the key's SIDs can ever be authenticated + return false; + } + + /** + * Returns an {@link InvalidKeyException} corresponding to the provided + * {@link KeyStoreException}. + */ + public static InvalidKeyException getInvalidKeyException( + AndroidKeyStoreKey key, KeyStoreException e) { + switch (e.getErrorCode()) { + case KeymasterDefs.KM_ERROR_KEY_EXPIRED: + return new KeyExpiredException(); + case KeymasterDefs.KM_ERROR_KEY_NOT_YET_VALID: + return new KeyNotYetValidException(); + case ResponseCode.KEY_NOT_FOUND: + // TODO is this the right exception in this case? + case ResponseCode.KEY_PERMANENTLY_INVALIDATED: + return new KeyPermanentlyInvalidatedException(); + case ResponseCode.LOCKED: + case ResponseCode.UNINITIALIZED: + // TODO b/173111727 remove response codes LOCKED and UNINITIALIZED + return new UserNotAuthenticatedException(); + default: + return new InvalidKeyException("Keystore operation failed", e); } - return e; } /** @@ -73,13 +131,13 @@ abstract class KeyStoreCryptoOperationUtils { * should succeed. */ public static GeneralSecurityException getExceptionForCipherInit( - KeyStore keyStore, AndroidKeyStoreKey key, int beginOpResultCode) { - if (beginOpResultCode == KeyStore.NO_ERROR) { + AndroidKeyStoreKey key, KeyStoreException e) { + if (e.getErrorCode() == KeyStore.NO_ERROR) { return null; } // Cipher-specific cases - switch (beginOpResultCode) { + switch (e.getErrorCode()) { case KeymasterDefs.KM_ERROR_INVALID_NONCE: return new InvalidAlgorithmParameterException("Invalid IV"); case KeymasterDefs.KM_ERROR_CALLER_NONCE_PROHIBITED: @@ -87,7 +145,7 @@ abstract class KeyStoreCryptoOperationUtils { } // General cases - return getInvalidKeyExceptionForInit(keyStore, key, beginOpResultCode); + return getInvalidKeyException(key, e); } /** -- cgit v1.2.3-59-g8ed1b From 4ba9a09bdde9de63a8a8d2068ee62c24dd46a1d2 Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Sat, 10 Oct 2020 08:27:47 -0700 Subject: Keystore 2.0 SPI: Update the chunked streamer. This patch makes the chunked streamer observe the simplified Keystore 2.0 operation interface. Keystore is now required to consume all supplied data or reject data outright if too much (more than 32KiB) is supplied in a single transaction. This allows for a simplified streamer logic and a simplified interface. We also no longer send entropy to Keystore. This will be handled by the Keystore 2.0 daemon. Test: None Bug: 159476414 Change-Id: Ie75d10fd5d5ac0da60e23e35467d0a7873230dea --- .../KeyStoreCryptoOperationChunkedStreamer.java | 152 +++++++++++---------- .../keystore2/KeyStoreCryptoOperationStreamer.java | 6 +- 2 files changed, 80 insertions(+), 78 deletions(-) diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java index 3bf9da080f30..6c733ba712d5 100644 --- a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java +++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java @@ -16,19 +16,17 @@ package android.security.keystore2; -import android.os.IBinder; -import android.security.KeyStore; +import android.annotation.NonNull; import android.security.KeyStoreException; +import android.security.KeyStoreOperation; import android.security.keymaster.KeymasterDefs; -import android.security.keymaster.OperationResult; import android.security.keystore.ArrayUtils; -import android.security.keystore.KeyStoreConnectException; import libcore.util.EmptyArray; /** - * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's - * {@code update} and {@code finish} operations. + * Helper for streaming a crypto operation's input and output via {@link KeyStoreOperation} + * service's {@code update} and {@code finish} operations. * *

The helper abstracts away issues that need to be solved in most code that uses KeyStore's * update and finish operations. Firstly, KeyStore's update operation can consume only a limited @@ -56,21 +54,34 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS */ interface Stream { /** - * Returns the result of the KeyStore {@code update} operation or null if keystore couldn't - * be reached. + * Returns the result of the KeyStoreOperation {@code update} if applicable. + * The return value may be null, e.g., when supplying AAD or to-be-signed data. + * + * @param input Data to update a KeyStoreOperation with. + * + * @throws KeyStoreException in case of error. */ - OperationResult update(byte[] input); + byte[] update(@NonNull byte[] input) throws KeyStoreException; /** - * Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't - * be reached. + * Returns the result of the KeyStore {@code finish} if applicable. + * + * @param input Optional data to update the operation with one last time. + * + * @param signature Optional HMAC signature when verifying an HMAC signature, must be + * null otherwise. + * + * @return Optional output data. Depending on the operation this may be a signature, + * some final bit of cipher, or plain text. + * + * @throws KeyStoreException in case of error. */ - OperationResult finish(byte[] input, byte[] siganture, byte[] additionalEntropy); + byte[] finish(byte[] input, byte[] signature) throws KeyStoreException; } // Binder buffer is about 1MB, but it's shared between all active transactions of the process. // Thus, it's safer to use a much smaller upper bound. - private static final int DEFAULT_CHUNK_SIZE_MAX = 64 * 1024; + private static final int DEFAULT_CHUNK_SIZE_MAX = 32 * 1024; // The chunk buffer will be sent to update until its size under this threshold. // This threshold should be <= the max input allowed for finish. // Setting this threshold <= 1 will effectivley disable buffering between updates. @@ -94,6 +105,9 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold, int chunkSizeMax) { + mChunkLength = 0; + mConsumedInputSizeBytes = 0; + mProducedOutputSizeBytes = 0; mKeyStoreStream = operation; mChunkSizeMax = chunkSizeMax; if (chunkSizeThreshold <= 0) { @@ -113,78 +127,67 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS } if (inputLength < 0 || inputOffset < 0 || (inputOffset + inputLength) > input.length) { throw new KeyStoreException(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR, - "Input offset and length out of bounds of input array"); + "Input offset and length out of bounds of input array"); } byte[] output = EmptyArray.BYTE; - while (inputLength > 0 || mChunkLength >= mChunkSizeThreshold) { + // Preamble: If there is leftover data, we fill it up with the new data provided + // and send it to Keystore. + if (mChunkLength > 0) { + // Fill current chunk and send it to Keystore int inputConsumed = ArrayUtils.copy(input, inputOffset, mChunk, mChunkLength, inputLength); inputLength -= inputConsumed; - inputOffset += inputConsumed; - mChunkLength += inputConsumed; + inputOffset += inputOffset; + byte[] o = mKeyStoreStream.update(mChunk); + if (o != null) { + output = ArrayUtils.concat(output, o); + } mConsumedInputSizeBytes += inputConsumed; + mChunkLength = 0; + } - if (mChunkLength > mChunkSizeMax) { - throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH, - "Chunk size exceeded max chunk size. Max: " + mChunkSizeMax - + " Actual: " + mChunkLength); + // Main loop: Send large enough chunks to Keystore. + while (inputLength >= mChunkSizeThreshold) { + int nextChunkSize = inputLength < mChunkSizeMax ? inputLength : mChunkSizeMax; + byte[] o = mKeyStoreStream.update(ArrayUtils.subarray(input, inputOffset, + nextChunkSize)); + inputLength -= nextChunkSize; + inputOffset += nextChunkSize; + mConsumedInputSizeBytes += nextChunkSize; + if (o != null) { + output = ArrayUtils.concat(output, o); } + } - if (mChunkLength >= mChunkSizeThreshold) { - OperationResult opResult = mKeyStoreStream.update( - ArrayUtils.subarray(mChunk, 0, mChunkLength)); - - if (opResult == null) { - throw new KeyStoreConnectException(); - } else if (opResult.resultCode != KeyStore.NO_ERROR) { - throw KeyStore.getKeyStoreException(opResult.resultCode); - } - if (opResult.inputConsumed <= 0) { - throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH, - "Keystore consumed 0 of " + mChunkLength + " bytes provided."); - } else if (opResult.inputConsumed > mChunkLength) { - throw new KeyStoreException(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR, - "Keystore consumed more input than provided. Provided: " - + mChunkLength + ", consumed: " + opResult.inputConsumed); - } - mChunkLength -= opResult.inputConsumed; - - if (mChunkLength > 0) { - // Partialy consumed, shift chunk contents - ArrayUtils.copy(mChunk, opResult.inputConsumed, mChunk, 0, mChunkLength); - } - - if ((opResult.output != null) && (opResult.output.length > 0)) { - // Output was produced - mProducedOutputSizeBytes += opResult.output.length; - output = ArrayUtils.concat(output, opResult.output); - } - } + // If we have left over data, that did not make the threshold, we store it in the chunk + // store. + if (inputLength > 0) { + mChunkLength = ArrayUtils.copy(input, inputOffset, mChunk, 0, inputLength); + mConsumedInputSizeBytes += inputLength; } + + mProducedOutputSizeBytes += output.length; return output; } public byte[] doFinal(byte[] input, int inputOffset, int inputLength, - byte[] signature, byte[] additionalEntropy) throws KeyStoreException { + byte[] signature) throws KeyStoreException { byte[] output = update(input, inputOffset, inputLength); byte[] finalChunk = ArrayUtils.subarray(mChunk, 0, mChunkLength); - OperationResult opResult = mKeyStoreStream.finish(finalChunk, signature, additionalEntropy); - - if (opResult == null) { - throw new KeyStoreConnectException(); - } else if (opResult.resultCode != KeyStore.NO_ERROR) { - throw KeyStore.getKeyStoreException(opResult.resultCode); - } - // If no error, assume all input consumed - mConsumedInputSizeBytes += finalChunk.length; - - if ((opResult.output != null) && (opResult.output.length > 0)) { - mProducedOutputSizeBytes += opResult.output.length; - output = ArrayUtils.concat(output, opResult.output); + byte[] o = mKeyStoreStream.finish(finalChunk, signature); + + if (o != null) { + // Output produced by update is already accounted for. We only add the bytes + // produced by finish. + mProducedOutputSizeBytes += o.length; + if (output != null) { + output = ArrayUtils.concat(output, o); + } else { + output = o; + } } - return output; } @@ -206,22 +209,21 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS */ public static class MainDataStream implements Stream { - private final KeyStore mKeyStore; - private final IBinder mOperationToken; + private final KeyStoreOperation mOperation; - public MainDataStream(KeyStore keyStore, IBinder operationToken) { - mKeyStore = keyStore; - mOperationToken = operationToken; + MainDataStream(KeyStoreOperation operation) { + mOperation = operation; } @Override - public OperationResult update(byte[] input) { - return mKeyStore.update(mOperationToken, null, input); + public byte[] update(byte[] input) throws KeyStoreException { + return mOperation.update(input); } @Override - public OperationResult finish(byte[] input, byte[] signature, byte[] additionalEntropy) { - return mKeyStore.finish(mOperationToken, null, input, signature, additionalEntropy); + public byte[] finish(byte[] input, byte[] signature) + throws KeyStoreException { + return mOperation.finish(input, signature); } } } diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java index fec3bbc3460a..07d6a69eda01 100644 --- a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java +++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java @@ -28,15 +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, byte[], byte[]) doFinal} operations which can be used to + * {@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, byte[] signature, - byte[] additionalEntropy) throws KeyStoreException; + byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature) + throws KeyStoreException; long getConsumedInputSizeBytes(); long getProducedOutputSizeBytes(); } -- cgit v1.2.3-59-g8ed1b From 4be5005c05a261ad9cdfb72d968871b2ba648798 Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Sat, 10 Oct 2020 08:26:50 -0700 Subject: Keystore 2.0 SPI: KeyParameter utilities. The wire type for key parameters is now generated from AIDL rather than the hand written parcelable KeymasterArguments. So we need some of the utilities for creating key parameters that the latter provided. We also nicked some utility function from KeymasterUtils. Bug: 159476414 Test: None Change-Id: I12c674b6a00dd3abbed4972d80ceb766a73881e8 --- .../keystore2/KeyStore2ParameterUtils.java | 313 +++++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java diff --git a/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java new file mode 100644 index 000000000000..ee67ed3f76d8 --- /dev/null +++ b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2020 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.keystore2; + +import android.annotation.NonNull; +import android.hardware.biometrics.BiometricManager; +import android.security.GateKeeper; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyProperties; +import android.security.keystore.UserAuthArgs; +import android.system.keystore2.Authorization; +import android.system.keystore2.KeyParameter; +import android.system.keystore2.SecurityLevel; + +import java.security.ProviderException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.function.Consumer; + +/** + * @hide + */ +public abstract class KeyStore2ParameterUtils { + + /** + * This function constructs a {@link KeyParameter} expressing a boolean value. + * @param tag Must be KeyMint tag with the associated type BOOL. + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeBool(int tag) { + int type = KeymasterDefs.getTagType(tag); + if (type != KeymasterDefs.KM_BOOL) { + throw new IllegalArgumentException("Not a boolean tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.boolValue = true; + return p; + } + + /** + * This function constructs a {@link KeyParameter} expressing an enum value. + * @param tag Must be KeyMint tag with the associated type ENUM or ENUM_REP. + * @param v A 32bit integer. + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeEnum(int tag, int v) { + int type = KeymasterDefs.getTagType(tag); + if (type != KeymasterDefs.KM_ENUM && type != KeymasterDefs.KM_ENUM_REP) { + throw new IllegalArgumentException("Not an enum or repeatable enum tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.integer = v; + return p; + } + + /** + * This function constructs a {@link KeyParameter} expressing an integer value. + * @param tag Must be KeyMint tag with the associated type UINT or UINT_REP. + * @param v A 32bit integer. + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeInt(int tag, int v) { + int type = KeymasterDefs.getTagType(tag); + if (type != KeymasterDefs.KM_UINT && type != KeymasterDefs.KM_UINT_REP) { + throw new IllegalArgumentException("Not an int or repeatable int tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.integer = v; + return p; + } + + /** + * This function constructs a {@link KeyParameter} expressing a long integer value. + * @param tag Must be KeyMint tag with the associated type ULONG or ULONG_REP. + * @param v A 64bit integer. + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeLong(int tag, long v) { + int type = KeymasterDefs.getTagType(tag); + if (type != KeymasterDefs.KM_ULONG && type != KeymasterDefs.KM_ULONG_REP) { + throw new IllegalArgumentException("Not a long or repeatable long tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.longInteger = v; + return p; + } + + /** + * This function constructs a {@link KeyParameter} expressing a blob. + * @param tag Must be KeyMint tag with the associated type BYTES. + * @param b A byte array to be stored in the new key parameter. + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeBytes(int tag, @NonNull byte[] b) { + if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_BYTES) { + throw new IllegalArgumentException("Not a bytes tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.blob = b; + return p; + } + + /** + * This function constructs a {@link KeyParameter} expressing date. + * @param tag Must be KeyMint tag with the associated type DATE. + * @param date A date + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeDate(int tag, @NonNull Date date) { + if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_DATE) { + throw new IllegalArgumentException("Not a date tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.longInteger = date.getTime(); + if (p.longInteger < 0) { + throw new IllegalArgumentException("Date tag value out of range: " + p.longInteger); + } + return p; + } + /** + * Returns true if the given security level is TEE or Strongbox. + * + * @param securityLevel the security level to query + * @return truw if the given security level is TEE or Strongbox. + */ + static boolean isSecureHardware(@SecurityLevel int securityLevel) { + return securityLevel == SecurityLevel.TRUSTED_ENVIRONMENT + || securityLevel == SecurityLevel.STRONGBOX; + } + + static long getUnsignedInt(@NonNull Authorization param) { + if (KeymasterDefs.getTagType(param.keyParameter.tag) != KeymasterDefs.KM_UINT) { + throw new IllegalArgumentException("Not an int tag: " + param.keyParameter.tag); + } + // KM_UINT is 32 bits wide so we must suppress sign extension. + return ((long) param.keyParameter.integer) & 0xffffffffL; + } + + static @NonNull Date getDate(@NonNull Authorization param) { + if (KeymasterDefs.getTagType(param.keyParameter.tag) != KeymasterDefs.KM_DATE) { + throw new IllegalArgumentException("Not a date tag: " + param.keyParameter.tag); + } + if (param.keyParameter.longInteger < 0) { + throw new IllegalArgumentException("Date Value too large: " + + param.keyParameter.longInteger); + } + return new Date(param.keyParameter.longInteger); + } + + static void forEachSetFlag(int flags, Consumer consumer) { + int offset = 0; + while (flags != 0) { + if ((flags & 1) == 0) { + consumer.accept(1 << offset); + } + offset += 1; + flags >>>= 1; + } + } + + private static long getRootSid() { + long rootSid = GateKeeper.getSecureUserId(); + if (rootSid == 0) { + throw new IllegalStateException("Secure lock screen must be enabled" + + " to create keys requiring user authentication"); + } + return rootSid; + } + + private static void addSids(@NonNull List params, @NonNull UserAuthArgs spec) { + // If both biometric and credential are accepted, then just use the root sid from gatekeeper + if (spec.getUserAuthenticationType() == (KeyProperties.AUTH_BIOMETRIC_STRONG + | KeyProperties.AUTH_DEVICE_CREDENTIAL)) { + if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) { + params.add(makeLong( + KeymasterDefs.KM_TAG_USER_SECURE_ID, + spec.getBoundToSpecificSecureUserId() + )); + } else { + // The key is authorized for use for the specified amount of time after the user has + // authenticated. Whatever unlocks the secure lock screen should authorize this key. + params.add(makeLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, getRootSid())); + } + } else { + List sids = new ArrayList<>(); + if ((spec.getUserAuthenticationType() & KeyProperties.AUTH_BIOMETRIC_STRONG) != 0) { + final BiometricManager bm = android.app.AppGlobals.getInitialApplication() + .getSystemService(BiometricManager.class); + + // TODO: Restore permission check in getAuthenticatorIds once the ID is no longer + // needed here. + + final long[] biometricSids = bm.getAuthenticatorIds(); + + if (biometricSids.length == 0) { + throw new IllegalStateException( + "At least one biometric must be enrolled to create keys requiring user" + + " authentication for every use"); + } + + if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) { + sids.add(spec.getBoundToSpecificSecureUserId()); + } else if (spec.isInvalidatedByBiometricEnrollment()) { + // The biometric-only SIDs will change on biometric enrollment or removal of all + // enrolled templates, invalidating the key. + for (long sid : biometricSids) { + sids.add(sid); + } + } else { + // The root SID will *not* change on fingerprint enrollment, or removal of all + // enrolled fingerprints, allowing the key to remain valid. + sids.add(getRootSid()); + } + } else if ((spec.getUserAuthenticationType() & KeyProperties.AUTH_DEVICE_CREDENTIAL) + != 0) { + sids.add(getRootSid()); + } else { + throw new IllegalStateException("Invalid or no authentication type specified."); + } + + for (int i = 0; i < sids.size(); i++) { + params.add(makeLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, sids.get(i))); + } + } + } + + /** + * Adds keymaster arguments to express the key's authorization policy supported by user + * authentication. + * + * @param args The arguments sent to keymaster that need to be populated from the spec + * @param spec The user authentication relevant portions of the spec passed in from the caller. + * This spec will be translated into the relevant keymaster tags to be loaded into args. + * @throws IllegalStateException if user authentication is required but the system is in a wrong + * state (e.g., secure lock screen not set up) for generating or importing keys that + * require user authentication. + */ + static void addUserAuthArgs(@NonNull List args, + @NonNull UserAuthArgs spec) { + + if (spec.isUserConfirmationRequired()) { + args.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED)); + } + if (spec.isUserPresenceRequired()) { + args.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED)); + } + if (spec.isUnlockedDeviceRequired()) { + args.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_UNLOCKED_DEVICE_REQUIRED)); + } + if (!spec.isUserAuthenticationRequired()) { + args.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED)); + } else { + if (spec.getUserAuthenticationValidityDurationSeconds() == 0) { + // Every use of this key needs to be authorized by the user. + addSids(args, spec); + args.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType() + )); + + if (spec.isUserAuthenticationValidWhileOnBody()) { + throw new ProviderException( + "Key validity extension while device is on-body is not " + + "supported for keys requiring fingerprint authentication"); + } + } else { + addSids(args, spec); + args.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType() + )); + args.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_AUTH_TIMEOUT, + spec.getUserAuthenticationValidityDurationSeconds() + )); + if (spec.isUserAuthenticationValidWhileOnBody()) { + args.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY + )); + } + } + } + } +} -- cgit v1.2.3-59-g8ed1b From 4545933da590ad92faaf075623a0d00a75debfdd Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Fri, 9 Oct 2020 14:23:17 -0700 Subject: Keystore 2.0 SPI: Evolve the Crypto SPI. This patch evolves the Crypto SPI to use the new Keystore 2.0 shim. The main changes are: * The SPI uses the AIDL defined KeyParameter instead of KeymasterArguments. * Operations are created directly from the KeystoreSecurityLevel that is part of the AndroidKeyStoreKey object. Also this patch deletes the DeletatingX509Certificate class. This is no longer needed, because public key operations are no longer performed by Keystore 2.0. We can delegate public certificate operations simply by wrapping such certificates into public keys that are understood by other providers, such as BouncyCastle. Bug: 159476414 Test: None Change-Id: Ice874a8121d80bf788da059b4e8420c7dd799d81 --- .../keystore2/AndroidKeyStore3DESCipherSpi.java | 51 +- .../AndroidKeyStoreAuthenticatedAESCipherSpi.java | 115 +-- .../keystore2/AndroidKeyStoreCipherSpiBase.java | 170 ++-- .../AndroidKeyStoreECDSASignatureSpi.java | 42 +- .../security/keystore2/AndroidKeyStoreHmacSpi.java | 113 +-- .../AndroidKeyStoreLoadStoreParameter.java | 13 +- .../keystore2/AndroidKeyStoreRSACipherSpi.java | 50 +- .../keystore2/AndroidKeyStoreRSASignatureSpi.java | 20 +- .../keystore2/AndroidKeyStoreSignatureSpiBase.java | 125 ++- .../security/keystore2/AndroidKeyStoreSpi.java | 1052 ++++++++++---------- ...AndroidKeyStoreUnauthenticatedAESCipherSpi.java | 49 +- .../keystore2/DelegatingX509Certificate.java | 212 ---- .../keystore2/KeyStoreCryptoOperationUtils.java | 34 + 13 files changed, 956 insertions(+), 1090 deletions(-) delete mode 100644 keystore/java/android/security/keystore2/DelegatingX509Certificate.java diff --git a/keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java index 275dcef8a78c..70713a47ad6d 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java @@ -16,10 +16,11 @@ package android.security.keystore2; -import android.security.keymaster.KeymasterArguments; +import android.annotation.NonNull; import android.security.keymaster.KeymasterDefs; import android.security.keystore.ArrayUtils; import android.security.keystore.KeyProperties; +import android.system.keystore2.KeyParameter; import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; @@ -30,6 +31,7 @@ import java.security.ProviderException; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidParameterSpecException; import java.util.Arrays; +import java.util.List; import javax.crypto.CipherSpi; import javax.crypto.spec.IvParameterSpec; @@ -67,15 +69,13 @@ public class AndroidKeyStore3DESCipherSpi extends AndroidKeyStoreCipherSpiBase { super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false); } - public static class NoPadding extends - AndroidKeyStore3DESCipherSpi.ECB { + public static class NoPadding extends ECB { public NoPadding() { super(KeymasterDefs.KM_PAD_NONE); } } - public static class PKCS7Padding extends - AndroidKeyStore3DESCipherSpi.ECB { + public static class PKCS7Padding extends ECB { public PKCS7Padding() { super(KeymasterDefs.KM_PAD_PKCS7); } @@ -87,15 +87,13 @@ public class AndroidKeyStore3DESCipherSpi extends AndroidKeyStoreCipherSpiBase { super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true); } - public static class NoPadding extends - AndroidKeyStore3DESCipherSpi.CBC { + public static class NoPadding extends CBC { public NoPadding() { super(KeymasterDefs.KM_PAD_NONE); } } - public static class PKCS7Padding extends - AndroidKeyStore3DESCipherSpi.CBC { + public static class PKCS7Padding extends CBC { public PKCS7Padding() { super(KeymasterDefs.KM_PAD_PKCS7); } @@ -254,7 +252,7 @@ public class AndroidKeyStore3DESCipherSpi extends AndroidKeyStoreCipherSpiBase { } @Override - protected void addAlgorithmSpecificParametersToBegin(KeymasterArguments keymasterArgs) { + protected void addAlgorithmSpecificParametersToBegin(@NonNull List parameters) { if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) { // IV is being reused for encryption: this violates security best practices. throw new IllegalStateException( @@ -262,23 +260,38 @@ public class AndroidKeyStore3DESCipherSpi extends AndroidKeyStoreCipherSpiBase { + " practices."); } - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_3DES); - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode); - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding); - if ((mIvRequired) && (mIv != null)) { - keymasterArgs.addBytes(KeymasterDefs.KM_TAG_NONCE, mIv); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, + KeymasterDefs.KM_ALGORITHM_3DES + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_BLOCK_MODE, + mKeymasterBlockMode + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, + mKeymasterPadding + )); + + if (mIvRequired && (mIv != null)) { + parameters.add(KeyStore2ParameterUtils.makeBytes(KeymasterDefs.KM_TAG_NONCE, mIv)); } } @Override protected void loadAlgorithmSpecificParametersFromBeginResult( - KeymasterArguments keymasterArgs) { + KeyParameter[] parameters) { mIvHasBeenUsed = true; // NOTE: Keymaster doesn't always return an IV, even if it's used. - byte[] returnedIv = keymasterArgs.getBytes(KeymasterDefs.KM_TAG_NONCE, null); - if ((returnedIv != null) && (returnedIv.length == 0)) { - returnedIv = null; + byte[] returnedIv = null; + if (parameters != null) { + for (KeyParameter p : parameters) { + if (p.tag == KeymasterDefs.KM_TAG_NONCE) { + returnedIv = p.blob; + break; + } + } } if (mIvRequired) { diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java index 43381c0078d9..dd094b7a5fd0 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java @@ -18,15 +18,13 @@ package android.security.keystore2; 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.KeyStoreOperation; import android.security.keymaster.KeymasterDefs; -import android.security.keymaster.OperationResult; import android.security.keystore.ArrayUtils; import android.security.keystore.KeyProperties; import android.security.keystore2.KeyStoreCryptoOperationChunkedStreamer.Stream; +import android.system.keystore2.KeyParameter; import libcore.util.EmptyArray; @@ -41,6 +39,7 @@ import java.security.ProviderException; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidParameterSpecException; import java.util.Arrays; +import java.util.List; import javax.crypto.CipherSpi; import javax.crypto.spec.GCMParameterSpec; @@ -175,26 +174,25 @@ abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreC @NonNull @Override protected KeyStoreCryptoOperationStreamer createMainDataStreamer( - KeyStore keyStore, IBinder operationToken) { - KeyStoreCryptoOperationStreamer - streamer = new KeyStoreCryptoOperationChunkedStreamer( + KeyStoreOperation operation) { + KeyStoreCryptoOperationStreamer streamer = new KeyStoreCryptoOperationChunkedStreamer( new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( - keyStore, operationToken), 0); + operation), 0); 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 AndroidKeyStoreAuthenticatedAESCipherSpi.BufferAllOutputUntilDoFinalStreamer(streamer); + return new BufferAllOutputUntilDoFinalStreamer(streamer); } } @NonNull @Override protected final KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer( - KeyStore keyStore, IBinder operationToken) { + KeyStoreOperation operation) { return new KeyStoreCryptoOperationChunkedStreamer( - new AndroidKeyStoreAuthenticatedAESCipherSpi.AdditionalAuthenticationDataStream(keyStore, operationToken), 0); + new AdditionalAuthenticationDataStream(operation), 0); } @Override @@ -214,17 +212,19 @@ abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreC @Override protected final void addAlgorithmSpecificParametersToBegin( - @NonNull KeymasterArguments keymasterArgs) { - super.addAlgorithmSpecificParametersToBegin(keymasterArgs); - keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mTagLengthBits); + @NonNull List parameters) { + super.addAlgorithmSpecificParametersToBegin(parameters); + parameters.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_MAC_LENGTH, + mTagLengthBits + )); } protected final int getTagLengthBits() { return mTagLengthBits; } - public static final class NoPadding extends - AndroidKeyStoreAuthenticatedAESCipherSpi.GCM { + public static final class NoPadding extends GCM { public NoPadding() { super(KeymasterDefs.KM_PAD_NONE); } @@ -290,31 +290,45 @@ abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreC @Override protected void addAlgorithmSpecificParametersToBegin( - @NonNull KeymasterArguments keymasterArgs) { + @NonNull List parameters) { 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."); } + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, + KeymasterDefs.KM_ALGORITHM_AES + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_BLOCK_MODE, + mKeymasterBlockMode + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, + mKeymasterPadding + )); - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES); - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode); - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding); if (mIv != null) { - keymasterArgs.addBytes(KeymasterDefs.KM_TAG_NONCE, mIv); + parameters.add(KeyStore2ParameterUtils.makeBytes(KeymasterDefs.KM_TAG_NONCE, mIv)); } } @Override protected final void loadAlgorithmSpecificParametersFromBeginResult( - @NonNull KeymasterArguments keymasterArgs) { + KeyParameter[] parameters) { mIvHasBeenUsed = true; // NOTE: Keymaster doesn't always return an IV, even if it's used. - byte[] returnedIv = keymasterArgs.getBytes(KeymasterDefs.KM_TAG_NONCE, null); - if ((returnedIv != null) && (returnedIv.length == 0)) { - returnedIv = null; + byte[] returnedIv = null; + if (parameters != null) { + for (KeyParameter p : parameters) { + if (p.tag == KeymasterDefs.KM_TAG_NONCE) { + returnedIv = p.blob; + break; + } + } } if (mIv == null) { @@ -353,8 +367,7 @@ abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreC private ByteArrayOutputStream mBufferedOutput = new ByteArrayOutputStream(); private long mProducedOutputSizeBytes; - private BufferAllOutputUntilDoFinalStreamer( - KeyStoreCryptoOperationStreamer delegate) { + private BufferAllOutputUntilDoFinalStreamer(KeyStoreCryptoOperationStreamer delegate) { mDelegate = delegate; } @@ -374,9 +387,8 @@ abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreC @Override public byte[] doFinal(byte[] input, int inputOffset, int inputLength, - byte[] signature, byte[] additionalEntropy) throws KeyStoreException { - byte[] output = mDelegate.doFinal(input, inputOffset, inputLength, signature, - additionalEntropy); + byte[] signature) throws KeyStoreException { + byte[] output = mDelegate.doFinal(input, inputOffset, inputLength, signature); if (output != null) { try { mBufferedOutput.write(output); @@ -407,48 +419,21 @@ abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreC */ private static class AdditionalAuthenticationDataStream implements Stream { - private final KeyStore mKeyStore; - private final IBinder mOperationToken; + private final KeyStoreOperation mOperation; - private AdditionalAuthenticationDataStream(KeyStore keyStore, IBinder operationToken) { - mKeyStore = keyStore; - mOperationToken = operationToken; + private AdditionalAuthenticationDataStream(KeyStoreOperation operation) { + mOperation = operation; } @Override - public OperationResult update(byte[] input) { - KeymasterArguments keymasterArgs = new KeymasterArguments(); - keymasterArgs.addBytes(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; + public byte[] update(byte[] input) throws KeyStoreException { + mOperation.updateAad(input); + return null; } @Override - public OperationResult finish(byte[] input, byte[] signature, 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 - ); + public byte[] finish(byte[] input, byte[] signature) { + return null; } } } \ No newline at end of file diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java index 94b5c4d23afe..b785ee5c6966 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java @@ -19,14 +19,11 @@ package android.security.keystore2; import android.annotation.CallSuper; 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.KeyStoreOperation; import android.security.keymaster.KeymasterDefs; -import android.security.keymaster.OperationResult; -import android.security.keystore.KeyStoreConnectException; import android.security.keystore.KeyStoreCryptoOperation; +import android.system.keystore2.KeyParameter; import libcore.util.EmptyArray; @@ -48,6 +45,8 @@ import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; +import java.util.List; import javax.crypto.AEADBadTagException; import javax.crypto.BadPaddingException; @@ -66,7 +65,7 @@ import javax.crypto.spec.SecretKeySpec; * @hide */ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStoreCryptoOperation { - private final KeyStore mKeyStore; + private static final String TAG = "AndroidKeyStoreCipherSpiBase"; // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after // doFinal finishes. @@ -76,15 +75,20 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor private SecureRandom mRng; /** - * Token referencing this operation inside keystore service. It is initialized by - * {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and on some error - * conditions in between. + * Object representing this operation inside keystore service. It is initialized + * by {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and on some + * error conditions in between. */ - private IBinder mOperationToken; - private long mOperationHandle; + private KeyStoreOperation mOperation; + /** + * The operation challenge is required when an operation needs user authorization. + * The challenge is subjected to an authenticator, e.g., Gatekeeper or a biometric + * authenticator, and included in the authentication token minted by this authenticator. + * It may be null, if the operation does not require authorization. + */ + private long mOperationChallenge; private KeyStoreCryptoOperationStreamer mMainDataStreamer; - private KeyStoreCryptoOperationStreamer - mAdditionalAuthenticationDataStreamer; + private KeyStoreCryptoOperationStreamer mAdditionalAuthenticationDataStreamer; private boolean mAdditionalAuthenticationDataStreamerClosed; /** @@ -96,7 +100,16 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor private Exception mCachedException; AndroidKeyStoreCipherSpiBase() { - mKeyStore = KeyStore.getInstance(); + mOperation = null; + mEncrypting = false; + mKeymasterPurposeOverride = -1; + mKey = null; + mRng = null; + mOperationChallenge = 0; + mMainDataStreamer = null; + mAdditionalAuthenticationDataStreamer = null; + mAdditionalAuthenticationDataStreamerClosed = false; + mCachedException = null; } @Override @@ -177,6 +190,11 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor mRng = random; } + private void abortOperation() { + KeyStoreCryptoOperationUtils.abortOperation(mOperation); + mOperation = null; + } + /** * Resets this cipher to its pristine pre-init state. This must be equivalent to obtaining a new * cipher instance. @@ -186,16 +204,12 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor */ @CallSuper protected void resetAll() { - IBinder operationToken = mOperationToken; - if (operationToken != null) { - mKeyStore.abort(operationToken); - } + abortOperation(); mEncrypting = false; mKeymasterPurposeOverride = -1; mKey = null; mRng = null; - mOperationToken = null; - mOperationHandle = 0; + mOperationChallenge = 0; mMainDataStreamer = null; mAdditionalAuthenticationDataStreamer = null; mAdditionalAuthenticationDataStreamerClosed = false; @@ -212,12 +226,8 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor */ @CallSuper protected void resetWhilePreservingInitState() { - IBinder operationToken = mOperationToken; - if (operationToken != null) { - mKeyStore.abort(operationToken); - } - mOperationToken = null; - mOperationHandle = 0; + abortOperation(); + mOperationChallenge = 0; mMainDataStreamer = null; mAdditionalAuthenticationDataStreamer = null; mAdditionalAuthenticationDataStreamerClosed = false; @@ -236,10 +246,8 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor throw new IllegalStateException("Not initialized"); } - KeymasterArguments keymasterInputArgs = new KeymasterArguments(); - addAlgorithmSpecificParametersToBegin(keymasterInputArgs); - byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( - mRng, getAdditionalEntropyAmountForBegin()); + List parameters = new ArrayList<>(); + addAlgorithmSpecificParametersToBegin(parameters); int purpose; if (mKeymasterPurposeOverride != -1) { @@ -248,46 +256,38 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor purpose = mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT; } - OperationResult opResult = mKeyStore.begin( - mKey.getAlias(), - purpose, - true, // permit aborting this operation if keystore runs out of resources - keymasterInputArgs, - additionalEntropy, - mKey.getUid()); - if (opResult == null) { - throw new KeyStoreConnectException(); - } - - // Store operation token and handle regardless of the error code returned by KeyStore to - // ensure that the operation gets aborted immediately if the code below throws an exception. - mOperationToken = opResult.token; - mOperationHandle = opResult.operationHandle; - - // If necessary, throw an exception due to KeyStore operation having failed. - GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit( - mKeyStore, mKey, opResult.resultCode); - if (e != null) { - if (e instanceof InvalidKeyException) { - throw (InvalidKeyException) e; - } else if (e instanceof InvalidAlgorithmParameterException) { - throw (InvalidAlgorithmParameterException) e; - } else { - throw new ProviderException("Unexpected exception type", e); + + parameters.add(KeyStore2ParameterUtils.makeEnum(KeymasterDefs.KM_TAG_PURPOSE, purpose)); + + try { + mOperation = mKey.getSecurityLevel().createOperation( + mKey.getKeyIdDescriptor(), + parameters + ); + } catch (KeyStoreException keyStoreException) { + GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit( + mKey, keyStoreException); + if (e != null) { + if (e instanceof InvalidKeyException) { + throw (InvalidKeyException) e; + } else if (e instanceof InvalidAlgorithmParameterException) { + throw (InvalidAlgorithmParameterException) e; + } else { + throw new ProviderException("Unexpected exception type", e); + } } } - if (mOperationToken == null) { - throw new ProviderException("Keystore returned null operation token"); - } - if (mOperationHandle == 0) { - throw new ProviderException("Keystore returned invalid operation handle"); - } + // Now we check if we got an operation challenge. This indicates that user authorization + // is required. And if we got a challenge we check if the authorization can possibly + // succeed. + mOperationChallenge = KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge( + mOperation, mKey); - loadAlgorithmSpecificParametersFromBeginResult(opResult.outParams); - mMainDataStreamer = createMainDataStreamer(mKeyStore, opResult.token); + loadAlgorithmSpecificParametersFromBeginResult(mOperation.getParameters()); + mMainDataStreamer = createMainDataStreamer(mOperation); mAdditionalAuthenticationDataStreamer = - createAdditionalAuthenticationDataStreamer(mKeyStore, opResult.token); + createAdditionalAuthenticationDataStreamer(mOperation); mAdditionalAuthenticationDataStreamerClosed = false; } @@ -299,10 +299,10 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor */ @NonNull protected KeyStoreCryptoOperationStreamer createMainDataStreamer( - KeyStore keyStore, IBinder operationToken) { + KeyStoreOperation operation) { return new KeyStoreCryptoOperationChunkedStreamer( new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( - keyStore, operationToken), 0); + operation), 0); } /** @@ -314,8 +314,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor */ @Nullable protected KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer( - @SuppressWarnings("unused") KeyStore keyStore, - @SuppressWarnings("unused") IBinder operationToken) { + @SuppressWarnings("unused") KeyStoreOperation operation) { return null; } @@ -358,9 +357,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor try { output = mAdditionalAuthenticationDataStreamer.doFinal( EmptyArray.BYTE, 0, 0, - null, // no signature - null // no additional entropy needed flushing AAD - ); + null); // no signature } finally { mAdditionalAuthenticationDataStreamerClosed = true; } @@ -503,17 +500,11 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor byte[] output; try { flushAAD(); - byte[] additionalEntropy = - KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( - mRng, getAdditionalEntropyAmountForFinish()); output = mMainDataStreamer.doFinal( input, inputOffset, inputLen, - null, // no signature involved - additionalEntropy); + null); // no signature involved } catch (KeyStoreException e) { switch (e.getErrorCode()) { - case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH: - throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT: throw (BadPaddingException) new BadPaddingException().initCause(e); case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED: @@ -742,10 +733,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor @Override public void finalize() throws Throwable { try { - IBinder operationToken = mOperationToken; - if (operationToken != null) { - mKeyStore.abort(operationToken); - } + abortOperation(); } finally { super.finalize(); } @@ -753,7 +741,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor @Override public final long getOperationHandle() { - return mOperationHandle; + return mOperationChallenge; } protected final void setKey(@NonNull AndroidKeyStoreKey key) { @@ -779,11 +767,6 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor return mEncrypting; } - @NonNull - protected final KeyStore getKeyStore() { - return mKeyStore; - } - protected final long getConsumedInputSizeBytes() { if (mMainDataStreamer == null) { throw new IllegalStateException("Not initialized"); @@ -901,11 +884,11 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor /** * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation. * - * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific + * @param parameters keystore/keymaster arguments to be populated with algorithm-specific * parameters. */ protected abstract void addAlgorithmSpecificParametersToBegin( - @NonNull KeymasterArguments keymasterArgs); + @NonNull List parameters); /** * Invoked to obtain algorithm-specific parameters from the result of the KeyStore's @@ -915,9 +898,8 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor * parameters, if not provided, must be generated by KeyStore and returned to the user of * {@code Cipher} and potentially reused after {@code doFinal}. * - * @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin} - * operation. + * @param parameters keystore/keymaster arguments returned by KeyStore {@code createOperation}. */ protected abstract void loadAlgorithmSpecificParametersFromBeginResult( - @NonNull KeymasterArguments keymasterArgs); + KeyParameter[] parameters); } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java index 95e4c52591e0..9f7f2383a416 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java @@ -17,19 +17,19 @@ package android.security.keystore2; import android.annotation.NonNull; -import android.os.IBinder; -import android.security.KeyStore; import android.security.KeyStoreException; -import android.security.keymaster.KeyCharacteristics; -import android.security.keymaster.KeymasterArguments; +import android.security.KeyStoreOperation; import android.security.keymaster.KeymasterDefs; import android.security.keystore.KeyProperties; +import android.system.keystore2.Authorization; +import android.system.keystore2.KeyParameter; import libcore.util.EmptyArray; import java.io.ByteArrayOutputStream; import java.security.InvalidKeyException; import java.security.SignatureSpi; +import java.util.List; /** * Base class for {@link SignatureSpi} providing Android KeyStore backed ECDSA signatures. @@ -44,10 +44,10 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature } @Override - protected KeyStoreCryptoOperationStreamer createMainDataStreamer(KeyStore keyStore, - IBinder operationToken) { + protected KeyStoreCryptoOperationStreamer createMainDataStreamer( + KeyStoreOperation operation) { return new TruncateToFieldSizeMessageStreamer( - super.createMainDataStreamer(keyStore, operationToken), + super.createMainDataStreamer(operation), getGroupSizeBits()); } @@ -81,8 +81,8 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature } @Override - public byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature, - byte[] additionalEntropy) throws KeyStoreException { + public byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature) + throws KeyStoreException { if (inputLength > 0) { mConsumedInputSizeBytes += inputLength; mInputBuffer.write(input, inputOffset, inputLength); @@ -94,7 +94,7 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature return mDelegate.doFinal(bufferedInput, 0, Math.min(bufferedInput.length, ((mGroupSizeBits + 7) / 8)), - signature, additionalEntropy); + signature); } @Override @@ -154,13 +154,13 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature + ". Only" + KeyProperties.KEY_ALGORITHM_EC + " supported"); } - KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); - int errorCode = getKeyStore().getKeyCharacteristics( - key.getAlias(), null, null, key.getUid(), keyCharacteristics); - if (errorCode != KeyStore.NO_ERROR) { - throw getKeyStore().getInvalidKeyException(key.getAlias(), key.getUid(), errorCode); + long keySizeBits = -1; + for (Authorization a : key.getAuthorizations()) { + if (a.keyParameter.tag == KeymasterDefs.KM_TAG_KEY_SIZE) { + keySizeBits = KeyStore2ParameterUtils.getUnsignedInt(a); + } } - long keySizeBits = keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, -1); + if (keySizeBits == -1) { throw new InvalidKeyException("Size of key not known"); } else if (keySizeBits > Integer.MAX_VALUE) { @@ -184,9 +184,13 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature @Override protected final void addAlgorithmSpecificParametersToBegin( - @NonNull KeymasterArguments keymasterArgs) { - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_EC); - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); + @NonNull List parameters) { + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_EC + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest + )); } @Override diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java index f7155b09750c..3dde2e592259 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java @@ -16,21 +16,20 @@ package android.security.keystore2; -import android.os.IBinder; -import android.security.KeyStore; import android.security.KeyStoreException; -import android.security.keymaster.KeymasterArguments; +import android.security.KeyStoreOperation; import android.security.keymaster.KeymasterDefs; -import android.security.keymaster.OperationResult; -import android.security.keystore.KeyStoreConnectException; import android.security.keystore.KeyStoreCryptoOperation; import android.security.keystore.KeymasterUtils; +import android.system.keystore2.KeyParameter; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.ProviderException; import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; +import java.util.List; import javax.crypto.MacSpi; @@ -41,6 +40,8 @@ import javax.crypto.MacSpi; */ public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation { + private static final String TAG = "AndroidKeyStoreHmacSpi"; + public static class HmacSHA1 extends AndroidKeyStoreHmacSpi { public HmacSHA1() { super(KeymasterDefs.KM_DIGEST_SHA1); @@ -71,7 +72,6 @@ public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreC } } - private final KeyStore mKeyStore = KeyStore.getInstance(); private final int mKeymasterDigest; private final int mMacSizeBits; @@ -80,12 +80,16 @@ public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreC // Fields below are reset when engineDoFinal succeeds. private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer; - private IBinder mOperationToken; - private long mOperationHandle; + private KeyStoreOperation mOperation; + private long mOperationChallenge; protected AndroidKeyStoreHmacSpi(int keymasterDigest) { mKeymasterDigest = keymasterDigest; mMacSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest); + mOperation = null; + mOperationChallenge = 0; + mKey = null; + mChunkedStreamer = null; } @Override @@ -127,24 +131,21 @@ public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreC } + private void abortOperation() { + KeyStoreCryptoOperationUtils.abortOperation(mOperation); + mOperation = null; + } + private void resetAll() { + abortOperation(); + mOperationChallenge = 0; mKey = null; - IBinder operationToken = mOperationToken; - if (operationToken != null) { - mKeyStore.abort(operationToken); - } - mOperationToken = null; - mOperationHandle = 0; mChunkedStreamer = null; } private void resetWhilePreservingInitState() { - IBinder operationToken = mOperationToken; - if (operationToken != null) { - mKeyStore.abort(operationToken); - } - mOperationToken = null; - mOperationHandle = 0; + abortOperation(); + mOperationChallenge = 0; mChunkedStreamer = null; } @@ -161,45 +162,40 @@ public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreC throw new IllegalStateException("Not initialized"); } - KeymasterArguments keymasterArgs = new KeymasterArguments(); - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC); - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); - keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mMacSizeBits); - - OperationResult opResult = mKeyStore.begin( - mKey.getAlias(), - KeymasterDefs.KM_PURPOSE_SIGN, - true, - keymasterArgs, - null, // no additional entropy needed for HMAC because it's deterministic - mKey.getUid()); - - if (opResult == null) { - throw new KeyStoreConnectException(); - } - - // Store operation token and handle regardless of the error code returned by KeyStore to - // ensure that the operation gets aborted immediately if the code below throws an exception. - mOperationToken = opResult.token; - mOperationHandle = opResult.operationHandle; + List parameters = new ArrayList<>(); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest + )); + parameters.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_MAC_LENGTH, mMacSizeBits + )); - // If necessary, throw an exception due to KeyStore operation having failed. - InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit( - mKeyStore, mKey, opResult.resultCode); - if (e != null) { - throw e; + try { + mOperation = mKey.getSecurityLevel().createOperation( + mKey.getKeyIdDescriptor(), + parameters + ); + } catch (KeyStoreException keyStoreException) { + // If necessary, throw an exception due to KeyStore operation having failed. + InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyException( + mKey, keyStoreException); + if (e != null) { + throw e; + } } - if (mOperationToken == null) { - throw new ProviderException("Keystore returned null operation token"); - } - if (mOperationHandle == 0) { - throw new ProviderException("Keystore returned invalid operation handle"); - } + // Now we check if we got an operation challenge. This indicates that user authorization + // is required. And if we got a challenge we check if the authorization can possibly + // succeed. + mOperationChallenge = KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge( + mOperation, mKey); mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer( new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( - mKeyStore, mOperationToken)); + mOperation)); } @Override @@ -238,9 +234,7 @@ public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreC try { result = mChunkedStreamer.doFinal( null, 0, 0, - null, // no signature provided -- this invocation will generate one - null // no additional entropy needed -- HMAC is deterministic - ); + null); // no signature provided -- this invocation will generate one } catch (KeyStoreException e) { throw new ProviderException("Keystore operation failed", e); } @@ -252,10 +246,7 @@ public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreC @Override public void finalize() throws Throwable { try { - IBinder operationToken = mOperationToken; - if (operationToken != null) { - mKeyStore.abort(operationToken); - } + abortOperation(); } finally { super.finalize(); } @@ -263,6 +254,6 @@ public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreC @Override public long getOperationHandle() { - return mOperationHandle; + return mOperationChallenge; } } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java b/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java index 38db36020fb7..afb10547411b 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java @@ -19,12 +19,15 @@ package android.security.keystore2; import java.security.KeyStore; import java.security.KeyStore.ProtectionParameter; +/** + * @hide + */ class AndroidKeyStoreLoadStoreParameter implements KeyStore.LoadStoreParameter { - private final int mUid; + private final int mNamespace; - AndroidKeyStoreLoadStoreParameter(int uid) { - mUid = uid; + AndroidKeyStoreLoadStoreParameter(int namespace) { + mNamespace = namespace; } @Override @@ -32,7 +35,7 @@ class AndroidKeyStoreLoadStoreParameter implements KeyStore.LoadStoreParameter { return null; } - int getUid() { - return mUid; + int getNamespace() { + return mNamespace; } } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java index c9c0b0de3463..a6ea9723db24 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java @@ -18,12 +18,11 @@ package android.security.keystore2; import android.annotation.NonNull; import android.annotation.Nullable; -import android.security.KeyStore; -import android.security.keymaster.KeyCharacteristics; -import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; import android.security.keystore.KeyProperties; import android.security.keystore.KeymasterUtils; +import android.system.keystore2.Authorization; +import android.system.keystore2.KeyParameter; import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; @@ -35,6 +34,7 @@ import java.security.ProviderException; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidParameterSpecException; import java.security.spec.MGF1ParameterSpec; +import java.util.List; import javax.crypto.Cipher; import javax.crypto.CipherSpi; @@ -294,15 +294,17 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase @Override protected final void addAlgorithmSpecificParametersToBegin( - KeymasterArguments keymasterArgs) { - super.addAlgorithmSpecificParametersToBegin(keymasterArgs); - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); + @NonNull List parameters) { + super.addAlgorithmSpecificParametersToBegin(parameters); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest + )); } @Override protected final void loadAlgorithmSpecificParametersFromBeginResult( - @NonNull KeymasterArguments keymasterArgs) { - super.loadAlgorithmSpecificParametersFromBeginResult(keymasterArgs); + KeyParameter[] parameters) { + super.loadAlgorithmSpecificParametersFromBeginResult(parameters); } @Override @@ -415,14 +417,13 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase } } - KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); - int errorCode = getKeyStore().getKeyCharacteristics( - keystoreKey.getAlias(), null, null, keystoreKey.getUid(), keyCharacteristics); - if (errorCode != KeyStore.NO_ERROR) { - throw getKeyStore().getInvalidKeyException( - keystoreKey.getAlias(), keystoreKey.getUid(), errorCode); + long keySizeBits = -1; + for (Authorization a : keystoreKey.getAuthorizations()) { + if (a.keyParameter.tag == KeymasterDefs.KM_TAG_KEY_SIZE) { + keySizeBits = KeyStore2ParameterUtils.getUnsignedInt(a); + } } - long keySizeBits = keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, -1); + if (keySizeBits == -1) { throw new InvalidKeyException("Size of key not known"); } else if (keySizeBits > Integer.MAX_VALUE) { @@ -459,25 +460,32 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase @Override protected void addAlgorithmSpecificParametersToBegin( - @NonNull KeymasterArguments keymasterArgs) { - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA); + @NonNull List parameters) { + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA + )); int keymasterPadding = getKeymasterPaddingOverride(); if (keymasterPadding == -1) { keymasterPadding = mKeymasterPadding; } - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, keymasterPadding); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, keymasterPadding + )); int purposeOverride = getKeymasterPurposeOverride(); if ((purposeOverride != -1) && ((purposeOverride == KeymasterDefs.KM_PURPOSE_SIGN) || (purposeOverride == KeymasterDefs.KM_PURPOSE_VERIFY))) { - // Keymaster sign/verify requires digest to be specified. For raw sign/verify it's NONE. - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE); + // Keymaster sign/verify requires digest to be specified. + // For raw sign/verify it's NONE. + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE + )); } } @Override protected void loadAlgorithmSpecificParametersFromBeginResult( - @NonNull KeymasterArguments keymasterArgs) { + KeyParameter[] parameters) { } @Override diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java index 6b2c098810e4..5f1b9c0586a1 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java @@ -17,20 +17,20 @@ package android.security.keystore2; import android.annotation.NonNull; -import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; import android.security.keystore.KeyProperties; +import android.system.keystore2.KeyParameter; import java.security.InvalidKeyException; import java.security.SignatureSpi; +import java.util.List; /** * Base class for {@link SignatureSpi} providing Android KeyStore backed RSA signatures. * * @hide */ -abstract class AndroidKeyStoreRSASignatureSpi extends - AndroidKeyStoreSignatureSpiBase { +abstract class AndroidKeyStoreRSASignatureSpi extends AndroidKeyStoreSignatureSpiBase { abstract static class PKCS1Padding extends AndroidKeyStoreRSASignatureSpi { PKCS1Padding(int keymasterDigest) { @@ -158,9 +158,15 @@ abstract class AndroidKeyStoreRSASignatureSpi extends @Override protected final void addAlgorithmSpecificParametersToBegin( - @NonNull KeymasterArguments keymasterArgs) { - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA); - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding); + @NonNull List parameters) { + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding + )); } } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java index 23818a784f89..55414b70d403 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java @@ -18,15 +18,12 @@ package android.security.keystore2; import android.annotation.CallSuper; import android.annotation.NonNull; -import android.os.IBinder; -import android.security.KeyStore; import android.security.KeyStoreException; -import android.security.keymaster.KeymasterArguments; +import android.security.KeyStoreOperation; import android.security.keymaster.KeymasterDefs; -import android.security.keymaster.OperationResult; import android.security.keystore.ArrayUtils; -import android.security.keystore.KeyStoreConnectException; import android.security.keystore.KeyStoreCryptoOperation; +import android.system.keystore2.KeyParameter; import libcore.util.EmptyArray; @@ -39,6 +36,8 @@ import java.security.PublicKey; import java.security.SecureRandom; import java.security.SignatureException; import java.security.SignatureSpi; +import java.util.ArrayList; +import java.util.List; /** * Base class for {@link SignatureSpi} implementations of Android KeyStore backed ciphers. @@ -47,7 +46,7 @@ import java.security.SignatureSpi; */ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi implements KeyStoreCryptoOperation { - private final KeyStore mKeyStore; + private static final String TAG = "AndroidKeyStoreSignatureSpiBase"; // Fields below are populated by SignatureSpi.engineInitSign/engineInitVerify and KeyStore.begin // and should be preserved after SignatureSpi.engineSign/engineVerify finishes. @@ -55,12 +54,18 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi private AndroidKeyStoreKey mKey; /** - * Token referencing this operation inside keystore service. It is initialized by - * {@code engineInitSign}/{@code engineInitVerify} and is invalidated when - * {@code engineSign}/{@code engineVerify} succeeds and on some error conditions in between. + * Object representing this operation inside keystore service. It is initialized + * by {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and on some + * error conditions in between. */ - private IBinder mOperationToken; - private long mOperationHandle; + private KeyStoreOperation mOperation; + /** + * The operation challenge is required when an operation needs user authorization. + * The challenge is subjected to an authenticator, e.g., Gatekeeper or a biometric + * authenticator, and included in the authentication token minted by this authenticator. + * It may be null, if the operation does not require authorization. + */ + private long mOperationChallenge; private KeyStoreCryptoOperationStreamer mMessageStreamer; /** @@ -72,7 +77,13 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi private Exception mCachedException; AndroidKeyStoreSignatureSpiBase() { - mKeyStore = KeyStore.getInstance(); + mOperation = null; + mOperationChallenge = 0; + mSigning = false; + mKey = null; + appRandom = null; + mMessageStreamer = null; + mCachedException = null; } @Override @@ -145,6 +156,11 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi mKey = key; } + private void abortOperation() { + KeyStoreCryptoOperationUtils.abortOperation(mOperation); + mOperation = null; + } + /** * Resets this cipher to its pristine pre-init state. This must be equivalent to obtaining a new * cipher instance. @@ -154,16 +170,11 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi */ @CallSuper protected void resetAll() { - IBinder operationToken = mOperationToken; - if (operationToken != null) { - mOperationToken = null; - mKeyStore.abort(operationToken); - } + abortOperation(); + mOperationChallenge = 0; mSigning = false; mKey = null; appRandom = null; - mOperationToken = null; - mOperationHandle = 0; mMessageStreamer = null; mCachedException = null; } @@ -178,12 +189,8 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi */ @CallSuper protected void resetWhilePreservingInitState() { - IBinder operationToken = mOperationToken; - if (operationToken != null) { - mOperationToken = null; - mKeyStore.abort(operationToken); - } - mOperationHandle = 0; + abortOperation(); + mOperationChallenge = 0; mMessageStreamer = null; mCachedException = null; } @@ -199,40 +206,29 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi throw new IllegalStateException("Not initialized"); } - KeymasterArguments keymasterInputArgs = new KeymasterArguments(); - addAlgorithmSpecificParametersToBegin(keymasterInputArgs); - - 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, - null, // no additional entropy for begin -- only finish might need some - mKey.getUid()); - if (opResult == null) { - throw new KeyStoreConnectException(); - } + List parameters = new ArrayList<>(); + addAlgorithmSpecificParametersToBegin(parameters); - // Store operation token and handle regardless of the error code returned by KeyStore to - // ensure that the operation gets aborted immediately if the code below throws an exception. - mOperationToken = opResult.token; - mOperationHandle = opResult.operationHandle; + int purpose = mSigning ? KeymasterDefs.KM_PURPOSE_SIGN : KeymasterDefs.KM_PURPOSE_VERIFY; - // If necessary, throw an exception due to KeyStore operation having failed. - InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit( - mKeyStore, mKey, opResult.resultCode); - if (e != null) { - throw e; - } + parameters.add(KeyStore2ParameterUtils.makeEnum(KeymasterDefs.KM_TAG_PURPOSE, purpose)); - if (mOperationToken == null) { - throw new ProviderException("Keystore returned null operation token"); - } - if (mOperationHandle == 0) { - throw new ProviderException("Keystore returned invalid operation handle"); + try { + mOperation = mKey.getSecurityLevel().createOperation( + mKey.getKeyIdDescriptor(), + parameters); + } catch (KeyStoreException keyStoreException) { + throw KeyStoreCryptoOperationUtils.getInvalidKeyException( + mKey, keyStoreException); } - mMessageStreamer = createMainDataStreamer(mKeyStore, opResult.token); + // Now we check if we got an operation challenge. This indicates that user authorization + // is required. And if we got a challenge we check if the authorization can possibly + // succeed. + mOperationChallenge = KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge( + mOperation, mKey); + + mMessageStreamer = createMainDataStreamer(mOperation); } /** @@ -242,15 +238,15 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi */ @NonNull protected KeyStoreCryptoOperationStreamer createMainDataStreamer( - KeyStore keyStore, IBinder operationToken) { + @NonNull KeyStoreOperation operation) { return new KeyStoreCryptoOperationChunkedStreamer( new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( - keyStore, operationToken)); + operation)); } @Override public final long getOperationHandle() { - return mOperationHandle; + return mOperationChallenge; } @Override @@ -330,8 +326,7 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi appRandom, getAdditionalEntropyAmountForSign()); signature = mMessageStreamer.doFinal( EmptyArray.BYTE, 0, 0, - null, // no signature provided -- it'll be generated by this invocation - additionalEntropy); + null); // no signature provided -- it'll be generated by this invocation } catch (InvalidKeyException | KeyStoreException e) { throw new SignatureException(e); } @@ -356,9 +351,7 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi try { byte[] output = mMessageStreamer.doFinal( EmptyArray.BYTE, 0, 0, - signature, - null // no additional entropy needed -- verification is deterministic - ); + signature); if (output.length != 0) { throw new ProviderException( "Signature verification unexpected produced output: " + output.length @@ -398,10 +391,6 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi throw new InvalidParameterException(); } - protected final KeyStore getKeyStore() { - return mKeyStore; - } - /** * Returns {@code true} if this signature is initialized for signing, {@code false} if this * signature is initialized for verification. @@ -426,9 +415,9 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi /** * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation. * - * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific + * @param parameters keystore/keymaster arguments to be populated with algorithm-specific * parameters. */ protected abstract void addAlgorithmSpecificParametersToBegin( - @NonNull KeymasterArguments keymasterArgs); + @NonNull List parameters); } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java index 96cfe411e73a..4c26864cb02b 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java @@ -16,23 +16,31 @@ package android.security.keystore2; -import android.security.Credentials; +import android.annotation.NonNull; +import android.hardware.biometrics.BiometricManager; import android.security.GateKeeper; -import android.security.KeyStore; +import android.security.KeyStore2; import android.security.KeyStoreParameter; -import android.security.keymaster.KeyCharacteristics; -import android.security.keymaster.KeymasterArguments; +import android.security.KeyStoreSecurityLevel; import android.security.keymaster.KeymasterDefs; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; import android.security.keystore.KeymasterUtils; import android.security.keystore.SecureKeyImportUnavailableException; import android.security.keystore.WrappedKeyEntry; +import android.system.keystore2.AuthenticatorSpec; +import android.system.keystore2.Domain; +import android.system.keystore2.IKeystoreSecurityLevel; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyEntryResponse; +import android.system.keystore2.KeyMetadata; +import android.system.keystore2.KeyParameter; +import android.system.keystore2.ResponseCode; +import android.system.keystore2.SecurityLevel; import android.util.Log; -import libcore.util.EmptyArray; - import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -48,7 +56,6 @@ import java.security.KeyStoreSpi; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.ProviderException; -import java.security.PublicKey; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; @@ -63,6 +70,7 @@ 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; @@ -87,52 +95,88 @@ import javax.crypto.SecretKey; * @hide */ public class AndroidKeyStoreSpi extends KeyStoreSpi { + public static final String TAG = "AndroidKeyStoreSpi"; public static final String NAME = "AndroidKeyStore"; - private KeyStore mKeyStore; - private int mUid = KeyStore.UID_SELF; + private KeyStore2 mKeyStore; + private int mNamespace = KeyProperties.NAMESPACE_APPLICATION; @Override public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException { - String userKeyAlias = Credentials.USER_PRIVATE_KEY + alias; - AndroidKeyStoreKey key; - if (!mKeyStore.contains(userKeyAlias, mUid)) { - // try legacy prefix for backward compatibility - userKeyAlias = Credentials.USER_SECRET_KEY + alias; - if (!mKeyStore.contains(userKeyAlias, mUid)) return null; - } try { - key = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(mKeyStore, - userKeyAlias, - mUid); + return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(mKeyStore, + alias, + mNamespace); } catch (KeyPermanentlyInvalidatedException e) { throw new UnrecoverableKeyException(e.getMessage()); + } catch (UnrecoverableKeyException e) { + Throwable cause = e.getCause(); + if (cause instanceof android.security.KeyStoreException) { + if (((android.security.KeyStoreException) cause).getErrorCode() + == ResponseCode.KEY_NOT_FOUND) { + return null; + } + } + throw e; } - return key; } - @Override - public Certificate[] engineGetCertificateChain(String alias) { + /** + * Make a key descriptor from the given alias and the mNamespace member. + * If mNamespace is -1 it sets the domain field to {@link Domain#APP} and {@link Domain#SELINUX} + * otherwise. The blob field is always set to null and the alias field to {@code alias} + * @param alias The alias of the new key descriptor. + * @return A new key descriptor. + */ + private KeyDescriptor makeKeyDescriptor(@NonNull String alias) { + KeyDescriptor descriptor = new KeyDescriptor(); + descriptor.domain = getTargetDomain(); + descriptor.nspace = mNamespace; // ignored if Domain.App; + descriptor.alias = alias; + descriptor.blob = null; + return descriptor; + } + + private @Domain int getTargetDomain() { + return mNamespace == KeyProperties.NAMESPACE_APPLICATION + ? Domain.APP + : Domain.SELINUX; + } + private KeyEntryResponse getKeyMetadata(String alias) { if (alias == null) { throw new NullPointerException("alias == null"); } - final X509Certificate leaf = (X509Certificate) engineGetCertificate(alias); + KeyDescriptor descriptor = makeKeyDescriptor(alias); + + try { + return mKeyStore.getKeyEntry(descriptor); + } catch (android.security.KeyStoreException e) { + if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) { + Log.w(TAG, "Could not get key metadata from Keystore.", e); + } + return null; + } + } + + @Override + public Certificate[] engineGetCertificateChain(String alias) { + KeyEntryResponse response = getKeyMetadata(alias); + + if (response == null || response.metadata.certificate == null) { + return null; + } + + final X509Certificate leaf = (X509Certificate) toCertificate(response.metadata.certificate); if (leaf == null) { return null; } final Certificate[] caList; - // Suppress the key not found warning for this call. It seems that this error is exclusively - // being thrown when there is a self signed certificate chain, so when the keystore service - // attempts to query for the CA details, it obviously fails to find them and returns a - // key not found exception. This is WAI, and throwing a stack trace here can be very - // misleading since the trace is not clear. - final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias, - mUid, - true /* suppressKeyNotFoundWarning */); + final byte[] caBytes = response.metadata.certificateChain; + if (caBytes != null) { final Collection caChain = toCertificates(caBytes); @@ -154,80 +198,26 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { @Override public Certificate engineGetCertificate(String alias) { - if (alias == null) { - throw new NullPointerException("alias == null"); + KeyEntryResponse response = getKeyMetadata(alias); + + if (response == null) { + return null; } - byte[] encodedCert = mKeyStore.get(Credentials.USER_CERTIFICATE + alias, mUid); + byte[] encodedCert = response.metadata.certificate; if (encodedCert != null) { - return getCertificateForPrivateKeyEntry(alias, encodedCert); + return toCertificate(encodedCert); } - encodedCert = mKeyStore.get(Credentials.CA_CERTIFICATE + alias, mUid); + encodedCert = response.metadata.certificateChain; if (encodedCert != null) { - return getCertificateForTrustedCertificateEntry(encodedCert); + return toCertificate(encodedCert); } // This entry/alias does not contain a certificate. return null; } - private Certificate getCertificateForTrustedCertificateEntry(byte[] encodedCert) { - // For this certificate there shouldn't be a private key in this KeyStore entry. Thus, - // there's no need to wrap this certificate as opposed to the certificate associated with - // a private key entry. - return toCertificate(encodedCert); - } - - private Certificate getCertificateForPrivateKeyEntry(String alias, byte[] encodedCert) { - // All crypto algorithms offered by Android Keystore for its private keys must also - // be offered for the corresponding public keys stored in the Android Keystore. The - // complication is that the underlying keystore service operates only on full key pairs, - // rather than just public keys or private keys. As a result, Android Keystore-backed - // crypto can only be offered for public keys for which keystore contains the - // corresponding private key. This is not the case for certificate-only entries (e.g., - // trusted certificates). - // - // getCertificate().getPublicKey() is the only way to obtain the public key - // corresponding to the private key stored in the KeyStore. Thus, we need to make sure - // that the returned public key points to the underlying key pair / private key - // when available. - - X509Certificate cert = toCertificate(encodedCert); - if (cert == null) { - // Failed to parse the certificate. - return null; - } - - String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; - if (mKeyStore.contains(privateKeyAlias, mUid)) { - // As expected, keystore contains the private key corresponding to this public key. Wrap - // the certificate so that its getPublicKey method returns an Android Keystore - // PublicKey. This key will delegate crypto operations involving this public key to - // Android Keystore when higher-priority providers do not offer these crypto - // operations for this key. - return wrapIntoKeyStoreCertificate(privateKeyAlias, mUid, cert); - } else { - // This KeyStore entry/alias is supposed to contain the private key corresponding to - // the public key in this certificate, but it does not for some reason. It's probably a - // bug. Let other providers handle crypto operations involving the public key returned - // by this certificate's getPublicKey. - return cert; - } - } - - /** - * 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, int uid, X509Certificate certificate) { - return (certificate != null) - ? new KeyStoreX509Certificate(privateKeyAlias, uid, certificate) : null; - } - private static X509Certificate toCertificate(byte[] bytes) { try { final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); @@ -251,37 +241,21 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } } - private Date getModificationDate(String alias) { - final long epochMillis = mKeyStore.getmtime(alias, mUid); - if (epochMillis == -1L) { - return null; - } - - return new Date(epochMillis); - } - @Override public Date engineGetCreationDate(String alias) { - if (alias == null) { - throw new NullPointerException("alias == null"); - } + KeyEntryResponse response = getKeyMetadata(alias); - Date d = getModificationDate(Credentials.USER_PRIVATE_KEY + alias); - if (d != null) { - return d; + if (response == null) { + return null; } - d = getModificationDate(Credentials.USER_SECRET_KEY + alias); - if (d != null) { - return d; - } - d = getModificationDate(Credentials.USER_CERTIFICATE + alias); - if (d != null) { - return d; - } - - return getModificationDate(Credentials.CA_CERTIFICATE + alias); + // TODO add modification time to key metadata. + return null; + // if (response.metadata.modificationTime == -1) { + // return null; + // } + // return new Date(response.metadata.modificationTime); } @Override @@ -354,7 +328,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain, - ProtectionParameter param) throws KeyStoreException { + java.security.KeyStore.ProtectionParameter param) throws KeyStoreException { + @SecurityLevel int securitylevel = SecurityLevel.TRUSTED_ENVIRONMENT; int flags = 0; KeyProtection spec; if (param == null) { @@ -362,17 +337,20 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } else if (param instanceof KeyStoreParameter) { spec = getLegacyKeyProtectionParameter(key); KeyStoreParameter legacySpec = (KeyStoreParameter) param; - if (legacySpec.isEncryptionRequired()) { - flags = KeyStore.FLAG_ENCRYPTED; - } } else if (param instanceof KeyProtection) { spec = (KeyProtection) param; if (spec.isCriticalToDeviceEncryption()) { - flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION; + // This key is should not be bound to the LSKF even if it is auth bound. + // This indicates that this key is used in the derivation for of the + // master key, that is used for the LSKF binding of other auth bound + // keys. This breaks up a circular dependency while retaining logical + // authentication binding of the key. + flags |= IKeystoreSecurityLevel + .KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING; } if (spec.isStrongBoxBacked()) { - flags |= KeyStore.FLAG_STRONGBOX; + securitylevel = SecurityLevel.STRONGBOX; } } else { throw new KeyStoreException( @@ -447,147 +425,169 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { chainBytes = null; } - final String pkeyAlias; + @Domain int targetDomain = getTargetDomain(); + + // If the given key is an AndroidKeyStorePrivateKey, we attempt to update + // its subcomponents with the given certificate and certificate chain. if (key instanceof AndroidKeyStorePrivateKey) { - pkeyAlias = ((AndroidKeyStoreKey) key).getAlias(); - } else { - pkeyAlias = null; - } + AndroidKeyStoreKey ksKey = (AndroidKeyStoreKey) key; + KeyDescriptor descriptor = ksKey.getUserKeyDescriptor(); - byte[] pkcs8EncodedPrivateKeyBytes; - KeymasterArguments importArgs; - final boolean shouldReplacePrivateKey; - if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) { - final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length()); - if (!alias.equals(keySubalias)) { - throw new KeyStoreException("Can only replace keys with same alias: " + alias - + " != " + keySubalias); - } - shouldReplacePrivateKey = false; - importArgs = null; - pkcs8EncodedPrivateKeyBytes = null; - } else { - shouldReplacePrivateKey = true; - // Make sure the PrivateKey format is the one we support. - final String keyFormat = key.getFormat(); - if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) { - throw new KeyStoreException( - "Unsupported private key export format: " + keyFormat - + ". Only private keys which export their key material in PKCS#8 format are" - + " supported."); - } + // This throws if the request cannot replace the entry. + assertCanReplace(alias, targetDomain, mNamespace, descriptor); - // Make sure we can actually encode the key. - pkcs8EncodedPrivateKeyBytes = key.getEncoded(); - if (pkcs8EncodedPrivateKeyBytes == null) { - throw new KeyStoreException("Private key did not export any key material"); + try { + mKeyStore.updateSubcomponents( + ((AndroidKeyStorePrivateKey) key).getKeyIdDescriptor(), + userCertBytes, chainBytes); + } catch (android.security.KeyStoreException e) { + throw new KeyStoreException("Failed to store certificate and certificate chain", e); } + return; + } - importArgs = new KeymasterArguments(); - try { - importArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, - KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm( - key.getAlgorithm())); - @KeyProperties.PurposeEnum int purposes = spec.getPurposes(); - importArgs.addEnums(KeymasterDefs.KM_TAG_PURPOSE, - KeyProperties.Purpose.allToKeymaster(purposes)); - if (spec.isDigestsSpecified()) { - importArgs.addEnums(KeymasterDefs.KM_TAG_DIGEST, - KeyProperties.Digest.allToKeymaster(spec.getDigests())); - } + // Make sure the PrivateKey format is the one we support. + final String keyFormat = key.getFormat(); + if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) { + throw new KeyStoreException( + "Unsupported private key export format: " + keyFormat + + ". Only private keys which export their key material in PKCS#8 format are" + + " supported."); + } - importArgs.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, - KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes())); - int[] keymasterEncryptionPaddings = - KeyProperties.EncryptionPadding.allToKeymaster( - spec.getEncryptionPaddings()); - if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) - && (spec.isRandomizedEncryptionRequired())) { - for (int keymasterPadding : keymasterEncryptionPaddings) { - if (!KeymasterUtils - .isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto( - keymasterPadding)) { - throw new KeyStoreException( - "Randomized encryption (IND-CPA) required but is violated by" - + " encryption padding mode: " - + KeyProperties.EncryptionPadding.fromKeymaster( - keymasterPadding) - + ". See KeyProtection documentation."); - } - } - } - importArgs.addEnums(KeymasterDefs.KM_TAG_PADDING, keymasterEncryptionPaddings); - importArgs.addEnums(KeymasterDefs.KM_TAG_PADDING, - KeyProperties.SignaturePadding.allToKeymaster(spec.getSignaturePaddings())); - KeymasterUtils.addUserAuthArgs(importArgs, spec); - importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, - spec.getKeyValidityStart()); - importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, - spec.getKeyValidityForOriginationEnd()); - importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, - spec.getKeyValidityForConsumptionEnd()); - } catch (IllegalArgumentException | IllegalStateException e) { - throw new KeyStoreException(e); - } + // Make sure we can actually encode the key. + byte[] pkcs8EncodedPrivateKeyBytes = key.getEncoded(); + if (pkcs8EncodedPrivateKeyBytes == null) { + throw new KeyStoreException("Private key did not export any key material"); } + final List importArgs = new ArrayList<>(); - boolean success = false; try { - // Store the private key, if necessary - if (shouldReplacePrivateKey) { - // Delete the stored private key and any related entries before importing the - // provided key - Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid); - KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); - int errorCode = mKeyStore.importKey( - Credentials.USER_PRIVATE_KEY + alias, - importArgs, - KeymasterDefs.KM_KEY_FORMAT_PKCS8, - pkcs8EncodedPrivateKeyBytes, - mUid, - flags, - resultingKeyCharacteristics); - if (errorCode != KeyStore.NO_ERROR) { - throw new KeyStoreException("Failed to store private key", - KeyStore.getKeyStoreException(errorCode)); + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, + KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm( + key.getAlgorithm())) + ); + KeyStore2ParameterUtils.forEachSetFlag(spec.getPurposes(), (purpose) -> { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PURPOSE, + KeyProperties.Purpose.toKeymaster(purpose) + )); + }); + if (spec.isDigestsSpecified()) { + for (String digest : spec.getDigests()) { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, + KeyProperties.Digest.toKeymaster(digest) + )); } - } else { - // Keep the stored private key around -- delete all other entry types - Credentials.deleteCertificateTypesForAlias(mKeyStore, alias, mUid); - Credentials.deleteLegacyKeyForAlias(mKeyStore, alias, mUid); } - - // Store the leaf certificate - int errorCode = mKeyStore.insert(Credentials.USER_CERTIFICATE + alias, userCertBytes, - mUid, flags); - if (errorCode != KeyStore.NO_ERROR) { - throw new KeyStoreException("Failed to store certificate #0", - KeyStore.getKeyStoreException(errorCode)); + for (String blockMode : spec.getBlockModes()) { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_BLOCK_MODE, + KeyProperties.BlockMode.toKeymaster(blockMode) + )); } - - // Store the certificate chain - errorCode = mKeyStore.insert(Credentials.CA_CERTIFICATE + alias, chainBytes, - mUid, flags); - if (errorCode != KeyStore.NO_ERROR) { - throw new KeyStoreException("Failed to store certificate chain", - KeyStore.getKeyStoreException(errorCode)); - } - success = true; - } finally { - if (!success) { - if (shouldReplacePrivateKey) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid); - } else { - Credentials.deleteCertificateTypesForAlias(mKeyStore, alias, mUid); - Credentials.deleteLegacyKeyForAlias(mKeyStore, alias, mUid); + int[] keymasterEncryptionPaddings = + KeyProperties.EncryptionPadding.allToKeymaster( + spec.getEncryptionPaddings()); + if (((spec.getPurposes() & KeyProperties.PURPOSE_DECRYPT) != 0) + && (spec.isRandomizedEncryptionRequired())) { + for (int keymasterPadding : keymasterEncryptionPaddings) { + if (!KeymasterUtils + .isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto( + keymasterPadding)) { + throw new KeyStoreException( + "Randomized encryption (IND-CPA) required but is violated by" + + " encryption padding mode: " + + KeyProperties.EncryptionPadding.fromKeymaster( + keymasterPadding) + + ". See KeyProtection documentation."); + } } } + for (int padding : keymasterEncryptionPaddings) { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, + padding + )); + } + for (String padding : spec.getSignaturePaddings()) { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, + KeyProperties.SignaturePadding.toKeymaster(padding) + )); + } + KeyStore2ParameterUtils.addUserAuthArgs(importArgs, spec); + if (spec.getKeyValidityStart() != null) { + importArgs.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart() + )); + } + if (spec.getKeyValidityForOriginationEnd() != null) { + importArgs.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + spec.getKeyValidityForOriginationEnd() + )); + } + if (spec.getKeyValidityForConsumptionEnd() != null) { + importArgs.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + spec.getKeyValidityForConsumptionEnd() + )); + } + } catch (IllegalArgumentException | IllegalStateException e) { + throw new KeyStoreException(e); + } + + try { + KeyStoreSecurityLevel securityLevelInterface = mKeyStore.getSecurityLevel( + securitylevel); + + KeyDescriptor descriptor = makeKeyDescriptor(alias); + + KeyMetadata metadata = securityLevelInterface.importKey(descriptor, null, + importArgs, flags, pkcs8EncodedPrivateKeyBytes); + + try { + mKeyStore.updateSubcomponents(metadata.key, userCertBytes, chainBytes); + } catch (android.security.KeyStoreException e) { + mKeyStore.deleteKey(metadata.key); + throw new KeyStoreException("Failed to store certificate and certificate chain", e); + } + + } catch (android.security.KeyStoreException e) { + throw new KeyStoreException("Failed to store private key", e); + } + } + + private static void assertCanReplace(String alias, @Domain int targetDomain, + int targetNamespace, KeyDescriptor descriptor) + throws KeyStoreException { + // If + // * the alias does not match, or + // * the domain does not match, or + // * the domain is Domain.SELINUX and the namespaces don not match, + // then the designated key location is not equivalent to the location of the + // given key parameter and cannot be updated. + // + // Note: mNamespace == KeyProperties.NAMESPACE_APPLICATION implies that the target domain + // is Domain.APP and Domain.SELINUX is the target domain otherwise. + if (alias != descriptor.alias + || descriptor.domain != targetDomain + || (descriptor.domain == Domain.SELINUX && descriptor.nspace != targetNamespace)) { + throw new KeyStoreException("Can only replace keys with same alias: " + alias + + " != " + descriptor.alias + " in the same target domain: " + targetDomain + + " != " + descriptor.domain + + (targetDomain == Domain.SELINUX ? " in the same target namespace: " + + targetNamespace + " != " + descriptor.nspace : "") + ); } } - private void setSecretKeyEntry(String entryAlias, SecretKey key, - ProtectionParameter param) + private void setSecretKeyEntry(String alias, SecretKey key, + java.security.KeyStore.ProtectionParameter param) throws KeyStoreException { if ((param != null) && (!(param instanceof KeyProtection))) { throw new KeyStoreException( @@ -596,28 +596,19 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } KeyProtection params = (KeyProtection) param; + @SecurityLevel int securityLevel = params.isStrongBoxBacked() ? SecurityLevel.STRONGBOX : + SecurityLevel.TRUSTED_ENVIRONMENT; + @Domain int targetDomain = (getTargetDomain()); + if (key instanceof AndroidKeyStoreSecretKey) { - // KeyStore-backed secret key. It cannot be duplicated into another entry and cannot - // overwrite its own entry. - String keyAliasInKeystore = ((AndroidKeyStoreSecretKey) key).getAlias(); - if (keyAliasInKeystore == null) { - throw new KeyStoreException("KeyStore-backed secret key does not have an alias"); - } - String keyAliasPrefix = Credentials.USER_PRIVATE_KEY; - if (!keyAliasInKeystore.startsWith(keyAliasPrefix)) { - // try legacy prefix - keyAliasPrefix = Credentials.USER_SECRET_KEY; - if (!keyAliasInKeystore.startsWith(keyAliasPrefix)) { - throw new KeyStoreException("KeyStore-backed secret key has invalid alias: " - + keyAliasInKeystore); - } - } - String keyEntryAlias = - keyAliasInKeystore.substring(keyAliasPrefix.length()); - if (!entryAlias.equals(keyEntryAlias)) { - throw new KeyStoreException("Can only replace KeyStore-backed keys with same" - + " alias: " + entryAlias + " != " + keyEntryAlias); - } + String keyAliasInKeystore = + ((AndroidKeyStoreSecretKey) key).getUserKeyDescriptor().alias; + + KeyDescriptor descriptor = ((AndroidKeyStoreSecretKey) key).getUserKeyDescriptor(); + + // This throws if the request cannot replace the existing key. + assertCanReplace(alias, targetDomain, mNamespace, descriptor); + // This is the entry where this key is already stored. No need to do anything. if (params != null) { throw new KeyStoreException("Modifying KeyStore-backed key using protection" @@ -646,13 +637,18 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { + " RAW format export"); } - KeymasterArguments args = new KeymasterArguments(); + final List importArgs = new ArrayList<>(); + try { int keymasterAlgorithm = - KeyProperties.KeyAlgorithm.toKeymasterSecretKeyAlgorithm(key.getAlgorithm()); - args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, keymasterAlgorithm); + KeyProperties.KeyAlgorithm.toKeymasterSecretKeyAlgorithm( + key.getAlgorithm()); + + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, + keymasterAlgorithm + )); - int[] keymasterDigests; if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { // JCA HMAC key algorithm implies a digest (e.g., HmacSHA256 key algorithm // implies SHA-256 digest). Because keymaster HMAC key is authorized only for one @@ -666,7 +662,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { "HMAC key algorithm digest unknown for key algorithm " + key.getAlgorithm()); } - keymasterDigests = new int[] {keymasterImpliedDigest}; + if (params.isDigestsSpecified()) { // Digest(s) explicitly specified in params -- check that the list consists of // exactly one digest, the one implied by key algorithm. @@ -676,172 +672,235 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { || (keymasterDigestsFromParams[0] != keymasterImpliedDigest)) { throw new KeyStoreException( "Unsupported digests specification: " - + Arrays.asList(params.getDigests()) + ". Only " - + KeyProperties.Digest.fromKeymaster(keymasterImpliedDigest) - + " supported for HMAC key algorithm " + key.getAlgorithm()); + + Arrays.asList(params.getDigests()) + ". Only " + + KeyProperties.Digest.fromKeymaster(keymasterImpliedDigest) + + " supported for HMAC key algorithm " + + key.getAlgorithm()); } } + int outputBits = KeymasterUtils.getDigestOutputSizeBits(keymasterImpliedDigest); + if (outputBits == -1) { + throw new ProviderException( + "HMAC key authorized for unsupported digest: " + + KeyProperties.Digest.fromKeymaster(keymasterImpliedDigest)); + } + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, keymasterImpliedDigest + )); + importArgs.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, outputBits + )); } else { - // Key algorithm does not imply a digest. if (params.isDigestsSpecified()) { - keymasterDigests = KeyProperties.Digest.allToKeymaster(params.getDigests()); + for (String digest : params.getDigests()) { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, + KeyProperties.Digest.toKeymaster(digest) + )); + } + } + } + + KeyStore2ParameterUtils.forEachSetFlag(params.getPurposes(), (purpose) -> { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PURPOSE, + KeyProperties.Purpose.toKeymaster(purpose) + )); + }); + + boolean indCpa = false; + if ((params.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) { + if (((KeyProtection) param).isRandomizedEncryptionRequired()) { + indCpa = true; } else { - keymasterDigests = EmptyArray.INT; + importArgs.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_CALLER_NONCE + )); } } - args.addEnums(KeymasterDefs.KM_TAG_DIGEST, keymasterDigests); - - @KeyProperties.PurposeEnum int purposes = params.getPurposes(); - int[] keymasterBlockModes = - KeyProperties.BlockMode.allToKeymaster(params.getBlockModes()); - if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) - && (params.isRandomizedEncryptionRequired())) { - for (int keymasterBlockMode : keymasterBlockModes) { - if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto( - keymasterBlockMode)) { - throw new KeyStoreException( - "Randomized encryption (IND-CPA) required but may be violated by" - + " block mode: " - + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode) - + ". See KeyProtection documentation."); - } + + for (String blockMode : params.getBlockModes()) { + int keymasterBlockMode = KeyProperties.BlockMode.toKeymaster(blockMode); + if (indCpa + && !KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto( + keymasterBlockMode)) { + throw new KeyStoreException( + "Randomized encryption (IND-CPA) required but may be violated by" + + " block mode: " + blockMode + + ". See KeyProtection documentation."); + } + if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES + && keymasterBlockMode == KeymasterDefs.KM_MODE_GCM) { + importArgs.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, + AndroidKeyStoreAuthenticatedAESCipherSpi.GCM + .MIN_SUPPORTED_TAG_LENGTH_BITS + )); + } + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_BLOCK_MODE, + keymasterBlockMode + )); } - args.addEnums(KeymasterDefs.KM_TAG_PURPOSE, - KeyProperties.Purpose.allToKeymaster(purposes)); - args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes); + if (params.getSignaturePaddings().length > 0) { throw new KeyStoreException("Signature paddings not supported for symmetric keys"); } - int[] keymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster( - params.getEncryptionPaddings()); - args.addEnums(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings); - KeymasterUtils.addUserAuthArgs(args, params); - KeymasterUtils.addMinMacLengthAuthorizationIfNecessary( - args, - keymasterAlgorithm, - keymasterBlockModes, - keymasterDigests); - args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, - params.getKeyValidityStart()); - args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, - params.getKeyValidityForOriginationEnd()); - args.addDateIfNotNull(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, - params.getKeyValidityForConsumptionEnd()); - - if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) - && (!params.isRandomizedEncryptionRequired())) { - // Permit caller-provided IV when encrypting with this key - args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); + + for (String padding : params.getEncryptionPaddings()) { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, + KeyProperties.EncryptionPadding.toKeymaster(padding) + )); + } + + KeyStore2ParameterUtils.addUserAuthArgs(importArgs, params); + + if (params.getKeyValidityStart() != null) { + importArgs.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_ACTIVE_DATETIME, params.getKeyValidityStart() + )); + } + if (params.getKeyValidityForOriginationEnd() != null) { + importArgs.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + params.getKeyValidityForOriginationEnd() + )); + } + if (params.getKeyValidityForConsumptionEnd() != null) { + importArgs.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + params.getKeyValidityForConsumptionEnd() + )); } } catch (IllegalArgumentException | IllegalStateException e) { throw new KeyStoreException(e); } + int flags = 0; if (params.isCriticalToDeviceEncryption()) { - flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION; - } - if (params.isStrongBoxBacked()) { - flags |= KeyStore.FLAG_STRONGBOX; + flags |= IKeystoreSecurityLevel.KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING; } - Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias, mUid); - String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + entryAlias; - int errorCode = mKeyStore.importKey( - keyAliasInKeystore, - args, - KeymasterDefs.KM_KEY_FORMAT_RAW, - keyMaterial, - mUid, - flags, - new KeyCharacteristics()); - if (errorCode != KeyStore.NO_ERROR) { - throw new KeyStoreException("Failed to import secret key. Keystore error code: " - + errorCode); + try { + KeyStoreSecurityLevel securityLevelInterface = mKeyStore.getSecurityLevel( + securityLevel); + + KeyDescriptor descriptor = makeKeyDescriptor(alias); + + securityLevelInterface.importKey(descriptor, null /* TODO attestationKey */, + importArgs, flags, keyMaterial); + } catch (android.security.KeyStoreException e) { + throw new KeyStoreException("Failed to import secret key.", e); } } private void setWrappedKeyEntry(String alias, WrappedKeyEntry entry, - ProtectionParameter param) throws KeyStoreException { + java.security.KeyStore.ProtectionParameter param) throws KeyStoreException { if (param != null) { throw new KeyStoreException("Protection parameters are specified inside wrapped keys"); } byte[] maskingKey = new byte[32]; - - KeymasterArguments args = new KeymasterArguments(); String[] parts = entry.getTransformation().split("/"); + List args = new ArrayList<>(); + String algorithm = parts[0]; if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) { - args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA); - } else if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) { - args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA); + args.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, + KeymasterDefs.KM_ALGORITHM_RSA + )); + } else { + throw new KeyStoreException("Algorithm \"" + algorithm + "\" not supported for " + + "wrapping. Only RSA wrapping keys are supported."); } if (parts.length > 1) { String mode = parts[1]; - if (KeyProperties.BLOCK_MODE_ECB.equalsIgnoreCase(mode)) { - args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB); - } else if (KeyProperties.BLOCK_MODE_CBC.equalsIgnoreCase(mode)) { - args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CBC); - } else if (KeyProperties.BLOCK_MODE_CTR.equalsIgnoreCase(mode)) { - args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CTR); - } else if (KeyProperties.BLOCK_MODE_GCM.equalsIgnoreCase(mode)) { - args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_GCM); - } + args.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_BLOCK_MODE, + KeyProperties.BlockMode.toKeymaster(mode) + )); } if (parts.length > 2) { - String padding = parts[2]; - if (KeyProperties.ENCRYPTION_PADDING_NONE.equalsIgnoreCase(padding)) { - // Noop - } else if (KeyProperties.ENCRYPTION_PADDING_PKCS7.equalsIgnoreCase(padding)) { - args.addEnums(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_PKCS7); - } else if (KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1.equalsIgnoreCase(padding)) { - args.addEnums(KeymasterDefs.KM_TAG_PADDING, - KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT); - } else if (KeyProperties.ENCRYPTION_PADDING_RSA_OAEP.equalsIgnoreCase(padding)) { - args.addEnums(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_RSA_OAEP); + @KeyProperties.EncryptionPaddingEnum int padding = + KeyProperties.EncryptionPadding.toKeymaster(parts[2]); + if (padding != KeymasterDefs.KM_PAD_NONE) { + args.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, + padding + )); } } KeyGenParameterSpec spec = (KeyGenParameterSpec) entry.getAlgorithmParameterSpec(); if (spec.isDigestsSpecified()) { - String digest = spec.getDigests()[0]; - if (KeyProperties.DIGEST_NONE.equalsIgnoreCase(digest)) { - // Noop - } else if (KeyProperties.DIGEST_MD5.equalsIgnoreCase(digest)) { - args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_MD5); - } else if (KeyProperties.DIGEST_SHA1.equalsIgnoreCase(digest)) { - args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA1); - } else if (KeyProperties.DIGEST_SHA224.equalsIgnoreCase(digest)) { - args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_224); - } else if (KeyProperties.DIGEST_SHA256.equalsIgnoreCase(digest)) { - args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_256); - } else if (KeyProperties.DIGEST_SHA384.equalsIgnoreCase(digest)) { - args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_384); - } else if (KeyProperties.DIGEST_SHA512.equalsIgnoreCase(digest)) { - args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_512); + @KeyProperties.DigestEnum int digest = + KeyProperties.Digest.toKeymaster(spec.getDigests()[0]); + if (digest != KeymasterDefs.KM_DIGEST_NONE) { + args.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, + digest + )); } } - int errorCode = mKeyStore.importWrappedKey( - Credentials.USER_PRIVATE_KEY + alias, - entry.getWrappedKeyBytes(), - Credentials.USER_PRIVATE_KEY + entry.getWrappingKeyAlias(), - maskingKey, - args, - GateKeeper.getSecureUserId(), - 0, // FIXME fingerprint id? - mUid, - new KeyCharacteristics()); - if (errorCode == KeymasterDefs.KM_ERROR_UNIMPLEMENTED) { - throw new SecureKeyImportUnavailableException("Could not import wrapped key"); - } else if (errorCode != KeyStore.NO_ERROR) { - throw new KeyStoreException("Failed to import wrapped key. Keystore error code: " - + errorCode); + KeyDescriptor wrappingkey = makeKeyDescriptor(entry.getWrappingKeyAlias()); + + KeyEntryResponse response = null; + try { + response = mKeyStore.getKeyEntry(wrappingkey); + } catch (android.security.KeyStoreException e) { + throw new KeyStoreException("Failed to load wrapping key.", e); + } + + KeyDescriptor wrappedKey = makeKeyDescriptor(alias); + + KeyStoreSecurityLevel securityLevel = new KeyStoreSecurityLevel(response.iSecurityLevel); + + final BiometricManager bm = android.app.AppGlobals.getInitialApplication() + .getSystemService(BiometricManager.class); + + long[] biometricSids = bm.getAuthenticatorIds(); + + List authenticatorSpecs = new ArrayList<>(); + + AuthenticatorSpec authenticatorSpec = new AuthenticatorSpec(); + // TODO Replace with HardwareAuthenticatorType.PASSWORD when KeyMint AIDL spec has landed. + authenticatorSpec.authenticatorType = 1; // HardwareAuthenticatorType.PASSWORD + authenticatorSpec.authenticatorId = GateKeeper.getSecureUserId(); + authenticatorSpecs.add(authenticatorSpec); + + for (long sid : biometricSids) { + AuthenticatorSpec authSpec = new AuthenticatorSpec(); + // TODO Replace with HardwareAuthenticatorType.FINGERPRINT when KeyMint AIDL spec has + // landed. + authSpec.authenticatorType = 2; // HardwareAuthenticatorType.FINGERPRINT + authSpec.authenticatorId = sid; + authenticatorSpecs.add(authSpec); + } + + try { + securityLevel.importWrappedKey( + wrappedKey, wrappingkey, + entry.getWrappedKeyBytes(), + null /* masking key is set to 32 bytes if null is given here */, + args, + authenticatorSpecs.toArray(new AuthenticatorSpec[0])); + } catch (android.security.KeyStoreException e) { + switch (e.getErrorCode()) { + case KeymasterDefs.KM_ERROR_UNIMPLEMENTED: { + throw new SecureKeyImportUnavailableException("Could not import wrapped key"); + } + default: + throw new KeyStoreException("Failed to import wrapped key. Keystore error " + + "code: " + e.getErrorCode(), e); + } } } @@ -851,6 +910,23 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { throw new KeyStoreException("Operation not supported because key encoding is unknown"); } + /** + * This function sets a trusted certificate entry. It fails if the given + * alias is already taken by an actual key entry. However, if the entry is a + * trusted certificate it will get silently replaced. + * @param alias the alias name + * @param cert the certificate + * + * @throws KeyStoreException if the alias is already taken by a secret or private + * key entry. + * @throws KeyStoreException with a nested {@link CertificateEncodingException} + * if the {@code cert.getEncoded()} throws. + * @throws KeyStoreException with a nested {@link android.security.KeyStoreException} if + * something went wrong while inserting the certificate into keystore. + * @throws NullPointerException if cert or alias is null. + * + * @hide + */ @Override public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException { if (isKeyEntry(alias)) { @@ -869,36 +945,43 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { throw new KeyStoreException(e); } - if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded, mUid, KeyStore.FLAG_NONE)) { - throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?"); + try { + mKeyStore.updateSubcomponents(makeKeyDescriptor(alias), + null /* publicCert - unused when used as pure certificate store. */, + encoded); + } catch (android.security.KeyStoreException e) { + throw new KeyStoreException("Couldn't insert certificate.", e); } } @Override public void engineDeleteEntry(String alias) throws KeyStoreException { - if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid)) { - throw new KeyStoreException("Failed to delete entry: " + alias); + KeyDescriptor descriptor = makeKeyDescriptor(alias); + try { + mKeyStore.deleteKey(descriptor); + } catch (android.security.KeyStoreException e) { + if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) { + throw new KeyStoreException("Failed to delete entry: " + alias, e); + } } } private Set getUniqueAliases() { - final String[] rawAliases = mKeyStore.list("", mUid); - if (rawAliases == null) { - return new HashSet(); - } - final Set aliases = new HashSet(rawAliases.length); - for (String alias : rawAliases) { - final int idx = alias.indexOf('_'); - if ((idx == -1) || (alias.length() <= idx)) { - Log.e(NAME, "invalid alias: " + alias); - continue; + try { + final KeyDescriptor[] keys = mKeyStore.list( + getTargetDomain(), + mNamespace + ); + final Set aliases = new HashSet<>(keys.length); + for (KeyDescriptor d : keys) { + aliases.add(d.alias); } - - aliases.add(new String(alias.substring(idx + 1))); + return aliases; + } catch (android.security.KeyStoreException e) { + Log.e(TAG, "Failed to list keystore entries.", e); + return null; } - - return aliases; } @Override @@ -912,10 +995,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { throw new NullPointerException("alias == null"); } - return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias, mUid) - || mKeyStore.contains(Credentials.USER_SECRET_KEY + alias, mUid) - || mKeyStore.contains(Credentials.USER_CERTIFICATE + alias, mUid) - || mKeyStore.contains(Credentials.CA_CERTIFICATE + alias, mUid); + return getKeyMetadata(alias) != null; } @Override @@ -929,22 +1009,30 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } private boolean isKeyEntry(String alias) { - return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias, mUid) || - mKeyStore.contains(Credentials.USER_SECRET_KEY + alias, mUid); - } - - - private boolean isCertificateEntry(String alias) { if (alias == null) { throw new NullPointerException("alias == null"); } - return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias, mUid); + KeyEntryResponse response = getKeyMetadata(alias); + // If response is null, there is no such entry. + // If response.iSecurityLevel is null, there is no private or secret key material stored. + return response != null && response.iSecurityLevel != null; } + @Override public boolean engineIsCertificateEntry(String alias) { - return !isKeyEntry(alias) && isCertificateEntry(alias); + if (alias == null) { + throw new NullPointerException("alias == null"); + } + KeyEntryResponse response = getKeyMetadata(alias); + // If response == null there is no such entry. + // If there is no certificateChain, then this is not a certificate entry. + // If there is a private key entry, this is the certificate chain for that + // key entry and not a CA certificate entry. + return response != null + && response.metadata.certificateChain != null + && response.iSecurityLevel == null; } @Override @@ -953,66 +1041,55 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { return null; } if (!"X.509".equalsIgnoreCase(cert.getType())) { - // Only X.509 certificates supported + Log.e(TAG, "In engineGetCertificateAlias: only X.509 certificates are supported."); return null; } byte[] targetCertBytes; try { targetCertBytes = cert.getEncoded(); } catch (CertificateEncodingException e) { + Log.e(TAG, "While trying to get the alias for a certificate.", e); return null; } if (targetCertBytes == null) { return null; } - final Set nonCaEntries = new HashSet(); - - /* - * First scan the PrivateKeyEntry types. The KeyStoreSpi documentation - * says to only compare the first certificate in the chain which is - * equivalent to the USER_CERTIFICATE prefix for the Android keystore - * convention. - */ - final String[] certAliases = mKeyStore.list(Credentials.USER_CERTIFICATE, mUid); - if (certAliases != null) { - for (String alias : certAliases) { - final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias, mUid); - if (certBytes == null) { - continue; - } - - nonCaEntries.add(alias); - - if (Arrays.equals(certBytes, targetCertBytes)) { - return alias; - } + KeyDescriptor[] keyDescriptors = null; + try { + keyDescriptors = mKeyStore.list( + getTargetDomain(), + mNamespace + ); + } catch (android.security.KeyStoreException e) { + Log.w(TAG, "Failed to get list of keystore entries.", e); + } + + String caAlias = null; + for (KeyDescriptor d : keyDescriptors) { + KeyEntryResponse response = getKeyMetadata(d.alias); + if (response == null) { + continue; } - } - - /* - * Look at all the TrustedCertificateEntry types. Skip all the - * PrivateKeyEntry we looked at above. - */ - final String[] caAliases = mKeyStore.list(Credentials.CA_CERTIFICATE, mUid); - if (certAliases != null) { - for (String alias : caAliases) { - if (nonCaEntries.contains(alias)) { - continue; - } - - final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias, mUid); - if (certBytes == null) { - continue; + /* + * The KeyStoreSpi documentation says to only compare the first certificate in the + * chain which is equivalent to the {@code response.metadata.certificate} field. + * So we look for a hit in this field first. For pure CA certificate entries, + * we check the {@code response.metadata.certificateChain} field. But we only + * return a CA alias if there was no hit in the certificate field of any other + * entry. + */ + if (response.metadata.certificate != null) { + if (Arrays.equals(response.metadata.certificate, targetCertBytes)) { + return d.alias; } - - if (Arrays.equals(certBytes, targetCertBytes)) { - return alias; + } else if (response.metadata.certificateChain != null && caAlias == null) { + if (Arrays.equals(response.metadata.certificateChain, targetCertBytes)) { + caAlias = d.alias; } } } - - return null; + return caAlias; } @Override @@ -1033,24 +1110,24 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } // Unfortunate name collision. - mKeyStore = KeyStore.getInstance(); - mUid = KeyStore.UID_SELF; + mKeyStore = KeyStore2.getInstance(); + mNamespace = KeyProperties.NAMESPACE_APPLICATION; } @Override public void engineLoad(LoadStoreParameter param) throws IOException, NoSuchAlgorithmException, CertificateException { - int uid = KeyStore.UID_SELF; + int namespace = KeyProperties.NAMESPACE_APPLICATION; if (param != null) { if (param instanceof AndroidKeyStoreLoadStoreParameter) { - uid = ((AndroidKeyStoreLoadStoreParameter) param).getUid(); + namespace = ((AndroidKeyStoreLoadStoreParameter) param).getNamespace(); } else { throw new IllegalArgumentException( "Unsupported param type: " + param.getClass()); } } - mKeyStore = KeyStore.getInstance(); - mUid = uid; + mKeyStore = KeyStore2.getInstance(); + mNamespace = namespace; } @Override @@ -1060,11 +1137,14 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { throw new KeyStoreException("entry == null"); } - Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid); - if (entry instanceof java.security.KeyStore.TrustedCertificateEntry) { java.security.KeyStore.TrustedCertificateEntry trE = (java.security.KeyStore.TrustedCertificateEntry) entry; + // engineSetCertificateEntry does not overwrite if the existing entry + // is a key entry, but the semantic of engineSetEntry is such that it + // overwrites any existing entry. Thus we delete any possible existing + // entry by this alias. + engineDeleteEntry(alias); engineSetCertificateEntry(alias, trE.getTrustedCertificate()); return; } @@ -1080,34 +1160,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { setWrappedKeyEntry(alias, wke, param); } else { throw new KeyStoreException( - "Entry must be a PrivateKeyEntry, SecretKeyEntry or TrustedCertificateEntry" - + "; was " + entry); - } - } - - /** - * {@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; - private final int mPrivateKeyUid; - KeyStoreX509Certificate(String privateKeyAlias, int privateKeyUid, - X509Certificate delegate) { - super(delegate); - mPrivateKeyAlias = privateKeyAlias; - mPrivateKeyUid = privateKeyUid; - } - - @Override - public PublicKey getPublicKey() { - PublicKey original = super.getPublicKey(); - return AndroidKeyStoreProvider.getAndroidKeyStorePublicKey( - mPrivateKeyAlias, mPrivateKeyUid, - original.getAlgorithm(), original.getEncoded()); + "Entry must be a PrivateKeyEntry, SecretKeyEntry, WrappedKeyEntry " + + "or TrustedCertificateEntry; was " + entry); } } } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java index 65678a37b938..3d5a8f63e7f9 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java @@ -18,10 +18,10 @@ package android.security.keystore2; import android.annotation.NonNull; import android.annotation.Nullable; -import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; import android.security.keystore.ArrayUtils; import android.security.keystore.KeyProperties; +import android.system.keystore2.KeyParameter; import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; @@ -32,6 +32,7 @@ import java.security.ProviderException; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidParameterSpecException; import java.util.Arrays; +import java.util.List; import javax.crypto.CipherSpi; import javax.crypto.spec.IvParameterSpec; @@ -48,15 +49,13 @@ class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSp super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false); } - public static class NoPadding extends - AndroidKeyStoreUnauthenticatedAESCipherSpi.ECB { + public static class NoPadding extends ECB { public NoPadding() { super(KeymasterDefs.KM_PAD_NONE); } } - public static class PKCS7Padding extends - AndroidKeyStoreUnauthenticatedAESCipherSpi.ECB { + public static class PKCS7Padding extends ECB { public PKCS7Padding() { super(KeymasterDefs.KM_PAD_PKCS7); } @@ -68,15 +67,13 @@ class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSp super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true); } - public static class NoPadding extends - AndroidKeyStoreUnauthenticatedAESCipherSpi.CBC { + public static class NoPadding extends CBC { public NoPadding() { super(KeymasterDefs.KM_PAD_NONE); } } - public static class PKCS7Padding extends - AndroidKeyStoreUnauthenticatedAESCipherSpi.CBC { + public static class PKCS7Padding extends CBC { public PKCS7Padding() { super(KeymasterDefs.KM_PAD_PKCS7); } @@ -88,8 +85,7 @@ class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSp super(KeymasterDefs.KM_MODE_CTR, keymasterPadding, true); } - public static class NoPadding extends - AndroidKeyStoreUnauthenticatedAESCipherSpi.CTR { + public static class NoPadding extends CTR { public NoPadding() { super(KeymasterDefs.KM_PAD_NONE); } @@ -245,7 +241,7 @@ class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSp @Override protected final void addAlgorithmSpecificParametersToBegin( - @NonNull KeymasterArguments keymasterArgs) { + @NonNull List parameters) { if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) { // IV is being reused for encryption: this violates security best practices. throw new IllegalStateException( @@ -253,23 +249,36 @@ class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSp + " practices."); } - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES); - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode); - keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding + )); if ((mIvRequired) && (mIv != null)) { - keymasterArgs.addBytes(KeymasterDefs.KM_TAG_NONCE, mIv); + parameters.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_NONCE, mIv + )); } } @Override protected final void loadAlgorithmSpecificParametersFromBeginResult( - @NonNull KeymasterArguments keymasterArgs) { + KeyParameter[] parameters) { mIvHasBeenUsed = true; // NOTE: Keymaster doesn't always return an IV, even if it's used. - byte[] returnedIv = keymasterArgs.getBytes(KeymasterDefs.KM_TAG_NONCE, null); - if ((returnedIv != null) && (returnedIv.length == 0)) { - returnedIv = null; + byte[] returnedIv = null; + if (parameters != null) { + for (KeyParameter p : parameters) { + if (p.tag == KeymasterDefs.KM_TAG_NONCE) { + returnedIv = p.blob; + break; + } + } } if (mIvRequired) { diff --git a/keystore/java/android/security/keystore2/DelegatingX509Certificate.java b/keystore/java/android/security/keystore2/DelegatingX509Certificate.java deleted file mode 100644 index 9dfee8ce098c..000000000000 --- a/keystore/java/android/security/keystore2/DelegatingX509Certificate.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.keystore2; - -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 getCriticalExtensionOIDs() { - return mDelegate.getCriticalExtensionOIDs(); - } - - @Override - public byte[] getExtensionValue(String oid) { - return mDelegate.getExtensionValue(oid); - } - - @Override - public Set 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 getExtendedKeyUsage() throws CertificateParsingException { - return mDelegate.getExtendedKeyUsage(); - } - - @Override - public Collection> getIssuerAlternativeNames() throws CertificateParsingException { - return mDelegate.getIssuerAlternativeNames(); - } - - @Override - public X500Principal getIssuerX500Principal() { - return mDelegate.getIssuerX500Principal(); - } - - @Override - public Collection> getSubjectAlternativeNames() throws CertificateParsingException { - return mDelegate.getSubjectAlternativeNames(); - } - - @Override - public X500Principal getSubjectX500Principal() { - return mDelegate.getSubjectX500Principal(); - } -} diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java index 4b48bb749133..3b11854bf7cb 100644 --- a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java +++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java @@ -21,6 +21,7 @@ import android.hardware.biometrics.BiometricManager; import android.security.GateKeeper; import android.security.KeyStore; import android.security.KeyStoreException; +import android.security.KeyStoreOperation; import android.security.keymaster.KeymasterDefs; import android.security.keystore.KeyExpiredException; import android.security.keystore.KeyNotYetValidException; @@ -28,6 +29,7 @@ import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.UserNotAuthenticatedException; import android.system.keystore2.Authorization; import android.system.keystore2.ResponseCode; +import android.util.Log; import libcore.util.EmptyArray; @@ -174,4 +176,36 @@ abstract class KeyStoreCryptoOperationUtils { } return sRng; } + + static void abortOperation(KeyStoreOperation operation) { + if (operation != null) { + try { + operation.abort(); + } catch (KeyStoreException e) { + // We log this error, but we can afford to ignore it. Dropping the reference + // to the KeyStoreOperation is enough to clean up all related resources even + // in the Keystore daemon. We log it anyway, because it may indicate some + // underlying problem that is worth debugging. + Log.w( + "KeyStoreCryptoOperationUtils", + "Encountered error trying to abort a keystore operation.", + e + ); + } + } + } + + static long getOrMakeOperationChallenge(KeyStoreOperation operation, AndroidKeyStoreKey key) + throws KeyPermanentlyInvalidatedException { + if (operation.getChallenge() != null) { + if (!KeyStoreCryptoOperationUtils.canUserAuthorizationSucceed(key)) { + throw new KeyPermanentlyInvalidatedException(); + } + return operation.getChallenge(); + } else { + // Keystore won't give us an operation challenge if the operation doesn't + // need user authorization. So we make our own. + return Math.randomLongInternal(); + } + } } -- cgit v1.2.3-59-g8ed1b From 38ab78f0a0b8e123022fe52a7a8ff0943ddd3690 Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Sun, 11 Oct 2020 15:01:48 -0700 Subject: Keystore 2.0 SPI: AndroidKeyStoreProvider loads keys from Keystore 2.0 This patch adjusts the AndroidKeyStoreProvider to register all services with the correct packages names. And the utility functions load key using the correct Keystore 2.0 methods. Bug: 159476414 Test: None Change-Id: I9268fd66d28e89e188e85991bcf90c7f19809232 --- .../AndroidKeyStoreBCWorkaroundProvider.java | 2 +- .../keystore2/AndroidKeyStoreProvider.java | 333 +++++++++------------ 2 files changed, 148 insertions(+), 187 deletions(-) diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java index 5b6971007077..dd943d422e62 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java @@ -40,7 +40,7 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider { // classes when this provider is instantiated and installed early on during each app's // initialization process. - private static final String PACKAGE_NAME = "android.security.keystore"; + private static final String PACKAGE_NAME = "android.security.keystore2"; private static final String KEYSTORE_SECRET_KEY_CLASS_NAME = PACKAGE_NAME + ".AndroidKeyStoreSecretKey"; private static final String KEYSTORE_PRIVATE_KEY_CLASS_NAME = diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java index b759733a7573..e7fcbdb84ab3 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java @@ -17,36 +17,33 @@ package android.security.keystore2; import android.annotation.NonNull; -import android.annotation.SystemApi; -import android.compat.annotation.UnsupportedAppUsage; import android.security.KeyStore; -import android.security.keymaster.ExportResult; -import android.security.keymaster.KeyCharacteristics; +import android.security.KeyStore2; +import android.security.KeyStoreSecurityLevel; import android.security.keymaster.KeymasterDefs; import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.KeyProperties; import android.security.keystore.KeyStoreCryptoOperation; +import android.system.keystore2.Authorization; +import android.system.keystore2.Domain; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyEntryResponse; +import android.system.keystore2.KeyMetadata; +import android.system.keystore2.ResponseCode; -import java.io.IOException; import java.security.KeyFactory; import java.security.KeyPair; -import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; 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.cert.CertificateException; -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; @@ -56,7 +53,6 @@ import javax.crypto.Mac; * * @hide */ -@SystemApi public class AndroidKeyStoreProvider extends Provider { private static final String PROVIDER_NAME = "AndroidKeyStore"; @@ -68,7 +64,7 @@ public class AndroidKeyStoreProvider extends Provider { // Instead, they need to be offered by AndroidKeyStoreBCWorkaroundProvider. See its Javadoc // for details. - private static final String PACKAGE_NAME = "android.security.keystore"; + private static final String PACKAGE_NAME = "android.security.keystore2"; private static final String DESEDE_SYSTEM_PROPERTY = "ro.hardware.keystore_desede"; @@ -165,7 +161,6 @@ public class AndroidKeyStoreProvider extends Provider { * @throws IllegalStateException if the provided primitive is not initialized. * @hide */ - @UnsupportedAppUsage public static long getKeyStoreOperationHandle(Object cryptoPrimitive) { if (cryptoPrimitive == null) { throw new NullPointerException(); @@ -191,192 +186,188 @@ public class AndroidKeyStoreProvider extends Provider { return ((KeyStoreCryptoOperation) spi).getOperationHandle(); } - /** @hide **/ - @NonNull - public static AndroidKeyStorePublicKey getAndroidKeyStorePublicKey( - @NonNull String alias, - int uid, - @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, uid, (ECPublicKey) publicKey); - } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { - return new AndroidKeyStoreRSAPublicKey(alias, uid, (RSAPublicKey) publicKey); - } else { - throw new ProviderException("Unsupported Android Keystore public key algorithm: " - + keyAlgorithm); - } - } - - @NonNull - private static AndroidKeyStorePrivateKey getAndroidKeyStorePrivateKey( - @NonNull AndroidKeyStorePublicKey publicKey) { - String keyAlgorithm = publicKey.getAlgorithm(); - if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { - return new AndroidKeyStoreECPrivateKey( - publicKey.getAlias(), publicKey.getUid(), ((ECKey) publicKey).getParams()); - } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { - return new AndroidKeyStoreRSAPrivateKey( - publicKey.getAlias(), publicKey.getUid(), ((RSAKey) publicKey).getModulus()); - } else { - throw new ProviderException("Unsupported Android Keystore public key algorithm: " - + keyAlgorithm); - } - } - - @NonNull - private static KeyCharacteristics getKeyCharacteristics(@NonNull KeyStore keyStore, - @NonNull String alias, int uid) - throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { - KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); - int errorCode = keyStore.getKeyCharacteristics( - alias, null, null, uid, keyCharacteristics); - if (errorCode == KeyStore.KEY_PERMANENTLY_INVALIDATED) { - throw (KeyPermanentlyInvalidatedException) - new KeyPermanentlyInvalidatedException( - "User changed or deleted their auth credentials", - KeyStore.getKeyStoreException(errorCode)); - } - if (errorCode != KeyStore.NO_ERROR) { - throw (UnrecoverableKeyException) - new UnrecoverableKeyException("Failed to obtain information about key") - .initCause(KeyStore.getKeyStoreException(errorCode)); - } - return keyCharacteristics; - } - + /** + * This helper function gets called if the key loaded from the keystore daemon + * is for an asymmetric algorithm. It constructs an instance of {@link AndroidKeyStorePublicKey} + * which implements {@link PublicKey}. + * + * @param descriptor The original key descriptor that was used to load the key. + * + * @param metadata The key metadata which includes the public key material, a reference to the + * stored private key material, the key characteristics. + * @param iSecurityLevel A binder interface that allows using the private key. + * @param algorithm Must indicate EC or RSA. + * @return AndroidKeyStorePublicKey + * @throws UnrecoverableKeyException + * @hide + */ @NonNull - private static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore( - @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid, - KeyCharacteristics keyCharacteristics) + static AndroidKeyStorePublicKey makeAndroidKeyStorePublicKeyFromKeyEntryResponse( + @NonNull KeyDescriptor descriptor, + @NonNull KeyMetadata metadata, + @NonNull KeyStoreSecurityLevel iSecurityLevel, int algorithm) throws UnrecoverableKeyException { - ExportResult exportResult = keyStore.exportKey( - privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null, uid); - if (exportResult.resultCode != KeyStore.NO_ERROR) { - throw (UnrecoverableKeyException) - new UnrecoverableKeyException("Failed to obtain X.509 form of public key") - .initCause(KeyStore.getKeyStoreException(exportResult.resultCode)); - } - final byte[] x509EncodedPublicKey = exportResult.exportData; - - Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM); - if (keymasterAlgorithm == null) { - throw new UnrecoverableKeyException("Key algorithm unknown"); + if (metadata.certificate == null) { + throw new UnrecoverableKeyException("Failed to obtain X.509 form of public key." + + " Keystore has no public certificate stored."); } + final byte[] x509EncodedPublicKey = metadata.certificate; String jcaKeyAlgorithm; try { jcaKeyAlgorithm = KeyProperties.KeyAlgorithm.fromKeymasterAsymmetricKeyAlgorithm( - keymasterAlgorithm); + algorithm); } catch (IllegalArgumentException e) { throw (UnrecoverableKeyException) new UnrecoverableKeyException("Failed to load private key") - .initCause(e); + .initCause(e); + } + + PublicKey publicKey; + try { + KeyFactory keyFactory = KeyFactory.getInstance(jcaKeyAlgorithm); + publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedPublicKey)); + } catch (NoSuchAlgorithmException e) { + throw new ProviderException( + "Failed to obtain " + jcaKeyAlgorithm + " KeyFactory", e); + } catch (InvalidKeySpecException e) { + throw new ProviderException("Invalid X.509 encoding of public key", e); } - return AndroidKeyStoreProvider.getAndroidKeyStorePublicKey( - privateKeyAlias, uid, jcaKeyAlgorithm, x509EncodedPublicKey); + KeyStoreSecurityLevel securityLevel = iSecurityLevel; + if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(jcaKeyAlgorithm)) { + + return new AndroidKeyStoreECPublicKey(descriptor, metadata, + iSecurityLevel, (ECPublicKey) publicKey); + } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(jcaKeyAlgorithm)) { + return new AndroidKeyStoreRSAPublicKey(descriptor, metadata, + iSecurityLevel, (RSAPublicKey) publicKey); + } else { + throw new ProviderException("Unsupported Android Keystore public key algorithm: " + + jcaKeyAlgorithm); + } } /** @hide **/ @NonNull public static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore( - @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid) + @NonNull KeyStore2 keyStore, @NonNull String privateKeyAlias, int namespace) throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { - return loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias, uid, - getKeyCharacteristics(keyStore, privateKeyAlias, uid)); - } - - @NonNull - private static KeyPair loadAndroidKeyStoreKeyPairFromKeystore( - @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid, - @NonNull KeyCharacteristics keyCharacteristics) - throws UnrecoverableKeyException { - AndroidKeyStorePublicKey publicKey = - loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias, uid, - keyCharacteristics); - AndroidKeyStorePrivateKey privateKey = - AndroidKeyStoreProvider.getAndroidKeyStorePrivateKey(publicKey); - return new KeyPair(publicKey, privateKey); + AndroidKeyStoreKey key = + loadAndroidKeyStoreKeyFromKeystore(keyStore, privateKeyAlias, namespace); + if (key instanceof AndroidKeyStorePublicKey) { + return (AndroidKeyStorePublicKey) key; + } else { + throw new UnrecoverableKeyException("No asymmetric key found by the given alias."); + } } /** @hide **/ @NonNull public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore( - @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid) + @NonNull KeyStore2 keyStore, @NonNull String privateKeyAlias, int namespace) throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { - return loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias, uid, - getKeyCharacteristics(keyStore, privateKeyAlias, uid)); - } - - @NonNull - private static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore( - @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid, - @NonNull KeyCharacteristics keyCharacteristics) - throws UnrecoverableKeyException { - KeyPair keyPair = loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias, uid, - keyCharacteristics); - return (AndroidKeyStorePrivateKey) keyPair.getPrivate(); + AndroidKeyStoreKey key = + loadAndroidKeyStoreKeyFromKeystore(keyStore, privateKeyAlias, namespace); + if (key instanceof AndroidKeyStorePublicKey) { + AndroidKeyStorePublicKey publicKey = (AndroidKeyStorePublicKey) key; + return new KeyPair(publicKey, publicKey.getPrivateKey()); + } else { + throw new UnrecoverableKeyException("No asymmetric key found by the given alias."); + } } /** @hide **/ @NonNull public static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore( - @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid) + @NonNull KeyStore2 keyStore, @NonNull String privateKeyAlias, int namespace) throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { - return loadAndroidKeyStorePrivateKeyFromKeystore(keyStore, privateKeyAlias, uid, - getKeyCharacteristics(keyStore, privateKeyAlias, uid)); + AndroidKeyStoreKey key = + loadAndroidKeyStoreKeyFromKeystore(keyStore, privateKeyAlias, namespace); + if (key instanceof AndroidKeyStorePublicKey) { + return ((AndroidKeyStorePublicKey) key).getPrivateKey(); + } else { + throw new UnrecoverableKeyException("No asymmetric key found by the given alias."); + } } + @NonNull - private static AndroidKeyStoreSecretKey loadAndroidKeyStoreSecretKeyFromKeystore( - @NonNull String secretKeyAlias, int uid, @NonNull KeyCharacteristics keyCharacteristics) + private static AndroidKeyStoreSecretKey makeAndroidKeyStoreSecretKeyFromKeyEntryResponse( + @NonNull KeyDescriptor descriptor, + @NonNull KeyEntryResponse response, int algorithm, int digest) throws UnrecoverableKeyException { - Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM); - if (keymasterAlgorithm == null) { - throw new UnrecoverableKeyException("Key algorithm unknown"); - } - - List keymasterDigests = keyCharacteristics.getEnums(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); + algorithm, digest); } catch (IllegalArgumentException e) { throw (UnrecoverableKeyException) new UnrecoverableKeyException("Unsupported secret key type").initCause(e); } - return new AndroidKeyStoreSecretKey(secretKeyAlias, uid, keyAlgorithmString); + return new AndroidKeyStoreSecretKey(descriptor, + response.metadata, keyAlgorithmString, + new KeyStoreSecurityLevel(response.iSecurityLevel)); } - /** @hide **/ + /** + * Loads an an AndroidKeyStoreKey from the AndroidKeyStore backend. + * + * @param keyStore The keystore2 backend. + * @param alias The alias of the key in the Keystore database. + * @param namespace The a Keystore namespace. This is used by system api only to request + * Android system specific keystore namespace, which can be configured + * in the device's SEPolicy. Third party apps and most system components + * set this parameter to -1 to indicate their application specific namespace. + * TODO b/171806779 link to public Keystore 2.0 documentation. + * See bug for more details for now. + * @hide + **/ @NonNull public static AndroidKeyStoreKey loadAndroidKeyStoreKeyFromKeystore( - @NonNull KeyStore keyStore, @NonNull String userKeyAlias, int uid) + @NonNull KeyStore2 keyStore, @NonNull String alias, int namespace) throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { - KeyCharacteristics keyCharacteristics = getKeyCharacteristics(keyStore, userKeyAlias, uid); - Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM); + KeyDescriptor descriptor = new KeyDescriptor(); + if (namespace == KeyProperties.NAMESPACE_APPLICATION) { + descriptor.nspace = 0; // ignored; + descriptor.domain = Domain.APP; + } else { + descriptor.nspace = namespace; + descriptor.domain = Domain.SELINUX; + } + descriptor.alias = alias; + descriptor.blob = null; + KeyEntryResponse response = null; + try { + response = keyStore.getKeyEntry(descriptor); + } catch (android.security.KeyStoreException e) { + if (e.getErrorCode() == ResponseCode.KEY_PERMANENTLY_INVALIDATED) { + throw new KeyPermanentlyInvalidatedException( + "User changed or deleted their auth credentials", + e); + } else { + throw (UnrecoverableKeyException) + new UnrecoverableKeyException("Failed to obtain information about key") + .initCause(e); + } + } + + Integer keymasterAlgorithm = null; + // We just need one digest for the algorithm name + int keymasterDigest = -1; + for (Authorization a : response.metadata.authorizations) { + switch (a.keyParameter.tag) { + case KeymasterDefs.KM_TAG_ALGORITHM: + keymasterAlgorithm = a.keyParameter.integer; + break; + case KeymasterDefs.KM_TAG_DIGEST: + if (keymasterDigest == -1) keymasterDigest = a.keyParameter.integer; + break; + } + } if (keymasterAlgorithm == null) { throw new UnrecoverableKeyException("Key algorithm unknown"); } @@ -384,45 +375,15 @@ public class AndroidKeyStoreProvider extends Provider { if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC || keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES || keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_3DES) { - return loadAndroidKeyStoreSecretKeyFromKeystore(userKeyAlias, uid, - keyCharacteristics); + return makeAndroidKeyStoreSecretKeyFromKeyEntryResponse(descriptor, response, + keymasterAlgorithm, keymasterDigest); } else if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_RSA || keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) { - return loadAndroidKeyStorePrivateKeyFromKeystore(keyStore, userKeyAlias, uid, - keyCharacteristics); + return makeAndroidKeyStorePublicKeyFromKeyEntryResponse(descriptor, response.metadata, + new KeyStoreSecurityLevel(response.iSecurityLevel), + keymasterAlgorithm); } else { throw new UnrecoverableKeyException("Key algorithm unknown"); } } - - /** - * Returns an {@code AndroidKeyStore} {@link java.security.KeyStore}} of the specified UID. - * The {@code KeyStore} contains keys and certificates owned by that UID. Such cross-UID - * access is permitted to a few system UIDs and only to a few other UIDs (e.g., Wi-Fi, VPN) - * all of which are system. - * - *

Note: the returned {@code KeyStore} is already initialized/loaded. Thus, there is - * no need to invoke {@code load} on it. - * - * @param uid Uid for which the keystore provider is requested. - * @throws KeyStoreException if a KeyStoreSpi implementation for the specified type is not - * available from the specified provider. - * @throws NoSuchProviderException If the specified provider is not registered in the security - * provider list. - * @hide - */ - @SystemApi - @NonNull - public static java.security.KeyStore getKeyStoreForUid(int uid) - throws KeyStoreException, NoSuchProviderException { - java.security.KeyStore result = - java.security.KeyStore.getInstance("AndroidKeyStore", PROVIDER_NAME); - try { - result.load(new AndroidKeyStoreLoadStoreParameter(uid)); - } catch (NoSuchAlgorithmException | CertificateException | IOException e) { - throw new KeyStoreException( - "Failed to load AndroidKeyStore KeyStore for UID " + uid, e); - } - return result; - } } -- cgit v1.2.3-59-g8ed1b From e6495d774b4e310aed99820cfe5a6ea731a9d2fb Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Tue, 6 Oct 2020 21:46:18 -0700 Subject: Keystore 2.0 SPI: Evolve Factory SPI We no longer need to get the key characteristics from the Keystore daemon to construct the KeyInfo for a key. Also we have to extract the key info from the KeyParameter AIDL type rather than from the hand written KeymasterArguments. This patch also exposes the correct security level for a key through KeyInfo. Bug: 159476414 Test: None Change-Id: I86a85e481e19fdadfed38a42aeac4ffe5f8b83fa --- .../keystore2/AndroidKeyStoreKeyFactorySpi.java | 20 +- .../AndroidKeyStoreSecretKeyFactorySpi.java | 251 +++++++++++---------- 2 files changed, 141 insertions(+), 130 deletions(-) diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java index 607bcae6570d..a8dd7f3f8b14 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java @@ -16,7 +16,6 @@ package android.security.keystore2; -import android.security.Credentials; import android.security.KeyStore; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyInfo; @@ -64,18 +63,9 @@ public class AndroidKeyStoreKeyFactorySpi extends KeyFactorySpi { "Unsupported key type: " + key.getClass().getName() + ". KeyInfo can be obtained only for Android Keystore private keys"); } - AndroidKeyStorePrivateKey - keystorePrivateKey = (AndroidKeyStorePrivateKey) key; - String keyAliasInKeystore = keystorePrivateKey.getAlias(); - String entryAlias; - if (keyAliasInKeystore.startsWith(Credentials.USER_PRIVATE_KEY)) { - entryAlias = keyAliasInKeystore.substring(Credentials.USER_PRIVATE_KEY.length()); - } else { - throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore); - } + AndroidKeyStorePrivateKey keystorePrivateKey = (AndroidKeyStorePrivateKey) key; @SuppressWarnings("unchecked") - T result = (T) AndroidKeyStoreSecretKeyFactorySpi.getKeyInfo( - mKeyStore, entryAlias, keyAliasInKeystore, keystorePrivateKey.getUid()); + T result = (T) AndroidKeyStoreSecretKeyFactorySpi.getKeyInfo(keystorePrivateKey); return result; } else if (X509EncodedKeySpec.class.equals(keySpecClass)) { if (!(key instanceof AndroidKeyStorePublicKey)) { @@ -98,8 +88,7 @@ public class AndroidKeyStoreKeyFactorySpi extends KeyFactorySpi { } } else if (RSAPublicKeySpec.class.equals(keySpecClass)) { if (key instanceof AndroidKeyStoreRSAPublicKey) { - AndroidKeyStoreRSAPublicKey - rsaKey = (AndroidKeyStoreRSAPublicKey) key; + AndroidKeyStoreRSAPublicKey rsaKey = (AndroidKeyStoreRSAPublicKey) key; @SuppressWarnings("unchecked") T result = (T) new RSAPublicKeySpec(rsaKey.getModulus(), rsaKey.getPublicExponent()); @@ -112,8 +101,7 @@ public class AndroidKeyStoreKeyFactorySpi extends KeyFactorySpi { } } else if (ECPublicKeySpec.class.equals(keySpecClass)) { if (key instanceof AndroidKeyStoreECPublicKey) { - AndroidKeyStoreECPublicKey - ecKey = (AndroidKeyStoreECPublicKey) key; + AndroidKeyStoreECPublicKey ecKey = (AndroidKeyStoreECPublicKey) key; @SuppressWarnings("unchecked") T result = (T) new ECPublicKeySpec(ecKey.getW(), ecKey.getParams()); return result; diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java index c2a8e10c1e51..9d3b9704d711 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java @@ -16,13 +16,15 @@ package android.security.keystore2; -import android.security.Credentials; +import android.annotation.NonNull; import android.security.GateKeeper; import android.security.KeyStore; -import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyInfo; +import android.security.keystore.KeyProperties; +import android.system.keystore2.Authorization; import java.math.BigInteger; import java.security.InvalidKeyException; @@ -64,137 +66,161 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName()); } AndroidKeyStoreKey keystoreKey = (AndroidKeyStoreKey) key; - String keyAliasInKeystore = keystoreKey.getAlias(); - String entryAlias; - if (keyAliasInKeystore.startsWith(Credentials.USER_PRIVATE_KEY)) { - entryAlias = keyAliasInKeystore.substring(Credentials.USER_PRIVATE_KEY.length()); - } else if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)){ - // key has legacy prefix - entryAlias = keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length()); - } else { - throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore); - } - return getKeyInfo(mKeyStore, entryAlias, keyAliasInKeystore, keystoreKey.getUid()); + return getKeyInfo(keystoreKey); } - static KeyInfo getKeyInfo(KeyStore keyStore, String entryAlias, String keyAliasInKeystore, - int keyUid) { - KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); - int errorCode = keyStore.getKeyCharacteristics( - keyAliasInKeystore, null, null, keyUid, keyCharacteristics); - if (errorCode != KeyStore.NO_ERROR) { - throw new ProviderException("Failed to obtain information about key." - + " Keystore error: " + errorCode); - } + static @NonNull KeyInfo getKeyInfo(@NonNull AndroidKeyStoreKey key) { - boolean insideSecureHardware; - @KeyProperties.OriginEnum int origin; - int keySize; - @KeyProperties.PurposeEnum int purposes; + @KeyProperties.SecurityLevelEnum int securityLevel = + KeyProperties.SECURITY_LEVEL_SOFTWARE; + boolean insideSecureHardware = false; + @KeyProperties.OriginEnum int origin = -1; + int keySize = -1; + @KeyProperties.PurposeEnum int purposes = 0; String[] encryptionPaddings; String[] signaturePaddings; - @KeyProperties.DigestEnum String[] digests; - @KeyProperties.BlockModeEnum String[] blockModes; - int keymasterSwEnforcedUserAuthenticators; - int keymasterHwEnforcedUserAuthenticators; - List keymasterSecureUserIds; + List digestsList = new ArrayList<>(); + List blockModesList = new ArrayList<>(); + int keymasterSwEnforcedUserAuthenticators = 0; + int keymasterHwEnforcedUserAuthenticators = 0; + List keymasterSecureUserIds = new ArrayList(); + List encryptionPaddingsList = new ArrayList(); + List signaturePaddingsList = new ArrayList(); + Date keyValidityStart = null; + Date keyValidityForOriginationEnd = null; + Date keyValidityForConsumptionEnd = null; + long userAuthenticationValidityDurationSeconds = 0; + boolean userAuthenticationRequired = true; + boolean userAuthenticationValidWhileOnBody = false; + boolean trustedUserPresenceRequired = false; + boolean trustedUserConfirmationRequired = false; try { - if (keyCharacteristics.hwEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) { - insideSecureHardware = true; - origin = KeyProperties.Origin.fromKeymaster( - keyCharacteristics.hwEnforced.getEnum(KeymasterDefs.KM_TAG_ORIGIN, -1)); - } else if (keyCharacteristics.swEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) { - insideSecureHardware = false; - origin = KeyProperties.Origin.fromKeymaster( - keyCharacteristics.swEnforced.getEnum(KeymasterDefs.KM_TAG_ORIGIN, -1)); - } else { - throw new ProviderException("Key origin not available"); - } - long keySizeUnsigned = - keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, -1); - if (keySizeUnsigned == -1) { - throw new ProviderException("Key size not available"); - } else if (keySizeUnsigned > Integer.MAX_VALUE) { - throw new ProviderException("Key too large: " + keySizeUnsigned + " bits"); - } - keySize = (int) keySizeUnsigned; - purposes = KeyProperties.Purpose.allFromKeymaster( - keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_PURPOSE)); - - List encryptionPaddingsList = new ArrayList(); - List signaturePaddingsList = new ArrayList(); - // Keymaster stores both types of paddings in the same array -- we split it into two. - for (int keymasterPadding : keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_PADDING)) { - try { - @KeyProperties.EncryptionPaddingEnum String jcaPadding = - KeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding); - encryptionPaddingsList.add(jcaPadding); - } catch (IllegalArgumentException e) { - try { - @KeyProperties.SignaturePaddingEnum String padding = - KeyProperties.SignaturePadding.fromKeymaster(keymasterPadding); - signaturePaddingsList.add(padding); - } catch (IllegalArgumentException e2) { - throw new ProviderException( - "Unsupported encryption padding: " + keymasterPadding); - } + for (Authorization a : key.getAuthorizations()) { + switch (a.keyParameter.tag) { + case KeymasterDefs.KM_TAG_ORIGIN: + insideSecureHardware = + KeyStore2ParameterUtils.isSecureHardware(a.securityLevel); + securityLevel = a.securityLevel; + origin = KeyProperties.Origin.fromKeymaster(a.keyParameter.integer); + break; + case KeymasterDefs.KM_TAG_KEY_SIZE: + long keySizeUnsigned = KeyStore2ParameterUtils.getUnsignedInt(a); + if (keySizeUnsigned > Integer.MAX_VALUE) { + throw new ProviderException( + "Key too large: " + keySizeUnsigned + " bits"); + } + keySize = (int) keySizeUnsigned; + break; + case KeymasterDefs.KM_TAG_PURPOSE: + purposes |= KeyProperties.Purpose.fromKeymaster(a.keyParameter.integer); + break; + case KeymasterDefs.KM_TAG_PADDING: + try { + if (a.keyParameter.integer == KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN + || a.keyParameter.integer == KeymasterDefs.KM_PAD_RSA_PSS) { + @KeyProperties.SignaturePaddingEnum String padding = + KeyProperties.SignaturePadding.fromKeymaster( + a.keyParameter.integer); + signaturePaddingsList.add(padding); + } else { + @KeyProperties.EncryptionPaddingEnum String jcaPadding = + KeyProperties.EncryptionPadding.fromKeymaster( + a.keyParameter.integer); + encryptionPaddingsList.add(jcaPadding); + } + } catch (IllegalArgumentException e) { + throw new ProviderException("Unsupported padding: " + + a.keyParameter.integer); + } + break; + case KeymasterDefs.KM_TAG_DIGEST: + digestsList.add(KeyProperties.Digest.fromKeymaster(a.keyParameter.integer)); + break; + case KeymasterDefs.KM_TAG_BLOCK_MODE: + blockModesList.add( + KeyProperties.BlockMode.fromKeymaster(a.keyParameter.integer) + ); + break; + case KeymasterDefs.KM_TAG_USER_AUTH_TYPE: + if (KeyStore2ParameterUtils.isSecureHardware(a.securityLevel)) { + keymasterHwEnforcedUserAuthenticators = a.keyParameter.integer; + } else { + keymasterSwEnforcedUserAuthenticators = a.keyParameter.integer; + } + break; + case KeymasterDefs.KM_TAG_USER_SECURE_ID: + keymasterSecureUserIds.add( + KeymasterArguments.toUint64(a.keyParameter.longInteger)); + break; + case KeymasterDefs.KM_TAG_ACTIVE_DATETIME: + keyValidityStart = KeyStore2ParameterUtils.getDate(a); + break; + case KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME: + keyValidityForOriginationEnd = + KeyStore2ParameterUtils.getDate(a); + break; + case KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME: + keyValidityForConsumptionEnd = + KeyStore2ParameterUtils.getDate(a); + break; + case KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED: + userAuthenticationRequired = false; + break; + case KeymasterDefs.KM_TAG_AUTH_TIMEOUT: + userAuthenticationValidityDurationSeconds = + KeyStore2ParameterUtils.getUnsignedInt(a); + if (userAuthenticationValidityDurationSeconds > Integer.MAX_VALUE) { + throw new ProviderException( + "User authentication timeout validity too long: " + + userAuthenticationValidityDurationSeconds + " seconds"); + } + break; + case KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY: + userAuthenticationValidWhileOnBody = + KeyStore2ParameterUtils.isSecureHardware(a.securityLevel); + break; + case KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED: + trustedUserPresenceRequired = + KeyStore2ParameterUtils.isSecureHardware(a.securityLevel); + break; + case KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED: + trustedUserConfirmationRequired = + KeyStore2ParameterUtils.isSecureHardware(a.securityLevel); + break; } - } - encryptionPaddings = - encryptionPaddingsList.toArray(new String[encryptionPaddingsList.size()]); - signaturePaddings = - signaturePaddingsList.toArray(new String[signaturePaddingsList.size()]); - - digests = KeyProperties.Digest.allFromKeymaster( - keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_DIGEST)); - blockModes = KeyProperties.BlockMode.allFromKeymaster( - keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_BLOCK_MODE)); - keymasterSwEnforcedUserAuthenticators = - keyCharacteristics.swEnforced.getEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); - keymasterHwEnforcedUserAuthenticators = - keyCharacteristics.hwEnforced.getEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); - keymasterSecureUserIds = - keyCharacteristics.getUnsignedLongs(KeymasterDefs.KM_TAG_USER_SECURE_ID); } catch (IllegalArgumentException e) { throw new ProviderException("Unsupported key characteristic", e); } - - Date keyValidityStart = keyCharacteristics.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME); - Date keyValidityForOriginationEnd = - keyCharacteristics.getDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME); - Date keyValidityForConsumptionEnd = - keyCharacteristics.getDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME); - boolean userAuthenticationRequired = - !keyCharacteristics.getBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); - long userAuthenticationValidityDurationSeconds = - keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT, 0); - if (userAuthenticationValidityDurationSeconds > Integer.MAX_VALUE) { - throw new ProviderException("User authentication timeout validity too long: " - + userAuthenticationValidityDurationSeconds + " seconds"); + if (keySize == -1) { + throw new ProviderException("Key size not available"); } + if (origin == -1) { + throw new ProviderException("Key origin not available"); + } + + encryptionPaddings = + encryptionPaddingsList.toArray(new String[0]); + signaturePaddings = + signaturePaddingsList.toArray(new String[0]); + boolean userAuthenticationRequirementEnforcedBySecureHardware = (userAuthenticationRequired) && (keymasterHwEnforcedUserAuthenticators != 0) && (keymasterSwEnforcedUserAuthenticators == 0); - boolean userAuthenticationValidWhileOnBody = - keyCharacteristics.hwEnforced.getBoolean(KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY); - boolean trustedUserPresenceRequired = - keyCharacteristics.hwEnforced.getBoolean( - KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED); + + String[] digests = digestsList.toArray(new String[0]); + String[] blockModes = blockModesList.toArray(new String[0]); boolean invalidatedByBiometricEnrollment = false; if (keymasterSwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_BIOMETRIC || keymasterHwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_BIOMETRIC) { // Fingerprint-only key; will be invalidated if the root SID isn't in the SID list. - invalidatedByBiometricEnrollment = keymasterSecureUserIds != null - && !keymasterSecureUserIds.isEmpty() + invalidatedByBiometricEnrollment = !keymasterSecureUserIds.isEmpty() && !keymasterSecureUserIds.contains(getGateKeeperSecureUserId()); } - boolean userConfirmationRequired = keyCharacteristics.hwEnforced.getBoolean(KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED); - - return new KeyInfo(entryAlias, + return new KeyInfo(key.getUserKeyDescriptor().alias, insideSecureHardware, origin, keySize, @@ -213,11 +239,8 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { userAuthenticationValidWhileOnBody, trustedUserPresenceRequired, invalidatedByBiometricEnrollment, - userConfirmationRequired, - // Keystore 1.0 does not tell us the exact security level of the key - // so we assume TEE if the key is in secure hardware. - insideSecureHardware ? KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT - : KeyProperties.SecurityLevelEnum.SOFTWARE); + trustedUserConfirmationRequired, + securityLevel); } private static BigInteger getGateKeeperSecureUserId() throws ProviderException { -- cgit v1.2.3-59-g8ed1b From 940e05164eb880809575eb0b08ffbd38fa67dc89 Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Thu, 15 Oct 2020 15:42:00 -0700 Subject: Keystore 2.0 SPI: Evolve the generator SPI. We delegate the generation of self signed certificates to the KeyMint backend. Also we use the KeyParamter AIDL type instead of KeymasterArguments to construct parameter lists. Bug: 159476414 Test: None Change-Id: I441a4d4df4ef04e3da8aeaff3274c609d549c979 --- .../keystore2/AndroidKeyStoreKeyGeneratorSpi.java | 186 ++++++--- .../AndroidKeyStoreKeyPairGeneratorSpi.java | 439 ++++++++------------- 2 files changed, 285 insertions(+), 340 deletions(-) diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java index 7d18be553122..ccd0a4bf92ff 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java @@ -16,15 +16,22 @@ package android.security.keystore2; -import android.security.Credentials; -import android.security.KeyStore; -import android.security.keymaster.KeyCharacteristics; +import android.security.KeyStore2; +import android.security.KeyStoreSecurityLevel; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; +import android.security.keystore.ArrayUtils; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.security.keystore.KeymasterUtils; import android.security.keystore.StrongBoxUnavailableException; +import android.system.keystore2.Domain; +import android.system.keystore2.IKeystoreSecurityLevel; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyMetadata; +import android.system.keystore2.KeyParameter; +import android.system.keystore2.SecurityLevel; +import android.util.Log; import libcore.util.EmptyArray; @@ -32,7 +39,9 @@ import java.security.InvalidAlgorithmParameterException; import java.security.ProviderException; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import javax.crypto.KeyGeneratorSpi; import javax.crypto.SecretKey; @@ -43,6 +52,7 @@ import javax.crypto.SecretKey; * @hide */ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { + private static final String TAG = "AndroidKeyStoreKeyGeneratorSpi"; public static class AES extends AndroidKeyStoreKeyGeneratorSpi { public AES() { @@ -105,7 +115,7 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { } } - private final KeyStore mKeyStore = KeyStore.getInstance(); + private final KeyStore2 mKeyStore = KeyStore2.getInstance(); private final int mKeymasterAlgorithm; private final int mKeymasterDigest; private final int mDefaultKeySizeBits; @@ -278,77 +288,141 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { throw new IllegalStateException("Not initialized"); } - KeymasterArguments args = new KeymasterArguments(); - args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits); - args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); - args.addEnums(KeymasterDefs.KM_TAG_PURPOSE, mKeymasterPurposes); - args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockModes); - args.addEnums(KeymasterDefs.KM_TAG_PADDING, mKeymasterPaddings); - args.addEnums(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests); - KeymasterUtils.addUserAuthArgs(args, spec); - KeymasterUtils.addMinMacLengthAuthorizationIfNecessary( - args, - mKeymasterAlgorithm, - mKeymasterBlockModes, - mKeymasterDigests); - args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart()); - args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, - spec.getKeyValidityForOriginationEnd()); - args.addDateIfNotNull(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, - spec.getKeyValidityForConsumptionEnd()); + List params = new ArrayList<>(); + + params.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits + )); + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm + )); + ArrayUtils.forEach(mKeymasterPurposes, (purpose) -> { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PURPOSE, purpose + )); + }); + ArrayUtils.forEach(mKeymasterBlockModes, (blockMode) -> { + if (blockMode == KeymasterDefs.KM_MODE_GCM + && mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES) { + params.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, + AndroidKeyStoreAuthenticatedAESCipherSpi.GCM + .MIN_SUPPORTED_TAG_LENGTH_BITS + )); + } + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_BLOCK_MODE, blockMode + )); + }); + ArrayUtils.forEach(mKeymasterPaddings, (padding) -> { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, padding + )); + }); + ArrayUtils.forEach(mKeymasterDigests, (digest) -> { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, digest + )); + }); + + if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC + && mKeymasterDigests.length != 0) { + int digestOutputSizeBits = KeymasterUtils.getDigestOutputSizeBits(mKeymasterDigests[0]); + if (digestOutputSizeBits == -1) { + throw new ProviderException( + "HMAC key authorized for unsupported digest: " + + KeyProperties.Digest.fromKeymaster(mKeymasterDigests[0])); + } + params.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, digestOutputSizeBits + )); + } + + KeyStore2ParameterUtils.addUserAuthArgs(params, spec); + + if (spec.getKeyValidityStart() != null) { + params.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart() + )); + } + if (spec.getKeyValidityForOriginationEnd() != null) { + params.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + spec.getKeyValidityForOriginationEnd() + )); + } + if (spec.getKeyValidityForConsumptionEnd() != null) { + params.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + spec.getKeyValidityForConsumptionEnd() + )); + } if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) && (!spec.isRandomizedEncryptionRequired())) { // Permit caller-provided IV when encrypting with this key - args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); + params.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_CALLER_NONCE + )); } byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( mRng, (mKeySizeBits + 7) / 8); - int flags = 0; + + @SecurityLevel int securityLevel = SecurityLevel.TRUSTED_ENVIRONMENT; if (spec.isStrongBoxBacked()) { - flags |= KeyStore.FLAG_STRONGBOX; + securityLevel = SecurityLevel.STRONGBOX; } + + int flags = 0; if (spec.isCriticalToDeviceEncryption()) { - flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION; + flags |= IKeystoreSecurityLevel.KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING; } - String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + spec.getKeystoreAlias(); - KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); - boolean success = false; + + KeyDescriptor descriptor = new KeyDescriptor(); + descriptor.alias = spec.getKeystoreAlias(); + descriptor.nspace = spec.getNamespace(); + descriptor.domain = descriptor.nspace == KeyProperties.NAMESPACE_APPLICATION + ? Domain.APP + : Domain.SELINUX; + descriptor.blob = null; + + KeyMetadata metadata = null; + KeyStoreSecurityLevel iSecurityLevel = null; try { - Credentials.deleteAllTypesForAlias(mKeyStore, spec.getKeystoreAlias(), spec.getUid()); - int errorCode = mKeyStore.generateKey( - keyAliasInKeystore, - args, - additionalEntropy, - spec.getUid(), + iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel); + metadata = iSecurityLevel.generateKey( + descriptor, + null, /* Attestation key not applicable to symmetric keys. */ + params, flags, - resultingKeyCharacteristics); - if (errorCode != KeyStore.NO_ERROR) { - if (errorCode == KeyStore.HARDWARE_TYPE_UNAVAILABLE) { + additionalEntropy); + } catch (android.security.KeyStoreException e) { + switch (e.getErrorCode()) { + // TODO replace with ErrorCode.HARDWARE_TYPE_UNAVAILABLE when KeyMint spec + // becomes available. + case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE: throw new StrongBoxUnavailableException("Failed to generate key"); - } else { - throw new ProviderException( - "Keystore operation failed", KeyStore.getKeyStoreException(errorCode)); - } + default: + throw new ProviderException("Keystore key generation failed", e); } - @KeyProperties.KeyAlgorithmEnum String keyAlgorithmJCA; + } + @KeyProperties.KeyAlgorithmEnum String keyAlgorithmJCA; + try { + keyAlgorithmJCA = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm( + mKeymasterAlgorithm, mKeymasterDigest); + } catch (IllegalArgumentException e) { try { - keyAlgorithmJCA = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm( - mKeymasterAlgorithm, mKeymasterDigest); - } catch (IllegalArgumentException e) { - throw new ProviderException("Failed to obtain JCA secret key algorithm name", e); - } - SecretKey result = new AndroidKeyStoreSecretKey( - keyAliasInKeystore, spec.getUid(), keyAlgorithmJCA); - success = true; - return result; - } finally { - if (!success) { - Credentials.deleteAllTypesForAlias( - mKeyStore, spec.getKeystoreAlias(), spec.getUid()); + mKeyStore.deleteKey(descriptor); + } catch (android.security.KeyStoreException kse) { + Log.e(TAG, "Failed to delete key after generating successfully but" + + " failed to get the algorithm string.", kse); } + throw new ProviderException("Failed to obtain JCA secret key algorithm name", e); } + SecretKey result = new AndroidKeyStoreSecretKey(descriptor, metadata, keyAlgorithmJCA, + iSecurityLevel); + return result; } } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index ff40c1586212..a747a0e727d8 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -16,58 +16,41 @@ package android.security.keystore2; +import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Build; -import android.security.Credentials; import android.security.KeyPairGeneratorSpec; -import android.security.KeyStore; -import android.security.keymaster.KeyCharacteristics; +import android.security.KeyStore2; +import android.security.KeyStoreException; +import android.security.KeyStoreSecurityLevel; import android.security.keymaster.KeymasterArguments; -import android.security.keymaster.KeymasterCertificateChain; import android.security.keymaster.KeymasterDefs; +import android.security.keystore.ArrayUtils; import android.security.keystore.KeyGenParameterSpec; -import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.security.keystore.KeyProperties; import android.security.keystore.KeymasterUtils; import android.security.keystore.SecureKeyImportUnavailableException; import android.security.keystore.StrongBoxUnavailableException; - -import com.android.org.bouncycastle.asn1.ASN1EncodableVector; -import com.android.org.bouncycastle.asn1.ASN1InputStream; -import com.android.org.bouncycastle.asn1.ASN1Integer; -import com.android.org.bouncycastle.asn1.ASN1ObjectIdentifier; -import com.android.org.bouncycastle.asn1.DERBitString; -import com.android.org.bouncycastle.asn1.DERNull; -import com.android.org.bouncycastle.asn1.DERSequence; -import com.android.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; -import com.android.org.bouncycastle.asn1.x509.AlgorithmIdentifier; -import com.android.org.bouncycastle.asn1.x509.Certificate; -import com.android.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import com.android.org.bouncycastle.asn1.x509.TBSCertificate; -import com.android.org.bouncycastle.asn1.x509.Time; -import com.android.org.bouncycastle.asn1.x509.V3TBSCertificateGenerator; -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 android.system.keystore2.Domain; +import android.system.keystore2.IKeystoreSecurityLevel; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyMetadata; +import android.system.keystore2.KeyParameter; +import android.system.keystore2.ResponseCode; +import android.system.keystore2.SecurityLevel; +import android.util.Log; import libcore.util.EmptyArray; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyPairGeneratorSpi; -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.CertificateParsingException; -import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.ECGenParameterSpec; import java.security.spec.RSAKeyGenParameterSpec; @@ -76,7 +59,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -96,6 +78,7 @@ import java.util.Set; * @hide */ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGeneratorSpi { + private static final String TAG = "AndroidKeyStoreKeyPairGeneratorSpi"; public static class RSA extends AndroidKeyStoreKeyPairGeneratorSpi { public RSA() { @@ -154,13 +137,12 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato private final int mOriginalKeymasterAlgorithm; - private KeyStore mKeyStore; + private KeyStore2 mKeyStore; private KeyGenParameterSpec mSpec; private String mEntryAlias; private int mEntryUid; - private boolean mEncryptionAtRestRequired; private @KeyProperties.KeyAlgorithmEnum String mJcaKeyAlgorithm; private int mKeymasterAlgorithm = -1; private int mKeySizeBits; @@ -172,7 +154,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato private int[] mKeymasterSignaturePaddings; private int[] mKeymasterDigests; - private BigInteger mRSAPublicExponent; + private Long mRSAPublicExponent; protected AndroidKeyStoreKeyPairGeneratorSpi(int keymasterAlgorithm) { mOriginalKeymasterAlgorithm = keymasterAlgorithm; @@ -283,7 +265,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber()); specBuilder.setCertificateNotBefore(legacySpec.getStartDate()); specBuilder.setCertificateNotAfter(legacySpec.getEndDate()); - encryptionAtRestRequired = legacySpec.isEncryptionRequired(); specBuilder.setUserAuthenticationRequired(false); spec = specBuilder.build(); @@ -301,7 +282,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato mEntryUid = spec.getUid(); mSpec = spec; mKeymasterAlgorithm = keymasterAlgorithm; - mEncryptionAtRestRequired = encryptionAtRestRequired; mKeySizeBits = spec.getKeySize(); initAlgorithmSpecificParameters(); if (mKeySizeBits == -1) { @@ -355,7 +335,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato mJcaKeyAlgorithm = jcaKeyAlgorithm; mRng = random; - mKeyStore = KeyStore.getInstance(); + mKeyStore = KeyStore2.getInstance(); success = true; } finally { if (!success) { @@ -366,7 +346,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato private void resetAll() { mEntryAlias = null; - mEntryUid = KeyStore.UID_SELF; + mEntryUid = KeyProperties.NAMESPACE_APPLICATION; mJcaKeyAlgorithm = null; mKeymasterAlgorithm = -1; mKeymasterPurposes = null; @@ -377,7 +357,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato mKeySizeBits = 0; mSpec = null; mRSAPublicExponent = null; - mEncryptionAtRestRequired = false; mRng = null; mKeyStore = null; } @@ -409,12 +388,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato throw new InvalidAlgorithmParameterException( "RSA public exponent must be positive: " + publicExponent); } - if (publicExponent.compareTo(KeymasterArguments.UINT64_MAX_VALUE) > 0) { + if ((publicExponent.signum() == -1) + || (publicExponent.compareTo(KeymasterArguments.UINT64_MAX_VALUE) > 0)) { throw new InvalidAlgorithmParameterException( "Unsupported RSA public exponent: " + publicExponent + ". Maximum supported value: " + KeymasterArguments.UINT64_MAX_VALUE); } - mRSAPublicExponent = publicExponent; + mRSAPublicExponent = publicExponent.longValue(); break; } case KeymasterDefs.KM_ALGORITHM_EC: @@ -451,204 +431,178 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato throw new IllegalStateException("Not initialized"); } - int flags = (mEncryptionAtRestRequired) ? KeyStore.FLAG_ENCRYPTED : 0; - if (((flags & KeyStore.FLAG_ENCRYPTED) != 0) - && (mKeyStore.state() != KeyStore.State.UNLOCKED)) { - throw new IllegalStateException( - "Encryption at rest using secure lock screen credential requested for key pair" - + ", but the user has not yet entered the credential"); - } + final @SecurityLevel int securityLevel = + mSpec.isStrongBoxBacked() + ? SecurityLevel.STRONGBOX + : SecurityLevel.TRUSTED_ENVIRONMENT; - if (mSpec.isStrongBoxBacked()) { - flags |= KeyStore.FLAG_STRONGBOX; - } - if (mSpec.isCriticalToDeviceEncryption()) { - flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION; - } + final int flags = + mSpec.isCriticalToDeviceEncryption() + ? IKeystoreSecurityLevel + .KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING + : 0; byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( mRng, (mKeySizeBits + 7) / 8); - Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid); - final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + mEntryAlias; + KeyDescriptor descriptor = new KeyDescriptor(); + descriptor.alias = mEntryAlias; + descriptor.domain = mEntryUid == KeyProperties.NAMESPACE_APPLICATION + ? Domain.APP + : Domain.SELINUX; + descriptor.nspace = mEntryUid; + descriptor.blob = null; + boolean success = false; try { - generateKeystoreKeyPair( - privateKeyAlias, constructKeyGenerationArguments(), additionalEntropy, flags); - KeyPair keyPair = loadKeystoreKeyPair(privateKeyAlias); + KeyStoreSecurityLevel iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel); + + KeyMetadata metadata = iSecurityLevel.generateKey(descriptor, null, + constructKeyGenerationArguments(), flags, additionalEntropy); - storeCertificateChain(flags, createCertificateChain(privateKeyAlias, keyPair)); + AndroidKeyStorePublicKey publicKey = + AndroidKeyStoreProvider.makeAndroidKeyStorePublicKeyFromKeyEntryResponse( + descriptor, metadata, iSecurityLevel, mKeymasterAlgorithm); success = true; - return keyPair; - } catch (ProviderException e) { - if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) { - throw new SecureKeyImportUnavailableException(e); - } else { - throw e; - } + return new KeyPair(publicKey, publicKey.getPrivateKey()); + } catch (android.security.KeyStoreException e) { + switch(e.getErrorCode()) { + case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE: + throw new StrongBoxUnavailableException("Failed to generated key pair.", e); + default: + ProviderException p = new ProviderException("Failed to generate key pair.", e); + if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) { + throw new SecureKeyImportUnavailableException(p); + } + throw p; + } + } catch (UnrecoverableKeyException e) { + throw new ProviderException( + "Failed to construct key object from newly generated key pair.", e); } finally { if (!success) { - Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid); + try { + mKeyStore.deleteKey(descriptor); + } catch (KeyStoreException e) { + if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) { + Log.e(TAG, "Failed to delete newly generated key after " + + "generation failed unexpectedly.", e); + } + } } } } - private Iterable createCertificateChain(final String privateKeyAlias, KeyPair keyPair) + private void addAttestationParameters(@NonNull List params) throws ProviderException { byte[] challenge = mSpec.getAttestationChallenge(); + if (challenge != null) { - KeymasterArguments args = new KeymasterArguments(); - args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, challenge); + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, challenge + )); if (mSpec.isDevicePropertiesAttestationIncluded()) { - args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND, - Build.BRAND.getBytes(StandardCharsets.UTF_8)); - args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE, - Build.DEVICE.getBytes(StandardCharsets.UTF_8)); - args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT, - Build.PRODUCT.getBytes(StandardCharsets.UTF_8)); - args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER, - Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8)); - args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MODEL, - Build.MODEL.getBytes(StandardCharsets.UTF_8)); + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND, + Build.BRAND.getBytes(StandardCharsets.UTF_8) + )); + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE, + Build.DEVICE.getBytes(StandardCharsets.UTF_8) + )); + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT, + Build.PRODUCT.getBytes(StandardCharsets.UTF_8) + )); + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER, + Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8) + )); + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_MODEL, + Build.MODEL.getBytes(StandardCharsets.UTF_8) + )); } - - return getAttestationChain(privateKeyAlias, keyPair, args); - } - - // Very short certificate chain in the non-attestation case. - return Collections.singleton(generateSelfSignedCertificateBytes(keyPair)); - } - - private void generateKeystoreKeyPair(final String privateKeyAlias, KeymasterArguments args, - byte[] additionalEntropy, final int flags) throws ProviderException { - KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); - int errorCode = mKeyStore.generateKey(privateKeyAlias, args, additionalEntropy, - mEntryUid, flags, resultingKeyCharacteristics); - if (errorCode != KeyStore.NO_ERROR) { - if (errorCode == KeyStore.HARDWARE_TYPE_UNAVAILABLE) { - throw new StrongBoxUnavailableException("Failed to generate key pair"); - } else { - throw new ProviderException( - "Failed to generate key pair", KeyStore.getKeyStoreException(errorCode)); + } else { + if (mSpec.isDevicePropertiesAttestationIncluded()) { + throw new ProviderException("An attestation challenge must be provided when " + + "requesting device properties attestation."); } } } - private KeyPair loadKeystoreKeyPair(final String privateKeyAlias) throws ProviderException { - try { - KeyPair result = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore( - mKeyStore, privateKeyAlias, mEntryUid); - if (!mJcaKeyAlgorithm.equalsIgnoreCase(result.getPrivate().getAlgorithm())) { - throw new ProviderException( - "Generated key pair algorithm does not match requested algorithm: " - + result.getPrivate().getAlgorithm() + " vs " + mJcaKeyAlgorithm); - } - return result; - } catch (UnrecoverableKeyException | KeyPermanentlyInvalidatedException e) { - throw new ProviderException("Failed to load generated key pair from keystore", e); + private Collection constructKeyGenerationArguments() { + List params = new ArrayList<>(); + params.add(KeyStore2ParameterUtils.makeInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits)); + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm + )); + ArrayUtils.forEach(mKeymasterPurposes, (purpose) -> { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PURPOSE, purpose + )); + }); + ArrayUtils.forEach(mKeymasterBlockModes, (blockMode) -> { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_BLOCK_MODE, blockMode + )); + }); + ArrayUtils.forEach(mKeymasterEncryptionPaddings, (padding) -> { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, padding + )); + }); + ArrayUtils.forEach(mKeymasterSignaturePaddings, (padding) -> { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, padding + )); + }); + ArrayUtils.forEach(mKeymasterDigests, (digest) -> { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, digest + )); + }); + + KeyStore2ParameterUtils.addUserAuthArgs(params, mSpec); + + if (mSpec.getKeyValidityStart() != null) { + params.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_ACTIVE_DATETIME, mSpec.getKeyValidityStart() + )); } - } - - private KeymasterArguments constructKeyGenerationArguments() { - KeymasterArguments args = new KeymasterArguments(); - args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits); - args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); - args.addEnums(KeymasterDefs.KM_TAG_PURPOSE, mKeymasterPurposes); - args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockModes); - args.addEnums(KeymasterDefs.KM_TAG_PADDING, mKeymasterEncryptionPaddings); - args.addEnums(KeymasterDefs.KM_TAG_PADDING, mKeymasterSignaturePaddings); - args.addEnums(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests); - - KeymasterUtils.addUserAuthArgs(args, mSpec); - args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, mSpec.getKeyValidityStart()); - args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, - mSpec.getKeyValidityForOriginationEnd()); - args.addDateIfNotNull(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, - mSpec.getKeyValidityForConsumptionEnd()); - addAlgorithmSpecificParameters(args); - - if (mSpec.isUniqueIdIncluded()) - args.addBoolean(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID); - - return args; - } - - private void storeCertificateChain(final int flags, Iterable iterable) - throws ProviderException { - Iterator iter = iterable.iterator(); - storeCertificate( - Credentials.USER_CERTIFICATE, iter.next(), flags, "Failed to store certificate"); - - if (!iter.hasNext()) { - return; + if (mSpec.getKeyValidityForOriginationEnd() != null) { + params.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + mSpec.getKeyValidityForOriginationEnd() + )); } - - ByteArrayOutputStream certificateConcatenationStream = new ByteArrayOutputStream(); - while (iter.hasNext()) { - byte[] data = iter.next(); - certificateConcatenationStream.write(data, 0, data.length); + if (mSpec.getKeyValidityForConsumptionEnd() != null) { + params.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + mSpec.getKeyValidityForConsumptionEnd() + )); } - storeCertificate(Credentials.CA_CERTIFICATE, certificateConcatenationStream.toByteArray(), - flags, "Failed to store attestation CA certificate"); - } + addAlgorithmSpecificParameters(params); - private void storeCertificate(String prefix, byte[] certificateBytes, final int flags, - String failureMessage) throws ProviderException { - int insertErrorCode = mKeyStore.insert( - prefix + mEntryAlias, - certificateBytes, - mEntryUid, - flags); - if (insertErrorCode != KeyStore.NO_ERROR) { - throw new ProviderException(failureMessage, - KeyStore.getKeyStoreException(insertErrorCode)); + if (mSpec.isUniqueIdIncluded()) { + params.add(KeyStore2ParameterUtils.makeBool(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID)); } - } - private byte[] generateSelfSignedCertificateBytes(KeyPair keyPair) throws ProviderException { - try { - return generateSelfSignedCertificate(keyPair.getPrivate(), keyPair.getPublic()) - .getEncoded(); - } catch (IOException | CertificateParsingException e) { - throw new ProviderException("Failed to generate self-signed certificate", e); - } catch (CertificateEncodingException e) { - throw new ProviderException( - "Failed to obtain encoded form of self-signed certificate", e); - } - } + addAttestationParameters(params); - private Iterable getAttestationChain(String privateKeyAlias, - KeyPair keyPair, KeymasterArguments args) - throws ProviderException { - final KeymasterCertificateChain outChain = new KeymasterCertificateChain(); - final int errorCode; - if (mSpec.isDevicePropertiesAttestationIncluded() - && mSpec.getAttestationChallenge() == null) { - throw new ProviderException("An attestation challenge must be provided when requesting " - + "device properties attestation."); - } - errorCode = mKeyStore.attestKey(privateKeyAlias, args, outChain); - if (errorCode != KeyStore.NO_ERROR) { - throw new ProviderException("Failed to generate attestation certificate chain", - KeyStore.getKeyStoreException(errorCode)); - } - Collection chain = outChain.getCertificates(); - if (chain.size() < 2) { - throw new ProviderException("Attestation certificate chain contained " - + chain.size() + " entries. At least two are required."); - } - return chain; + return params; } - private void addAlgorithmSpecificParameters(KeymasterArguments keymasterArgs) { + private void addAlgorithmSpecificParameters(List params) { switch (mKeymasterAlgorithm) { case KeymasterDefs.KM_ALGORITHM_RSA: - keymasterArgs.addUnsignedLong( - KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, mRSAPublicExponent); + params.add(KeyStore2ParameterUtils.makeLong( + KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, mRSAPublicExponent + )); break; case KeymasterDefs.KM_ALGORITHM_EC: break; @@ -657,89 +611,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } } - private X509Certificate generateSelfSignedCertificate(PrivateKey privateKey, - PublicKey publicKey) throws CertificateParsingException, IOException { - String signatureAlgorithm = - getCertificateSignatureAlgorithm(mKeymasterAlgorithm, mKeySizeBits, mSpec); - if (signatureAlgorithm == null) { - // Key cannot be used to sign a certificate - return generateSelfSignedCertificateWithFakeSignature(publicKey); - } else { - // Key can be used to sign a certificate - try { - return generateSelfSignedCertificateWithValidSignature( - privateKey, publicKey, signatureAlgorithm); - } catch (Exception e) { - // Failed to generate the self-signed certificate with valid signature. Fall back - // to generating a self-signed certificate with a fake signature. This is done for - // all exception types because we prefer key pair generation to succeed and end up - // producing a self-signed certificate with an invalid signature to key pair - // generation failing. - return generateSelfSignedCertificateWithFakeSignature(publicKey); - } - } - } - - @SuppressWarnings("deprecation") - private X509Certificate generateSelfSignedCertificateWithValidSignature( - PrivateKey privateKey, PublicKey publicKey, String signatureAlgorithm) throws Exception { - final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); - certGen.setPublicKey(publicKey); - certGen.setSerialNumber(mSpec.getCertificateSerialNumber()); - certGen.setSubjectDN(mSpec.getCertificateSubject()); - certGen.setIssuerDN(mSpec.getCertificateSubject()); - certGen.setNotBefore(mSpec.getCertificateNotBefore()); - certGen.setNotAfter(mSpec.getCertificateNotAfter()); - certGen.setSignatureAlgorithm(signatureAlgorithm); - return certGen.generate(privateKey); - } - - @SuppressWarnings("deprecation") - private X509Certificate generateSelfSignedCertificateWithFakeSignature( - PublicKey publicKey) throws IOException, CertificateParsingException { - V3TBSCertificateGenerator tbsGenerator = new V3TBSCertificateGenerator(); - ASN1ObjectIdentifier sigAlgOid; - AlgorithmIdentifier sigAlgId; - byte[] signature; - switch (mKeymasterAlgorithm) { - case KeymasterDefs.KM_ALGORITHM_EC: - sigAlgOid = X9ObjectIdentifiers.ecdsa_with_SHA256; - sigAlgId = new AlgorithmIdentifier(sigAlgOid); - ASN1EncodableVector v = new ASN1EncodableVector(); - v.add(new ASN1Integer(BigInteger.valueOf(0))); - v.add(new ASN1Integer(BigInteger.valueOf(0))); - signature = new DERSequence().getEncoded(); - break; - case KeymasterDefs.KM_ALGORITHM_RSA: - sigAlgOid = PKCSObjectIdentifiers.sha256WithRSAEncryption; - sigAlgId = new AlgorithmIdentifier(sigAlgOid, DERNull.INSTANCE); - signature = new byte[1]; - break; - default: - throw new ProviderException("Unsupported key algorithm: " + mKeymasterAlgorithm); - } - - try (ASN1InputStream publicKeyInfoIn = new ASN1InputStream(publicKey.getEncoded())) { - tbsGenerator.setSubjectPublicKeyInfo( - SubjectPublicKeyInfo.getInstance(publicKeyInfoIn.readObject())); - } - tbsGenerator.setSerialNumber(new ASN1Integer(mSpec.getCertificateSerialNumber())); - X509Principal subject = - new X509Principal(mSpec.getCertificateSubject().getEncoded()); - tbsGenerator.setSubject(subject); - tbsGenerator.setIssuer(subject); - tbsGenerator.setStartDate(new Time(mSpec.getCertificateNotBefore())); - tbsGenerator.setEndDate(new Time(mSpec.getCertificateNotAfter())); - tbsGenerator.setSignature(sigAlgId); - TBSCertificate tbsCertificate = tbsGenerator.generateTBSCertificate(); - - ASN1EncodableVector result = new ASN1EncodableVector(); - result.add(tbsCertificate); - result.add(sigAlgId); - result.add(new DERBitString(signature)); - return new X509CertificateObject(Certificate.getInstance(new DERSequence(result))); - } - private static int getDefaultKeySize(int keymasterAlgorithm) { switch (keymasterAlgorithm) { case KeymasterDefs.KM_ALGORITHM_EC: -- cgit v1.2.3-59-g8ed1b From 6180e85e369c5554c62a7a87c9f946f1801f3202 Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Mon, 19 Oct 2020 17:48:08 -0700 Subject: Keystore 2.0 SPI: Zygote install Keystore2 provider conditionally This patch makes Zygote install the Keystore 2.0 SPI as "AndroidKeyStore" and the old Keystore SPI as "AndroidKeyStoreLegacy" if the platform property ro.android.security.keystore2.enable is set to true. This allows us to boot Android with vital components, such as LockSettingsService, still using the legacy Keystore, while we run CTS tests against the new implementation, migrate other system components, and perform migration tests. This CL will be superseded by a CL that makes Zygote install the Keystore 2.0 SPI exclusively when the migration of all dependent components is complete. Bug: 171305684 Test: None Change-Id: I9e32578285167c4d63f4f536a07fe98473a883e0 --- core/java/com/android/internal/os/ZygoteInit.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index aa37334b2c54..6a67670d8160 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -73,6 +73,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.security.Provider; import java.security.Security; +import java.util.Optional; /** * Startup class for the zygote process. @@ -225,7 +226,17 @@ public class ZygoteInit { // AndroidKeyStoreProvider.install() manipulates the list of JCA providers to insert // preferred providers. Note this is not done via security.properties as the JCA providers // are not on the classpath in the case of, for example, raw dalvikvm runtimes. - AndroidKeyStoreProvider.install(); + // TODO b/171305684 This code is used to conditionally enable the installation of the + // Keystore 2.0 provider to enable teams adjusting to Keystore 2.0 at their own + // pace. This code will be removed when all calling code was adjusted to + // Keystore 2.0. + Optional keystore2_enabled = + android.sysprop.Keystore2Properties.keystore2_enabled(); + if (keystore2_enabled.isPresent() && keystore2_enabled.get()) { + android.security.keystore2.AndroidKeyStoreProvider.install(); + } else { + AndroidKeyStoreProvider.install(); + } Log.i(TAG, "Installed AndroidKeyStoreProvider in " + (SystemClock.uptimeMillis() - startTime) + "ms."); Trace.traceEnd(Trace.TRACE_TAG_DALVIK); -- cgit v1.2.3-59-g8ed1b From 4392c6977ce935a084ab30baeed511f170a606d5 Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Tue, 20 Oct 2020 08:16:52 -0700 Subject: Keystore 2.0 SPI: Install legacy Keystore provider as AndroidKeyStoreLegacy With this patch we install the old Keystore provider as AndroidKeyStoreLegacy when the Keystore 2.0 provider is installed as AndroidKeyStore. This allows system components to keep using the old keystore while we can run CTS tests against the new provider. The tests are still mostly failing at this point. Installing the new SPI can be enabled by setting the property ro.android.security.keystore2.enable=true Bug: 159476414 Test: This enables running CTS tests against Keystore 2.0. Change-Id: I9731d9783ccf8f2705a5ca7335e00c8f4c8debba --- .../AndroidKeyStoreBCWorkaroundProvider.java | 12 +++++++--- .../security/keystore/AndroidKeyStoreProvider.java | 16 ++++++++++--- .../keystore2/AndroidKeyStoreProvider.java | 26 ++++++++++++++++++++++ 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java index 624321cbf5ea..5730234184ab 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java @@ -34,7 +34,7 @@ import java.security.Provider; * * @hide */ -class AndroidKeyStoreBCWorkaroundProvider extends Provider { +public class AndroidKeyStoreBCWorkaroundProvider extends Provider { // IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these // classes when this provider is instantiated and installed early on during each app's @@ -50,8 +50,14 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider { private static final String DESEDE_SYSTEM_PROPERTY = "ro.hardware.keystore_desede"; - AndroidKeyStoreBCWorkaroundProvider() { - super("AndroidKeyStoreBCWorkaround", + /** @hide */ + public AndroidKeyStoreBCWorkaroundProvider() { + this("AndroidKeyStoreBCWorkaround"); + } + + /** @hide **/ + public AndroidKeyStoreBCWorkaroundProvider(String providerName) { + super(providerName, 1.0, "Android KeyStore security provider to work around Bouncy Castle"); diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java index d1b4464c1aed..3ac9d68d5a9f 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java @@ -71,14 +71,20 @@ public class AndroidKeyStoreProvider extends Provider { private static final String DESEDE_SYSTEM_PROPERTY = "ro.hardware.keystore_desede"; - /** @hide **/ + /** @hide */ public AndroidKeyStoreProvider() { - super(PROVIDER_NAME, 1.0, "Android KeyStore security provider"); + this(PROVIDER_NAME); + } + + /** @hide **/ + public AndroidKeyStoreProvider(String providerName) { + super(providerName, 1.0, "Android KeyStore security provider"); boolean supports3DES = "true".equals(android.os.SystemProperties.get(DESEDE_SYSTEM_PROPERTY)); // java.security.KeyStore put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStoreSpi"); + put("alg.alias.KeyStore.AndroidKeyStoreLegacy", "AndroidKeyStore"); // java.security.KeyPairGenerator put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC"); @@ -438,8 +444,12 @@ public class AndroidKeyStoreProvider extends Provider { @NonNull public static java.security.KeyStore getKeyStoreForUid(int uid) throws KeyStoreException, NoSuchProviderException { + String providerName = PROVIDER_NAME; + if (android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) { + providerName = "AndroidKeyStoreLegacy"; + } java.security.KeyStore result = - java.security.KeyStore.getInstance("AndroidKeyStore", PROVIDER_NAME); + java.security.KeyStore.getInstance(providerName); try { result.load(new AndroidKeyStoreLoadStoreParameter(uid)); } catch (NoSuchAlgorithmException | CertificateException | IOException e) { diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java index e7fcbdb84ab3..b2e32a3175e3 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java @@ -110,6 +110,23 @@ public class AndroidKeyStoreProvider extends Provider { putSecretKeyFactoryImpl("HmacSHA512"); } + private static boolean sInstalled = false; + + /** + * This function indicates whether or not this provider was installed. This is manly used + * as indicator for + * {@link android.security.keystore.AndroidKeyStoreProvider#getKeyStoreForUid(int)} + * to whether or not to retrieve the Keystore provider by "AndroidKeyStoreLegacy". + * This function can be removed once the transition to Keystore 2.0 is complete. + * b/171305684 + * + * @return true if this provider was installed. + * @hide + */ + public static boolean isInstalled() { + return sInstalled; + } + /** * Installs a new instance of this provider (and the * {@link AndroidKeyStoreBCWorkaroundProvider}). @@ -125,17 +142,26 @@ public class AndroidKeyStoreProvider extends Provider { break; } } + sInstalled = true; Security.addProvider(new AndroidKeyStoreProvider()); + Security.addProvider( + new android.security.keystore.AndroidKeyStoreProvider( + "AndroidKeyStoreLegacy")); Provider workaroundProvider = new AndroidKeyStoreBCWorkaroundProvider(); + Provider legacyWorkaroundProvider = + new android.security.keystore.AndroidKeyStoreBCWorkaroundProvider( + "AndroidKeyStoreBCWorkaroundLegacy"); if (bcProviderIndex != -1) { // Bouncy Castle provider found -- install the workaround provider above it. // insertProviderAt uses 1-based positions. + Security.insertProviderAt(legacyWorkaroundProvider, bcProviderIndex + 1); Security.insertProviderAt(workaroundProvider, bcProviderIndex + 1); } else { // Bouncy Castle provider not found -- install the workaround provider at lowest // priority. Security.addProvider(workaroundProvider); + Security.addProvider(legacyWorkaroundProvider); } } -- cgit v1.2.3-59-g8ed1b