| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.security.keystore; |
| |
| import android.os.IBinder; |
| import android.security.KeyStore; |
| import android.security.KeyStoreException; |
| import android.security.keymaster.KeymasterDefs; |
| import android.security.keymaster.OperationResult; |
| |
| 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. |
| * |
| * <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 |
| * 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. Thirdly, when the input is smaller than a threshold, skipping update |
| * and passing input data directly to final improves performance. This threshold is configurable; |
| * using a threshold <= 1 causes the helper act eagerly, which may be required for some types of |
| * operations (e.g. ciphers). |
| * |
| * <p>The helper exposes {@link #update(byte[], int, int) update} and |
| * {@link #doFinal(byte[], int, int, byte[], byte[]) doFinal} operations which can be used to |
| * conveniently implement various JCA crypto primitives. |
| * |
| * <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 |
| * parameters to {@code update} and {@code final} operations. |
| * |
| * @hide |
| */ |
| class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationStreamer { |
| |
| /** |
| * Bidirectional chunked data stream over a KeyStore crypto operation. |
| */ |
| interface Stream { |
| /** |
| * Returns the result of the KeyStore {@code update} operation or null if keystore couldn't |
| * be reached. |
| */ |
| OperationResult update(byte[] input); |
| |
| /** |
| * Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't |
| * be reached. |
| */ |
| OperationResult finish(byte[] input, byte[] siganture, byte[] additionalEntropy); |
| } |
| |
| // 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; |
| // 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. |
| private static final int DEFAULT_CHUNK_SIZE_THRESHOLD = 2 * 1024; |
| |
| private final Stream mKeyStoreStream; |
| private final int mChunkSizeMax; |
| private final int mChunkSizeThreshold; |
| private final byte[] mChunk; |
| private int mChunkLength = 0; |
| private long mConsumedInputSizeBytes; |
| private long mProducedOutputSizeBytes; |
| |
| KeyStoreCryptoOperationChunkedStreamer(Stream operation) { |
| this(operation, DEFAULT_CHUNK_SIZE_THRESHOLD, DEFAULT_CHUNK_SIZE_MAX); |
| } |
| |
| KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold) { |
| this(operation, chunkSizeThreshold, DEFAULT_CHUNK_SIZE_MAX); |
| } |
| |
| KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold, |
| int chunkSizeMax) { |
| mKeyStoreStream = operation; |
| mChunkSizeMax = chunkSizeMax; |
| if (chunkSizeThreshold <= 0) { |
| mChunkSizeThreshold = 1; |
| } else if (chunkSizeThreshold > chunkSizeMax) { |
| mChunkSizeThreshold = chunkSizeMax; |
| } else { |
| mChunkSizeThreshold = chunkSizeThreshold; |
| } |
| mChunk = new byte[mChunkSizeMax]; |
| } |
| |
| public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException { |
| if (inputLength == 0 || input == null) { |
| // No input provided |
| return EmptyArray.BYTE; |
| } |
| 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"); |
| } |
| |
| byte[] output = EmptyArray.BYTE; |
| |
| while (inputLength > 0 || mChunkLength >= mChunkSizeThreshold) { |
| int inputConsumed = ArrayUtils.copy(input, inputOffset, mChunk, mChunkLength, |
| inputLength); |
| inputLength -= inputConsumed; |
| inputOffset += inputConsumed; |
| mChunkLength += inputConsumed; |
| mConsumedInputSizeBytes += inputConsumed; |
| |
| if (mChunkLength > mChunkSizeMax) { |
| throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH, |
| "Chunk size exceeded max chunk size. Max: " + mChunkSizeMax |
| + " Actual: " + mChunkLength); |
| } |
| |
| 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); |
| } |
| } |
| } |
| return output; |
| } |
| |
| public byte[] doFinal(byte[] input, int inputOffset, int inputLength, |
| byte[] signature, byte[] additionalEntropy) 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); |
| } |
| |
| return output; |
| } |
| |
| @Override |
| public long getConsumedInputSizeBytes() { |
| return mConsumedInputSizeBytes; |
| } |
| |
| @Override |
| public long getProducedOutputSizeBytes() { |
| return mProducedOutputSizeBytes; |
| } |
| |
| /** |
| * Main data stream via a KeyStore streaming operation. |
| * |
| * <p>For example, for an encryption operation, this is the stream through which plaintext is |
| * provided and ciphertext is obtained. |
| */ |
| public static class MainDataStream implements Stream { |
| |
| private final KeyStore mKeyStore; |
| private final IBinder mOperationToken; |
| |
| public MainDataStream(KeyStore keyStore, IBinder operationToken) { |
| mKeyStore = keyStore; |
| mOperationToken = operationToken; |
| } |
| |
| @Override |
| public OperationResult update(byte[] input) { |
| return mKeyStore.update(mOperationToken, null, input); |
| } |
| |
| @Override |
| public OperationResult finish(byte[] input, byte[] signature, byte[] additionalEntropy) { |
| return mKeyStore.finish(mOperationToken, null, input, signature, additionalEntropy); |
| } |
| } |
| } |