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 libcore.util.EmptyArray;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.ProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.AEADBadTagException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherSpi;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;
abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStoreCryptoOperation {
private final KeyStore mKeyStore;
private boolean mEncrypting;
private int mKeymasterPurposeOverride = -1;
private AndroidKeyStoreKey mKey;
private SecureRandom mRng;
private IBinder mOperationToken;
private long mOperationHandle;
private KeyStoreCryptoOperationStreamer mMainDataStreamer;
private KeyStoreCryptoOperationStreamer mAdditionalAuthenticationDataStreamer;
private boolean mAdditionalAuthenticationDataStreamerClosed;
private Exception mCachedException;
AndroidKeyStoreCipherSpiBase() {
mKeyStore = KeyStore.getInstance();
}
@Override
protected final void engineInit(int opmode, Key key, SecureRandom random)
throws InvalidKeyException {
resetAll();
boolean success = false;
try {
init(opmode, key, random);
initAlgorithmSpecificParameters();
try {
ensureKeystoreOperationInitialized();
} catch (InvalidAlgorithmParameterException e) {
throw new InvalidKeyException(e);
}
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
@Override
protected final void engineInit(int opmode, Key key, AlgorithmParameters params,
SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
resetAll();
boolean success = false;
try {
init(opmode, key, random);
initAlgorithmSpecificParameters(params);
ensureKeystoreOperationInitialized();
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
@Override
protected final void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
resetAll();
boolean success = false;
try {
init(opmode, key, random);
initAlgorithmSpecificParameters(params);
ensureKeystoreOperationInitialized();
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
switch (opmode) {
case Cipher.ENCRYPT_MODE:
case Cipher.WRAP_MODE:
mEncrypting = true;
break;
case Cipher.DECRYPT_MODE:
case Cipher.UNWRAP_MODE:
mEncrypting = false;
break;
default:
throw new InvalidParameterException("Unsupported opmode: " + opmode);
}
initKey(opmode, key);
if (mKey == null) {
throw new ProviderException("initKey did not initialize the key");
}
mRng = random;
}
@CallSuper
protected void resetAll() {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mKeyStore.abort(operationToken);
}
mEncrypting = false;
mKeymasterPurposeOverride = -1;
mKey = null;
mRng = null;
mOperationToken = null;
mOperationHandle = 0;
mMainDataStreamer = null;
mAdditionalAuthenticationDataStreamer = null;
mAdditionalAuthenticationDataStreamerClosed = false;
mCachedException = null;
}
@CallSuper
protected void resetWhilePreservingInitState() {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mKeyStore.abort(operationToken);
}
mOperationToken = null;
mOperationHandle = 0;
mMainDataStreamer = null;
mAdditionalAuthenticationDataStreamer = null;
mAdditionalAuthenticationDataStreamerClosed = false;
mCachedException = null;
}
private void ensureKeystoreOperationInitialized() throws InvalidKeyException,
InvalidAlgorithmParameterException {
if (mMainDataStreamer != null) {
return;
}
if (mCachedException != null) {
return;
}
if (mKey == null) {
throw new IllegalStateException("Not initialized");
}
KeymasterArguments keymasterInputArgs = new KeymasterArguments();
addAlgorithmSpecificParametersToBegin(keymasterInputArgs);
byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, getAdditionalEntropyAmountForBegin());
int purpose;
if (mKeymasterPurposeOverride != -1) {
purpose = mKeymasterPurposeOverride;
} else {
purpose = mEncrypting
? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT;
}
OperationResult opResult = mKeyStore.begin(
mKey.getAlias(),
purpose,
true,
keymasterInputArgs,
additionalEntropy,
mKey.getUid());
if (opResult == null) {
throw new KeyStoreConnectException();
}
mOperationToken = opResult.token;
mOperationHandle = opResult.operationHandle;
GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit(
mKeyStore, mKey, opResult.resultCode);
if (e != null) {
if (e instanceof InvalidKeyException) {
throw (InvalidKeyException) e;
} else if (e instanceof InvalidAlgorithmParameterException) {
throw (InvalidAlgorithmParameterException) e;
} else {
throw new ProviderException("Unexpected exception type", e);
}
}
if (mOperationToken == null) {
throw new ProviderException("Keystore returned null operation token");
}
if (mOperationHandle == 0) {
throw new ProviderException("Keystore returned invalid operation handle");
}
loadAlgorithmSpecificParametersFromBeginResult(opResult.outParams);
mMainDataStreamer = createMainDataStreamer(mKeyStore, opResult.token);
mAdditionalAuthenticationDataStreamer =
createAdditionalAuthenticationDataStreamer(mKeyStore, opResult.token);
mAdditionalAuthenticationDataStreamerClosed = false;
}
@NonNull
protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
KeyStore keyStore, IBinder operationToken) {
return new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
keyStore, operationToken));
}
@Nullable
protected KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer(
@SuppressWarnings("unused") KeyStore keyStore,
@SuppressWarnings("unused") IBinder operationToken) {
return null;
}
@Override
protected final byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
if (mCachedException != null) {
return null;
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
mCachedException = e;
return null;
}
if (inputLen == 0) {
return null;
}
byte[] output;
try {
flushAAD();
output = mMainDataStreamer.update(input, inputOffset, inputLen);
} catch (KeyStoreException e) {
mCachedException = e;
return null;
}
if (output.length == 0) {
return null;
}
return output;
}
private void flushAAD() throws KeyStoreException {
if ((mAdditionalAuthenticationDataStreamer != null)
&& (!mAdditionalAuthenticationDataStreamerClosed)) {
byte[] output;
try {
output = mAdditionalAuthenticationDataStreamer.doFinal(
EmptyArray.BYTE, 0, 0,
null,
null
);
} finally {
mAdditionalAuthenticationDataStreamerClosed = true;
}
if ((output != null) && (output.length > 0)) {
throw new ProviderException(
"AAD update unexpectedly returned data: " + output.length + " bytes");
}
}
}
@Override
protected final int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException {
byte[] outputCopy = engineUpdate(input, inputOffset, inputLen);
if (outputCopy == null) {
return 0;
}
int outputAvailable = output.length - outputOffset;
if (outputCopy.length > outputAvailable) {
throw new ShortBufferException("Output buffer too short. Produced: "
+ outputCopy.length + ", available: " + outputAvailable);
}
System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
return outputCopy.length;
}
@Override
protected final int engineUpdate(ByteBuffer input, ByteBuffer output)
throws ShortBufferException {
if (input == null) {
throw new NullPointerException("input == null");
}
if (output == null) {
throw new NullPointerException("output == null");
}
int inputSize = input.remaining();
byte[] outputArray;
if (input.hasArray()) {
outputArray =
engineUpdate(
input.array(), input.arrayOffset() + input.position(), inputSize);
input.position(input.position() + inputSize);
} else {
byte[] inputArray = new byte[inputSize];
input.get(inputArray);
outputArray = engineUpdate(inputArray, 0, inputSize);
}
int outputSize = (outputArray != null) ? outputArray.length : 0;
if (outputSize > 0) {
int outputBufferAvailable = output.remaining();
try {
output.put(outputArray);
} catch (BufferOverflowException e) {
throw new ShortBufferException(
"Output buffer too small. Produced: " + outputSize + ", available: "
+ outputBufferAvailable);
}
}
return outputSize;
}
@Override
protected final void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) {
if (mCachedException != null) {
return;
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
mCachedException = e;
return;
}
if (mAdditionalAuthenticationDataStreamerClosed) {
throw new IllegalStateException(
"AAD can only be provided before Cipher.update is invoked");
}
if (mAdditionalAuthenticationDataStreamer == null) {
throw new IllegalStateException("This cipher does not support AAD");
}
byte[] output;
try {
output = mAdditionalAuthenticationDataStreamer.update(input, inputOffset, inputLen);
} catch (KeyStoreException e) {
mCachedException = e;
return;
}
if ((output != null) && (output.length > 0)) {
throw new ProviderException("AAD update unexpectedly produced output: "
+ output.length + " bytes");
}
}
@Override
protected final void engineUpdateAAD(ByteBuffer src) {
if (src == null) {
throw new IllegalArgumentException("src == null");
}
if (!src.hasRemaining()) {
return;
}
byte[] input;
int inputOffset;
int inputLen;
if (src.hasArray()) {
input = src.array();
inputOffset = src.arrayOffset() + src.position();
inputLen = src.remaining();
src.position(src.limit());
} else {
input = new byte[src.remaining()];
inputOffset = 0;
inputLen = input.length;
src.get(input);
}
engineUpdateAAD(input, inputOffset, inputLen);
}
@Override
protected final byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
throws IllegalBlockSizeException, BadPaddingException {
if (mCachedException != null) {
throw (IllegalBlockSizeException)
new IllegalBlockSizeException().initCause(mCachedException);
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
}
byte[] output;
try {
flushAAD();
byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, getAdditionalEntropyAmountForFinish());
output = mMainDataStreamer.doFinal(
input, inputOffset, inputLen,
null,
additionalEntropy);
} catch (KeyStoreException e) {
switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH:
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT:
throw (BadPaddingException) new BadPaddingException().initCause(e);
case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
throw (AEADBadTagException) new AEADBadTagException().initCause(e);
default:
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
}
}
resetWhilePreservingInitState();
return output;
}
@Override
protected final int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
BadPaddingException {
byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen);
if (outputCopy == null) {
return 0;
}
int outputAvailable = output.length - outputOffset;
if (outputCopy.length > outputAvailable) {
throw new ShortBufferException("Output buffer too short. Produced: "
+ outputCopy.length + ", available: " + outputAvailable);
}
System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
return outputCopy.length;
}
@Override
protected final int engineDoFinal(ByteBuffer input, ByteBuffer output)
throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
if (input == null) {
throw new NullPointerException("input == null");
}
if (output == null) {
throw new NullPointerException("output == null");
}
int inputSize = input.remaining();
byte[] outputArray;
if (input.hasArray()) {
outputArray =
engineDoFinal(
input.array(), input.arrayOffset() + input.position(), inputSize);
input.position(input.position() + inputSize);
} else {
byte[] inputArray = new byte[inputSize];
input.get(inputArray);
outputArray = engineDoFinal(inputArray, 0, inputSize);
}
int outputSize = (outputArray != null) ? outputArray.length : 0;
if (outputSize > 0) {
int outputBufferAvailable = output.remaining();
try {
output.put(outputArray);
} catch (BufferOverflowException e) {
throw new ShortBufferException(
"Output buffer too small. Produced: " + outputSize + ", available: "
+ outputBufferAvailable);
}
}
return outputSize;
}
@Override
protected final byte[] engineWrap(Key key)
throws IllegalBlockSizeException, InvalidKeyException {
if (mKey == null) {
throw new IllegalStateException("Not initilized");
}
if (!isEncrypting()) {
throw new IllegalStateException(
"Cipher must be initialized in Cipher.WRAP_MODE to wrap keys");
}
if (key == null) {
throw new NullPointerException("key == null");
}
byte[] encoded = null;
if (key instanceof SecretKey) {
if ("RAW".equalsIgnoreCase(key.getFormat())) {
encoded = key.getEncoded();
}
if (encoded == null) {
try {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(key.getAlgorithm());
SecretKeySpec spec =
(SecretKeySpec) keyFactory.getKeySpec(
(SecretKey) key, SecretKeySpec.class);
encoded = spec.getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to wrap key because it does not export its key material",
e);
}
}
} else if (key instanceof PrivateKey) {
if ("PKCS8".equalsIgnoreCase(key.getFormat())) {
encoded = key.getEncoded();
}
if (encoded == null) {
try {
KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm());
PKCS8EncodedKeySpec spec =
keyFactory.getKeySpec(key, PKCS8EncodedKeySpec.class);
encoded = spec.getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to wrap key because it does not export its key material",
e);
}
}
} else if (key instanceof PublicKey) {
if ("X.509".equalsIgnoreCase(key.getFormat())) {
encoded = key.getEncoded();
}
if (encoded == null) {
try {
KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm());
X509EncodedKeySpec spec =
keyFactory.getKeySpec(key, X509EncodedKeySpec.class);
encoded = spec.getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to wrap key because it does not export its key material",
e);
}
}
} else {
throw new InvalidKeyException("Unsupported key type: " + key.getClass().getName());
}
if (encoded == null) {
throw new InvalidKeyException(
"Failed to wrap key because it does not export its key material");
}
try {
return engineDoFinal(encoded, 0, encoded.length);
} catch (BadPaddingException e) {
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
}
}
@Override
protected final Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm,
int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException {
if (mKey == null) {
throw new IllegalStateException("Not initilized");
}
if (isEncrypting()) {
throw new IllegalStateException(
"Cipher must be initialized in Cipher.WRAP_MODE to wrap keys");
}
if (wrappedKey == null) {
throw new NullPointerException("wrappedKey == null");
}
byte[] encoded;
try {
encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length);
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new InvalidKeyException("Failed to unwrap key", e);
}
switch (wrappedKeyType) {
case Cipher.SECRET_KEY:
{
return new SecretKeySpec(encoded, wrappedKeyAlgorithm);
}
case Cipher.PRIVATE_KEY:
{
KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
try {
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to create private key from its PKCS#8 encoded form", e);
}
}
case Cipher.PUBLIC_KEY:
{
KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
try {
return keyFactory.generatePublic(new X509EncodedKeySpec(encoded));
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to create public key from its X.509 encoded form", e);
}
}
default:
throw new InvalidParameterException(
"Unsupported wrappedKeyType: " + wrappedKeyType);
}
}
@Override
protected final void engineSetMode(String mode) throws NoSuchAlgorithmException {
throw new UnsupportedOperationException();
}
@Override
protected final void engineSetPadding(String arg0) throws NoSuchPaddingException {
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;
}
protected final void setKeymasterPurposeOverride(int keymasterPurpose) {
mKeymasterPurposeOverride = keymasterPurpose;
}
protected final int getKeymasterPurposeOverride() {
return mKeymasterPurposeOverride;
}
protected final boolean isEncrypting() {
return mEncrypting;
}
@NonNull
protected final KeyStore getKeyStore() {
return mKeyStore;
}
protected final long getConsumedInputSizeBytes() {
if (mMainDataStreamer == null) {
throw new IllegalStateException("Not initialized");
}
return mMainDataStreamer.getConsumedInputSizeBytes();
}
protected final long getProducedOutputSizeBytes() {
if (mMainDataStreamer == null) {
throw new IllegalStateException("Not initialized");
}
return mMainDataStreamer.getProducedOutputSizeBytes();
}
static String opmodeToString(int opmode) {
switch (opmode) {
case Cipher.ENCRYPT_MODE:
return "ENCRYPT_MODE";
case Cipher.DECRYPT_MODE:
return "DECRYPT_MODE";
case Cipher.WRAP_MODE:
return "WRAP_MODE";
case Cipher.UNWRAP_MODE:
return "UNWRAP_MODE";
default:
return String.valueOf(opmode);
}
}
protected abstract void initKey(int opmode, @Nullable Key key) throws InvalidKeyException;
@Nullable
@Override
protected abstract AlgorithmParameters engineGetParameters();
protected abstract void initAlgorithmSpecificParameters() throws InvalidKeyException;
protected abstract void initAlgorithmSpecificParameters(
@Nullable AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException;
protected abstract void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
throws InvalidAlgorithmParameterException;
protected abstract int getAdditionalEntropyAmountForBegin();
protected abstract int getAdditionalEntropyAmountForFinish();
protected abstract void addAlgorithmSpecificParametersToBegin(
@NonNull KeymasterArguments keymasterArgs);
protected abstract void loadAlgorithmSpecificParametersFromBeginResult(
@NonNull KeymasterArguments keymasterArgs);
}