From 5552c89fa9cb5ac72edbbcb5a71ef14a07f5ea11 Mon Sep 17 00:00:00 2001 From: Alex Klyubin Date: Thu, 4 Jun 2015 14:45:54 -0700 Subject: RSA encrypt with private key in Android Keystore. This adds support for RSA encryption using private key and no padding. This mode of operation is needed because JCA does not offer an RSA Signature primitive that does not apply padding. Bug: 18088752 Bug: 20912868 Change-Id: I0b481b4c19916f601aa270fada5eabfb12987e8d --- .../keystore/AndroidKeyStoreCipherSpiBase.java | 28 +++++++-- .../keystore/AndroidKeyStoreRSACipherSpi.java | 69 +++++++++++++++++++--- 2 files changed, 85 insertions(+), 12 deletions(-) (limited to 'keystore') diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java index fd9bdb8..0e8d03e 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java @@ -56,6 +56,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after // doFinal finishes. private boolean mEncrypting; + private int mKeymasterPurposeOverride = -1; private AndroidKeyStoreKey mKey; private SecureRandom mRng; @@ -165,6 +166,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor mKeyStore.abort(operationToken); } mEncrypting = false; + mKeymasterPurposeOverride = -1; mKey = null; mRng = null; mOperationToken = null; @@ -210,9 +212,16 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( mRng, getAdditionalEntropyAmountForBegin()); + int purpose; + if (mKeymasterPurposeOverride != -1) { + purpose = mKeymasterPurposeOverride; + } else { + purpose = mEncrypting + ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT; + } OperationResult opResult = mKeyStore.begin( mKey.getAlias(), - mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT, + purpose, true, // permit aborting this operation if keystore runs out of resources keymasterInputArgs, additionalEntropy); @@ -346,11 +355,11 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor } catch (KeyStoreException e) { switch (e.getErrorCode()) { case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH: - throw new IllegalBlockSizeException(); + throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT: - throw new BadPaddingException(); + throw (BadPaddingException) new BadPaddingException().initCause(e); case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED: - throw new AEADBadTagException(); + throw (AEADBadTagException) new AEADBadTagException().initCause(e); default: throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); } @@ -437,6 +446,17 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor } /** + * Overrides the default purpose/type of the crypto operation. + */ + protected final void setKeymasterPurposeOverride(int keymasterPurpose) { + mKeymasterPurposeOverride = keymasterPurpose; + } + + protected final int getKeymasterPurposeOverride() { + return mKeymasterPurposeOverride; + } + + /** * Returns {@code true} if this cipher is initialized for encryption, {@code false} if this * cipher is initialized for decryption. */ diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java index f70c323..5643caf 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java @@ -60,6 +60,13 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase } @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 @@ -152,8 +159,11 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase paddedInput.length - bufferedInput.length, bufferedInput.length); } else { - // No need to pad input - paddedInput = bufferedInput; + // 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); } @@ -412,14 +422,46 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase } 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"); + // 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 { - 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"); + // Public key + switch (opmode) { + case Cipher.ENCRYPT_MODE: + case Cipher.WRAP_MODE: + // Permitted + return; + 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); } } @@ -438,6 +480,10 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase setKey(keystoreKey); } + protected boolean isEncryptingUsingPrivateKeyPermitted() { + return false; + } + @Override protected final void resetAll() { mModulusSizeBytes = -1; @@ -454,6 +500,13 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase @NonNull KeymasterArguments keymasterArgs) { keymasterArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA); keymasterArgs.addInt(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.addInt(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE); + } } @Override -- cgit v1.1