am 20a88152: Merge "Expose RSA Cipher from Android Keystore Provider." into mnc-dev
* commit '20a88152a56316e5ebe362887de0ec63370e6467': Expose RSA Cipher from Android Keystore Provider.
This commit is contained in:
@@ -43,13 +43,17 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider {
|
||||
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 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider {
|
||||
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 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider {
|
||||
|
||||
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 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
|
||||
*/
|
||||
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 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
|
||||
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 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -19,7 +19,7 @@ package android.security.keystore;
|
||||
import java.security.Key;
|
||||
|
||||
/**
|
||||
* {@link Key} backed by AndroidKeyStore.
|
||||
* {@link Key} backed by Android Keystore.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ package android.security.keystore;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
/**
|
||||
* {@link SecretKey} backed by keystore.
|
||||
* {@link SecretKey} backed by Android Keystore.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
|
||||
@@ -44,12 +44,12 @@ import java.io.IOException;
|
||||
*
|
||||
* @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 @@ public class KeyStoreCryptoOperationChunkedStreamer {
|
||||
// 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 @@ public class KeyStoreCryptoOperationChunkedStreamer {
|
||||
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 @@ public class KeyStoreCryptoOperationChunkedStreamer {
|
||||
|
||||
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 @@ public class KeyStoreCryptoOperationChunkedStreamer {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user