diff options
-rw-r--r-- | keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java | 152 | ||||
-rw-r--r-- | keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java | 6 |
2 files changed, 80 insertions, 78 deletions
diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java index 3bf9da080f30..6c733ba712d5 100644 --- a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java +++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java @@ -16,19 +16,17 @@ package android.security.keystore2; -import android.os.IBinder; -import android.security.KeyStore; +import android.annotation.NonNull; import android.security.KeyStoreException; +import android.security.KeyStoreOperation; import android.security.keymaster.KeymasterDefs; -import android.security.keymaster.OperationResult; import android.security.keystore.ArrayUtils; -import android.security.keystore.KeyStoreConnectException; import libcore.util.EmptyArray; /** - * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's - * {@code update} and {@code finish} operations. + * Helper for streaming a crypto operation's input and output via {@link KeyStoreOperation} + * service's {@code update} and {@code finish} operations. * * <p>The helper abstracts away issues that need to be solved in most code that uses KeyStore's * update and finish operations. Firstly, KeyStore's update operation can consume only a limited @@ -56,21 +54,34 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS */ interface Stream { /** - * Returns the result of the KeyStore {@code update} operation or null if keystore couldn't - * be reached. + * Returns the result of the KeyStoreOperation {@code update} if applicable. + * The return value may be null, e.g., when supplying AAD or to-be-signed data. + * + * @param input Data to update a KeyStoreOperation with. + * + * @throws KeyStoreException in case of error. */ - OperationResult update(byte[] input); + byte[] update(@NonNull byte[] input) throws KeyStoreException; /** - * Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't - * be reached. + * Returns the result of the KeyStore {@code finish} if applicable. + * + * @param input Optional data to update the operation with one last time. + * + * @param signature Optional HMAC signature when verifying an HMAC signature, must be + * null otherwise. + * + * @return Optional output data. Depending on the operation this may be a signature, + * some final bit of cipher, or plain text. + * + * @throws KeyStoreException in case of error. */ - OperationResult finish(byte[] input, byte[] siganture, byte[] additionalEntropy); + byte[] finish(byte[] input, byte[] signature) throws KeyStoreException; } // Binder buffer is about 1MB, but it's shared between all active transactions of the process. // Thus, it's safer to use a much smaller upper bound. - private static final int DEFAULT_CHUNK_SIZE_MAX = 64 * 1024; + private static final int DEFAULT_CHUNK_SIZE_MAX = 32 * 1024; // The chunk buffer will be sent to update until its size under this threshold. // This threshold should be <= the max input allowed for finish. // Setting this threshold <= 1 will effectivley disable buffering between updates. @@ -94,6 +105,9 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold, int chunkSizeMax) { + mChunkLength = 0; + mConsumedInputSizeBytes = 0; + mProducedOutputSizeBytes = 0; mKeyStoreStream = operation; mChunkSizeMax = chunkSizeMax; if (chunkSizeThreshold <= 0) { @@ -113,78 +127,67 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS } if (inputLength < 0 || inputOffset < 0 || (inputOffset + inputLength) > input.length) { throw new KeyStoreException(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR, - "Input offset and length out of bounds of input array"); + "Input offset and length out of bounds of input array"); } byte[] output = EmptyArray.BYTE; - while (inputLength > 0 || mChunkLength >= mChunkSizeThreshold) { + // Preamble: If there is leftover data, we fill it up with the new data provided + // and send it to Keystore. + if (mChunkLength > 0) { + // Fill current chunk and send it to Keystore int inputConsumed = ArrayUtils.copy(input, inputOffset, mChunk, mChunkLength, inputLength); inputLength -= inputConsumed; - inputOffset += inputConsumed; - mChunkLength += inputConsumed; + inputOffset += inputOffset; + byte[] o = mKeyStoreStream.update(mChunk); + if (o != null) { + output = ArrayUtils.concat(output, o); + } mConsumedInputSizeBytes += inputConsumed; + mChunkLength = 0; + } - if (mChunkLength > mChunkSizeMax) { - throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH, - "Chunk size exceeded max chunk size. Max: " + mChunkSizeMax - + " Actual: " + mChunkLength); + // Main loop: Send large enough chunks to Keystore. + while (inputLength >= mChunkSizeThreshold) { + int nextChunkSize = inputLength < mChunkSizeMax ? inputLength : mChunkSizeMax; + byte[] o = mKeyStoreStream.update(ArrayUtils.subarray(input, inputOffset, + nextChunkSize)); + inputLength -= nextChunkSize; + inputOffset += nextChunkSize; + mConsumedInputSizeBytes += nextChunkSize; + if (o != null) { + output = ArrayUtils.concat(output, o); } + } - if (mChunkLength >= mChunkSizeThreshold) { - OperationResult opResult = mKeyStoreStream.update( - ArrayUtils.subarray(mChunk, 0, mChunkLength)); - - if (opResult == null) { - throw new KeyStoreConnectException(); - } else if (opResult.resultCode != KeyStore.NO_ERROR) { - throw KeyStore.getKeyStoreException(opResult.resultCode); - } - if (opResult.inputConsumed <= 0) { - throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH, - "Keystore consumed 0 of " + mChunkLength + " bytes provided."); - } else if (opResult.inputConsumed > mChunkLength) { - throw new KeyStoreException(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR, - "Keystore consumed more input than provided. Provided: " - + mChunkLength + ", consumed: " + opResult.inputConsumed); - } - mChunkLength -= opResult.inputConsumed; - - if (mChunkLength > 0) { - // Partialy consumed, shift chunk contents - ArrayUtils.copy(mChunk, opResult.inputConsumed, mChunk, 0, mChunkLength); - } - - if ((opResult.output != null) && (opResult.output.length > 0)) { - // Output was produced - mProducedOutputSizeBytes += opResult.output.length; - output = ArrayUtils.concat(output, opResult.output); - } - } + // If we have left over data, that did not make the threshold, we store it in the chunk + // store. + if (inputLength > 0) { + mChunkLength = ArrayUtils.copy(input, inputOffset, mChunk, 0, inputLength); + mConsumedInputSizeBytes += inputLength; } + + mProducedOutputSizeBytes += output.length; return output; } public byte[] doFinal(byte[] input, int inputOffset, int inputLength, - byte[] signature, byte[] additionalEntropy) throws KeyStoreException { + byte[] signature) throws KeyStoreException { byte[] output = update(input, inputOffset, inputLength); byte[] finalChunk = ArrayUtils.subarray(mChunk, 0, mChunkLength); - OperationResult opResult = mKeyStoreStream.finish(finalChunk, signature, additionalEntropy); - - if (opResult == null) { - throw new KeyStoreConnectException(); - } else if (opResult.resultCode != KeyStore.NO_ERROR) { - throw KeyStore.getKeyStoreException(opResult.resultCode); - } - // If no error, assume all input consumed - mConsumedInputSizeBytes += finalChunk.length; - - if ((opResult.output != null) && (opResult.output.length > 0)) { - mProducedOutputSizeBytes += opResult.output.length; - output = ArrayUtils.concat(output, opResult.output); + byte[] o = mKeyStoreStream.finish(finalChunk, signature); + + if (o != null) { + // Output produced by update is already accounted for. We only add the bytes + // produced by finish. + mProducedOutputSizeBytes += o.length; + if (output != null) { + output = ArrayUtils.concat(output, o); + } else { + output = o; + } } - return output; } @@ -206,22 +209,21 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS */ public static class MainDataStream implements Stream { - private final KeyStore mKeyStore; - private final IBinder mOperationToken; + private final KeyStoreOperation mOperation; - public MainDataStream(KeyStore keyStore, IBinder operationToken) { - mKeyStore = keyStore; - mOperationToken = operationToken; + MainDataStream(KeyStoreOperation operation) { + mOperation = operation; } @Override - public OperationResult update(byte[] input) { - return mKeyStore.update(mOperationToken, null, input); + public byte[] update(byte[] input) throws KeyStoreException { + return mOperation.update(input); } @Override - public OperationResult finish(byte[] input, byte[] signature, byte[] additionalEntropy) { - return mKeyStore.finish(mOperationToken, null, input, signature, additionalEntropy); + public byte[] finish(byte[] input, byte[] signature) + throws KeyStoreException { + return mOperation.finish(input, signature); } } } diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java index fec3bbc3460a..07d6a69eda01 100644 --- a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java +++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java @@ -28,15 +28,15 @@ import android.security.KeyStoreException; * 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 * remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and - * {@link #doFinal(byte[], int, int, byte[], byte[]) doFinal} operations which can be used to + * {@link #doFinal(byte[], int, int, byte[]) doFinal} operations which can be used to * conveniently implement various JCA crypto primitives. * * @hide */ interface KeyStoreCryptoOperationStreamer { byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException; - byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature, - byte[] additionalEntropy) throws KeyStoreException; + byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature) + throws KeyStoreException; long getConsumedInputSizeBytes(); long getProducedOutputSizeBytes(); } |