| /* |
| * 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; |
| } |
| } |