Merge "Make NONEwithECDSA truncate input when necessary." into mnc-dev

This commit is contained in:
Alex Klyubin
2015-06-25 01:31:17 +00:00
committed by Android (Google) Code Review
8 changed files with 151 additions and 48 deletions

View File

@@ -363,8 +363,9 @@ abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreC
@Override @Override
public byte[] doFinal(byte[] input, int inputOffset, int inputLength, public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
byte[] additionalEntropy) throws KeyStoreException { byte[] signature, byte[] additionalEntropy) throws KeyStoreException {
byte[] output = mDelegate.doFinal(input, inputOffset, inputLength, additionalEntropy); byte[] output = mDelegate.doFinal(input, inputOffset, inputLength, signature,
additionalEntropy);
if (output != null) { if (output != null) {
try { try {
mBufferedOutput.write(output); mBufferedOutput.write(output);
@@ -425,7 +426,7 @@ abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreC
} }
@Override @Override
public OperationResult finish(byte[] additionalEntropy) { public OperationResult finish(byte[] signature, byte[] additionalEntropy) {
if ((additionalEntropy != null) && (additionalEntropy.length > 0)) { if ((additionalEntropy != null) && (additionalEntropy.length > 0)) {
throw new ProviderException("AAD stream does not support additional entropy"); throw new ProviderException("AAD stream does not support additional entropy");
} }

View File

@@ -353,6 +353,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
try { try {
output = mAdditionalAuthenticationDataStreamer.doFinal( output = mAdditionalAuthenticationDataStreamer.doFinal(
EmptyArray.BYTE, 0, 0, EmptyArray.BYTE, 0, 0,
null, // no signature
null // no additional entropy needed flushing AAD null // no additional entropy needed flushing AAD
); );
} finally { } finally {
@@ -469,7 +470,10 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
byte[] additionalEntropy = byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, getAdditionalEntropyAmountForFinish()); mRng, getAdditionalEntropyAmountForFinish());
output = mMainDataStreamer.doFinal(input, inputOffset, inputLen, additionalEntropy); output = mMainDataStreamer.doFinal(
input, inputOffset, inputLen,
null, // no signature involved
additionalEntropy);
} catch (KeyStoreException e) { } catch (KeyStoreException e) {
switch (e.getErrorCode()) { switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH: case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH:

View File

@@ -17,11 +17,16 @@
package android.security.keystore; package android.security.keystore;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.os.IBinder;
import android.security.KeyStore; import android.security.KeyStore;
import android.security.KeyStoreException;
import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs; import android.security.keymaster.KeymasterDefs;
import libcore.util.EmptyArray;
import java.io.ByteArrayOutputStream;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.SignatureSpi; import java.security.SignatureSpi;
@@ -36,6 +41,71 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature
public NONE() { public NONE() {
super(KeymasterDefs.KM_DIGEST_NONE); super(KeymasterDefs.KM_DIGEST_NONE);
} }
@Override
protected KeyStoreCryptoOperationStreamer createMainDataStreamer(KeyStore keyStore,
IBinder operationToken) {
return new TruncateToFieldSizeMessageStreamer(
super.createMainDataStreamer(keyStore, operationToken),
getGroupSizeBits());
}
/**
* Streamer which buffers all input, then truncates it to field size, and then sends it into
* KeyStore via the provided delegate streamer.
*/
private static class TruncateToFieldSizeMessageStreamer
implements KeyStoreCryptoOperationStreamer {
private final KeyStoreCryptoOperationStreamer mDelegate;
private final int mGroupSizeBits;
private final ByteArrayOutputStream mInputBuffer = new ByteArrayOutputStream();
private long mConsumedInputSizeBytes;
private TruncateToFieldSizeMessageStreamer(
KeyStoreCryptoOperationStreamer delegate,
int groupSizeBits) {
mDelegate = delegate;
mGroupSizeBits = groupSizeBits;
}
@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();
// Truncate input at field size (bytes)
return mDelegate.doFinal(bufferedInput,
0,
Math.min(bufferedInput.length, ((mGroupSizeBits + 7) / 8)),
signature, additionalEntropy);
}
@Override
public long getConsumedInputSizeBytes() {
return mConsumedInputSizeBytes;
}
@Override
public long getProducedOutputSizeBytes() {
return mDelegate.getProducedOutputSizeBytes();
}
}
} }
public final static class SHA1 extends AndroidKeyStoreECDSASignatureSpi { public final static class SHA1 extends AndroidKeyStoreECDSASignatureSpi {
@@ -70,7 +140,7 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature
private final int mKeymasterDigest; private final int mKeymasterDigest;
private int mGroupSizeBytes = -1; private int mGroupSizeBits = -1;
AndroidKeyStoreECDSASignatureSpi(int keymasterDigest) { AndroidKeyStoreECDSASignatureSpi(int keymasterDigest) {
mKeymasterDigest = keymasterDigest; mKeymasterDigest = keymasterDigest;
@@ -95,14 +165,14 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature
} else if (keySizeBits > Integer.MAX_VALUE) { } else if (keySizeBits > Integer.MAX_VALUE) {
throw new InvalidKeyException("Key too large: " + keySizeBits + " bits"); throw new InvalidKeyException("Key too large: " + keySizeBits + " bits");
} }
mGroupSizeBytes = (int) ((keySizeBits + 7) / 8); mGroupSizeBits = (int) keySizeBits;
super.initKey(key); super.initKey(key);
} }
@Override @Override
protected final void resetAll() { protected final void resetAll() {
mGroupSizeBytes = -1; mGroupSizeBits = -1;
super.resetAll(); super.resetAll();
} }
@@ -112,14 +182,21 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature
} }
@Override @Override
protected void addAlgorithmSpecificParametersToBegin( protected final void addAlgorithmSpecificParametersToBegin(
@NonNull KeymasterArguments keymasterArgs) { @NonNull KeymasterArguments keymasterArgs) {
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_EC); keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_EC);
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
} }
@Override @Override
protected int getAdditionalEntropyAmountForSign() { protected final int getAdditionalEntropyAmountForSign() {
return mGroupSizeBytes; return (mGroupSizeBits + 7) / 8;
}
protected final int getGroupSizeBits() {
if (mGroupSizeBits == -1) {
throw new IllegalStateException("Not initialized");
}
return mGroupSizeBits;
} }
} }

