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
This commit is contained in:
Alex Klyubin
2015-07-20 09:01:34 -07:00
parent ada70be897
commit 3ab1f04004

View File

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