1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.security.keystore; 18 19 import android.os.IBinder; 20 import android.security.KeyStore; 21 import android.security.KeyStoreException; 22 import android.security.keymaster.KeymasterDefs; 23 import android.security.keymaster.OperationResult; 24 25 import libcore.util.EmptyArray; 26 27 /** 28 * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's 29 * {@code update} and {@code finish} operations. 30 * 31 * <p>The helper abstracts away issues that need to be solved in most code that uses KeyStore's 32 * update and finish operations. Firstly, KeyStore's update operation can consume only a limited 33 * amount of data in one go because the operations are marshalled via Binder. Secondly, the update 34 * operation may consume less data than provided, in which case the caller has to buffer the 35 * remainder for next time. Thirdly, when the input is smaller than a threshold, skipping update 36 * and passing input data directly to final improves performance. This threshold is configurable; 37 * using a threshold <= 1 causes the helper act eagerly, which may be required for some types of 38 * operations (e.g. ciphers). 39 * 40 * <p>The helper exposes {@link #update(byte[], int, int) update} and 41 * {@link #doFinal(byte[], int, int, byte[], byte[]) doFinal} operations which can be used to 42 * conveniently implement various JCA crypto primitives. 43 * 44 * <p>Bidirectional chunked streaming of data via a KeyStore crypto operation is abstracted away as 45 * a {@link Stream} to avoid having this class deal with operation tokens and occasional additional 46 * parameters to {@code update} and {@code final} operations. 47 * 48 * @hide 49 */ 50 class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationStreamer { 51 52 /** 53 * Bidirectional chunked data stream over a KeyStore crypto operation. 54 */ 55 interface Stream { 56 /** 57 * Returns the result of the KeyStore {@code update} operation or null if keystore couldn't 58 * be reached. 59 */ update(byte[] input)60 OperationResult update(byte[] input); 61 62 /** 63 * Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't 64 * be reached. 65 */ finish(byte[] input, byte[] siganture, byte[] additionalEntropy)66 OperationResult finish(byte[] input, byte[] siganture, byte[] additionalEntropy); 67 } 68 69 // Binder buffer is about 1MB, but it's shared between all active transactions of the process. 70 // Thus, it's safer to use a much smaller upper bound. 71 private static final int DEFAULT_CHUNK_SIZE_MAX = 64 * 1024; 72 // The chunk buffer will be sent to update until its size under this threshold. 73 // This threshold should be <= the max input allowed for finish. 74 // Setting this threshold <= 1 will effectivley disable buffering between updates. 75 private static final int DEFAULT_CHUNK_SIZE_THRESHOLD = 2 * 1024; 76 77 private final Stream mKeyStoreStream; 78 private final int mChunkSizeMax; 79 private final int mChunkSizeThreshold; 80 private final byte[] mChunk; 81 private int mChunkLength = 0; 82 private long mConsumedInputSizeBytes; 83 private long mProducedOutputSizeBytes; 84 KeyStoreCryptoOperationChunkedStreamer(Stream operation)85 KeyStoreCryptoOperationChunkedStreamer(Stream operation) { 86 this(operation, DEFAULT_CHUNK_SIZE_THRESHOLD, DEFAULT_CHUNK_SIZE_MAX); 87 } 88 KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold)89 KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold) { 90 this(operation, chunkSizeThreshold, DEFAULT_CHUNK_SIZE_MAX); 91 } 92 KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold, int chunkSizeMax)93 KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold, 94 int chunkSizeMax) { 95 mKeyStoreStream = operation; 96 mChunkSizeMax = chunkSizeMax; 97 if (chunkSizeThreshold <= 0) { 98 mChunkSizeThreshold = 1; 99 } else if (chunkSizeThreshold > chunkSizeMax) { 100 mChunkSizeThreshold = chunkSizeMax; 101 } else { 102 mChunkSizeThreshold = chunkSizeThreshold; 103 } 104 mChunk = new byte[mChunkSizeMax]; 105 } 106 update(byte[] input, int inputOffset, int inputLength)107 public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException { 108 if (inputLength == 0 || input == null) { 109 // No input provided 110 return EmptyArray.BYTE; 111 } 112 if (inputLength < 0 || inputOffset < 0 || (inputOffset + inputLength) > input.length) { 113 throw new KeyStoreException(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR, 114 "Input offset and length out of bounds of input array"); 115 } 116 117 byte[] output = EmptyArray.BYTE; 118 119 while (inputLength > 0 || mChunkLength >= mChunkSizeThreshold) { 120 int inputConsumed = ArrayUtils.copy(input, inputOffset, mChunk, mChunkLength, 121 inputLength); 122 inputLength -= inputConsumed; 123 inputOffset += inputConsumed; 124 mChunkLength += inputConsumed; 125 mConsumedInputSizeBytes += inputConsumed; 126 127 if (mChunkLength > mChunkSizeMax) { 128 throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH, 129 "Chunk size exceeded max chunk size. Max: " + mChunkSizeMax 130 + " Actual: " + mChunkLength); 131 } 132 133 if (mChunkLength >= mChunkSizeThreshold) { 134 OperationResult opResult = mKeyStoreStream.update( 135 ArrayUtils.subarray(mChunk, 0, mChunkLength)); 136 137 if (opResult == null) { 138 throw new KeyStoreConnectException(); 139 } else if (opResult.resultCode != KeyStore.NO_ERROR) { 140 throw KeyStore.getKeyStoreException(opResult.resultCode); 141 } 142 if (opResult.inputConsumed <= 0) { 143 throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH, 144 "Keystore consumed 0 of " + mChunkLength + " bytes provided."); 145 } else if (opResult.inputConsumed > mChunkLength) { 146 throw new KeyStoreException(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR, 147 "Keystore consumed more input than provided. Provided: " 148 + mChunkLength + ", consumed: " + opResult.inputConsumed); 149 } 150 mChunkLength -= opResult.inputConsumed; 151 152 if (mChunkLength > 0) { 153 // Partialy consumed, shift chunk contents 154 ArrayUtils.copy(mChunk, opResult.inputConsumed, mChunk, 0, mChunkLength); 155 } 156 157 if ((opResult.output != null) && (opResult.output.length > 0)) { 158 // Output was produced 159 mProducedOutputSizeBytes += opResult.output.length; 160 output = ArrayUtils.concat(output, opResult.output); 161 } 162 } 163 } 164 return output; 165 } 166 doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature, byte[] additionalEntropy)167 public byte[] doFinal(byte[] input, int inputOffset, int inputLength, 168 byte[] signature, byte[] additionalEntropy) throws KeyStoreException { 169 byte[] output = update(input, inputOffset, inputLength); 170 byte[] finalChunk = ArrayUtils.subarray(mChunk, 0, mChunkLength); 171 OperationResult opResult = mKeyStoreStream.finish(finalChunk, signature, additionalEntropy); 172 173 if (opResult == null) { 174 throw new KeyStoreConnectException(); 175 } else if (opResult.resultCode != KeyStore.NO_ERROR) { 176 throw KeyStore.getKeyStoreException(opResult.resultCode); 177 } 178 // If no error, assume all input consumed 179 mConsumedInputSizeBytes += finalChunk.length; 180 181 if ((opResult.output != null) && (opResult.output.length > 0)) { 182 mProducedOutputSizeBytes += opResult.output.length; 183 output = ArrayUtils.concat(output, opResult.output); 184 } 185 186 return output; 187 } 188 189 @Override getConsumedInputSizeBytes()190 public long getConsumedInputSizeBytes() { 191 return mConsumedInputSizeBytes; 192 } 193 194 @Override getProducedOutputSizeBytes()195 public long getProducedOutputSizeBytes() { 196 return mProducedOutputSizeBytes; 197 } 198 199 /** 200 * Main data stream via a KeyStore streaming operation. 201 * 202 * <p>For example, for an encryption operation, this is the stream through which plaintext is 203 * provided and ciphertext is obtained. 204 */ 205 public static class MainDataStream implements Stream { 206 207 private final KeyStore mKeyStore; 208 private final IBinder mOperationToken; 209 MainDataStream(KeyStore keyStore, IBinder operationToken)210 public MainDataStream(KeyStore keyStore, IBinder operationToken) { 211 mKeyStore = keyStore; 212 mOperationToken = operationToken; 213 } 214 215 @Override update(byte[] input)216 public OperationResult update(byte[] input) { 217 return mKeyStore.update(mOperationToken, null, input); 218 } 219 220 @Override finish(byte[] input, byte[] signature, byte[] additionalEntropy)221 public OperationResult finish(byte[] input, byte[] signature, byte[] additionalEntropy) { 222 return mKeyStore.finish(mOperationToken, null, input, signature, additionalEntropy); 223 } 224 } 225 } 226