Merge "Add unauthenticated AES ciphers backed by AndroidKeyStore."
This commit is contained in:
@ -457,7 +457,7 @@ public class AndroidKeyStore extends KeyStoreSpi {
|
|||||||
|
|
||||||
String keyAlgorithmString = key.getAlgorithm();
|
String keyAlgorithmString = key.getAlgorithm();
|
||||||
@KeyStoreKeyConstraints.AlgorithmEnum int keyAlgorithm;
|
@KeyStoreKeyConstraints.AlgorithmEnum int keyAlgorithm;
|
||||||
@KeyStoreKeyConstraints.AlgorithmEnum Integer digest;
|
@KeyStoreKeyConstraints.DigestEnum Integer digest;
|
||||||
try {
|
try {
|
||||||
keyAlgorithm =
|
keyAlgorithm =
|
||||||
KeyStoreKeyConstraints.Algorithm.fromJCASecretKeyAlgorithm(keyAlgorithmString);
|
KeyStoreKeyConstraints.Algorithm.fromJCASecretKeyAlgorithm(keyAlgorithmString);
|
||||||
@ -493,12 +493,6 @@ public class AndroidKeyStore extends KeyStoreSpi {
|
|||||||
if (digest != null) {
|
if (digest != null) {
|
||||||
args.addInt(KeymasterDefs.KM_TAG_DIGEST,
|
args.addInt(KeymasterDefs.KM_TAG_DIGEST,
|
||||||
KeyStoreKeyConstraints.Digest.toKeymaster(digest));
|
KeyStoreKeyConstraints.Digest.toKeymaster(digest));
|
||||||
}
|
|
||||||
if (keyAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) {
|
|
||||||
if (digest == null) {
|
|
||||||
throw new IllegalStateException("Digest algorithm must be specified for key"
|
|
||||||
+ " algorithm " + keyAlgorithmString);
|
|
||||||
}
|
|
||||||
Integer digestOutputSizeBytes =
|
Integer digestOutputSizeBytes =
|
||||||
KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest);
|
KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest);
|
||||||
if (digestOutputSizeBytes != null) {
|
if (digestOutputSizeBytes != null) {
|
||||||
@ -507,6 +501,12 @@ public class AndroidKeyStore extends KeyStoreSpi {
|
|||||||
args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes);
|
args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (keyAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) {
|
||||||
|
if (digest == null) {
|
||||||
|
throw new IllegalStateException("Digest algorithm must be specified for key"
|
||||||
|
+ " algorithm " + keyAlgorithmString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@KeyStoreKeyConstraints.PurposeEnum int purposes = (params.getPurposes() != null)
|
@KeyStoreKeyConstraints.PurposeEnum int purposes = (params.getPurposes() != null)
|
||||||
? params.getPurposes()
|
? params.getPurposes()
|
||||||
@ -560,6 +560,12 @@ public class AndroidKeyStore extends KeyStoreSpi {
|
|||||||
// TODO: Remove this once keymaster does not require us to specify the size of imported key.
|
// TODO: Remove this once keymaster does not require us to specify the size of imported key.
|
||||||
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keyMaterial.length * 8);
|
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keyMaterial.length * 8);
|
||||||
|
|
||||||
|
if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
|
||||||
|
|| ((purposes & KeyStoreKeyConstraints.Purpose.DECRYPT) != 0)) {
|
||||||
|
// Permit caller-specified IV. This is needed for the Cipher abstraction.
|
||||||
|
args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
|
||||||
|
}
|
||||||
|
|
||||||
Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias);
|
Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias);
|
||||||
String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias;
|
String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias;
|
||||||
int errorCode = mKeyStore.importKey(
|
int errorCode = mKeyStore.importKey(
|
||||||
|
@ -41,7 +41,30 @@ public class AndroidKeyStoreProvider extends Provider {
|
|||||||
put("KeyGenerator.HmacSHA256", KeyStoreKeyGeneratorSpi.HmacSHA256.class.getName());
|
put("KeyGenerator.HmacSHA256", KeyStoreKeyGeneratorSpi.HmacSHA256.class.getName());
|
||||||
|
|
||||||
// javax.crypto.Mac
|
// javax.crypto.Mac
|
||||||
put("Mac.HmacSHA256", KeyStoreHmacSpi.HmacSHA256.class.getName());
|
putMacImpl("HmacSHA256", KeyStoreHmacSpi.HmacSHA256.class.getName());
|
||||||
put("Mac.HmacSHA256 SupportedKeyClasses", KeyStoreSecretKey.class.getName());
|
|
||||||
|
// javax.crypto.Cipher
|
||||||
|
putSymmetricCipherImpl("AES/ECB/NoPadding",
|
||||||
|
KeyStoreCipherSpi.AES.ECB.NoPadding.class.getName());
|
||||||
|
putSymmetricCipherImpl("AES/ECB/PKCS7Padding",
|
||||||
|
KeyStoreCipherSpi.AES.ECB.PKCS7Padding.class.getName());
|
||||||
|
|
||||||
|
putSymmetricCipherImpl("AES/CBC/NoPadding",
|
||||||
|
KeyStoreCipherSpi.AES.CBC.NoPadding.class.getName());
|
||||||
|
putSymmetricCipherImpl("AES/CBC/PKCS7Padding",
|
||||||
|
KeyStoreCipherSpi.AES.CBC.PKCS7Padding.class.getName());
|
||||||
|
|
||||||
|
putSymmetricCipherImpl("AES/CTR/NoPadding",
|
||||||
|
KeyStoreCipherSpi.AES.CTR.NoPadding.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putMacImpl(String algorithm, String implClass) {
|
||||||
|
put("Mac." + algorithm, implClass);
|
||||||
|
put("Mac." + algorithm + " SupportedKeyClasses", KeyStoreSecretKey.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putSymmetricCipherImpl(String transformation, String implClass) {
|
||||||
|
put("Cipher." + transformation, implClass);
|
||||||
|
put("Cipher." + transformation + " SupportedKeyClasses", KeyStoreSecretKey.class.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
540
keystore/java/android/security/KeyStoreCipherSpi.java
Normal file
540
keystore/java/android/security/KeyStoreCipherSpi.java
Normal file
@ -0,0 +1,540 @@
|
|||||||
|
package android.security;
|
||||||
|
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.security.keymaster.KeymasterArguments;
|
||||||
|
import android.security.keymaster.KeymasterDefs;
|
||||||
|
import android.security.keymaster.OperationResult;
|
||||||
|
|
||||||
|
import java.security.AlgorithmParameters;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.Key;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
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 KeyStoreCipherSpi extends CipherSpi {
|
||||||
|
|
||||||
|
public abstract static class AES extends KeyStoreCipherSpi {
|
||||||
|
protected AES(@KeyStoreKeyConstraints.BlockModeEnum int blockMode,
|
||||||
|
@KeyStoreKeyConstraints.PaddingEnum int padding, boolean ivUsed) {
|
||||||
|
super(KeyStoreKeyConstraints.Algorithm.AES,
|
||||||
|
blockMode,
|
||||||
|
padding,
|
||||||
|
16,
|
||||||
|
ivUsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class ECB extends AES {
|
||||||
|
protected ECB(@KeyStoreKeyConstraints.PaddingEnum int padding) {
|
||||||
|
super(KeyStoreKeyConstraints.BlockMode.ECB, padding, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NoPadding extends ECB {
|
||||||
|
public NoPadding() {
|
||||||
|
super(KeyStoreKeyConstraints.Padding.NONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PKCS7Padding extends ECB {
|
||||||
|
public PKCS7Padding() {
|
||||||
|
super(KeyStoreKeyConstraints.Padding.PKCS7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class CBC extends AES {
|
||||||
|
protected CBC(@KeyStoreKeyConstraints.BlockModeEnum int padding) {
|
||||||
|
super(KeyStoreKeyConstraints.BlockMode.CBC, padding, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NoPadding extends CBC {
|
||||||
|
public NoPadding() {
|
||||||
|
super(KeyStoreKeyConstraints.Padding.NONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PKCS7Padding extends CBC {
|
||||||
|
public PKCS7Padding() {
|
||||||
|
super(KeyStoreKeyConstraints.Padding.PKCS7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class CTR extends AES {
|
||||||
|
protected CTR(@KeyStoreKeyConstraints.BlockModeEnum int padding) {
|
||||||
|
super(KeyStoreKeyConstraints.BlockMode.CTR, padding, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NoPadding extends CTR {
|
||||||
|
public NoPadding() {
|
||||||
|
super(KeyStoreKeyConstraints.Padding.NONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final KeyStore mKeyStore;
|
||||||
|
private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm;
|
||||||
|
private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockMode;
|
||||||
|
private final @KeyStoreKeyConstraints.PaddingEnum int mPadding;
|
||||||
|
private final int mBlockSizeBytes;
|
||||||
|
private final boolean mIvUsed;
|
||||||
|
|
||||||
|
// Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after
|
||||||
|
// doFinal finishes.
|
||||||
|
protected boolean mEncrypting;
|
||||||
|
private KeyStoreSecretKey mKey;
|
||||||
|
private SecureRandom mRng;
|
||||||
|
private boolean mFirstOperationInitiated;
|
||||||
|
byte[] mIv;
|
||||||
|
|
||||||
|
// Fields below must be reset
|
||||||
|
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 KeyStoreCryptoOperationChunkedStreamer mMainDataStreamer;
|
||||||
|
|
||||||
|
protected KeyStoreCipherSpi(
|
||||||
|
@KeyStoreKeyConstraints.AlgorithmEnum int algorithm,
|
||||||
|
@KeyStoreKeyConstraints.BlockModeEnum int blockMode,
|
||||||
|
@KeyStoreKeyConstraints.PaddingEnum int padding,
|
||||||
|
int blockSizeBytes,
|
||||||
|
boolean ivUsed) {
|
||||||
|
mKeyStore = KeyStore.getInstance();
|
||||||
|
mAlgorithm = algorithm;
|
||||||
|
mBlockMode = blockMode;
|
||||||
|
mPadding = padding;
|
||||||
|
mBlockSizeBytes = blockSizeBytes;
|
||||||
|
mIvUsed = ivUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
|
||||||
|
init(opmode, key, random);
|
||||||
|
initAlgorithmSpecificParameters();
|
||||||
|
ensureKeystoreOperationInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
|
||||||
|
throws InvalidKeyException, InvalidAlgorithmParameterException {
|
||||||
|
init(opmode, key, random);
|
||||||
|
initAlgorithmSpecificParameters(params);
|
||||||
|
ensureKeystoreOperationInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
|
||||||
|
SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
|
||||||
|
init(opmode, key, random);
|
||||||
|
initAlgorithmSpecificParameters(params);
|
||||||
|
ensureKeystoreOperationInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
|
||||||
|
reset();
|
||||||
|
if (!(key instanceof KeyStoreSecretKey)) {
|
||||||
|
throw new InvalidKeyException(
|
||||||
|
"Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
|
||||||
|
}
|
||||||
|
mKey = (KeyStoreSecretKey) 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 reset() {
|
||||||
|
IBinder operationToken = mOperationToken;
|
||||||
|
if (operationToken != null) {
|
||||||
|
mOperationToken = null;
|
||||||
|
mKeyStore.abort(operationToken);
|
||||||
|
}
|
||||||
|
mMainDataStreamer = null;
|
||||||
|
mAdditionalEntropyForBegin = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureKeystoreOperationInitialized() {
|
||||||
|
if (mMainDataStreamer != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mKey == null) {
|
||||||
|
throw new IllegalStateException("Not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
KeymasterArguments keymasterInputArgs = new KeymasterArguments();
|
||||||
|
keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mAlgorithm);
|
||||||
|
keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mBlockMode);
|
||||||
|
keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mPadding);
|
||||||
|
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();
|
||||||
|
} else if (opResult.resultCode != KeyStore.NO_ERROR) {
|
||||||
|
throw new CryptoOperationException("Failed to start keystore operation",
|
||||||
|
KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opResult.token == null) {
|
||||||
|
throw new CryptoOperationException("Keystore returned null operation token");
|
||||||
|
}
|
||||||
|
mOperationToken = opResult.token;
|
||||||
|
loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs);
|
||||||
|
mFirstOperationInitiated = true;
|
||||||
|
mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer(
|
||||||
|
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
|
||||||
|
mKeyStore, opResult.token));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
|
||||||
|
ensureKeystoreOperationInitialized();
|
||||||
|
|
||||||
|
if (inputLen == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] output;
|
||||||
|
try {
|
||||||
|
output = mMainDataStreamer.update(input, inputOffset, inputLen);
|
||||||
|
} catch (KeymasterException e) {
|
||||||
|
throw new CryptoOperationException("Keystore operation failed", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
|
||||||
|
int outputOffset) throws ShortBufferException {
|
||||||
|
ensureKeystoreOperationInitialized();
|
||||||
|
|
||||||
|
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 {
|
||||||
|
ensureKeystoreOperationInitialized();
|
||||||
|
|
||||||
|
byte[] output;
|
||||||
|
try {
|
||||||
|
output = mMainDataStreamer.doFinal(input, inputOffset, inputLen);
|
||||||
|
} catch (KeymasterException 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 new CryptoOperationException("Keystore operation failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reset();
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (!mIvUsed) {
|
||||||
|
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 RuntimeException("Failed to obtain AES AlgorithmParameters", e);
|
||||||
|
} catch (InvalidParameterSpecException e) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"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 (!mIvUsed) {
|
||||||
|
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 (!mIvUsed) {
|
||||||
|
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 (!mIvUsed) {
|
||||||
|
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 (mIvUsed) {
|
||||||
|
// IV is needed
|
||||||
|
if ((mIv == null) && (mEncrypting)) {
|
||||||
|
// TODO: Switch to keymaster-generated IV code below once keymaster supports
|
||||||
|
// that.
|
||||||
|
// IV is needed but was not provided by the caller -- generate an IV.
|
||||||
|
mIv = new byte[mBlockSizeBytes];
|
||||||
|
SecureRandom rng = (mRng != null) ? mRng : new SecureRandom();
|
||||||
|
rng.nextBytes(mIv);
|
||||||
|
// // IV was not provided by the caller and thus will be generated by keymaster.
|
||||||
|
// // Mix in some additional entropy from the provided SecureRandom.
|
||||||
|
// if (mRng != null) {
|
||||||
|
// mAdditionalEntropyForBegin = new byte[mBlockSizeBytes];
|
||||||
|
// mRng.nextBytes(mAdditionalEntropyForBegin);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((mIvUsed) && (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 (mIvUsed) {
|
||||||
|
if (mIv == null) {
|
||||||
|
mIv = returnedIv;
|
||||||
|
} else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
|
||||||
|
throw new CryptoOperationException("IV in use differs from provided IV");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (returnedIv != null) {
|
||||||
|
throw new CryptoOperationException(
|
||||||
|
"IV in use despite IV not being used by this transformation");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package android.security;
|
package android.security;
|
||||||
|
|
||||||
|
import android.os.IBinder;
|
||||||
import android.security.keymaster.OperationResult;
|
import android.security.keymaster.OperationResult;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@ -10,32 +11,36 @@ import java.io.IOException;
|
|||||||
* {@code update} and {@code finish} operations.
|
* {@code update} and {@code finish} operations.
|
||||||
*
|
*
|
||||||
* <p>The helper abstracts away to issues that need to be solved in most code that uses KeyStore's
|
* <p>The helper abstracts away to issues that need to be solved in most code that uses KeyStore's
|
||||||
* update and finish operations. Firstly, KeyStore's update and finish operations can consume only a
|
* update and finish operations. Firstly, KeyStore's update operation can consume only a limited
|
||||||
* limited amount of data in one go because the operations are marshalled via Binder. Secondly, the
|
* amount of data in one go because the operations are marshalled via Binder. Secondly, the update
|
||||||
* update operation may consume less data than provided, in which case the caller has to buffer
|
* operation may consume less data than provided, in which case the caller has to buffer the
|
||||||
* the remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and
|
* remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and
|
||||||
* {@link #doFinal(byte[], int, int) doFinal} operations which can be used to conveniently implement
|
* {@link #doFinal(byte[], int, int) doFinal} operations which can be used to conveniently implement
|
||||||
* various JCA crypto primitives.
|
* various JCA crypto primitives.
|
||||||
*
|
*
|
||||||
* <p>KeyStore operation through which data is streamed is abstracted away as
|
* <p>Bidirectional chunked streaming of data via a KeyStore crypto operation is abstracted away as
|
||||||
* {@link KeyStoreOperation} to avoid having this class deal with operation tokens and occasional
|
* a {@link Stream} to avoid having this class deal with operation tokens and occasional additional
|
||||||
* additional parameters to update and final operations.
|
* parameters to {@code update} and {@code final} operations.
|
||||||
*
|
*
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public class KeyStoreCryptoOperationChunkedStreamer {
|
public class KeyStoreCryptoOperationChunkedStreamer {
|
||||||
public interface KeyStoreOperation {
|
|
||||||
/**
|
/**
|
||||||
* Returns the result of the KeyStore update operation or null if keystore couldn't be
|
* Bidirectional chunked data stream over a KeyStore crypto operation.
|
||||||
* reached.
|
*/
|
||||||
|
public interface Stream {
|
||||||
|
/**
|
||||||
|
* Returns the result of the KeyStore {@code update} operation or null if keystore couldn't
|
||||||
|
* be reached.
|
||||||
*/
|
*/
|
||||||
OperationResult update(byte[] input);
|
OperationResult update(byte[] input);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the result of the KeyStore finish operation or null if keystore couldn't be
|
* Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't
|
||||||
* reached.
|
* be reached.
|
||||||
*/
|
*/
|
||||||
OperationResult finish(byte[] input);
|
OperationResult finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Binder buffer is about 1MB, but it's shared between all active transactions of the process.
|
// Binder buffer is about 1MB, but it's shared between all active transactions of the process.
|
||||||
@ -43,19 +48,19 @@ public class KeyStoreCryptoOperationChunkedStreamer {
|
|||||||
private static final int DEFAULT_MAX_CHUNK_SIZE = 64 * 1024;
|
private static final int DEFAULT_MAX_CHUNK_SIZE = 64 * 1024;
|
||||||
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
||||||
|
|
||||||
private final KeyStoreOperation mKeyStoreOperation;
|
private final Stream mKeyStoreStream;
|
||||||
private final int mMaxChunkSize;
|
private final int mMaxChunkSize;
|
||||||
|
|
||||||
private byte[] mBuffered = EMPTY_BYTE_ARRAY;
|
private byte[] mBuffered = EMPTY_BYTE_ARRAY;
|
||||||
private int mBufferedOffset;
|
private int mBufferedOffset;
|
||||||
private int mBufferedLength;
|
private int mBufferedLength;
|
||||||
|
|
||||||
public KeyStoreCryptoOperationChunkedStreamer(KeyStoreOperation operation) {
|
public KeyStoreCryptoOperationChunkedStreamer(Stream operation) {
|
||||||
this(operation, DEFAULT_MAX_CHUNK_SIZE);
|
this(operation, DEFAULT_MAX_CHUNK_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyStoreCryptoOperationChunkedStreamer(KeyStoreOperation operation, int maxChunkSize) {
|
public KeyStoreCryptoOperationChunkedStreamer(Stream operation, int maxChunkSize) {
|
||||||
mKeyStoreOperation = operation;
|
mKeyStoreStream = operation;
|
||||||
mMaxChunkSize = maxChunkSize;
|
mMaxChunkSize = maxChunkSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,10 +78,9 @@ public class KeyStoreCryptoOperationChunkedStreamer {
|
|||||||
if ((mBufferedLength + inputLength) > mMaxChunkSize) {
|
if ((mBufferedLength + inputLength) > mMaxChunkSize) {
|
||||||
// Too much input for one chunk -- extract one max-sized chunk and feed it into the
|
// Too much input for one chunk -- extract one max-sized chunk and feed it into the
|
||||||
// update operation.
|
// update operation.
|
||||||
chunk = new byte[mMaxChunkSize];
|
inputBytesInChunk = mMaxChunkSize - mBufferedLength;
|
||||||
System.arraycopy(mBuffered, mBufferedOffset, chunk, 0, mBufferedLength);
|
chunk = concat(mBuffered, mBufferedOffset, mBufferedLength,
|
||||||
inputBytesInChunk = chunk.length - mBufferedLength;
|
input, inputOffset, inputBytesInChunk);
|
||||||
System.arraycopy(input, inputOffset, chunk, mBufferedLength, inputBytesInChunk);
|
|
||||||
} else {
|
} else {
|
||||||
// All of available input fits into one chunk.
|
// All of available input fits into one chunk.
|
||||||
if ((mBufferedLength == 0) && (inputOffset == 0)
|
if ((mBufferedLength == 0) && (inputOffset == 0)
|
||||||
@ -87,17 +91,16 @@ public class KeyStoreCryptoOperationChunkedStreamer {
|
|||||||
inputBytesInChunk = input.length;
|
inputBytesInChunk = input.length;
|
||||||
} else {
|
} else {
|
||||||
// Need to combine buffered data with input data into one array.
|
// Need to combine buffered data with input data into one array.
|
||||||
chunk = new byte[mBufferedLength + inputLength];
|
|
||||||
inputBytesInChunk = inputLength;
|
inputBytesInChunk = inputLength;
|
||||||
System.arraycopy(mBuffered, mBufferedOffset, chunk, 0, mBufferedLength);
|
chunk = concat(mBuffered, mBufferedOffset, mBufferedLength,
|
||||||
System.arraycopy(input, inputOffset, chunk, mBufferedLength, inputLength);
|
input, inputOffset, inputBytesInChunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Update input array references to reflect that some of its bytes are now in mBuffered.
|
// Update input array references to reflect that some of its bytes are now in mBuffered.
|
||||||
inputOffset += inputBytesInChunk;
|
inputOffset += inputBytesInChunk;
|
||||||
inputLength -= inputBytesInChunk;
|
inputLength -= inputBytesInChunk;
|
||||||
|
|
||||||
OperationResult opResult = mKeyStoreOperation.update(chunk);
|
OperationResult opResult = mKeyStoreStream.update(chunk);
|
||||||
if (opResult == null) {
|
if (opResult == null) {
|
||||||
throw new KeyStoreConnectException();
|
throw new KeyStoreConnectException();
|
||||||
} else if (opResult.resultCode != KeyStore.NO_ERROR) {
|
} else if (opResult.resultCode != KeyStore.NO_ERROR) {
|
||||||
@ -176,53 +179,115 @@ public class KeyStoreCryptoOperationChunkedStreamer {
|
|||||||
inputOffset = 0;
|
inputOffset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] updateOutput = null;
|
// Flush all buffered input and provided input into keystore/keymaster.
|
||||||
if ((mBufferedLength + inputLength) > mMaxChunkSize) {
|
byte[] output = update(input, inputOffset, inputLength);
|
||||||
updateOutput = update(input, inputOffset, inputLength);
|
output = concat(output, flush());
|
||||||
inputOffset += inputLength;
|
|
||||||
inputLength = 0;
|
|
||||||
}
|
|
||||||
// All of available input fits into one chunk.
|
|
||||||
|
|
||||||
byte[] finalChunk;
|
OperationResult opResult = mKeyStoreStream.finish();
|
||||||
if ((mBufferedLength == 0) && (inputOffset == 0)
|
|
||||||
&& (inputLength == input.length)) {
|
|
||||||
// Nothing buffered and all of input array needs to be fed into the finish operation.
|
|
||||||
finalChunk = input;
|
|
||||||
} else {
|
|
||||||
// Need to combine buffered data with input data into one array.
|
|
||||||
finalChunk = new byte[mBufferedLength + inputLength];
|
|
||||||
System.arraycopy(mBuffered, mBufferedOffset, finalChunk, 0, mBufferedLength);
|
|
||||||
System.arraycopy(input, inputOffset, finalChunk, mBufferedLength, inputLength);
|
|
||||||
}
|
|
||||||
mBuffered = EMPTY_BYTE_ARRAY;
|
|
||||||
mBufferedLength = 0;
|
|
||||||
mBufferedOffset = 0;
|
|
||||||
|
|
||||||
OperationResult opResult = mKeyStoreOperation.finish(finalChunk);
|
|
||||||
if (opResult == null) {
|
if (opResult == null) {
|
||||||
throw new KeyStoreConnectException();
|
throw new KeyStoreConnectException();
|
||||||
} else if (opResult.resultCode != KeyStore.NO_ERROR) {
|
} else if (opResult.resultCode != KeyStore.NO_ERROR) {
|
||||||
throw KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode);
|
throw KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opResult.inputConsumed != finalChunk.length) {
|
return concat(output, opResult.output);
|
||||||
throw new CryptoOperationException("Unexpected number of bytes consumed by finish: "
|
|
||||||
+ opResult.inputConsumed + " instead of " + finalChunk.length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the concatenation of the output of update and finish.
|
/**
|
||||||
byte[] result;
|
* Passes all of buffered input into the the KeyStore operation (via the {@code update}
|
||||||
byte[] finishOutput = opResult.output;
|
* operation) and returns output.
|
||||||
if ((updateOutput == null) || (updateOutput.length == 0)) {
|
*/
|
||||||
result = finishOutput;
|
public byte[] flush() throws KeymasterException {
|
||||||
} else if ((finishOutput == null) || (finishOutput.length == 0)) {
|
if (mBufferedLength <= 0) {
|
||||||
result = updateOutput;
|
return EMPTY_BYTE_ARRAY;
|
||||||
} else {
|
}
|
||||||
result = new byte[updateOutput.length + finishOutput.length];
|
|
||||||
System.arraycopy(updateOutput, 0, result, 0, updateOutput.length);
|
byte[] chunk = subarray(mBuffered, mBufferedOffset, mBufferedLength);
|
||||||
System.arraycopy(finishOutput, 0, result, updateOutput.length, finishOutput.length);
|
mBuffered = EMPTY_BYTE_ARRAY;
|
||||||
|
mBufferedLength = 0;
|
||||||
|
mBufferedOffset = 0;
|
||||||
|
|
||||||
|
OperationResult opResult = mKeyStoreStream.update(chunk);
|
||||||
|
if (opResult == null) {
|
||||||
|
throw new KeyStoreConnectException();
|
||||||
|
} else if (opResult.resultCode != KeyStore.NO_ERROR) {
|
||||||
|
throw KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opResult.inputConsumed < chunk.length) {
|
||||||
|
throw new CryptoOperationException("Keystore failed to consume all input. Provided: "
|
||||||
|
+ chunk.length + ", consumed: " + opResult.inputConsumed);
|
||||||
|
} else if (opResult.inputConsumed > chunk.length) {
|
||||||
|
throw new CryptoOperationException("Keystore consumed more input than provided"
|
||||||
|
+ " . Provided: " + chunk.length + ", consumed: " + opResult.inputConsumed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (opResult.output != null) ? opResult.output : EMPTY_BYTE_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] concat(byte[] arr1, byte[] arr2) {
|
||||||
|
if ((arr1 == null) || (arr1.length == 0)) {
|
||||||
|
return arr2;
|
||||||
|
} else if ((arr2 == null) || (arr2.length == 0)) {
|
||||||
|
return arr1;
|
||||||
|
} else {
|
||||||
|
byte[] result = new byte[arr1.length + arr2.length];
|
||||||
|
System.arraycopy(arr1, 0, result, 0, arr1.length);
|
||||||
|
System.arraycopy(arr2, 0, result, arr1.length, arr2.length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] concat(byte[] arr1, int offset1, int len1, byte[] arr2, int offset2,
|
||||||
|
int len2) {
|
||||||
|
if (len1 == 0) {
|
||||||
|
return subarray(arr2, offset2, len2);
|
||||||
|
} else if (len2 == 0) {
|
||||||
|
return subarray(arr1, offset1, len1);
|
||||||
|
} else {
|
||||||
|
byte[] result = new byte[len1 + len2];
|
||||||
|
System.arraycopy(arr1, offset1, result, 0, len1);
|
||||||
|
System.arraycopy(arr2, offset2, result, len1, len2);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] subarray(byte[] arr, int offset, int len) {
|
||||||
|
if (len == 0) {
|
||||||
|
return EMPTY_BYTE_ARRAY;
|
||||||
|
}
|
||||||
|
if ((offset == 0) && (len == arr.length)) {
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
byte[] result = new byte[len];
|
||||||
|
System.arraycopy(arr, offset, result, 0, len);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main data stream via a KeyStore streaming operation.
|
||||||
|
*
|
||||||
|
* <p>For example, for an encryption operation, this is the stream through which plaintext is
|
||||||
|
* provided and ciphertext is obtained.
|
||||||
|
*/
|
||||||
|
public static class MainDataStream implements Stream {
|
||||||
|
|
||||||
|
private final KeyStore mKeyStore;
|
||||||
|
private final IBinder mOperationToken;
|
||||||
|
|
||||||
|
public MainDataStream(KeyStore keyStore, IBinder operationToken) {
|
||||||
|
mKeyStore = keyStore;
|
||||||
|
mOperationToken = operationToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OperationResult update(byte[] input) {
|
||||||
|
return mKeyStore.update(mOperationToken, null, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OperationResult finish() {
|
||||||
|
return mKeyStore.finish(mOperationToken, null, null);
|
||||||
}
|
}
|
||||||
return (result != null) ? result : EMPTY_BYTE_ARRAY;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,8 @@ public abstract class KeyStoreHmacSpi extends MacSpi {
|
|||||||
throw new CryptoOperationException("Keystore returned null operation token");
|
throw new CryptoOperationException("Keystore returned null operation token");
|
||||||
}
|
}
|
||||||
mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer(
|
mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer(
|
||||||
new KeyStoreStreamingConsumer(mKeyStore, mOperationToken));
|
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
|
||||||
|
mKeyStore, mOperationToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -147,28 +148,4 @@ public abstract class KeyStoreHmacSpi extends MacSpi {
|
|||||||
super.finalize();
|
super.finalize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* KeyStore-backed consumer of {@code MacSpi}'s chunked stream.
|
|
||||||
*/
|
|
||||||
private static class KeyStoreStreamingConsumer
|
|
||||||
implements KeyStoreCryptoOperationChunkedStreamer.KeyStoreOperation {
|
|
||||||
private final KeyStore mKeyStore;
|
|
||||||
private final IBinder mOperationToken;
|
|
||||||
|
|
||||||
private KeyStoreStreamingConsumer(KeyStore keyStore, IBinder operationToken) {
|
|
||||||
mKeyStore = keyStore;
|
|
||||||
mOperationToken = operationToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OperationResult update(byte[] input) {
|
|
||||||
return mKeyStore.update(mOperationToken, null, input);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OperationResult finish(byte[] input) {
|
|
||||||
return mKeyStore.finish(mOperationToken, null, input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -222,16 +222,6 @@ public abstract class KeyStoreKeyConstraints {
|
|||||||
throw new IllegalArgumentException("Unsupported key algorithm: " + algorithm);
|
throw new IllegalArgumentException("Unsupported key algorithm: " + algorithm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
public static String toJCAKeyPairAlgorithm(@AlgorithmEnum int algorithm) {
|
|
||||||
switch (algorithm) {
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unsupported key alorithm: " + algorithm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@ -306,6 +296,20 @@ public abstract class KeyStoreKeyConstraints {
|
|||||||
throw new IllegalArgumentException("Unknown padding: " + padding);
|
throw new IllegalArgumentException("Unknown padding: " + padding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static @PaddingEnum int fromJCAPadding(String padding) {
|
||||||
|
String paddingLower = padding.toLowerCase(Locale.US);
|
||||||
|
if ("nopadding".equals(paddingLower)) {
|
||||||
|
return NONE;
|
||||||
|
} else if ("pkcs7padding".equals(paddingLower)) {
|
||||||
|
return PKCS7;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unknown padding: " + padding);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@ -418,7 +422,7 @@ public abstract class KeyStoreKeyConstraints {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@IntDef({BlockMode.ECB})
|
@IntDef({BlockMode.ECB, BlockMode.CBC, BlockMode.CTR})
|
||||||
public @interface BlockModeEnum {}
|
public @interface BlockModeEnum {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -427,11 +431,15 @@ public abstract class KeyStoreKeyConstraints {
|
|||||||
public static abstract class BlockMode {
|
public static abstract class BlockMode {
|
||||||
private BlockMode() {}
|
private BlockMode() {}
|
||||||
|
|
||||||
/**
|
/** Electronic Codebook (ECB) block mode. */
|
||||||
* Electronic Codebook (ECB) block mode.
|
|
||||||
*/
|
|
||||||
public static final int ECB = 0;
|
public static final int ECB = 0;
|
||||||
|
|
||||||
|
/** Cipher Block Chaining (CBC) block mode. */
|
||||||
|
public static final int CBC = 1;
|
||||||
|
|
||||||
|
/** Counter (CTR) block mode. */
|
||||||
|
public static final int CTR = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
@ -439,6 +447,10 @@ public abstract class KeyStoreKeyConstraints {
|
|||||||
switch (mode) {
|
switch (mode) {
|
||||||
case ECB:
|
case ECB:
|
||||||
return KeymasterDefs.KM_MODE_ECB;
|
return KeymasterDefs.KM_MODE_ECB;
|
||||||
|
case CBC:
|
||||||
|
return KeymasterDefs.KM_MODE_CBC;
|
||||||
|
case CTR:
|
||||||
|
return KeymasterDefs.KM_MODE_CTR;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unknown block mode: " + mode);
|
throw new IllegalArgumentException("Unknown block mode: " + mode);
|
||||||
}
|
}
|
||||||
@ -451,6 +463,10 @@ public abstract class KeyStoreKeyConstraints {
|
|||||||
switch (mode) {
|
switch (mode) {
|
||||||
case KeymasterDefs.KM_MODE_ECB:
|
case KeymasterDefs.KM_MODE_ECB:
|
||||||
return ECB;
|
return ECB;
|
||||||
|
case KeymasterDefs.KM_MODE_CBC:
|
||||||
|
return CBC;
|
||||||
|
case KeymasterDefs.KM_MODE_CTR:
|
||||||
|
return CTR;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unknown block mode: " + mode);
|
throw new IllegalArgumentException("Unknown block mode: " + mode);
|
||||||
}
|
}
|
||||||
@ -463,9 +479,29 @@ public abstract class KeyStoreKeyConstraints {
|
|||||||
switch (mode) {
|
switch (mode) {
|
||||||
case ECB:
|
case ECB:
|
||||||
return "ECB";
|
return "ECB";
|
||||||
|
case CBC:
|
||||||
|
return "CBC";
|
||||||
|
case CTR:
|
||||||
|
return "CTR";
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unknown block mode: " + mode);
|
throw new IllegalArgumentException("Unknown block mode: " + mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static @BlockModeEnum int fromJCAMode(String mode) {
|
||||||
|
String modeLower = mode.toLowerCase(Locale.US);
|
||||||
|
if ("ecb".equals(modeLower)) {
|
||||||
|
return ECB;
|
||||||
|
} else if ("cbc".equals(modeLower)) {
|
||||||
|
return CBC;
|
||||||
|
} else if ("ctr".equals(modeLower)) {
|
||||||
|
return CTR;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unknown block mode: " + mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
|
|||||||
|
|
||||||
private final KeyStore mKeyStore = KeyStore.getInstance();
|
private final KeyStore mKeyStore = KeyStore.getInstance();
|
||||||
private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm;
|
private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm;
|
||||||
private final @KeyStoreKeyConstraints.AlgorithmEnum Integer mDigest;
|
private final @KeyStoreKeyConstraints.DigestEnum Integer mDigest;
|
||||||
private final int mDefaultKeySizeBits;
|
private final int mDefaultKeySizeBits;
|
||||||
|
|
||||||
private KeyGeneratorSpec mSpec;
|
private KeyGeneratorSpec mSpec;
|
||||||
@ -76,12 +76,6 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
|
|||||||
if (mDigest != null) {
|
if (mDigest != null) {
|
||||||
args.addInt(KeymasterDefs.KM_TAG_DIGEST,
|
args.addInt(KeymasterDefs.KM_TAG_DIGEST,
|
||||||
KeyStoreKeyConstraints.Digest.toKeymaster(mDigest));
|
KeyStoreKeyConstraints.Digest.toKeymaster(mDigest));
|
||||||
}
|
|
||||||
if (mAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) {
|
|
||||||
if (mDigest == null) {
|
|
||||||
throw new IllegalStateException("Digest algorithm must be specified for key"
|
|
||||||
+ " algorithm " + KeyStoreKeyConstraints.Algorithm.toString(mAlgorithm));
|
|
||||||
}
|
|
||||||
Integer digestOutputSizeBytes =
|
Integer digestOutputSizeBytes =
|
||||||
KeyStoreKeyConstraints.Digest.getOutputSizeBytes(mDigest);
|
KeyStoreKeyConstraints.Digest.getOutputSizeBytes(mDigest);
|
||||||
if (digestOutputSizeBytes != null) {
|
if (digestOutputSizeBytes != null) {
|
||||||
@ -90,6 +84,12 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
|
|||||||
args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes);
|
args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (mAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) {
|
||||||
|
if (mDigest == null) {
|
||||||
|
throw new IllegalStateException("Digest algorithm must be specified for key"
|
||||||
|
+ " algorithm " + KeyStoreKeyConstraints.Algorithm.toString(mAlgorithm));
|
||||||
|
}
|
||||||
|
}
|
||||||
int keySizeBits = (spec.getKeySize() != null) ? spec.getKeySize() : mDefaultKeySizeBits;
|
int keySizeBits = (spec.getKeySize() != null) ? spec.getKeySize() : mDefaultKeySizeBits;
|
||||||
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits);
|
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits);
|
||||||
@KeyStoreKeyConstraints.PurposeEnum int purposes = (spec.getPurposes() != null)
|
@KeyStoreKeyConstraints.PurposeEnum int purposes = (spec.getPurposes() != null)
|
||||||
|
Reference in New Issue
Block a user