diff options
author | Alex Klyubin <klyubin@google.com> | 2015-05-28 11:07:19 -0700 |
---|---|---|
committer | Alex Klyubin <klyubin@google.com> | 2015-05-29 09:53:05 -0700 |
commit | 7cbcfd4fc1e538bd391a20cdd00dd1494ace2d0e (patch) | |
tree | 252bee590e6f62dcb0f90ca122bf9f49ebf150c9 /keystore | |
parent | 80370952e9008ba3949d6e9bc3412e62a05e1ba3 (diff) | |
download | frameworks_base-7cbcfd4fc1e538bd391a20cdd00dd1494ace2d0e.zip frameworks_base-7cbcfd4fc1e538bd391a20cdd00dd1494ace2d0e.tar.gz frameworks_base-7cbcfd4fc1e538bd391a20cdd00dd1494ace2d0e.tar.bz2 |
Refactor Android Keystore CipherSpi base class.
This makes Android Keystore's CipherSpi base class suitable for
implementing AES and RSA ciphers. Previously, the class was heavily
biased towards only AES.
Bug: 18088752
Change-Id: I6bd1ca54165592d28482e56471dcfe0344337cf4
Diffstat (limited to 'keystore')
6 files changed, 840 insertions, 690 deletions
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java index 3774e36..8f195d0 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java @@ -58,17 +58,17 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider { // javax.crypto.Cipher putSymmetricCipherImpl("AES/ECB/NoPadding", - PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$ECB$NoPadding"); + PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$ECB$NoPadding"); putSymmetricCipherImpl("AES/ECB/PKCS7Padding", - PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$ECB$PKCS7Padding"); + PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$ECB$PKCS7Padding"); putSymmetricCipherImpl("AES/CBC/NoPadding", - PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$CBC$NoPadding"); + PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CBC$NoPadding"); putSymmetricCipherImpl("AES/CBC/PKCS7Padding", - PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$CBC$PKCS7Padding"); + PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CBC$PKCS7Padding"); putSymmetricCipherImpl("AES/CTR/NoPadding", - PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$CTR$NoPadding"); + PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CTR$NoPadding"); } private void putMacImpl(String algorithm, String implClass) { diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpi.java deleted file mode 100644 index 27df5e7..0000000 --- a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpi.java +++ /dev/null @@ -1,685 +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.keystore; - -import android.os.IBinder; -import android.security.KeyStore; -import android.security.KeyStoreException; -import android.security.keymaster.KeymasterArguments; -import android.security.keymaster.KeymasterDefs; -import android.security.keymaster.OperationResult; -import android.security.keystore.KeyProperties; - -import java.security.AlgorithmParameters; -import java.security.GeneralSecurityException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; -import java.security.ProviderException; -import java.security.SecureRandom; -import java.security.spec.AlgorithmParameterSpec; -import java.security.spec.InvalidParameterSpecException; -import java.util.Arrays; - -import javax.crypto.AEADBadTagException; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.CipherSpi; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.IvParameterSpec; - -/** - * Base class for {@link CipherSpi} providing Android KeyStore backed ciphers. - * - * @hide - */ -public abstract class AndroidKeyStoreCipherSpi extends CipherSpi - implements KeyStoreCryptoOperation { - - public abstract static class AES extends AndroidKeyStoreCipherSpi { - protected AES(int keymasterBlockMode, int keymasterPadding, boolean ivUsed) { - super(KeymasterDefs.KM_ALGORITHM_AES, - keymasterBlockMode, - keymasterPadding, - 16, - ivUsed); - } - - public abstract static class ECB extends AES { - protected ECB(int keymasterPadding) { - super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false); - } - - public static class NoPadding extends ECB { - public NoPadding() { - super(KeymasterDefs.KM_PAD_NONE); - } - } - - public static class PKCS7Padding extends ECB { - public PKCS7Padding() { - super(KeymasterDefs.KM_PAD_PKCS7); - } - } - } - - public abstract static class CBC extends AES { - protected CBC(int keymasterPadding) { - super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true); - } - - public static class NoPadding extends CBC { - public NoPadding() { - super(KeymasterDefs.KM_PAD_NONE); - } - } - - public static class PKCS7Padding extends CBC { - public PKCS7Padding() { - super(KeymasterDefs.KM_PAD_PKCS7); - } - } - } - - public abstract static class CTR extends AES { - protected CTR(int keymasterPadding) { - super(KeymasterDefs.KM_MODE_CTR, keymasterPadding, true); - } - - public static class NoPadding extends CTR { - public NoPadding() { - super(KeymasterDefs.KM_PAD_NONE); - } - } - } - } - - private final KeyStore mKeyStore; - private final int mKeymasterAlgorithm; - private final int mKeymasterBlockMode; - private final int mKeymasterPadding; - private final int mBlockSizeBytes; - - /** Whether this transformation requires an IV. */ - private final boolean mIvRequired; - - // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after - // doFinal finishes. - protected boolean mEncrypting; - private AndroidKeyStoreSecretKey mKey; - private SecureRandom mRng; - private boolean mFirstOperationInitiated; - private byte[] mIv; - /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */ - private boolean mIvHasBeenUsed; - - // Fields below must be reset after doFinal - private byte[] mAdditionalEntropyForBegin; - - /** - * Token referencing this operation inside keystore service. It is initialized by - * {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and one some - * error conditions in between. - */ - private IBinder mOperationToken; - private long mOperationHandle; - private KeyStoreCryptoOperationChunkedStreamer mMainDataStreamer; - - /** - * Encountered exception which could not be immediately thrown because it was encountered inside - * a method that does not throw checked exception. This exception will be thrown from - * {@code engineDoFinal}. Once such an exception is encountered, {@code engineUpdate} and - * {@code engineDoFinal} start ignoring input data. - */ - private Exception mCachedException; - - protected AndroidKeyStoreCipherSpi( - int keymasterAlgorithm, - int keymasterBlockMode, - int keymasterPadding, - int blockSizeBytes, - boolean ivUsed) { - mKeyStore = KeyStore.getInstance(); - mKeymasterAlgorithm = keymasterAlgorithm; - mKeymasterBlockMode = keymasterBlockMode; - mKeymasterPadding = keymasterPadding; - mBlockSizeBytes = blockSizeBytes; - mIvRequired = ivUsed; - } - - @Override - protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException { - resetAll(); - - boolean success = false; - try { - init(opmode, key, random); - initAlgorithmSpecificParameters(); - try { - ensureKeystoreOperationInitialized(); - } catch (InvalidAlgorithmParameterException e) { - throw new InvalidKeyException(e); - } - success = true; - } finally { - if (!success) { - resetAll(); - } - } - } - - @Override - protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) - throws InvalidKeyException, InvalidAlgorithmParameterException { - resetAll(); - - boolean success = false; - try { - init(opmode, key, random); - initAlgorithmSpecificParameters(params); - ensureKeystoreOperationInitialized(); - success = true; - } finally { - if (!success) { - resetAll(); - } - } - } - - @Override - protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, - SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { - resetAll(); - - boolean success = false; - try { - init(opmode, key, random); - initAlgorithmSpecificParameters(params); - ensureKeystoreOperationInitialized(); - success = true; - } finally { - if (!success) { - resetAll(); - } - } - } - - private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException { - if (!(key instanceof AndroidKeyStoreSecretKey)) { - throw new InvalidKeyException( - "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null")); - } - mKey = (AndroidKeyStoreSecretKey) key; - mRng = random; - mIv = null; - mFirstOperationInitiated = false; - - if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.DECRYPT_MODE)) { - throw new UnsupportedOperationException( - "Only ENCRYPT and DECRYPT modes supported. Mode: " + opmode); - } - mEncrypting = opmode == Cipher.ENCRYPT_MODE; - } - - private void resetAll() { - IBinder operationToken = mOperationToken; - if (operationToken != null) { - mOperationToken = null; - mKeyStore.abort(operationToken); - } - mEncrypting = false; - mKey = null; - mRng = null; - mFirstOperationInitiated = false; - mIv = null; - mIvHasBeenUsed = false; - mAdditionalEntropyForBegin = null; - mOperationToken = null; - mOperationHandle = 0; - mMainDataStreamer = null; - mCachedException = null; - } - - private void resetWhilePreservingInitState() { - IBinder operationToken = mOperationToken; - if (operationToken != null) { - mOperationToken = null; - mKeyStore.abort(operationToken); - } - mOperationHandle = 0; - mMainDataStreamer = null; - mAdditionalEntropyForBegin = null; - mCachedException = null; - } - - private void ensureKeystoreOperationInitialized() throws InvalidKeyException, - InvalidAlgorithmParameterException { - if (mMainDataStreamer != null) { - return; - } - if (mCachedException != null) { - return; - } - if (mKey == null) { - throw new IllegalStateException("Not initialized"); - } - if ((mEncrypting) && (mIvRequired) && (mIvHasBeenUsed)) { - // IV is being reused for encryption: this violates security best practices. - throw new IllegalStateException( - "IV has already been used. Reusing IV in encryption mode violates security best" - + " practices."); - } - - KeymasterArguments keymasterInputArgs = new KeymasterArguments(); - keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); - keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode); - keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding); - addAlgorithmSpecificParametersToBegin(keymasterInputArgs); - - KeymasterArguments keymasterOutputArgs = new KeymasterArguments(); - OperationResult opResult = mKeyStore.begin( - mKey.getAlias(), - mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT, - true, // permit aborting this operation if keystore runs out of resources - keymasterInputArgs, - mAdditionalEntropyForBegin, - keymasterOutputArgs); - mAdditionalEntropyForBegin = null; - if (opResult == null) { - throw new KeyStoreConnectException(); - } - - // Store operation token and handle regardless of the error code returned by KeyStore to - // ensure that the operation gets aborted immediately if the code below throws an exception. - mOperationToken = opResult.token; - mOperationHandle = opResult.operationHandle; - - // If necessary, throw an exception due to KeyStore operation having failed. - GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit( - mKeyStore, mKey, opResult.resultCode); - if (e != null) { - if (e instanceof InvalidKeyException) { - throw (InvalidKeyException) e; - } else if (e instanceof InvalidAlgorithmParameterException) { - throw (InvalidAlgorithmParameterException) e; - } else { - throw new ProviderException("Unexpected exception type", e); - } - } - - if (mOperationToken == null) { - throw new ProviderException("Keystore returned null operation token"); - } - if (mOperationHandle == 0) { - throw new ProviderException("Keystore returned invalid operation handle"); - } - - loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs); - mFirstOperationInitiated = true; - mIvHasBeenUsed = true; - mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer( - new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( - mKeyStore, opResult.token)); - } - - @Override - protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { - if (mCachedException != null) { - return null; - } - try { - ensureKeystoreOperationInitialized(); - } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { - mCachedException = e; - return null; - } - - if (inputLen == 0) { - return null; - } - - byte[] output; - try { - output = mMainDataStreamer.update(input, inputOffset, inputLen); - } catch (KeyStoreException e) { - mCachedException = e; - return null; - } - - if (output.length == 0) { - return null; - } - - return output; - } - - @Override - protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, - int outputOffset) throws ShortBufferException { - byte[] outputCopy = engineUpdate(input, inputOffset, inputLen); - if (outputCopy == null) { - return 0; - } - int outputAvailable = output.length - outputOffset; - if (outputCopy.length > outputAvailable) { - throw new ShortBufferException("Output buffer too short. Produced: " - + outputCopy.length + ", available: " + outputAvailable); - } - System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); - return outputCopy.length; - } - - @Override - protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) - throws IllegalBlockSizeException, BadPaddingException { - if (mCachedException != null) { - throw (IllegalBlockSizeException) - new IllegalBlockSizeException().initCause(mCachedException); - } - - try { - ensureKeystoreOperationInitialized(); - } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { - throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); - } - - byte[] output; - try { - output = mMainDataStreamer.doFinal(input, inputOffset, inputLen); - } catch (KeyStoreException e) { - switch (e.getErrorCode()) { - case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH: - throw new IllegalBlockSizeException(); - case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT: - throw new BadPaddingException(); - case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED: - throw new AEADBadTagException(); - default: - throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); - } - } - - resetWhilePreservingInitState(); - return output; - } - - @Override - protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, - int outputOffset) throws ShortBufferException, IllegalBlockSizeException, - BadPaddingException { - byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen); - if (outputCopy == null) { - return 0; - } - int outputAvailable = output.length - outputOffset; - if (outputCopy.length > outputAvailable) { - throw new ShortBufferException("Output buffer too short. Produced: " - + outputCopy.length + ", available: " + outputAvailable); - } - System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); - return outputCopy.length; - } - - @Override - protected int engineGetBlockSize() { - return mBlockSizeBytes; - } - - @Override - protected byte[] engineGetIV() { - return (mIv != null) ? mIv.clone() : null; - } - - @Override - protected int engineGetOutputSize(int inputLen) { - return inputLen + 3 * engineGetBlockSize(); - } - - @Override - protected void engineSetMode(String mode) throws NoSuchAlgorithmException { - // This should never be invoked because all algorithms registered with the AndroidKeyStore - // provide explicitly specify block mode. - throw new UnsupportedOperationException(); - } - - @Override - protected void engineSetPadding(String arg0) throws NoSuchPaddingException { - // This should never be invoked because all algorithms registered with the AndroidKeyStore - // provide explicitly specify padding mode. - throw new UnsupportedOperationException(); - } - - @Override - public void finalize() throws Throwable { - try { - IBinder operationToken = mOperationToken; - if (operationToken != null) { - mKeyStore.abort(operationToken); - } - } finally { - super.finalize(); - } - } - - @Override - public long getOperationHandle() { - return mOperationHandle; - } - - // The methods below may need to be overridden by subclasses that use algorithm-specific - // parameters. - - /** - * Returns algorithm-specific parameters used by this {@code CipherSpi} instance or {@code null} - * if no algorithm-specific parameters are used. - * - * <p>This implementation only handles the IV parameter. - */ - @Override - protected AlgorithmParameters engineGetParameters() { - if (!mIvRequired) { - return null; - } - if ((mIv != null) && (mIv.length > 0)) { - try { - AlgorithmParameters params = - AlgorithmParameters.getInstance(KeyProperties.KEY_ALGORITHM_AES); - params.init(new IvParameterSpec(mIv)); - return params; - } catch (NoSuchAlgorithmException e) { - throw new ProviderException("Failed to obtain AES AlgorithmParameters", e); - } catch (InvalidParameterSpecException e) { - throw new ProviderException( - "Failed to initialize AES AlgorithmParameters with an IV", e); - } - } - return null; - } - - /** - * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters - * may need to be stored to be reused after {@code doFinal}. - * - * <p>The default implementation only handles the IV parameters. - * - * @param params algorithm parameters. - * - * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be - * automatically configured and thus {@code Cipher.init} needs to be invoked with - * explicitly provided parameters. - */ - protected void initAlgorithmSpecificParameters(AlgorithmParameterSpec params) - throws InvalidAlgorithmParameterException { - if (!mIvRequired) { - if (params != null) { - throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); - } - return; - } - - // IV is used - if (params == null) { - if (!mEncrypting) { - // IV must be provided by the caller - throw new InvalidAlgorithmParameterException( - "IvParameterSpec must be provided when decrypting"); - } - return; - } - if (!(params instanceof IvParameterSpec)) { - throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported"); - } - mIv = ((IvParameterSpec) params).getIV(); - if (mIv == null) { - throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec"); - } - } - - /** - * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters - * may need to be stored to be reused after {@code doFinal}. - * - * <p>The default implementation only handles the IV parameters. - * - * @param params algorithm parameters. - * - * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be - * automatically configured and thus {@code Cipher.init} needs to be invoked with - * explicitly provided parameters. - */ - protected void initAlgorithmSpecificParameters(AlgorithmParameters params) - throws InvalidAlgorithmParameterException { - if (!mIvRequired) { - if (params != null) { - throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); - } - return; - } - - // IV is used - if (params == null) { - if (!mEncrypting) { - // IV must be provided by the caller - throw new InvalidAlgorithmParameterException("IV required when decrypting" - + ". Use IvParameterSpec or AlgorithmParameters to provide it."); - } - return; - } - - IvParameterSpec ivSpec; - try { - ivSpec = params.getParameterSpec(IvParameterSpec.class); - } catch (InvalidParameterSpecException e) { - if (!mEncrypting) { - // IV must be provided by the caller - throw new InvalidAlgorithmParameterException("IV required when decrypting" - + ", but not found in parameters: " + params, e); - } - mIv = null; - return; - } - mIv = ivSpec.getIV(); - if (mIv == null) { - throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters"); - } - } - - /** - * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters - * may need to be stored to be reused after {@code doFinal}. - * - * <p>The default implementation only handles the IV parameter. - * - * @throws InvalidKeyException if some/all of the parameters cannot be automatically configured - * and thus {@code Cipher.init} needs to be invoked with explicitly provided parameters. - */ - protected void initAlgorithmSpecificParameters() throws InvalidKeyException { - if (!mIvRequired) { - return; - } - - // IV is used - if (!mEncrypting) { - throw new InvalidKeyException("IV required when decrypting" - + ". Use IvParameterSpec or AlgorithmParameters to provide it."); - } - } - - /** - * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation. - * - * <p>The default implementation takes care of the IV. - * - * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific - * parameters. - */ - protected void addAlgorithmSpecificParametersToBegin(KeymasterArguments keymasterArgs) { - if (!mFirstOperationInitiated) { - // First begin operation -- see if we need to provide additional entropy for IV - // generation. - if (mIvRequired) { - // IV is needed - if ((mIv == null) && (mEncrypting)) { - // IV was not provided by the caller and thus will be generated by keymaster. - // Mix in some additional entropy from the provided SecureRandom. - mAdditionalEntropyForBegin = - KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( - mRng, mBlockSizeBytes); - } - } - } - - if ((mIvRequired) && (mIv != null)) { - keymasterArgs.addBlob(KeymasterDefs.KM_TAG_NONCE, mIv); - } - } - - /** - * Invoked by {@code engineInit} to obtain algorithm-specific parameters from the result of the - * Keymaster's {@code begin} operation. Some of these parameters may need to be reused after - * {@code doFinal} by {@link #addAlgorithmSpecificParametersToBegin(KeymasterArguments)}. - * - * <p>The default implementation only takes care of the IV. - * - * @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin} - * operation. - */ - protected void loadAlgorithmSpecificParametersFromBeginResult( - KeymasterArguments keymasterArgs) { - // NOTE: Keymaster doesn't always return an IV, even if it's used. - byte[] returnedIv = keymasterArgs.getBlob(KeymasterDefs.KM_TAG_NONCE, null); - if ((returnedIv != null) && (returnedIv.length == 0)) { - returnedIv = null; - } - - if (mIvRequired) { - if (mIv == null) { - mIv = returnedIv; - } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) { - throw new ProviderException("IV in use differs from provided IV"); - } - } else { - if (returnedIv != null) { - throw new ProviderException( - "IV in use despite IV not being used by this transformation"); - } - } - } -} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java new file mode 100644 index 0000000..4104dcc --- /dev/null +++ b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; + +import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.security.KeyStore; +import android.security.KeyStoreException; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keymaster.OperationResult; + +import java.nio.ByteBuffer; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.ProviderException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; + +import javax.crypto.AEADBadTagException; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.CipherSpi; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.ShortBufferException; + +/** + * Base class for {@link CipherSpi} implementations of Android KeyStore backed ciphers. + * + * @hide + */ +abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStoreCryptoOperation { + private final KeyStore mKeyStore; + + // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after + // doFinal finishes. + private boolean mEncrypting; + private AndroidKeyStoreKey mKey; + private SecureRandom mRng; + + /** + * Token referencing this operation inside keystore service. It is initialized by + * {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and on some error + * conditions in between. + */ + private IBinder mOperationToken; + private long mOperationHandle; + private KeyStoreCryptoOperationChunkedStreamer mMainDataStreamer; + + /** + * Encountered exception which could not be immediately thrown because it was encountered inside + * a method that does not throw checked exception. This exception will be thrown from + * {@code engineDoFinal}. Once such an exception is encountered, {@code engineUpdate} and + * {@code engineDoFinal} start ignoring input data. + */ + private Exception mCachedException; + + AndroidKeyStoreCipherSpiBase() { + mKeyStore = KeyStore.getInstance(); + } + + @Override + protected final void engineInit(int opmode, Key key, SecureRandom random) + throws InvalidKeyException { + resetAll(); + + boolean success = false; + try { + init(opmode, key, random); + initAlgorithmSpecificParameters(); + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidKeyException(e); + } + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + @Override + protected final void engineInit(int opmode, Key key, AlgorithmParameters params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + init(opmode, key, random); + initAlgorithmSpecificParameters(params); + ensureKeystoreOperationInitialized(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + @Override + protected final void engineInit(int opmode, Key key, AlgorithmParameterSpec params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + init(opmode, key, random); + initAlgorithmSpecificParameters(params); + ensureKeystoreOperationInitialized(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.DECRYPT_MODE)) { + throw new UnsupportedOperationException( + "Only ENCRYPT and DECRYPT modes supported. Mode: " + opmode); + } + mEncrypting = opmode == Cipher.ENCRYPT_MODE; + initKey(opmode, key); + if (mKey == null) { + throw new ProviderException("initKey did not initialize the key"); + } + mRng = random; + } + + /** + * Resets this cipher to its pristine pre-init state. This must be equivalent to obtaining a new + * cipher instance. + * + * <p>Subclasses storing additional state should override this method, reset the additional + * state, and then chain to superclass. + */ + @CallSuper + protected void resetAll() { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mOperationToken = null; + mKeyStore.abort(operationToken); + } + mEncrypting = false; + mKey = null; + mRng = null; + mOperationToken = null; + mOperationHandle = 0; + mMainDataStreamer = null; + mCachedException = null; + } + + /** + * Resets this cipher while preserving the initialized state. This must be equivalent to + * rolling back the cipher's state to just after the most recent {@code engineInit} completed + * successfully. + * + * <p>Subclasses storing additional post-init state should override this method, reset the + * additional state, and then chain to superclass. + */ + @CallSuper + protected void resetWhilePreservingInitState() { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mOperationToken = null; + mKeyStore.abort(operationToken); + } + mOperationHandle = 0; + mMainDataStreamer = null; + mCachedException = null; + } + + private void ensureKeystoreOperationInitialized() throws InvalidKeyException, + InvalidAlgorithmParameterException { + if (mMainDataStreamer != null) { + return; + } + if (mCachedException != null) { + return; + } + if (mKey == null) { + throw new IllegalStateException("Not initialized"); + } + + KeymasterArguments keymasterInputArgs = new KeymasterArguments(); + addAlgorithmSpecificParametersToBegin(keymasterInputArgs); + byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, getAdditionalEntropyAmountForBegin()); + + KeymasterArguments keymasterOutputArgs = new KeymasterArguments(); + OperationResult opResult = mKeyStore.begin( + mKey.getAlias(), + mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT, + true, // permit aborting this operation if keystore runs out of resources + keymasterInputArgs, + additionalEntropy, + keymasterOutputArgs); + if (opResult == null) { + throw new KeyStoreConnectException(); + } + + // Store operation token and handle regardless of the error code returned by KeyStore to + // ensure that the operation gets aborted immediately if the code below throws an exception. + mOperationToken = opResult.token; + mOperationHandle = opResult.operationHandle; + + // If necessary, throw an exception due to KeyStore operation having failed. + GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit( + mKeyStore, mKey, opResult.resultCode); + if (e != null) { + if (e instanceof InvalidKeyException) { + throw (InvalidKeyException) e; + } else if (e instanceof InvalidAlgorithmParameterException) { + throw (InvalidAlgorithmParameterException) e; + } else { + throw new ProviderException("Unexpected exception type", e); + } + } + + if (mOperationToken == null) { + throw new ProviderException("Keystore returned null operation token"); + } + if (mOperationHandle == 0) { + throw new ProviderException("Keystore returned invalid operation handle"); + } + + loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs); + mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer( + new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( + mKeyStore, opResult.token)); + } + + @Override + protected final byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { + if (mCachedException != null) { + return null; + } + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + mCachedException = e; + return null; + } + + if (inputLen == 0) { + return null; + } + + byte[] output; + try { + output = mMainDataStreamer.update(input, inputOffset, inputLen); + } catch (KeyStoreException e) { + mCachedException = e; + return null; + } + + if (output.length == 0) { + return null; + } + + return output; + } + + @Override + protected final int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, + int outputOffset) throws ShortBufferException { + byte[] outputCopy = engineUpdate(input, inputOffset, inputLen); + if (outputCopy == null) { + return 0; + } + int outputAvailable = output.length - outputOffset; + if (outputCopy.length > outputAvailable) { + throw new ShortBufferException("Output buffer too short. Produced: " + + outputCopy.length + ", available: " + outputAvailable); + } + System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); + return outputCopy.length; + } + + @Override + protected final int engineUpdate(ByteBuffer input, ByteBuffer output) + throws ShortBufferException { + return super.engineUpdate(input, output); + } + + @Override + protected final void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) { + super.engineUpdateAAD(input, inputOffset, inputLen); + } + + @Override + protected final void engineUpdateAAD(ByteBuffer src) { + super.engineUpdateAAD(src); + } + + @Override + protected final byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) + throws IllegalBlockSizeException, BadPaddingException { + if (mCachedException != null) { + throw (IllegalBlockSizeException) + new IllegalBlockSizeException().initCause(mCachedException); + } + + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); + } + + byte[] output; + try { + output = mMainDataStreamer.doFinal(input, inputOffset, inputLen); + } catch (KeyStoreException e) { + switch (e.getErrorCode()) { + case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH: + throw new IllegalBlockSizeException(); + case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT: + throw new BadPaddingException(); + case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED: + throw new AEADBadTagException(); + default: + throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); + } + } + + resetWhilePreservingInitState(); + return output; + } + + @Override + protected final int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, + int outputOffset) throws ShortBufferException, IllegalBlockSizeException, + BadPaddingException { + byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen); + if (outputCopy == null) { + return 0; + } + int outputAvailable = output.length - outputOffset; + if (outputCopy.length > outputAvailable) { + throw new ShortBufferException("Output buffer too short. Produced: " + + outputCopy.length + ", available: " + outputAvailable); + } + System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); + return outputCopy.length; + } + + @Override + protected final int engineDoFinal(ByteBuffer input, ByteBuffer output) + throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + return super.engineDoFinal(input, output); + } + + @Override + protected final byte[] engineWrap(Key key) + throws IllegalBlockSizeException, InvalidKeyException { + return super.engineWrap(key); + } + + @Override + protected final Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, + int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException { + return super.engineUnwrap(wrappedKey, wrappedKeyAlgorithm, wrappedKeyType); + } + + @Override + protected final void engineSetMode(String mode) throws NoSuchAlgorithmException { + // This should never be invoked because all algorithms registered with the AndroidKeyStore + // provide explicitly specify block mode. + throw new UnsupportedOperationException(); + } + + @Override + protected final void engineSetPadding(String arg0) throws NoSuchPaddingException { + // This should never be invoked because all algorithms registered with the AndroidKeyStore + // provide explicitly specify padding mode. + throw new UnsupportedOperationException(); + } + + @Override + protected final int engineGetKeySize(Key key) throws InvalidKeyException { + throw new UnsupportedOperationException(); + } + + @CallSuper + @Override + public void finalize() throws Throwable { + try { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mKeyStore.abort(operationToken); + } + } finally { + super.finalize(); + } + } + + @Override + public final long getOperationHandle() { + return mOperationHandle; + } + + protected final void setKey(@NonNull AndroidKeyStoreKey key) { + mKey = key; + } + + /** + * Returns {@code true} if this cipher is initialized for encryption, {@code false} if this + * cipher is initialized for decryption. + */ + protected final boolean isEncrypting() { + return mEncrypting; + } + + @NonNull + protected final KeyStore getKeyStore() { + return mKeyStore; + } + + // The methods below need to be implemented by subclasses. + + /** + * Initializes this cipher with the provided key. + * + * @throws InvalidKeyException if the {@code key} is not suitable for this cipher in the + * specified {@code opmode}. + * + * @see #setKey(AndroidKeyStoreKey) + */ + protected abstract void initKey(int opmode, @Nullable Key key) throws InvalidKeyException; + + /** + * Returns algorithm-specific parameters used by this cipher or {@code null} if no + * algorithm-specific parameters are used. + */ + @Nullable + @Override + protected abstract AlgorithmParameters engineGetParameters(); + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters when no additional + * initialization parameters were provided. + * + * @throws InvalidKeyException if this cipher cannot be configured based purely on the provided + * key and needs additional parameters to be provided to {@code Cipher.init}. + */ + protected abstract void initAlgorithmSpecificParameters() throws InvalidKeyException; + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional + * parameters were provided. + * + * @param params additional algorithm parameters or {@code null} if not specified. + * + * @throws InvalidAlgorithmParameterException if there is insufficient information to configure + * this cipher or if the provided parameters are not suitable for this cipher. + */ + protected abstract void initAlgorithmSpecificParameters( + @Nullable AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException; + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional + * parameters were provided. + * + * @param params additional algorithm parameters or {@code null} if not specified. + * + * @throws InvalidAlgorithmParameterException if there is insufficient information to configure + * this cipher or if the provided parameters are not suitable for this cipher. + */ + protected abstract void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params) + throws InvalidAlgorithmParameterException; + + /** + * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's + * {@code begin} operation. + * + * <p>For decryption, this should be {@code 0} because decryption should not be consuming any + * entropy. For encryption, this value should match (or exceed) the amount of Shannon entropy of + * the ciphertext produced by this cipher assuming the key, the plaintext, and all explicitly + * provided parameters to {@code Cipher.init} are known. For example, for AES CBC encryption + * with an explicitly provided IV this should be {@code 0}, whereas for the case where IV is + * generated by the KeyStore's {@code begin} operation this should be {@code 16}. For RSA with + * OAEP this should be the size of the OAEP hash output. For RSA with PKCS#1 padding this should + * be the size of the padding string or could be raised (for simplicity) to the size of the + * modulus. + */ + protected abstract int getAdditionalEntropyAmountForBegin(); + + /** + * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation. + * + * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific + * parameters. + */ + protected abstract void addAlgorithmSpecificParametersToBegin( + @NonNull KeymasterArguments keymasterArgs); + + /** + * Invoked to obtain algorithm-specific parameters from the result of the KeyStore's + * {@code begin} operation. + * + * <p>Some parameters, such as IV, are not required to be provided to {@code Cipher.init}. Such + * parameters, if not provided, must be generated by KeyStore and returned to the user of + * {@code Cipher} and potentially reused after {@code doFinal}. + * + * @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin} + * operation. + */ + protected abstract void loadAlgorithmSpecificParametersFromBeginResult( + @NonNull KeymasterArguments keymasterArgs); +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java new file mode 100644 index 0000000..47cd1d1 --- /dev/null +++ b/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java @@ -0,0 +1,292 @@ +package android.security.keystore; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; + +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.ProviderException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.util.Arrays; + +import javax.crypto.CipherSpi; +import javax.crypto.spec.IvParameterSpec; + +/** + * Base class for Android Keystore unauthenticated AES {@link CipherSpi} implementations. + * + * @hide + */ +class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase { + + abstract static class ECB extends AndroidKeyStoreUnauthenticatedAESCipherSpi { + protected ECB(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false); + } + + public static class NoPadding extends ECB { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + } + + public static class PKCS7Padding extends ECB { + public PKCS7Padding() { + super(KeymasterDefs.KM_PAD_PKCS7); + } + } + } + + abstract static class CBC extends AndroidKeyStoreUnauthenticatedAESCipherSpi { + protected CBC(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true); + } + + public static class NoPadding extends CBC { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + } + + public static class PKCS7Padding extends CBC { + public PKCS7Padding() { + super(KeymasterDefs.KM_PAD_PKCS7); + } + } + } + + abstract static class CTR extends AndroidKeyStoreUnauthenticatedAESCipherSpi { + protected CTR(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_CTR, keymasterPadding, true); + } + + public static class NoPadding extends CTR { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + } + } + + private static final int BLOCK_SIZE_BYTES = 16; + + private final int mKeymasterBlockMode; + private final int mKeymasterPadding; + /** Whether this transformation requires an IV. */ + private final boolean mIvRequired; + + private byte[] mIv; + + /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */ + private boolean mIvHasBeenUsed; + + AndroidKeyStoreUnauthenticatedAESCipherSpi( + int keymasterBlockMode, + int keymasterPadding, + boolean ivRequired) { + mKeymasterBlockMode = keymasterBlockMode; + mKeymasterPadding = keymasterPadding; + mIvRequired = ivRequired; + } + + @Override + protected final void resetAll() { + mIv = null; + mIvHasBeenUsed = false; + super.resetAll(); + } + + @Override + protected final void resetWhilePreservingInitState() { + super.resetWhilePreservingInitState(); + } + + @Override + protected final void initKey(int opmode, Key key) throws InvalidKeyException { + if (!(key instanceof AndroidKeyStoreSecretKey)) { + throw new InvalidKeyException( + "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null")); + } + if (!KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(key.getAlgorithm())) { + throw new InvalidKeyException( + "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " + + KeyProperties.KEY_ALGORITHM_AES + " supported"); + } + setKey((AndroidKeyStoreSecretKey) key); + } + + @Override + protected final void initAlgorithmSpecificParameters() throws InvalidKeyException { + if (!mIvRequired) { + return; + } + + // IV is used + if (!isEncrypting()) { + throw new InvalidKeyException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + } + + @Override + protected final void initAlgorithmSpecificParameters(AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + if (!mIvRequired) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException( + "IvParameterSpec must be provided when decrypting"); + } + return; + } + if (!(params instanceof IvParameterSpec)) { + throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported"); + } + mIv = ((IvParameterSpec) params).getIV(); + if (mIv == null) { + throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec"); + } + } + + @Override + protected final void initAlgorithmSpecificParameters(AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + if (!mIvRequired) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + return; + } + + IvParameterSpec ivSpec; + try { + ivSpec = params.getParameterSpec(IvParameterSpec.class); + } catch (InvalidParameterSpecException e) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ", but not found in parameters: " + params, e); + } + mIv = null; + return; + } + mIv = ivSpec.getIV(); + if (mIv == null) { + throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters"); + } + } + + @Override + protected final int getAdditionalEntropyAmountForBegin() { + if ((mIvRequired) && (mIv == null) && (isEncrypting())) { + // IV will need to be generated + return BLOCK_SIZE_BYTES; + } + + return 0; + } + + @Override + protected final void addAlgorithmSpecificParametersToBegin( + @NonNull KeymasterArguments keymasterArgs) { + if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) { + // IV is being reused for encryption: this violates security best practices. + throw new IllegalStateException( + "IV has already been used. Reusing IV in encryption mode violates security best" + + " practices."); + } + + keymasterArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES); + keymasterArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode); + keymasterArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding); + if ((mIvRequired) && (mIv != null)) { + keymasterArgs.addBlob(KeymasterDefs.KM_TAG_NONCE, mIv); + } + } + + @Override + protected final void loadAlgorithmSpecificParametersFromBeginResult( + @NonNull KeymasterArguments keymasterArgs) { + mIvHasBeenUsed = true; + + // NOTE: Keymaster doesn't always return an IV, even if it's used. + byte[] returnedIv = keymasterArgs.getBlob(KeymasterDefs.KM_TAG_NONCE, null); + if ((returnedIv != null) && (returnedIv.length == 0)) { + returnedIv = null; + } + + if (mIvRequired) { + if (mIv == null) { + mIv = returnedIv; + } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) { + throw new ProviderException("IV in use differs from provided IV"); + } + } else { + if (returnedIv != null) { + throw new ProviderException( + "IV in use despite IV not being used by this transformation"); + } + } + } + + @Override + protected final int engineGetBlockSize() { + return BLOCK_SIZE_BYTES; + } + + @Override + protected final int engineGetOutputSize(int inputLen) { + return inputLen + 3 * BLOCK_SIZE_BYTES; + } + + @Override + protected final byte[] engineGetIV() { + return ArrayUtils.cloneIfNotEmpty(mIv); + } + + @Nullable + @Override + protected final AlgorithmParameters engineGetParameters() { + if (!mIvRequired) { + return null; + } + if ((mIv != null) && (mIv.length > 0)) { + try { + AlgorithmParameters params = AlgorithmParameters.getInstance("AES"); + params.init(new IvParameterSpec(mIv)); + return params; + } catch (NoSuchAlgorithmException e) { + throw new ProviderException( + "Failed to obtain AES AlgorithmParameters", e); + } catch (InvalidParameterSpecException e) { + throw new ProviderException( + "Failed to initialize AES AlgorithmParameters with an IV", + e); + } + } + return null; + } +} diff --git a/keystore/java/android/security/keystore/ArrayUtils.java b/keystore/java/android/security/keystore/ArrayUtils.java index 81be384..26172d2 100644 --- a/keystore/java/android/security/keystore/ArrayUtils.java +++ b/keystore/java/android/security/keystore/ArrayUtils.java @@ -32,6 +32,10 @@ public abstract class ArrayUtils { return ((array != null) && (array.length > 0)) ? array.clone() : array; } + public static byte[] cloneIfNotEmpty(byte[] array) { + return ((array != null) && (array.length > 0)) ? array.clone() : array; + } + public static byte[] concat(byte[] arr1, byte[] arr2) { return concat(arr1, 0, (arr1 != null) ? arr1.length : 0, arr2, 0, (arr2 != null) ? arr2.length : 0); diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java index 6ae76f1..27c1b2a 100644 --- a/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java +++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java @@ -19,6 +19,8 @@ package android.security.keystore; import android.security.KeyStore; import android.security.keymaster.KeymasterDefs; +import libcore.util.EmptyArray; + import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; @@ -94,6 +96,9 @@ abstract class KeyStoreCryptoOperationUtils { * RNG. */ static byte[] getRandomBytesToMixIntoKeystoreRng(SecureRandom rng, int sizeBytes) { + if (sizeBytes <= 0) { + return EmptyArray.BYTE; + } if (rng == null) { rng = getRng(); } |