View File

@@ -234,6 +234,7 @@ public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreC
try { try {
result = mChunkedStreamer.doFinal( result = mChunkedStreamer.doFinal(
null, 0, 0, null, 0, 0,
null, // no signature provided -- this invocation will generate one
null // no additional entropy needed -- HMAC is deterministic null // no additional entropy needed -- HMAC is deterministic
); );
} catch (KeyStoreException e) { } catch (KeyStoreException e) {

View File

@@ -150,8 +150,7 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase
@Override @Override
public byte[] doFinal(byte[] input, int inputOffset, int inputLength, public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
byte[] additionalEntropy) byte[] signature, byte[] additionalEntropy) throws KeyStoreException {
throws KeyStoreException {
if (inputLength > 0) { if (inputLength > 0) {
mConsumedInputSizeBytes += inputLength; mConsumedInputSizeBytes += inputLength;
mInputBuffer.write(input, inputOffset, inputLength); mInputBuffer.write(input, inputOffset, inputLength);
@@ -174,7 +173,8 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase
"Message size (" + bufferedInput.length + " bytes) must be smaller than" "Message size (" + bufferedInput.length + " bytes) must be smaller than"
+ " modulus (" + mModulusSizeBytes + " bytes)"); + " modulus (" + mModulusSizeBytes + " bytes)");
} }
return mDelegate.doFinal(paddedInput, 0, paddedInput.length, additionalEntropy); return mDelegate.doFinal(paddedInput, 0, paddedInput.length, signature,
additionalEntropy);
} }
@Override @Override

View File

@@ -58,7 +58,7 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
*/ */
private IBinder mOperationToken; private IBinder mOperationToken;
private long mOperationHandle; private long mOperationHandle;
private KeyStoreCryptoOperationChunkedStreamer mMessageStreamer; private KeyStoreCryptoOperationStreamer mMessageStreamer;
/** /**
* Encountered exception which could not be immediately thrown because it was encountered inside * Encountered exception which could not be immediately thrown because it was encountered inside
@@ -229,9 +229,20 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
throw new ProviderException("Keystore returned invalid operation handle"); throw new ProviderException("Keystore returned invalid operation handle");
} }
mMessageStreamer = new KeyStoreCryptoOperationChunkedStreamer( mMessageStreamer = createMainDataStreamer(mKeyStore, opResult.token);
}
/**
* Creates a streamer which sends the message to be signed/verified into the provided KeyStore
*
* <p>This implementation returns a working streamer.
*/
@NonNull
protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
KeyStore keyStore, IBinder operationToken) {
return new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
mKeyStore, opResult.token)); keyStore, operationToken));
} }
@Override @Override
@@ -314,7 +325,10 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
byte[] additionalEntropy = byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
appRandom, getAdditionalEntropyAmountForSign()); appRandom, getAdditionalEntropyAmountForSign());
signature = mMessageStreamer.doFinal(EmptyArray.BYTE, 0, 0, additionalEntropy); signature = mMessageStreamer.doFinal(
EmptyArray.BYTE, 0, 0,
null, // no signature provided -- it'll be generated by this invocation
additionalEntropy);
} catch (InvalidKeyException | KeyStoreException e) { } catch (InvalidKeyException | KeyStoreException e) {
throw new SignatureException(e); throw new SignatureException(e);
} }
@@ -329,31 +343,37 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
throw new SignatureException(mCachedException); throw new SignatureException(mCachedException);
} }
boolean result;
try { try {
ensureKeystoreOperationInitialized(); ensureKeystoreOperationInitialized();
mMessageStreamer.flush(); } catch (InvalidKeyException e) {
OperationResult opResult = mKeyStore.finish(mOperationToken, null, signature);
if (opResult == null) {
throw new KeyStoreConnectException();
}
switch (opResult.resultCode) {
case KeyStore.NO_ERROR:
result = true;
break;
case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
result = false;
break;
default:
throw new SignatureException(
KeyStore.getKeyStoreException(opResult.resultCode));
}
} catch (InvalidKeyException | KeyStoreException e) {
throw new SignatureException(e); throw new SignatureException(e);
} }
boolean verified;
try {
byte[] output = mMessageStreamer.doFinal(
EmptyArray.BYTE, 0, 0,
signature,
null // no additional entropy needed -- verification is deterministic
);
if (output.length != 0) {
throw new ProviderException(
"Signature verification unexpected produced output: " + output.length
+ " bytes");
}
verified = true;
} catch (KeyStoreException e) {
switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
verified = false;
break;
default:
throw new SignatureException(e);
}
}
resetWhilePreservingInitState(); resetWhilePreservingInitState();
return result; return verified;
} }
@Override @Override

