blob: ccc3153749fb8c0c830406d74bc1fad67e58a1b0 [file] [log] [blame]
/*
* 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 libcore.util.EmptyArray;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.ProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.AEADBadTagException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherSpi;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;
/**
* Base class for {@link CipherSpi} implementations of Android KeyStore backed ciphers.
*
* @hide
*/
abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStoreCryptoOperation {
private final KeyStore mKeyStore;
// Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after
// doFinal finishes.
private boolean mEncrypting;
private int mKeymasterPurposeOverride = -1;
private AndroidKeyStoreKey mKey;
private SecureRandom mRng;
/**
* Token referencing this operation inside keystore service. It is initialized by
* {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and on some error
* conditions in between.
*/
private IBinder mOperationToken;
private long mOperationHandle;
private KeyStoreCryptoOperationStreamer mMainDataStreamer;
private KeyStoreCryptoOperationStreamer mAdditionalAuthenticationDataStreamer;
private boolean mAdditionalAuthenticationDataStreamerClosed;
/**
* Encountered exception which could not be immediately thrown because it was encountered inside
* a method that does not throw checked exception. This exception will be thrown from
* {@code engineDoFinal}. Once such an exception is encountered, {@code engineUpdate} and
* {@code engineDoFinal} start ignoring input data.
*/
private Exception mCachedException;
AndroidKeyStoreCipherSpiBase() {
mKeyStore = KeyStore.getInstance();
}
@Override
protected final void engineInit(int opmode, Key key, SecureRandom random)
throws InvalidKeyException {
resetAll();
boolean success = false;
try {
init(opmode, key, random);
initAlgorithmSpecificParameters();
try {
ensureKeystoreOperationInitialized();
} catch (InvalidAlgorithmParameterException e) {
throw new InvalidKeyException(e);
}
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
@Override
protected final void engineInit(int opmode, Key key, AlgorithmParameters params,
SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
resetAll();
boolean success = false;
try {
init(opmode, key, random);
initAlgorithmSpecificParameters(params);
ensureKeystoreOperationInitialized();
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
@Override
protected final void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
resetAll();
boolean success = false;
try {
init(opmode, key, random);
initAlgorithmSpecificParameters(params);
ensureKeystoreOperationInitialized();
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
switch (opmode) {
case Cipher.ENCRYPT_MODE:
case Cipher.WRAP_MODE:
mEncrypting = true;
break;
case Cipher.DECRYPT_MODE:
case Cipher.UNWRAP_MODE:
mEncrypting = false;
break;
default:
throw new InvalidParameterException("Unsupported opmode: " + opmode);
}
initKey(opmode, key);
if (mKey == null) {
throw new ProviderException("initKey did not initialize the key");
}
mRng = random;
}
/**
* Resets this cipher to its pristine pre-init state. This must be equivalent to obtaining a new
* cipher instance.
*
* <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) {
mKeyStore.abort(operationToken);
}
mEncrypting = false;
mKeymasterPurposeOverride = -1;
mKey = null;
mRng = null;
mOperationToken = null;
mOperationHandle = 0;
mMainDataStreamer = null;
mAdditionalAuthenticationDataStreamer = null;
mAdditionalAuthenticationDataStreamerClosed = false;
mCachedException = null;
}
/**
* Resets this cipher while preserving the initialized state. This must be equivalent to
* rolling back the cipher's state to just after the most recent {@code engineInit} completed
* successfully.
*
* <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) {
mKeyStore.abort(operationToken);
}
mOperationToken = null;
mOperationHandle = 0;
mMainDataStreamer = null;
mAdditionalAuthenticationDataStreamer = null;
mAdditionalAuthenticationDataStreamerClosed = false;
mCachedException = null;
}
private void ensureKeystoreOperationInitialized() throws InvalidKeyException,
InvalidAlgorithmParameterException {
if (mMainDataStreamer != null) {
return;
}
if (mCachedException != null) {
return;
}
if (mKey == null) {
throw new IllegalStateException("Not initialized");
}
KeymasterArguments keymasterInputArgs = new KeymasterArguments();
addAlgorithmSpecificParametersToBegin(keymasterInputArgs);
byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, getAdditionalEntropyAmountForBegin());
int purpose;
if (mKeymasterPurposeOverride != -1) {
purpose = mKeymasterPurposeOverride;
} else {
purpose = mEncrypting
? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT;
}
OperationResult opResult = mKeyStore.begin(
mKey.getAlias(),
purpose,
true, // permit aborting this operation if keystore runs out of resources
keymasterInputArgs,
additionalEntropy,
mKey.getUid());
if (opResult == null) {
throw new KeyStoreConnectException();
}
// Store operation token and handle regardless of the error code returned by KeyStore to
// ensure that the operation gets aborted immediately if the code below throws an exception.
mOperationToken = opResult.token;
mOperationHandle = opResult.operationHandle;
// If necessary, throw an exception due to KeyStore operation having failed.
GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit(
mKeyStore, mKey, opResult.resultCode);
if (e != null) {
if (e instanceof InvalidKeyException) {
throw (InvalidKeyException) e;
} else if (e instanceof InvalidAlgorithmParameterException) {
throw (InvalidAlgorithmParameterException) e;
} else {
throw new ProviderException("Unexpected exception type", e);
}
}
if (mOperationToken == null) {
throw new ProviderException("Keystore returned null operation token");
}
if (mOperationHandle == 0) {
throw new ProviderException("Keystore returned invalid operation handle");
}
loadAlgorithmSpecificParametersFromBeginResult(opResult.outParams);
mMainDataStreamer = createMainDataStreamer(mKeyStore, opResult.token);
mAdditionalAuthenticationDataStreamer =
createAdditionalAuthenticationDataStreamer(mKeyStore, opResult.token);
mAdditionalAuthenticationDataStreamerClosed = false;
}
/**
* Creates a streamer which sends plaintext/ciphertext into the provided KeyStore and receives
* the corresponding ciphertext/plaintext from the KeyStore.
*
* <p>This implementation returns a working streamer.
*/
@NonNull
protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
KeyStore keyStore, IBinder operationToken) {
return new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
keyStore, operationToken), 0);
}
/**
* Creates a streamer which sends Additional Authentication Data (AAD) into the KeyStore.
*
* <p>This implementation returns {@code null}.
*
* @return stream or {@code null} if AAD is not supported by this cipher.
*/
@Nullable
protected KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer(
@SuppressWarnings("unused") KeyStore keyStore,
@SuppressWarnings("unused") IBinder operationToken) {
return null;
}
@Override
protected final byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
if (mCachedException != null) {
return null;
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
mCachedException = e;
return null;
}
if (inputLen == 0) {
return null;
}
byte[] output;
try {
flushAAD();
output = mMainDataStreamer.update(input, inputOffset, inputLen);
} catch (KeyStoreException e) {
mCachedException = e;
return null;
}
if (output.length == 0) {
return null;
}
return output;
}
private void flushAAD() throws KeyStoreException {
if ((mAdditionalAuthenticationDataStreamer != null)
&& (!mAdditionalAuthenticationDataStreamerClosed)) {
byte[] output;
try {
output = mAdditionalAuthenticationDataStreamer.doFinal(
EmptyArray.BYTE, 0, 0,
null, // no signature
null // no additional entropy needed flushing AAD
);
} finally {
mAdditionalAuthenticationDataStreamerClosed = true;
}
if ((output != null) && (output.length > 0)) {
throw new ProviderException(
"AAD update unexpectedly returned data: " + output.length + " bytes");
}
}
}
@Override
protected final int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException {
byte[] outputCopy = engineUpdate(input, inputOffset, inputLen);
if (outputCopy == null) {
return 0;
}
int outputAvailable = output.length - outputOffset;
if (outputCopy.length > outputAvailable) {
throw new ShortBufferException("Output buffer too short. Produced: "
+ outputCopy.length + ", available: " + outputAvailable);
}
System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
return outputCopy.length;
}
@Override
protected final int engineUpdate(ByteBuffer input, ByteBuffer output)
throws ShortBufferException {
if (input == null) {
throw new NullPointerException("input == null");
}
if (output == null) {
throw new NullPointerException("output == null");
}
int inputSize = input.remaining();
byte[] outputArray;
if (input.hasArray()) {
outputArray =
engineUpdate(
input.array(), input.arrayOffset() + input.position(), inputSize);
input.position(input.position() + inputSize);
} else {
byte[] inputArray = new byte[inputSize];
input.get(inputArray);
outputArray = engineUpdate(inputArray, 0, inputSize);
}
int outputSize = (outputArray != null) ? outputArray.length : 0;
if (outputSize > 0) {
int outputBufferAvailable = output.remaining();
try {
output.put(outputArray);
} catch (BufferOverflowException e) {
throw new ShortBufferException(
"Output buffer too small. Produced: " + outputSize + ", available: "
+ outputBufferAvailable);
}
}
return outputSize;
}
@Override
protected final void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) {
if (mCachedException != null) {
return;
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
mCachedException = e;
return;
}
if (mAdditionalAuthenticationDataStreamerClosed) {
throw new IllegalStateException(
"AAD can only be provided before Cipher.update is invoked");
}
if (mAdditionalAuthenticationDataStreamer == null) {
throw new IllegalStateException("This cipher does not support AAD");
}
byte[] output;
try {
output = mAdditionalAuthenticationDataStreamer.update(input, inputOffset, inputLen);
} catch (KeyStoreException e) {
mCachedException = e;
return;
}
if ((output != null) && (output.length > 0)) {
throw new ProviderException("AAD update unexpectedly produced output: "
+ output.length + " bytes");
}
}
@Override
protected final void engineUpdateAAD(ByteBuffer src) {
if (src == null) {
throw new IllegalArgumentException("src == null");
}
if (!src.hasRemaining()) {
return;
}
byte[] input;
int inputOffset;
int inputLen;
if (src.hasArray()) {
input = src.array();
inputOffset = src.arrayOffset() + src.position();
inputLen = src.remaining();
src.position(src.limit());
} else {
input = new byte[src.remaining()];
inputOffset = 0;
inputLen = input.length;
src.get(input);
}
engineUpdateAAD(input, inputOffset, inputLen);
}
@Override
protected final byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
throws IllegalBlockSizeException, BadPaddingException {
if (mCachedException != null) {
throw (IllegalBlockSizeException)
new IllegalBlockSizeException().initCause(mCachedException);
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
}
byte[] output;
try {
flushAAD();
byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, getAdditionalEntropyAmountForFinish());
output = mMainDataStreamer.doFinal(
input, inputOffset, inputLen,
null, // no signature involved
additionalEntropy);
} catch (KeyStoreException e) {
switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH:
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT:
throw (BadPaddingException) new BadPaddingException().initCause(e);
case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
throw (AEADBadTagException) new AEADBadTagException().initCause(e);
default:
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
}
}
resetWhilePreservingInitState();
return output;
}
@Override
protected final int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
BadPaddingException {
byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen);
if (outputCopy == null) {
return 0;
}
int outputAvailable = output.length - outputOffset;
if (outputCopy.length > outputAvailable) {
throw new ShortBufferException("Output buffer too short. Produced: "
+ outputCopy.length + ", available: " + outputAvailable);
}
System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
return outputCopy.length;
}
@Override
protected final int engineDoFinal(ByteBuffer input, ByteBuffer output)
throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
if (input == null) {
throw new NullPointerException("input == null");
}
if (output == null) {
throw new NullPointerException("output == null");
}
int inputSize = input.remaining();
byte[] outputArray;
if (input.hasArray()) {
outputArray =
engineDoFinal(
input.array(), input.arrayOffset() + input.position(), inputSize);
input.position(input.position() + inputSize);
} else {
byte[] inputArray = new byte[inputSize];
input.get(inputArray);
outputArray = engineDoFinal(inputArray, 0, inputSize);
}
int outputSize = (outputArray != null) ? outputArray.length : 0;
if (outputSize > 0) {
int outputBufferAvailable = output.remaining();
try {
output.put(outputArray);
} catch (BufferOverflowException e) {
throw new ShortBufferException(
"Output buffer too small. Produced: " + outputSize + ", available: "
+ outputBufferAvailable);
}
}
return outputSize;
}
@Override
protected final byte[] engineWrap(Key key)
throws IllegalBlockSizeException, InvalidKeyException {
if (mKey == null) {
throw new IllegalStateException("Not initilized");
}
if (!isEncrypting()) {
throw new IllegalStateException(
"Cipher must be initialized in Cipher.WRAP_MODE to wrap keys");
}
if (key == null) {
throw new NullPointerException("key == null");
}
byte[] encoded = null;
if (key instanceof SecretKey) {
if ("RAW".equalsIgnoreCase(key.getFormat())) {
encoded = key.getEncoded();
}
if (encoded == null) {
try {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(key.getAlgorithm());
SecretKeySpec spec =
(SecretKeySpec) keyFactory.getKeySpec(
(SecretKey) key, SecretKeySpec.class);
encoded = spec.getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to wrap key because it does not export its key material",
e);
}
}
} else if (key instanceof PrivateKey) {
if ("PKCS8".equalsIgnoreCase(key.getFormat())) {
encoded = key.getEncoded();
}
if (encoded == null) {
try {
KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm());
PKCS8EncodedKeySpec spec =
keyFactory.getKeySpec(key, PKCS8EncodedKeySpec.class);
encoded = spec.getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to wrap key because it does not export its key material",
e);
}
}
} else if (key instanceof PublicKey) {
if ("X.509".equalsIgnoreCase(key.getFormat())) {
encoded = key.getEncoded();
}
if (encoded == null) {
try {
KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm());
X509EncodedKeySpec spec =
keyFactory.getKeySpec(key, X509EncodedKeySpec.class);
encoded = spec.getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to wrap key because it does not export its key material",
e);
}
}
} else {
throw new InvalidKeyException("Unsupported key type: " + key.getClass().getName());
}
if (encoded == null) {
throw new InvalidKeyException(
"Failed to wrap key because it does not export its key material");
}
try {
return engineDoFinal(encoded, 0, encoded.length);
} catch (BadPaddingException e) {
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
}
}
@Override
protected final Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm,
int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException {
if (mKey == null) {
throw new IllegalStateException("Not initilized");
}
if (isEncrypting()) {
throw new IllegalStateException(
"Cipher must be initialized in Cipher.WRAP_MODE to wrap keys");
}
if (wrappedKey == null) {
throw new NullPointerException("wrappedKey == null");
}
byte[] encoded;
try {
encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length);
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new InvalidKeyException("Failed to unwrap key", e);
}
switch (wrappedKeyType) {
case Cipher.SECRET_KEY:
{
return new SecretKeySpec(encoded, wrappedKeyAlgorithm);
// break;
}
case Cipher.PRIVATE_KEY:
{
KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
try {
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to create private key from its PKCS#8 encoded form", e);
}
// break;
}
case Cipher.PUBLIC_KEY:
{
KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
try {
return keyFactory.generatePublic(new X509EncodedKeySpec(encoded));
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to create public key from its X.509 encoded form", e);
}
// break;
}
default:
throw new InvalidParameterException(
"Unsupported wrappedKeyType: " + wrappedKeyType);
}
}
@Override
protected final void engineSetMode(String mode) throws NoSuchAlgorithmException {
// This should never be invoked because all algorithms registered with the AndroidKeyStore
// provide explicitly specify block mode.
throw new UnsupportedOperationException();
}
@Override
protected final void engineSetPadding(String arg0) throws NoSuchPaddingException {
// This should never be invoked because all algorithms registered with the AndroidKeyStore
// provide explicitly specify padding mode.
throw new UnsupportedOperationException();
}
@Override
protected final int engineGetKeySize(Key key) throws InvalidKeyException {
throw new UnsupportedOperationException();
}
@CallSuper
@Override
public void finalize() throws Throwable {
try {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mKeyStore.abort(operationToken);
}
} finally {
super.finalize();
}
}
@Override
public final long getOperationHandle() {
return mOperationHandle;
}
protected final void setKey(@NonNull AndroidKeyStoreKey key) {
mKey = key;
}
/**
* Overrides the default purpose/type of the crypto operation.
*/
protected final void setKeymasterPurposeOverride(int keymasterPurpose) {
mKeymasterPurposeOverride = keymasterPurpose;
}
protected final int getKeymasterPurposeOverride() {
return mKeymasterPurposeOverride;
}
/**
* Returns {@code true} if this cipher is initialized for encryption, {@code false} if this
* cipher is initialized for decryption.
*/
protected final boolean isEncrypting() {
return mEncrypting;
}
@NonNull
protected final KeyStore getKeyStore() {
return mKeyStore;
}
protected final long getConsumedInputSizeBytes() {
if (mMainDataStreamer == null) {
throw new IllegalStateException("Not initialized");
}
return mMainDataStreamer.getConsumedInputSizeBytes();
}
protected final long getProducedOutputSizeBytes() {
if (mMainDataStreamer == null) {
throw new IllegalStateException("Not initialized");
}
return mMainDataStreamer.getProducedOutputSizeBytes();
}
static String opmodeToString(int opmode) {
switch (opmode) {
case Cipher.ENCRYPT_MODE:
return "ENCRYPT_MODE";
case Cipher.DECRYPT_MODE:
return "DECRYPT_MODE";
case Cipher.WRAP_MODE:
return "WRAP_MODE";
case Cipher.UNWRAP_MODE:
return "UNWRAP_MODE";
default:
return String.valueOf(opmode);
}
}
// The methods below need to be implemented by subclasses.
/**
* Initializes this cipher with the provided key.
*
* @throws InvalidKeyException if the {@code key} is not suitable for this cipher in the
* specified {@code opmode}.
*
* @see #setKey(AndroidKeyStoreKey)
*/
protected abstract void initKey(int opmode, @Nullable Key key) throws InvalidKeyException;
/**
* Returns algorithm-specific parameters used by this cipher or {@code null} if no
* algorithm-specific parameters are used.
*/
@Nullable
@Override
protected abstract AlgorithmParameters engineGetParameters();
/**
* Invoked by {@code engineInit} to initialize algorithm-specific parameters when no additional
* initialization parameters were provided.
*
* @throws InvalidKeyException if this cipher cannot be configured based purely on the provided
* key and needs additional parameters to be provided to {@code Cipher.init}.
*/
protected abstract void initAlgorithmSpecificParameters() throws InvalidKeyException;
/**
* Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional
* parameters were provided.
*
* @param params additional algorithm parameters or {@code null} if not specified.
*
* @throws InvalidAlgorithmParameterException if there is insufficient information to configure
* this cipher or if the provided parameters are not suitable for this cipher.
*/
protected abstract void initAlgorithmSpecificParameters(
@Nullable AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException;
/**
* Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional
* parameters were provided.
*
* @param params additional algorithm parameters or {@code null} if not specified.
*
* @throws InvalidAlgorithmParameterException if there is insufficient information to configure
* this cipher or if the provided parameters are not suitable for this cipher.
*/
protected abstract void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
throws InvalidAlgorithmParameterException;
/**
* Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's
* {@code begin} operation. This amount of entropy is typically what's consumed to generate
* random parameters, such as IV.
*
* <p>For decryption, the return value should be {@code 0} because decryption should not be
* consuming any entropy. For encryption, the value combined with
* {@link #getAdditionalEntropyAmountForFinish()} should match (or exceed) the amount of Shannon
* entropy of the ciphertext produced by this cipher assuming the key, the plaintext, and all
* explicitly provided parameters to {@code Cipher.init} are known. For example, for AES CBC
* encryption with an explicitly provided IV the return value should be {@code 0}, whereas for
* the case where IV is generated by the KeyStore's {@code begin} operation it should be
* {@code 16}.
*/
protected abstract int getAdditionalEntropyAmountForBegin();
/**
* Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's
* {@code finish} operation. This amount of entropy is typically what's consumed by encryption
* padding scheme.
*
* <p>For decryption, the return value should be {@code 0} because decryption should not be
* consuming any entropy. For encryption, the value combined with
* {@link #getAdditionalEntropyAmountForBegin()} should match (or exceed) the amount of Shannon
* entropy of the ciphertext produced by this cipher assuming the key, the plaintext, and all
* explicitly provided parameters to {@code Cipher.init} are known. For example, for RSA with
* OAEP the return value should be the size of the OAEP hash output. For RSA with PKCS#1 padding
* the return value should be the size of the padding string or could be raised (for simplicity)
* to the size of the modulus.
*/
protected abstract int getAdditionalEntropyAmountForFinish();
/**
* Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation.
*
* @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific
* parameters.
*/
protected abstract void addAlgorithmSpecificParametersToBegin(
@NonNull KeymasterArguments keymasterArgs);
/**
* Invoked to obtain algorithm-specific parameters from the result of the KeyStore's
* {@code begin} operation.
*
* <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);
}