Merge "Expose RSA Cipher from Android Keystore Provider." into mnc-dev
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
index 03be759..aa2b946 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
@@ -43,13 +43,17 @@
private static final String PACKAGE_NAME = "android.security.keystore";
private static final String KEYSTORE_SECRET_KEY_CLASS_NAME =
PACKAGE_NAME + ".AndroidKeyStoreSecretKey";
+ private static final String KEYSTORE_PRIVATE_KEY_CLASS_NAME =
+ PACKAGE_NAME + ".AndroidKeyStorePrivateKey";
+ private static final String KEYSTORE_PUBLIC_KEY_CLASS_NAME =
+ PACKAGE_NAME + ".AndroidKeyStorePublicKey";
AndroidKeyStoreBCWorkaroundProvider() {
super("AndroidKeyStoreBCWorkaround",
1.0,
"Android KeyStore security provider to work around Bouncy Castle");
- // javax.crypto.Mac
+ // --------------------- javax.crypto.Mac
putMacImpl("HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA1");
put("Alg.Alias.Mac.1.2.840.113549.2.7", "HmacSHA1");
put("Alg.Alias.Mac.HMAC-SHA1", "HmacSHA1");
@@ -75,7 +79,7 @@
put("Alg.Alias.Mac.HMAC-SHA512", "HmacSHA512");
put("Alg.Alias.Mac.HMAC/SHA512", "HmacSHA512");
- // javax.crypto.Cipher
+ // --------------------- javax.crypto.Cipher
putSymmetricCipherImpl("AES/ECB/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$ECB$NoPadding");
putSymmetricCipherImpl("AES/ECB/PKCS7Padding",
@@ -88,6 +92,36 @@
putSymmetricCipherImpl("AES/CTR/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CTR$NoPadding");
+
+ putAsymmetricCipherImpl("RSA/ECB/NoPadding",
+ PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$NoPadding");
+ put("Alg.Alias.Cipher.RSA/None/NoPadding", "RSA/ECB/NoPadding");
+ putAsymmetricCipherImpl("RSA/ECB/PKCS1Padding",
+ PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$PKCS1Padding");
+ put("Alg.Alias.Cipher.RSA/None/PKCS1Padding", "RSA/ECB/PKCS1Padding");
+ putAsymmetricCipherImpl("RSA/ECB/OAEPPadding",
+ PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA1AndMGF1Padding");
+ put("Alg.Alias.Cipher.RSA/None/OAEPPadding", "RSA/ECB/OAEPPadding");
+ putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-1AndMGF1Padding",
+ PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA1AndMGF1Padding");
+ put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-1AndMGF1Padding",
+ "RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
+ putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-224AndMGF1Padding",
+ PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA224AndMGF1Padding");
+ put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-224AndMGF1Padding",
+ "RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
+ putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-256AndMGF1Padding",
+ PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA256AndMGF1Padding");
+ put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-256AndMGF1Padding",
+ "RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
+ putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-384AndMGF1Padding",
+ PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA384AndMGF1Padding");
+ put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-384AndMGF1Padding",
+ "RSA/ECB/OAEPWithSHA-384AndMGF1Padding");
+ putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-512AndMGF1Padding",
+ PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA512AndMGF1Padding");
+ put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-512AndMGF1Padding",
+ "RSA/ECB/OAEPWithSHA-512AndMGF1Padding");
}
private void putMacImpl(String algorithm, String implClass) {
@@ -99,4 +133,10 @@
put("Cipher." + transformation, implClass);
put("Cipher." + transformation + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME);
}
+
+ private void putAsymmetricCipherImpl(String transformation, String implClass) {
+ put("Cipher." + transformation, implClass);
+ put("Cipher." + transformation + " SupportedKeyClasses",
+ KEYSTORE_PRIVATE_KEY_CLASS_NAME + "|" + KEYSTORE_PUBLIC_KEY_CLASS_NAME);
+ }
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
index 3ad3c9d..fd9bdb8 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
@@ -66,7 +66,7 @@
*/
private IBinder mOperationToken;
private long mOperationHandle;
- private KeyStoreCryptoOperationChunkedStreamer mMainDataStreamer;
+ private KeyStoreCryptoOperationStreamer mMainDataStreamer;
/**
* Encountered exception which could not be immediately thrown because it was encountered inside
@@ -210,7 +210,6 @@
byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, getAdditionalEntropyAmountForBegin());
- KeymasterArguments keymasterOutputArgs = new KeymasterArguments();
OperationResult opResult = mKeyStore.begin(
mKey.getAlias(),
mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT,
@@ -247,9 +246,21 @@
}
loadAlgorithmSpecificParametersFromBeginResult(opResult.outParams);
- mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer(
+ mMainDataStreamer = createMainDataStreamer(mKeyStore, opResult.token);
+ }
+
+ /**
+ * 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(
- mKeyStore, opResult.token));
+ keyStore, operationToken));
}
@Override
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreKey.java
index 6098e5c..1751aa5 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKey.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKey.java
@@ -19,7 +19,7 @@
import java.security.Key;
/**
- * {@link Key} backed by AndroidKeyStore.
+ * {@link Key} backed by Android Keystore.
*
* @hide
*/
diff --git a/keystore/java/android/security/keystore/AndroidKeyStorePrivateKey.java b/keystore/java/android/security/keystore/AndroidKeyStorePrivateKey.java
new file mode 100644
index 0000000..b586ad4
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStorePrivateKey.java
@@ -0,0 +1,31 @@
+/*
+ * 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 java.security.PrivateKey;
+
+/**
+ * {@link PrivateKey} backed by Android Keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStorePrivateKey extends AndroidKeyStoreKey implements PrivateKey {
+
+ public AndroidKeyStorePrivateKey(String alias, String algorithm) {
+ super(alias, algorithm);
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java b/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java
new file mode 100644
index 0000000..8133d46
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java
@@ -0,0 +1,44 @@
+/*
+ * 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 java.security.PublicKey;
+
+/**
+ * {@link PublicKey} backed by Android Keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implements PublicKey {
+
+ private final byte[] mEncoded;
+
+ public AndroidKeyStorePublicKey(String alias, String algorithm, byte[] x509EncodedForm) {
+ super(alias, algorithm);
+ mEncoded = ArrayUtils.cloneIfNotEmpty(x509EncodedForm);
+ }
+
+ @Override
+ public String getFormat() {
+ return "X.509";
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ return ArrayUtils.cloneIfNotEmpty(mEncoded);
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java
new file mode 100644
index 0000000..f70c323
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java
@@ -0,0 +1,487 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.security.KeyStore;
+import android.security.KeyStoreException;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+
+import libcore.util.EmptyArray;
+
+import java.io.ByteArrayOutputStream;
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.ProviderException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+import java.security.spec.MGF1ParameterSpec;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherSpi;
+import javax.crypto.spec.OAEPParameterSpec;
+import javax.crypto.spec.PSource;
+
+/**
+ * Base class for {@link CipherSpi} providing Android KeyStore backed RSA encryption/decryption.
+ *
+ * @hide
+ */
+abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase {
+
+ /**
+ * Raw RSA cipher without any padding.
+ */
+ public static final class NoPadding extends AndroidKeyStoreRSACipherSpi {
+ public NoPadding() {
+ super(KeymasterDefs.KM_PAD_NONE);
+ }
+
+ @Override
+ protected void initAlgorithmSpecificParameters() throws InvalidKeyException {}
+
+ @Override
+ protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameterSpec params)
+ throws InvalidAlgorithmParameterException {
+ if (params != null) {
+ throw new InvalidAlgorithmParameterException(
+ "Unexpected parameters: " + params + ". No parameters supported");
+ }
+ }
+
+ @Override
+ protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
+ throws InvalidAlgorithmParameterException {
+
+ if (params != null) {
+ throw new InvalidAlgorithmParameterException(
+ "Unexpected parameters: " + params + ". No parameters supported");
+ }
+ }
+
+ @Override
+ protected AlgorithmParameters engineGetParameters() {
+ return null;
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForBegin() {
+ return 0;
+ }
+
+ @Override
+ @NonNull
+ protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
+ KeyStore keyStore, IBinder operationToken) {
+ if (isEncrypting()) {
+ // KeyStore's RSA encryption without padding expects the input to be of the same
+ // length as the modulus. We thus have to buffer all input to pad it with leading
+ // zeros.
+ return new ZeroPaddingEncryptionStreamer(
+ super.createMainDataStreamer(keyStore, operationToken),
+ getModulusSizeBytes());
+ } else {
+ return super.createMainDataStreamer(keyStore, operationToken);
+ }
+ }
+
+ /**
+ * Streamer which buffers all plaintext input, then pads it with leading zeros to match
+ * modulus size, and then sends it into KeyStore to obtain ciphertext.
+ */
+ private static class ZeroPaddingEncryptionStreamer
+ implements KeyStoreCryptoOperationStreamer {
+
+ private final KeyStoreCryptoOperationStreamer mDelegate;
+ private final int mModulusSizeBytes;
+ private final ByteArrayOutputStream mInputBuffer = new ByteArrayOutputStream();
+
+ private ZeroPaddingEncryptionStreamer(
+ KeyStoreCryptoOperationStreamer delegate,
+ int modulusSizeBytes) {
+ mDelegate = delegate;
+ mModulusSizeBytes = modulusSizeBytes;
+ }
+
+ @Override
+ public byte[] update(byte[] input, int inputOffset, int inputLength)
+ throws KeyStoreException {
+ if (inputLength > 0) {
+ mInputBuffer.write(input, inputOffset, inputLength);
+ }
+ return EmptyArray.BYTE;
+ }
+
+ @Override
+ public byte[] doFinal(byte[] input, int inputOffset, int inputLength)
+ throws KeyStoreException {
+ if (inputLength > 0) {
+ mInputBuffer.write(input, inputOffset, inputLength);
+ }
+ byte[] bufferedInput = mInputBuffer.toByteArray();
+ mInputBuffer.reset();
+ byte[] paddedInput;
+ if (bufferedInput.length < mModulusSizeBytes) {
+ // Pad input with leading zeros
+ paddedInput = new byte[mModulusSizeBytes];
+ System.arraycopy(
+ bufferedInput, 0,
+ paddedInput,
+ paddedInput.length - bufferedInput.length,
+ bufferedInput.length);
+ } else {
+ // No need to pad input
+ paddedInput = bufferedInput;
+ }
+ return mDelegate.doFinal(paddedInput, 0, paddedInput.length);
+ }
+ }
+ }
+
+ /**
+ * RSA cipher with PKCS#1 v1.5 encryption padding.
+ */
+ public static final class PKCS1Padding extends AndroidKeyStoreRSACipherSpi {
+ public PKCS1Padding() {
+ super(KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT);
+ }
+
+ @Override
+ protected void initAlgorithmSpecificParameters() throws InvalidKeyException {}
+
+ @Override
+ protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameterSpec params)
+ throws InvalidAlgorithmParameterException {
+ if (params != null) {
+ throw new InvalidAlgorithmParameterException(
+ "Unexpected parameters: " + params + ". No parameters supported");
+ }
+ }
+
+ @Override
+ protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
+ throws InvalidAlgorithmParameterException {
+
+ if (params != null) {
+ throw new InvalidAlgorithmParameterException(
+ "Unexpected parameters: " + params + ". No parameters supported");
+ }
+ }
+
+ @Override
+ protected AlgorithmParameters engineGetParameters() {
+ return null;
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForBegin() {
+ return (isEncrypting()) ? getModulusSizeBytes() : 0;
+ }
+ }
+
+ /**
+ * RSA cipher with OAEP encryption padding. Only SHA-1 based MGF1 is supported as MGF.
+ */
+ abstract static class OAEPWithMGF1Padding extends AndroidKeyStoreRSACipherSpi {
+
+ private static final String MGF_ALGORITGM_MGF1 = "MGF1";
+
+ private int mKeymasterDigest = -1;
+ private int mDigestOutputSizeBytes;
+
+ OAEPWithMGF1Padding(int keymasterDigest) {
+ super(KeymasterDefs.KM_PAD_RSA_OAEP);
+ mKeymasterDigest = keymasterDigest;
+ mDigestOutputSizeBytes =
+ (KeymasterUtils.getDigestOutputSizeBits(keymasterDigest) + 7) / 8;
+ }
+
+ @Override
+ protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {}
+
+ @Override
+ protected final void initAlgorithmSpecificParameters(
+ @Nullable AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException {
+ if (params == null) {
+ return;
+ }
+
+ if (!(params instanceof OAEPParameterSpec)) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported parameter spec: " + params
+ + ". Only OAEPParameterSpec supported");
+ }
+ OAEPParameterSpec spec = (OAEPParameterSpec) params;
+ if (!MGF_ALGORITGM_MGF1.equalsIgnoreCase(spec.getMGFAlgorithm())) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported MGF: " + spec.getMGFAlgorithm()
+ + ". Only " + MGF_ALGORITGM_MGF1 + " supported");
+ }
+ String jcaDigest = spec.getDigestAlgorithm();
+ int keymasterDigest;
+ try {
+ keymasterDigest = KeyProperties.Digest.toKeymaster(jcaDigest);
+ } catch (IllegalArgumentException e) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported digest: " + jcaDigest, e);
+ }
+ switch (keymasterDigest) {
+ case KeymasterDefs.KM_DIGEST_SHA1:
+ case KeymasterDefs.KM_DIGEST_SHA_2_224:
+ case KeymasterDefs.KM_DIGEST_SHA_2_256:
+ case KeymasterDefs.KM_DIGEST_SHA_2_384:
+ case KeymasterDefs.KM_DIGEST_SHA_2_512:
+ // Permitted.
+ break;
+ default:
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported digest: " + jcaDigest);
+ }
+ AlgorithmParameterSpec mgfParams = spec.getMGFParameters();
+ if (mgfParams == null) {
+ throw new InvalidAlgorithmParameterException("MGF parameters must be provided");
+ }
+ // Check whether MGF parameters match the OAEPParameterSpec
+ if (!(mgfParams instanceof MGF1ParameterSpec)) {
+ throw new InvalidAlgorithmParameterException("Unsupported MGF parameters"
+ + ": " + mgfParams + ". Only MGF1ParameterSpec supported");
+ }
+ MGF1ParameterSpec mgfSpec = (MGF1ParameterSpec) mgfParams;
+ String mgf1JcaDigest = mgfSpec.getDigestAlgorithm();
+ if (!KeyProperties.DIGEST_SHA1.equalsIgnoreCase(mgf1JcaDigest)) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported MGF1 digest: " + mgf1JcaDigest
+ + ". Only " + KeyProperties.DIGEST_SHA1 + " supported");
+ }
+ PSource pSource = spec.getPSource();
+ if (!(pSource instanceof PSource.PSpecified)) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported source of encoding input P: " + pSource
+ + ". Only pSpecifiedEmpty (PSource.PSpecified.DEFAULT) supported");
+ }
+ PSource.PSpecified pSourceSpecified = (PSource.PSpecified) pSource;
+ byte[] pSourceValue = pSourceSpecified.getValue();
+ if ((pSourceValue != null) && (pSourceValue.length > 0)) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported source of encoding input P: " + pSource
+ + ". Only pSpecifiedEmpty (PSource.PSpecified.DEFAULT) supported");
+ }
+ mKeymasterDigest = keymasterDigest;
+ mDigestOutputSizeBytes =
+ (KeymasterUtils.getDigestOutputSizeBits(keymasterDigest) + 7) / 8;
+ }
+
+ @Override
+ protected final void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
+ throws InvalidAlgorithmParameterException {
+ if (params == null) {
+ return;
+ }
+
+ OAEPParameterSpec spec;
+ try {
+ spec = params.getParameterSpec(OAEPParameterSpec.class);
+ } catch (InvalidParameterSpecException e) {
+ throw new InvalidAlgorithmParameterException("OAEP parameters required"
+ + ", but not found in parameters: " + params, e);
+ }
+ if (spec == null) {
+ throw new InvalidAlgorithmParameterException("OAEP parameters required"
+ + ", but not provided in parameters: " + params);
+ }
+ initAlgorithmSpecificParameters(spec);
+ }
+
+ @Override
+ protected final AlgorithmParameters engineGetParameters() {
+ OAEPParameterSpec spec =
+ new OAEPParameterSpec(
+ KeyProperties.Digest.fromKeymaster(mKeymasterDigest),
+ MGF_ALGORITGM_MGF1,
+ MGF1ParameterSpec.SHA1,
+ PSource.PSpecified.DEFAULT);
+ try {
+ AlgorithmParameters params = AlgorithmParameters.getInstance("OAEP");
+ params.init(spec);
+ return params;
+ } catch (NoSuchAlgorithmException e) {
+ throw new ProviderException(
+ "Failed to obtain OAEP AlgorithmParameters", e);
+ } catch (InvalidParameterSpecException e) {
+ throw new ProviderException(
+ "Failed to initialize OAEP AlgorithmParameters with an IV",
+ e);
+ }
+ }
+
+ @Override
+ protected final void addAlgorithmSpecificParametersToBegin(
+ KeymasterArguments keymasterArgs) {
+ super.addAlgorithmSpecificParametersToBegin(keymasterArgs);
+ keymasterArgs.addInt(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
+ }
+
+ @Override
+ protected final void loadAlgorithmSpecificParametersFromBeginResult(
+ @NonNull KeymasterArguments keymasterArgs) {
+ super.loadAlgorithmSpecificParametersFromBeginResult(keymasterArgs);
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForBegin() {
+ return (isEncrypting()) ? mDigestOutputSizeBytes : 0;
+ }
+ }
+
+ public static class OAEPWithSHA1AndMGF1Padding extends OAEPWithMGF1Padding {
+ public OAEPWithSHA1AndMGF1Padding() {
+ super(KeymasterDefs.KM_DIGEST_SHA1);
+ }
+ }
+
+ public static class OAEPWithSHA224AndMGF1Padding extends OAEPWithMGF1Padding {
+ public OAEPWithSHA224AndMGF1Padding() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_224);
+ }
+ }
+
+ public static class OAEPWithSHA256AndMGF1Padding extends OAEPWithMGF1Padding {
+ public OAEPWithSHA256AndMGF1Padding() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_256);
+ }
+ }
+
+ public static class OAEPWithSHA384AndMGF1Padding extends OAEPWithMGF1Padding {
+ public OAEPWithSHA384AndMGF1Padding() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_384);
+ }
+ }
+
+ public static class OAEPWithSHA512AndMGF1Padding extends OAEPWithMGF1Padding {
+ public OAEPWithSHA512AndMGF1Padding() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_512);
+ }
+ }
+
+ private final int mKeymasterPadding;
+
+ private int mModulusSizeBytes = -1;
+
+ AndroidKeyStoreRSACipherSpi(int keymasterPadding) {
+ mKeymasterPadding = keymasterPadding;
+ }
+
+ @Override
+ protected final void initKey(int opmode, Key key) throws InvalidKeyException {
+ if (key == null) {
+ throw new InvalidKeyException("Unsupported key: null");
+ }
+ if (!KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(key.getAlgorithm())) {
+ throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
+ + ". Only " + KeyProperties.KEY_ALGORITHM_RSA + " supported");
+ }
+ AndroidKeyStoreKey keystoreKey;
+ if (key instanceof AndroidKeyStorePrivateKey) {
+ keystoreKey = (AndroidKeyStoreKey) key;
+ } else if (key instanceof AndroidKeyStorePublicKey) {
+ keystoreKey = (AndroidKeyStoreKey) key;
+ } else {
+ throw new InvalidKeyException("Unsupported key type: " + key);
+ }
+
+ if (keystoreKey instanceof PrivateKey) {
+ if ((opmode != Cipher.DECRYPT_MODE) && (opmode != Cipher.UNWRAP_MODE)) {
+ throw new InvalidKeyException("Private key cannot be used with opmode: " + opmode
+ + ". Only DECRYPT_MODE and UNWRAP_MODE supported");
+ }
+ } else {
+ if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.WRAP_MODE)) {
+ throw new InvalidKeyException("Public key cannot be used with opmode: " + opmode
+ + ". Only ENCRYPT_MODE and WRAP_MODE supported");
+ }
+ }
+
+ KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
+ int errorCode = getKeyStore().getKeyCharacteristics(
+ keystoreKey.getAlias(), null, null, keyCharacteristics);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw getKeyStore().getInvalidKeyException(keystoreKey.getAlias(), errorCode);
+ }
+ int keySizeBits = keyCharacteristics.getInt(KeymasterDefs.KM_TAG_KEY_SIZE, -1);
+ if (keySizeBits == -1) {
+ throw new InvalidKeyException("Size of key not known");
+ }
+ mModulusSizeBytes = (keySizeBits + 7) / 8;
+
+ setKey(keystoreKey);
+ }
+
+ @Override
+ protected final void resetAll() {
+ mModulusSizeBytes = -1;
+ super.resetAll();
+ }
+
+ @Override
+ protected final void resetWhilePreservingInitState() {
+ super.resetWhilePreservingInitState();
+ }
+
+ @Override
+ protected void addAlgorithmSpecificParametersToBegin(
+ @NonNull KeymasterArguments keymasterArgs) {
+ keymasterArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
+ keymasterArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
+ }
+
+ @Override
+ protected void loadAlgorithmSpecificParametersFromBeginResult(
+ @NonNull KeymasterArguments keymasterArgs) {
+ }
+
+ @Override
+ protected final int engineGetBlockSize() {
+ // Not a block cipher, according to the RI
+ return 0;
+ }
+
+ @Override
+ protected final byte[] engineGetIV() {
+ // IV never used
+ return null;
+ }
+
+ @Override
+ protected final int engineGetOutputSize(int inputLen) {
+ return getModulusSizeBytes();
+ }
+
+ protected final int getModulusSizeBytes() {
+ if (mModulusSizeBytes == -1) {
+ throw new IllegalStateException("Not initialized");
+ }
+ return mModulusSizeBytes;
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSAPublicKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSAPublicKey.java
new file mode 100644
index 0000000..36bc997b
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSAPublicKey.java
@@ -0,0 +1,55 @@
+/*
+ * 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 java.math.BigInteger;
+import java.security.interfaces.RSAPublicKey;
+
+/**
+ * {@link RSAPublicKey} backed by Android Keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreRSAPublicKey extends AndroidKeyStorePublicKey implements RSAPublicKey {
+ private final BigInteger mModulus;
+ private final BigInteger mPublicExponent;
+
+ public AndroidKeyStoreRSAPublicKey(String alias, byte[] x509EncodedForm, BigInteger modulus,
+ BigInteger publicExponent) {
+ super(alias, "RSA", x509EncodedForm);
+ mModulus = modulus;
+ mPublicExponent = publicExponent;
+ }
+
+ public AndroidKeyStoreRSAPublicKey(String alias, RSAPublicKey info) {
+ this(alias, info.getEncoded(), info.getModulus(), info.getPublicExponent());
+ if (!"X.509".equalsIgnoreCase(info.getFormat())) {
+ throw new IllegalArgumentException(
+ "Unsupported key export format: " + info.getFormat());
+ }
+ }
+
+ @Override
+ public BigInteger getModulus() {
+ return mModulus;
+ }
+
+ @Override
+ public BigInteger getPublicExponent() {
+ return mPublicExponent;
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.java
index f75516b..af354ab 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.java
@@ -19,7 +19,7 @@
import javax.crypto.SecretKey;
/**
- * {@link SecretKey} backed by keystore.
+ * {@link SecretKey} backed by Android Keystore.
*
* @hide
*/
diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java
index 7d57e5f..47b4996 100644
--- a/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java
+++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java
@@ -44,12 +44,12 @@
*
* @hide
*/
-public class KeyStoreCryptoOperationChunkedStreamer {
+class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationStreamer {
/**
* Bidirectional chunked data stream over a KeyStore crypto operation.
*/
- public interface Stream {
+ interface Stream {
/**
* Returns the result of the KeyStore {@code update} operation or null if keystore couldn't
* be reached.
@@ -66,12 +66,11 @@
// Binder buffer is about 1MB, but it's shared between all active transactions of the process.
// Thus, it's safer to use a much smaller upper bound.
private static final int DEFAULT_MAX_CHUNK_SIZE = 64 * 1024;
- private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
private final Stream mKeyStoreStream;
private final int mMaxChunkSize;
- private byte[] mBuffered = EMPTY_BYTE_ARRAY;
+ private byte[] mBuffered = EmptyArray.BYTE;
private int mBufferedOffset;
private int mBufferedLength;
@@ -84,10 +83,11 @@
mMaxChunkSize = maxChunkSize;
}
+ @Override
public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException {
if (inputLength == 0) {
// No input provided
- return EMPTY_BYTE_ARRAY;
+ return EmptyArray.BYTE;
}
ByteArrayOutputStream bufferedOutput = null;
@@ -129,7 +129,7 @@
if (opResult.inputConsumed == chunk.length) {
// The whole chunk was consumed
- mBuffered = EMPTY_BYTE_ARRAY;
+ mBuffered = EmptyArray.BYTE;
mBufferedOffset = 0;
mBufferedLength = 0;
} else if (opResult.inputConsumed == 0) {
@@ -185,17 +185,18 @@
if (bufferedOutput == null) {
// No output produced
- return EMPTY_BYTE_ARRAY;
+ return EmptyArray.BYTE;
} else {
return bufferedOutput.toByteArray();
}
}
+ @Override
public byte[] doFinal(byte[] input, int inputOffset, int inputLength)
throws KeyStoreException {
if (inputLength == 0) {
// No input provided -- simplify the rest of the code
- input = EMPTY_BYTE_ARRAY;
+ input = EmptyArray.BYTE;
inputOffset = 0;
}
diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java
new file mode 100644
index 0000000..2fb8f20
--- /dev/null
+++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java
@@ -0,0 +1,39 @@
+/*
+ * 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.security.KeyStore;
+import android.security.KeyStoreException;
+
+/**
+ * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's
+ * {@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 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.
+ *
+ * @hide
+ */
+interface KeyStoreCryptoOperationStreamer {
+ byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException;
+ byte[] doFinal(byte[] input, int inputOffset, int inputLength) throws KeyStoreException;
+}