| /* |
| * 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 boolean isEncryptingUsingPrivateKeyPermitted() { |
| // RSA encryption with no padding using private key is is a way to implement raw RSA |
| // signatures. We have to support this. |
| 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; |
| } |
| |
| @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 long mConsumedInputSizeBytes; |
| |
| 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); |
| mConsumedInputSizeBytes += inputLength; |
| } |
| return EmptyArray.BYTE; |
| } |
| |
| @Override |
| public byte[] doFinal(byte[] input, int inputOffset, int inputLength, |
| byte[] additionalEntropy) |
| throws KeyStoreException { |
| if (inputLength > 0) { |
| mConsumedInputSizeBytes += inputLength; |
| 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 { |
| // RI throws BadPaddingException in this scenario. INVALID_ARGUMENT below will |
| // be translated into BadPaddingException. |
| throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_ARGUMENT, |
| "Message size (" + bufferedInput.length + " bytes) must be smaller than" |
| + " modulus (" + mModulusSizeBytes + " bytes)"); |
| } |
| return mDelegate.doFinal(paddedInput, 0, paddedInput.length, additionalEntropy); |
| } |
| |
| @Override |
| public long getConsumedInputSizeBytes() { |
| return mConsumedInputSizeBytes; |
| } |
| |
| @Override |
| public long getProducedOutputSizeBytes() { |
| return mDelegate.getProducedOutputSizeBytes(); |
| } |
| } |
| } |
| |
| /** |
| * 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 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 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: |
| if (!isEncryptingUsingPrivateKeyPermitted()) { |
| throw new InvalidKeyException( |
| "RSA private keys cannot be used with Cipher.ENCRYPT_MODE" |
| + ". Only RSA public keys supported for this mode"); |
| } |
| // JCA doesn't provide a way to generate raw RSA signatures (with arbitrary |
| // padding). Thus, encrypting with private key is used instead. |
| setKeymasterPurposeOverride(KeymasterDefs.KM_PURPOSE_SIGN); |
| break; |
| case Cipher.WRAP_MODE: |
| throw new InvalidKeyException( |
| "RSA private keys cannot be used with Cipher.WRAP_MODE" |
| + ". 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 opmode: " |
| + opmode + ". Only RSA private keys supported for this opmode."); |
| // break; |
| default: |
| throw new InvalidKeyException( |
| "RSA public keys cannot be used with opmode: " + opmode); |
| } |
| } |
| |
| KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); |
| int errorCode = getKeyStore().getKeyCharacteristics( |
| keystoreKey.getAlias(), null, null, keyCharacteristics); |
| if (errorCode != KeyStore.NO_ERROR) { |
| throw getKeyStore().getInvalidKeyException(keystoreKey.getAlias(), 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); |
| } |
| |
| protected boolean isEncryptingUsingPrivateKeyPermitted() { |
| return false; |
| } |
| |
| @Override |
| protected final void resetAll() { |
| mModulusSizeBytes = -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); |
| keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding); |
| 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; |
| } |
| } |