summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--keystore/java/android/security/AndroidKeyStore.java20
-rw-r--r--keystore/java/android/security/AndroidKeyStoreProvider.java27
-rw-r--r--keystore/java/android/security/KeyStoreCipherSpi.java540
-rw-r--r--keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java177
-rw-r--r--keystore/java/android/security/KeyStoreHmacSpi.java27
-rw-r--r--keystore/java/android/security/KeyStoreKeyConstraints.java64
-rw-r--r--keystore/java/android/security/KeyStoreKeyGeneratorSpi.java14
7 files changed, 758 insertions, 111 deletions
diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java
index 1d16ca1..846d1f1 100644
--- a/keystore/java/android/security/AndroidKeyStore.java
+++ b/keystore/java/android/security/AndroidKeyStore.java
@@ -457,7 +457,7 @@ public class AndroidKeyStore extends KeyStoreSpi {
String keyAlgorithmString = key.getAlgorithm();
@KeyStoreKeyConstraints.AlgorithmEnum int keyAlgorithm;
- @KeyStoreKeyConstraints.AlgorithmEnum Integer digest;
+ @KeyStoreKeyConstraints.DigestEnum Integer digest;
try {
keyAlgorithm =
KeyStoreKeyConstraints.Algorithm.fromJCASecretKeyAlgorithm(keyAlgorithmString);
@@ -493,12 +493,6 @@ public class AndroidKeyStore extends KeyStoreSpi {
if (digest != null) {
args.addInt(KeymasterDefs.KM_TAG_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 =
KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest);
if (digestOutputSizeBytes != null) {
@@ -507,6 +501,12 @@ public class AndroidKeyStore extends KeyStoreSpi {
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)
? 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.
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);
String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias;
int errorCode = mKeyStore.importKey(
diff --git a/keystore/java/android/security/AndroidKeyStoreProvider.java b/keystore/java/android/security/AndroidKeyStoreProvider.java
index 7313c48..6cf9b7a 100644
--- a/keystore/java/android/security/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/AndroidKeyStoreProvider.java
@@ -41,7 +41,30 @@ public class AndroidKeyStoreProvider extends Provider {
put("KeyGenerator.HmacSHA256", KeyStoreKeyGeneratorSpi.HmacSHA256.class.getName());
// javax.crypto.Mac
- put("Mac.HmacSHA256", KeyStoreHmacSpi.HmacSHA256.class.getName());
- put("Mac.HmacSHA256 SupportedKeyClasses", KeyStoreSecretKey.class.getName());
+ putMacImpl("HmacSHA256", KeyStoreHmacSpi.HmacSHA256.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());
}
}
diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java
new file mode 100644
index 0000000..6863236
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreCipherSpi.java
@@ -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");
+ }
+ }
+ }
+}
diff --git a/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java
index a37ddce..6385947 100644
--- a/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java
+++ b/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java
@@ -1,5 +1,6 @@
package android.security;
+import android.os.IBinder;
import android.security.keymaster.OperationResult;
import java.io.ByteArrayOutputStream;
@@ -10,32 +11,36 @@ import java.io.IOException;
* {@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
- * update and finish operations. Firstly, KeyStore's update and finish operations can consume only a
- * limited amount of data in one go because the operations are marshalled via Binder. Secondly, the
- * update operation may consume less data than provided, in which case the caller has to buffer
- * the remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and
+ * update and finish operations. Firstly, KeyStore's update operation can consume only a limited
+ * amount of data in one go because the operations are marshalled via Binder. Secondly, the update
+ * operation may consume less data than provided, in which case the caller has to buffer the
+ * 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
* various JCA crypto primitives.
*
- * <p>KeyStore operation through which data is streamed is abstracted away as
- * {@link KeyStoreOperation} to avoid having this class deal with operation tokens and occasional
- * additional parameters to update and final operations.
+ * <p>Bidirectional chunked streaming of data via a KeyStore crypto operation is abstracted away as
+ * a {@link Stream} to avoid having this class deal with operation tokens and occasional additional
+ * parameters to {@code update} and {@code final} operations.
*
* @hide
*/
public class KeyStoreCryptoOperationChunkedStreamer {
- public interface KeyStoreOperation {
+
+ /**
+ * Bidirectional chunked data stream over a KeyStore crypto operation.
+ */
+ public interface Stream {
/**
- * Returns the result of the KeyStore update operation or null if keystore couldn't be
- * reached.
+ * Returns the result of the KeyStore {@code update} operation or null if keystore couldn't
+ * be reached.
*/
OperationResult update(byte[] input);
/**
- * Returns the result of the KeyStore finish operation or null if keystore couldn't be
- * reached.
+ * Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't
+ * be reached.
*/
- OperationResult finish(byte[] input);
+ OperationResult finish();
}
// 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 byte[] EMPTY_BYTE_ARRAY = new byte[0];
- private final KeyStoreOperation mKeyStoreOperation;
+ private final Stream mKeyStoreStream;
private final int mMaxChunkSize;
private byte[] mBuffered = EMPTY_BYTE_ARRAY;
private int mBufferedOffset;
private int mBufferedLength;
- public KeyStoreCryptoOperationChunkedStreamer(KeyStoreOperation operation) {
+ public KeyStoreCryptoOperationChunkedStreamer(Stream operation) {
this(operation, DEFAULT_MAX_CHUNK_SIZE);
}
- public KeyStoreCryptoOperationChunkedStreamer(KeyStoreOperation operation, int maxChunkSize) {
- mKeyStoreOperation = operation;
+ public KeyStoreCryptoOperationChunkedStreamer(Stream operation, int maxChunkSize) {
+ mKeyStoreStream = operation;
mMaxChunkSize = maxChunkSize;
}
@@ -73,10 +78,9 @@ public class KeyStoreCryptoOperationChunkedStreamer {
if ((mBufferedLength + inputLength) > mMaxChunkSize) {
// Too much input for one chunk -- extract one max-sized chunk and feed it into the
// update operation.
- chunk = new byte[mMaxChunkSize];
- System.arraycopy(mBuffered, mBufferedOffset, chunk, 0, mBufferedLength);
- inputBytesInChunk = chunk.length - mBufferedLength;
- System.arraycopy(input, inputOffset, chunk, mBufferedLength, inputBytesInChunk);
+ inputBytesInChunk = mMaxChunkSize - mBufferedLength;
+ chunk = concat(mBuffered, mBufferedOffset, mBufferedLength,
+ input, inputOffset, inputBytesInChunk);
} else {
// All of available input fits into one chunk.
if ((mBufferedLength == 0) && (inputOffset == 0)
@@ -87,17 +91,16 @@ public class KeyStoreCryptoOperationChunkedStreamer {
inputBytesInChunk = input.length;
} else {
// Need to combine buffered data with input data into one array.
- chunk = new byte[mBufferedLength + inputLength];
inputBytesInChunk = inputLength;
- System.arraycopy(mBuffered, mBufferedOffset, chunk, 0, mBufferedLength);
- System.arraycopy(input, inputOffset, chunk, mBufferedLength, inputLength);
+ chunk = concat(mBuffered, mBufferedOffset, mBufferedLength,
+ input, inputOffset, inputBytesInChunk);
}
}
// Update input array references to reflect that some of its bytes are now in mBuffered.
inputOffset += inputBytesInChunk;
inputLength -= inputBytesInChunk;
- OperationResult opResult = mKeyStoreOperation.update(chunk);
+ OperationResult opResult = mKeyStoreStream.update(chunk);
if (opResult == null) {
throw new KeyStoreConnectException();
} else if (opResult.resultCode != KeyStore.NO_ERROR) {
@@ -176,53 +179,115 @@ public class KeyStoreCryptoOperationChunkedStreamer {
inputOffset = 0;
}
- byte[] updateOutput = null;
- if ((mBufferedLength + inputLength) > mMaxChunkSize) {
- updateOutput = update(input, inputOffset, inputLength);
- inputOffset += inputLength;
- inputLength = 0;
+ // Flush all buffered input and provided input into keystore/keymaster.
+ byte[] output = update(input, inputOffset, inputLength);
+ output = concat(output, flush());
+
+ OperationResult opResult = mKeyStoreStream.finish();
+ if (opResult == null) {
+ throw new KeyStoreConnectException();
+ } else if (opResult.resultCode != KeyStore.NO_ERROR) {
+ throw KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode);
}
- // All of available input fits into one chunk.
- byte[] finalChunk;
- 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);
+ return concat(output, opResult.output);
+ }
+
+ /**
+ * Passes all of buffered input into the the KeyStore operation (via the {@code update}
+ * operation) and returns output.
+ */
+ public byte[] flush() throws KeymasterException {
+ if (mBufferedLength <= 0) {
+ return EMPTY_BYTE_ARRAY;
}
+
+ byte[] chunk = subarray(mBuffered, mBufferedOffset, mBufferedLength);
mBuffered = EMPTY_BYTE_ARRAY;
mBufferedLength = 0;
mBufferedOffset = 0;
- OperationResult opResult = mKeyStoreOperation.finish(finalChunk);
+ 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 != finalChunk.length) {
- throw new CryptoOperationException("Unexpected number of bytes consumed by finish: "
- + opResult.inputConsumed + " instead of " + finalChunk.length);
+ 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 the concatenation of the output of update and finish.
- byte[] result;
- byte[] finishOutput = opResult.output;
- if ((updateOutput == null) || (updateOutput.length == 0)) {
- result = finishOutput;
- } else if ((finishOutput == null) || (finishOutput.length == 0)) {
- result = updateOutput;
+ 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 {
- result = new byte[updateOutput.length + finishOutput.length];
- System.arraycopy(updateOutput, 0, result, 0, updateOutput.length);
- System.arraycopy(finishOutput, 0, result, updateOutput.length, finishOutput.length);
+ 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;
}
}
diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java
index 3080d7b..e3c98b8 100644
--- a/keystore/java/android/security/KeyStoreHmacSpi.java
+++ b/keystore/java/android/security/KeyStoreHmacSpi.java
@@ -93,7 +93,8 @@ public abstract class KeyStoreHmacSpi extends MacSpi {
throw new CryptoOperationException("Keystore returned null operation token");
}
mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer(
- new KeyStoreStreamingConsumer(mKeyStore, mOperationToken));
+ new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
+ mKeyStore, mOperationToken));
}
@Override
@@ -147,28 +148,4 @@ public abstract class KeyStoreHmacSpi extends MacSpi {
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);
- }
- }
}
diff --git a/keystore/java/android/security/KeyStoreKeyConstraints.java b/keystore/java/android/security/KeyStoreKeyConstraints.java
index b5e2436..58ea388 100644
--- a/keystore/java/android/security/KeyStoreKeyConstraints.java
+++ b/keystore/java/android/security/KeyStoreKeyConstraints.java
@@ -222,16 +222,6 @@ public abstract class KeyStoreKeyConstraints {
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)
@@ -306,6 +296,20 @@ public abstract class KeyStoreKeyConstraints {
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)
@@ -418,7 +422,7 @@ public abstract class KeyStoreKeyConstraints {
}
@Retention(RetentionPolicy.SOURCE)
- @IntDef({BlockMode.ECB})
+ @IntDef({BlockMode.ECB, BlockMode.CBC, BlockMode.CTR})
public @interface BlockModeEnum {}
/**
@@ -427,11 +431,15 @@ public abstract class KeyStoreKeyConstraints {
public static abstract class BlockMode {
private BlockMode() {}
- /**
- * Electronic Codebook (ECB) block mode.
- */
+ /** Electronic Codebook (ECB) block mode. */
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
*/
@@ -439,6 +447,10 @@ public abstract class KeyStoreKeyConstraints {
switch (mode) {
case ECB:
return KeymasterDefs.KM_MODE_ECB;
+ case CBC:
+ return KeymasterDefs.KM_MODE_CBC;
+ case CTR:
+ return KeymasterDefs.KM_MODE_CTR;
default:
throw new IllegalArgumentException("Unknown block mode: " + mode);
}
@@ -451,6 +463,10 @@ public abstract class KeyStoreKeyConstraints {
switch (mode) {
case KeymasterDefs.KM_MODE_ECB:
return ECB;
+ case KeymasterDefs.KM_MODE_CBC:
+ return CBC;
+ case KeymasterDefs.KM_MODE_CTR:
+ return CTR;
default:
throw new IllegalArgumentException("Unknown block mode: " + mode);
}
@@ -463,9 +479,29 @@ public abstract class KeyStoreKeyConstraints {
switch (mode) {
case ECB:
return "ECB";
+ case CBC:
+ return "CBC";
+ case CTR:
+ return "CTR";
default:
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);
+ }
+ }
}
}
diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
index f1f9436..3e5b5c0 100644
--- a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
@@ -35,7 +35,7 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
private final KeyStore mKeyStore = KeyStore.getInstance();
private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm;
- private final @KeyStoreKeyConstraints.AlgorithmEnum Integer mDigest;
+ private final @KeyStoreKeyConstraints.DigestEnum Integer mDigest;
private final int mDefaultKeySizeBits;
private KeyGeneratorSpec mSpec;
@@ -76,12 +76,6 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
if (mDigest != null) {
args.addInt(KeymasterDefs.KM_TAG_DIGEST,
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 =
KeyStoreKeyConstraints.Digest.getOutputSizeBytes(mDigest);
if (digestOutputSizeBytes != null) {
@@ -90,6 +84,12 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
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;
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits);
@KeyStoreKeyConstraints.PurposeEnum int purposes = (spec.getPurposes() != null)