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.keystore2; 18 19 import android.annotation.NonNull; 20 import android.security.KeyStoreException; 21 import android.security.KeyStoreOperation; 22 import android.security.keymaster.KeymasterDefs; 23 import android.security.keystore.ArrayUtils; 24 25 import libcore.util.EmptyArray; 26 27 /** 28 * Helper for streaming a crypto operation's input and output via {@link KeyStoreOperation} 29 * service's {@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 KeyStoreOperation {@code update} if applicable. 58 * The return value may be null, e.g., when supplying AAD or to-be-signed data. 59 * 60 * @param input Data to update a KeyStoreOperation with. 61 * 62 * @throws KeyStoreException in case of error. 63 */ update(@onNull byte[] input)64 byte[] update(@NonNull byte[] input) throws KeyStoreException; 65 66 /** 67 * Returns the result of the KeyStore {@code finish} if applicable. 68 * 69 * @param input Optional data to update the operation with one last time. 70 * 71 * @param signature Optional HMAC signature when verifying an HMAC signature, must be 72 * null otherwise. 73 * 74 * @return Optional output data. Depending on the operation this may be a signature, 75 * some final bit of cipher, or plain text. 76 * 77 * @throws KeyStoreException in case of error. 78 */ finish(byte[] input, byte[] signature)79 byte[] finish(byte[] input, byte[] signature) throws KeyStoreException; 80 } 81 82 // Binder buffer is about 1MB, but it's shared between all active transactions of the process. 83 // Thus, it's safer to use a much smaller upper bound. 84 private static final int DEFAULT_CHUNK_SIZE_MAX = 32 * 1024; 85 // The chunk buffer will be sent to update until its size under this threshold. 86 // This threshold should be <= the max input allowed for finish. 87 // Setting this threshold <= 1 will effectivley disable buffering between updates. 88 private static final int DEFAULT_CHUNK_SIZE_THRESHOLD = 2 * 1024; 89 90 private final Stream mKeyStoreStream; 91 private final int mChunkSizeMax; 92 private final int mChunkSizeThreshold; 93 private final byte[] mChunk; 94 private int mChunkLength = 0; 95 private long mConsumedInputSizeBytes; 96 private long mProducedOutputSizeBytes; 97 KeyStoreCryptoOperationChunkedStreamer(Stream operation)98 KeyStoreCryptoOperationChunkedStreamer(Stream operation) { 99 this(operation, DEFAULT_CHUNK_SIZE_THRESHOLD, DEFAULT_CHUNK_SIZE_MAX); 100 } 101 KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold)102 KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold) { 103 this(operation, chunkSizeThreshold, DEFAULT_CHUNK_SIZE_MAX); 104 } 105 KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold, int chunkSizeMax)106 KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold, 107 int chunkSizeMax) { 108 mChunkLength = 0; 109 mConsumedInputSizeBytes = 0; 110 mProducedOutputSizeBytes = 0; 111 mKeyStoreStream = operation; 112 mChunkSizeMax = chunkSizeMax; 113 if (chunkSizeThreshold <= 0) { 114 mChunkSizeThreshold = 1; 115 } else if (chunkSizeThreshold > chunkSizeMax) { 116 mChunkSizeThreshold = chunkSizeMax; 117 } else { 118 mChunkSizeThreshold = chunkSizeThreshold; 119 } 120 mChunk = new byte[mChunkSizeMax]; 121 } 122 update(byte[] input, int inputOffset, int inputLength)123 public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException { 124 if (inputLength == 0 || input == null) { 125 // No input provided 126 return EmptyArray.BYTE; 127 } 128 if (inputLength < 0 || inputOffset < 0 || (inputOffset + inputLength) > input.length) { 129 throw new KeyStoreException(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR, 130 "Input offset and length out of bounds of input array"); 131 } 132 133 byte[] output = EmptyArray.BYTE; 134 135 // Preamble: If there is leftover data, we fill it up with the new data provided 136 // and send it to Keystore. 137 if (mChunkLength > 0) { 138 // Fill current chunk and send it to Keystore 139 int inputConsumed = ArrayUtils.copy(input, inputOffset, mChunk, mChunkLength, 140 inputLength); 141 inputLength -= inputConsumed; 142 inputOffset += inputConsumed; 143 mChunkLength += inputConsumed; 144 if (mChunkLength < mChunkSizeMax) return output; 145 byte[] o = mKeyStoreStream.update(mChunk); 146 if (o != null) { 147 output = ArrayUtils.concat(output, o); 148 } 149 mConsumedInputSizeBytes += inputConsumed; 150 mChunkLength = 0; 151 } 152 153 // Main loop: Send large enough chunks to Keystore. 154 while (inputLength >= mChunkSizeThreshold) { 155 int nextChunkSize = inputLength < mChunkSizeMax ? inputLength : mChunkSizeMax; 156 byte[] o = mKeyStoreStream.update(ArrayUtils.subarray(input, inputOffset, 157 nextChunkSize)); 158 inputLength -= nextChunkSize; 159 inputOffset += nextChunkSize; 160 mConsumedInputSizeBytes += nextChunkSize; 161 if (o != null) { 162 output = ArrayUtils.concat(output, o); 163 } 164 } 165 166 // If we have left over data, that did not make the threshold, we store it in the chunk 167 // store. 168 if (inputLength > 0) { 169 mChunkLength = ArrayUtils.copy(input, inputOffset, mChunk, 0, inputLength); 170 mConsumedInputSizeBytes += inputLength; 171 } 172 173 mProducedOutputSizeBytes += output.length; 174 return output; 175 } 176 doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature)177 public byte[] doFinal(byte[] input, int inputOffset, int inputLength, 178 byte[] signature) throws KeyStoreException { 179 byte[] output = update(input, inputOffset, inputLength); 180 byte[] finalChunk = ArrayUtils.subarray(mChunk, 0, mChunkLength); 181 byte[] o = mKeyStoreStream.finish(finalChunk, signature); 182 183 if (o != null) { 184 // Output produced by update is already accounted for. We only add the bytes 185 // produced by finish. 186 mProducedOutputSizeBytes += o.length; 187 if (output != null) { 188 output = ArrayUtils.concat(output, o); 189 } else { 190 output = o; 191 } 192 } 193 return output; 194 } 195 196 @Override getConsumedInputSizeBytes()197 public long getConsumedInputSizeBytes() { 198 return mConsumedInputSizeBytes; 199 } 200 201 @Override getProducedOutputSizeBytes()202 public long getProducedOutputSizeBytes() { 203 return mProducedOutputSizeBytes; 204 } 205 206 /** 207 * Main data stream via a KeyStore streaming operation. 208 * 209 * <p>For example, for an encryption operation, this is the stream through which plaintext is 210 * provided and ciphertext is obtained. 211 */ 212 public static class MainDataStream implements Stream { 213 214 private final KeyStoreOperation mOperation; 215 MainDataStream(KeyStoreOperation operation)216 MainDataStream(KeyStoreOperation operation) { 217 mOperation = operation; 218 } 219 220 @Override update(byte[] input)221 public byte[] update(byte[] input) throws KeyStoreException { 222 return mOperation.update(input); 223 } 224 225 @Override finish(byte[] input, byte[] signature)226 public byte[] finish(byte[] input, byte[] signature) 227 throws KeyStoreException { 228 return mOperation.finish(input, signature); 229 } 230 } 231 } 232