am 614b39f3: Merge "Refactor Android Keystore CipherSpi base class." into mnc-dev
* commit '614b39f3de7747e9e1cd00d8985ec6fa9b356217': Refactor Android Keystore CipherSpi base class.
This commit is contained in:
@@ -77,17 +77,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) {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user