blob: 2ae68fa80117383cbdb3c20f64ccfe4cd0148fe1 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.security.keystore;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.security.KeyStore;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
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 boolean adjustConfigForEncryptingWithPrivateKey() {
// RSA encryption with no padding using private key is a way to implement raw RSA
// signatures which JCA does not expose via Signature. We thus have to support this.
setKeymasterPurposeOverride(KeymasterDefs.KM_PURPOSE_SIGN);
return true;
}
@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
protected final int getAdditionalEntropyAmountForFinish() {
return 0;
}
}
/**
* 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 boolean adjustConfigForEncryptingWithPrivateKey() {
// RSA encryption with PCKS#1 padding using private key is a way to implement RSA
// signatures with PKCS#1 padding. We have to support this for legacy reasons.
setKeymasterPurposeOverride(KeymasterDefs.KM_PURPOSE_SIGN);
setKeymasterPaddingOverride(KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN);
return true;
}
@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
protected final int getAdditionalEntropyAmountForFinish() {
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.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
}
@Override
protected final void loadAlgorithmSpecificParametersFromBeginResult(
@NonNull KeymasterArguments keymasterArgs) {
super.loadAlgorithmSpecificParametersFromBeginResult(keymasterArgs);
}
@Override
protected final int getAdditionalEntropyAmountForBegin() {
return 0;
}
@Override
protected final int getAdditionalEntropyAmountForFinish() {
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 mKeymasterPaddingOverride;
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) {
// Private key
switch (opmode) {
case Cipher.DECRYPT_MODE:
case Cipher.UNWRAP_MODE:
// Permitted
break;
case Cipher.ENCRYPT_MODE:
case Cipher.WRAP_MODE:
if (!adjustConfigForEncryptingWithPrivateKey()) {
throw new InvalidKeyException(
"RSA private keys cannot be used with " + opmodeToString(opmode)
+ " and padding "
+ KeyProperties.EncryptionPadding.fromKeymaster(mKeymasterPadding)
+ ". Only RSA public keys supported for this mode");
}
break;
default:
throw new InvalidKeyException(
"RSA private keys cannot be used with opmode: " + opmode);
}
} else {
// Public key
switch (opmode) {
case Cipher.ENCRYPT_MODE:
case Cipher.WRAP_MODE:
// Permitted
break;
case Cipher.DECRYPT_MODE:
case Cipher.UNWRAP_MODE:
throw new InvalidKeyException(
"RSA public keys cannot be used with " + opmodeToString(opmode)
+ " and padding "
+ KeyProperties.EncryptionPadding.fromKeymaster(mKeymasterPadding)
+ ". Only RSA private keys supported for this opmode.");
// break;
default:
throw new InvalidKeyException(
"RSA public keys cannot be used with " + opmodeToString(opmode));
}
}
KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
int errorCode = getKeyStore().getKeyCharacteristics(
keystoreKey.getAlias(), null, null, keystoreKey.getUid(), keyCharacteristics);
if (errorCode != KeyStore.NO_ERROR) {
throw getKeyStore().getInvalidKeyException(
keystoreKey.getAlias(), keystoreKey.getUid(), errorCode);
}
long keySizeBits = keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, -1);
if (keySizeBits == -1) {
throw new InvalidKeyException("Size of key not known");
} else if (keySizeBits > Integer.MAX_VALUE) {
throw new InvalidKeyException("Key too large: " + keySizeBits + " bits");
}
mModulusSizeBytes = (int) ((keySizeBits + 7) / 8);
setKey(keystoreKey);
}
/**
* Adjusts the configuration of this cipher for encrypting using the private key.
*
* <p>The default implementation does nothing and refuses to adjust the configuration.
*
* @return {@code true} if the configuration has been adjusted, {@code false} if encrypting
* using private key is not permitted for this cipher.
*/
protected boolean adjustConfigForEncryptingWithPrivateKey() {
return false;
}
@Override
protected final void resetAll() {
mModulusSizeBytes = -1;
mKeymasterPaddingOverride = -1;
super.resetAll();
}
@Override
protected final void resetWhilePreservingInitState() {
super.resetWhilePreservingInitState();
}
@Override
protected void addAlgorithmSpecificParametersToBegin(
@NonNull KeymasterArguments keymasterArgs) {
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
int keymasterPadding = getKeymasterPaddingOverride();
if (keymasterPadding == -1) {
keymasterPadding = mKeymasterPadding;
}
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, keymasterPadding);
int purposeOverride = getKeymasterPurposeOverride();
if ((purposeOverride != -1)
&& ((purposeOverride == KeymasterDefs.KM_PURPOSE_SIGN)
|| (purposeOverride == KeymasterDefs.KM_PURPOSE_VERIFY))) {
// Keymaster sign/verify requires digest to be specified. For raw sign/verify it's NONE.
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE);
}
}
@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;
}
/**
* Overrides the default padding of the crypto operation.
*/
protected final void setKeymasterPaddingOverride(int keymasterPadding) {
mKeymasterPaddingOverride = keymasterPadding;
}
protected final int getKeymasterPaddingOverride() {
return mKeymasterPaddingOverride;
}
}