Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3ab1f040 authored by Alex Klyubin's avatar Alex Klyubin
Browse files

Raw RSA Cipher relies on keymaster for padding and range checks.

This makes Android Keystore's RSA/ECB/NoPadding Cipher implementation
rely on the underlying keystore/keymaster to left-pad the input (if
necessary) and to reject invalid input. Prior to this change the
Cipher implementation attempted to do it itself, but wasn't doing it
right anyway.

This fixes a regression where Android Keystore's raw RSA Cipher
("RSA/ECB/NoPadding") refused to encrypt plaintexts of the same length
(in bytes) as RSA modulus which were nevertheless numerically smaller
than the RSA modulus.

Bug: 22599805
Change-Id: I591a8115a574eaf8f6075f29b50d93a87532c5eb
parent ada70be8
Loading
Loading
Loading
Loading
+0 −90
Original line number Diff line number Diff line
@@ -18,16 +18,11 @@ 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;
@@ -103,91 +98,6 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase
        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[] signature, 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, signature,
                        additionalEntropy);
            }

            @Override
            public long getConsumedInputSizeBytes() {
                return mConsumedInputSizeBytes;
            }

            @Override
            public long getProducedOutputSizeBytes() {
                return mDelegate.getProducedOutputSizeBytes();
            }
        }
    }

    /**