diff options
13 files changed, 956 insertions, 1090 deletions
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<KeyParameter> 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<KeyParameter> 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<KeyParameter> 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<KeyParameter> 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<KeyParameter> 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<KeyParameter> 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<KeyParameter> 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<KeyParameter> 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<KeyParameter> 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<KeyParameter> 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<KeyParameter> 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<KeyParameter> 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<X509Certificate> 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<KeyParameter> 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<KeyParameter> 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<KeyParameter> 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<AuthenticatorSpec> 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<String> getUniqueAliases() { - final String[] rawAliases = mKeyStore.list("", mUid); - if (rawAliases == null) { - return new HashSet<String>(); - } - final Set<String> aliases = new HashSet<String>(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<String> 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<String> nonCaEntries = new HashSet<String>(); - - /* - * 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<KeyParameter> 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<String> getCriticalExtensionOIDs() { - return mDelegate.getCriticalExtensionOIDs(); - } - - @Override - public byte[] getExtensionValue(String oid) { - return mDelegate.getExtensionValue(oid); - } - - @Override - public Set<String> getNonCriticalExtensionOIDs() { - return mDelegate.getNonCriticalExtensionOIDs(); - } - - @Override - public boolean hasUnsupportedCriticalExtension() { - return mDelegate.hasUnsupportedCriticalExtension(); - } - - @Override - public void checkValidity() throws CertificateExpiredException, - CertificateNotYetValidException { - mDelegate.checkValidity(); - } - - @Override - public void checkValidity(Date date) throws CertificateExpiredException, - CertificateNotYetValidException { - mDelegate.checkValidity(date); - } - - @Override - public int getBasicConstraints() { - return mDelegate.getBasicConstraints(); - } - - @Override - public Principal getIssuerDN() { - return mDelegate.getIssuerDN(); - } - - @Override - public boolean[] getIssuerUniqueID() { - return mDelegate.getIssuerUniqueID(); - } - - @Override - public boolean[] getKeyUsage() { - return mDelegate.getKeyUsage(); - } - - @Override - public Date getNotAfter() { - return mDelegate.getNotAfter(); - } - - @Override - public Date getNotBefore() { - return mDelegate.getNotBefore(); - } - - @Override - public BigInteger getSerialNumber() { - return mDelegate.getSerialNumber(); - } - - @Override - public String getSigAlgName() { - return mDelegate.getSigAlgName(); - } - - @Override - public String getSigAlgOID() { - return mDelegate.getSigAlgOID(); - } - - @Override - public byte[] getSigAlgParams() { - return mDelegate.getSigAlgParams(); - } - - @Override - public byte[] getSignature() { - return mDelegate.getSignature(); - } - - @Override - public Principal getSubjectDN() { - return mDelegate.getSubjectDN(); - } - - @Override - public boolean[] getSubjectUniqueID() { - return mDelegate.getSubjectUniqueID(); - } - - @Override - public byte[] getTBSCertificate() throws CertificateEncodingException { - return mDelegate.getTBSCertificate(); - } - - @Override - public int getVersion() { - return mDelegate.getVersion(); - } - - @Override - public byte[] getEncoded() throws CertificateEncodingException { - return mDelegate.getEncoded(); - } - - @Override - public PublicKey getPublicKey() { - return mDelegate.getPublicKey(); - } - - @Override - public String toString() { - return mDelegate.toString(); - } - - @Override - public void verify(PublicKey key) - throws CertificateException, - NoSuchAlgorithmException, - InvalidKeyException, - NoSuchProviderException, - SignatureException { - mDelegate.verify(key); - } - - @Override - public void verify(PublicKey key, String sigProvider) - throws CertificateException, - NoSuchAlgorithmException, - InvalidKeyException, - NoSuchProviderException, - SignatureException { - mDelegate.verify(key, sigProvider); - } - - @Override - public List<String> getExtendedKeyUsage() throws CertificateParsingException { - return mDelegate.getExtendedKeyUsage(); - } - - @Override - public Collection<List<?>> getIssuerAlternativeNames() throws CertificateParsingException { - return mDelegate.getIssuerAlternativeNames(); - } - - @Override - public X500Principal getIssuerX500Principal() { - return mDelegate.getIssuerX500Principal(); - } - - @Override - public Collection<List<?>> getSubjectAlternativeNames() throws CertificateParsingException { - return mDelegate.getSubjectAlternativeNames(); - } - - @Override - public X500Principal getSubjectX500Principal() { - return mDelegate.getSubjectX500Principal(); - } -} diff --git a/keystore/java/android/security/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(); + } + } } |