View File

@@ -35,8 +35,8 @@ import java.io.IOException;
* amount of data in one go because the operations are marshalled via Binder. Secondly, the update * amount of data in one go because the operations are marshalled via Binder. Secondly, the update
* operation may consume less data than provided, in which case the caller has to buffer the * operation may consume less data than provided, in which case the caller has to buffer the
* remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and * remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and
* {@link #doFinal(byte[], int, int, byte[]) doFinal} operations which can be used to conveniently * {@link #doFinal(byte[], int, int, byte[], byte[]) doFinal} operations which can be used to
* implement various JCA crypto primitives. * conveniently implement various JCA crypto primitives.
* *
* <p>Bidirectional chunked streaming of data via a KeyStore crypto operation is abstracted away as * <p>Bidirectional chunked streaming of data via a KeyStore crypto operation is abstracted away as
* a {@link Stream} to avoid having this class deal with operation tokens and occasional additional * a {@link Stream} to avoid having this class deal with operation tokens and occasional additional
@@ -60,7 +60,7 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS
* Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't * Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't
* be reached. * be reached.
*/ */
OperationResult finish(byte[] additionalEntropy); OperationResult finish(byte[] siganture, byte[] additionalEntropy);
} }
// Binder buffer is about 1MB, but it's shared between all active transactions of the process. // Binder buffer is about 1MB, but it's shared between all active transactions of the process.
@@ -201,8 +201,8 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS
} }
@Override @Override
public byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] additionalEntropy) public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
throws KeyStoreException { byte[] signature, byte[] additionalEntropy) throws KeyStoreException {
if (inputLength == 0) { if (inputLength == 0) {
// No input provided -- simplify the rest of the code // No input provided -- simplify the rest of the code
input = EmptyArray.BYTE; input = EmptyArray.BYTE;
@@ -213,7 +213,7 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS
byte[] output = update(input, inputOffset, inputLength); byte[] output = update(input, inputOffset, inputLength);
output = ArrayUtils.concat(output, flush()); output = ArrayUtils.concat(output, flush());
OperationResult opResult = mKeyStoreStream.finish(additionalEntropy); OperationResult opResult = mKeyStoreStream.finish(signature, additionalEntropy);
if (opResult == null) { if (opResult == null) {
throw new KeyStoreConnectException(); throw new KeyStoreConnectException();
} else if (opResult.resultCode != KeyStore.NO_ERROR) { } else if (opResult.resultCode != KeyStore.NO_ERROR) {
@@ -286,8 +286,8 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS
} }
@Override @Override
public OperationResult finish(byte[] additionalEntropy) { public OperationResult finish(byte[] signature, byte[] additionalEntropy) {
return mKeyStore.finish(mOperationToken, null, null, additionalEntropy); return mKeyStore.finish(mOperationToken, null, signature, additionalEntropy);
} }
} }
} }

View File

@@ -28,15 +28,15 @@ import android.security.KeyStoreException;
* amount of data in one go because the operations are marshalled via Binder. Secondly, the update * amount of data in one go because the operations are marshalled via Binder. Secondly, the update
* operation may consume less data than provided, in which case the caller has to buffer the * operation may consume less data than provided, in which case the caller has to buffer the
* remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and * remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and
* {@link #doFinal(byte[], int, int, byte[]) doFinal} operations which can be used to conveniently * {@link #doFinal(byte[], int, int, byte[], byte[]) doFinal} operations which can be used to
* implement various JCA crypto primitives. * conveniently implement various JCA crypto primitives.
* *
* @hide * @hide
*/ */
interface KeyStoreCryptoOperationStreamer { interface KeyStoreCryptoOperationStreamer {
byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException; byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException;
byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] additionalEntropy) byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature,
throws KeyStoreException; byte[] additionalEntropy) throws KeyStoreException;
long getConsumedInputSizeBytes(); long getConsumedInputSizeBytes();
long getProducedOutputSizeBytes(); long getProducedOutputSizeBytes();
} }