blob: dbb79bcd5aeafd3a8d86768f9c7b3478dfd3e8ad [file] [log] [blame]
Alex Klyubincc21bb32015-03-31 16:50:37 -07001/*
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
Alex Klyubindcdaf872015-05-13 15:57:09 -070017package android.security.keystore;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070018
Alex Klyubinb406f242015-03-31 13:39:38 -070019import android.os.IBinder;
Alex Klyubindcdaf872015-05-13 15:57:09 -070020import android.security.KeyStore;
21import android.security.KeyStoreException;
Alex Klyubin53d544a2015-07-15 17:15:08 -070022import android.security.keymaster.KeymasterDefs;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070023import android.security.keymaster.OperationResult;
24
Alex Klyubin5927c9f2015-04-10 13:28:03 -070025import libcore.util.EmptyArray;
26
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070027import java.io.ByteArrayOutputStream;
28import java.io.IOException;
Alex Klyubin53d544a2015-07-15 17:15:08 -070029import java.security.ProviderException;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070030
31/**
32 * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's
33 * {@code update} and {@code finish} operations.
34 *
35 * <p>The helper abstracts away to issues that need to be solved in most code that uses KeyStore's
Alex Klyubinb406f242015-03-31 13:39:38 -070036 * update and finish operations. Firstly, KeyStore's update operation can consume only a limited
37 * amount of data in one go because the operations are marshalled via Binder. Secondly, the update
38 * operation may consume less data than provided, in which case the caller has to buffer the
39 * remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and
Alex Klyubind23dc502015-06-24 12:25:52 -070040 * {@link #doFinal(byte[], int, int, byte[], byte[]) doFinal} operations which can be used to
41 * conveniently implement various JCA crypto primitives.
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070042 *
Alex Klyubinb406f242015-03-31 13:39:38 -070043 * <p>Bidirectional chunked streaming of data via a KeyStore crypto operation is abstracted away as
44 * a {@link Stream} to avoid having this class deal with operation tokens and occasional additional
45 * parameters to {@code update} and {@code final} operations.
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070046 *
47 * @hide
48 */
Alex Klyubin4f389fd2015-05-29 14:22:54 -070049class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationStreamer {
Alex Klyubinb406f242015-03-31 13:39:38 -070050
51 /**
52 * Bidirectional chunked data stream over a KeyStore crypto operation.
53 */
Alex Klyubin4f389fd2015-05-29 14:22:54 -070054 interface Stream {
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070055 /**
Alex Klyubinb406f242015-03-31 13:39:38 -070056 * Returns the result of the KeyStore {@code update} operation or null if keystore couldn't
57 * be reached.
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070058 */
59 OperationResult update(byte[] input);
60
61 /**
Alex Klyubinb406f242015-03-31 13:39:38 -070062 * Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't
63 * be reached.
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070064 */
Alex Klyubind23dc502015-06-24 12:25:52 -070065 OperationResult finish(byte[] siganture, byte[] additionalEntropy);
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070066 }
67
68 // Binder buffer is about 1MB, but it's shared between all active transactions of the process.
69 // Thus, it's safer to use a much smaller upper bound.
70 private static final int DEFAULT_MAX_CHUNK_SIZE = 64 * 1024;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070071
Alex Klyubinb406f242015-03-31 13:39:38 -070072 private final Stream mKeyStoreStream;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070073 private final int mMaxChunkSize;
74
Alex Klyubin4f389fd2015-05-29 14:22:54 -070075 private byte[] mBuffered = EmptyArray.BYTE;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070076 private int mBufferedOffset;
77 private int mBufferedLength;
Alex Klyubin00af27b2015-06-01 16:07:53 -070078 private long mConsumedInputSizeBytes;
79 private long mProducedOutputSizeBytes;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070080
Alex Klyubinb406f242015-03-31 13:39:38 -070081 public KeyStoreCryptoOperationChunkedStreamer(Stream operation) {
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070082 this(operation, DEFAULT_MAX_CHUNK_SIZE);
83 }
84
Alex Klyubinb406f242015-03-31 13:39:38 -070085 public KeyStoreCryptoOperationChunkedStreamer(Stream operation, int maxChunkSize) {
86 mKeyStoreStream = operation;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070087 mMaxChunkSize = maxChunkSize;
88 }
89
Alex Klyubin4f389fd2015-05-29 14:22:54 -070090 @Override
Alex Klyubinb4834ae2015-04-02 15:53:46 -070091 public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException {
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070092 if (inputLength == 0) {
93 // No input provided
Alex Klyubin4f389fd2015-05-29 14:22:54 -070094 return EmptyArray.BYTE;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -070095 }
96
97 ByteArrayOutputStream bufferedOutput = null;
98
99 while (inputLength > 0) {
100 byte[] chunk;
101 int inputBytesInChunk;
102 if ((mBufferedLength + inputLength) > mMaxChunkSize) {
103 // Too much input for one chunk -- extract one max-sized chunk and feed it into the
104 // update operation.
Alex Klyubinb406f242015-03-31 13:39:38 -0700105 inputBytesInChunk = mMaxChunkSize - mBufferedLength;
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700106 chunk = ArrayUtils.concat(mBuffered, mBufferedOffset, mBufferedLength,
Alex Klyubinb406f242015-03-31 13:39:38 -0700107 input, inputOffset, inputBytesInChunk);
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700108 } else {
109 // All of available input fits into one chunk.
110 if ((mBufferedLength == 0) && (inputOffset == 0)
111 && (inputLength == input.length)) {
112 // Nothing buffered and all of input array needs to be fed into the update
113 // operation.
114 chunk = input;
115 inputBytesInChunk = input.length;
116 } else {
117 // Need to combine buffered data with input data into one array.
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700118 inputBytesInChunk = inputLength;
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700119 chunk = ArrayUtils.concat(mBuffered, mBufferedOffset, mBufferedLength,
Alex Klyubinb406f242015-03-31 13:39:38 -0700120 input, inputOffset, inputBytesInChunk);
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700121 }
122 }
123 // Update input array references to reflect that some of its bytes are now in mBuffered.
124 inputOffset += inputBytesInChunk;
125 inputLength -= inputBytesInChunk;
Alex Klyubin00af27b2015-06-01 16:07:53 -0700126 mConsumedInputSizeBytes += inputBytesInChunk;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700127
Alex Klyubinb406f242015-03-31 13:39:38 -0700128 OperationResult opResult = mKeyStoreStream.update(chunk);
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700129 if (opResult == null) {
130 throw new KeyStoreConnectException();
131 } else if (opResult.resultCode != KeyStore.NO_ERROR) {
Alex Klyubinb4834ae2015-04-02 15:53:46 -0700132 throw KeyStore.getKeyStoreException(opResult.resultCode);
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700133 }
134
135 if (opResult.inputConsumed == chunk.length) {
136 // The whole chunk was consumed
Alex Klyubin4f389fd2015-05-29 14:22:54 -0700137 mBuffered = EmptyArray.BYTE;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700138 mBufferedOffset = 0;
139 mBufferedLength = 0;
Alex Klyubin53d544a2015-07-15 17:15:08 -0700140 } else if (opResult.inputConsumed <= 0) {
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700141 // Nothing was consumed. More input needed.
142 if (inputLength > 0) {
143 // More input is available, but it wasn't included into the previous chunk
144 // because the chunk reached its maximum permitted size.
145 // Shouldn't have happened.
Alex Klyubin53d544a2015-07-15 17:15:08 -0700146 throw new KeyStoreException(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR,
147 "Keystore consumed nothing from max-sized chunk: " + chunk.length
148 + " bytes");
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700149 }
150 mBuffered = chunk;
151 mBufferedOffset = 0;
152 mBufferedLength = chunk.length;
153 } else if (opResult.inputConsumed < chunk.length) {
154 // The chunk was consumed only partially -- buffer the rest of the chunk
155 mBuffered = chunk;
156 mBufferedOffset = opResult.inputConsumed;
157 mBufferedLength = chunk.length - opResult.inputConsumed;
158 } else {
Alex Klyubin53d544a2015-07-15 17:15:08 -0700159 throw new KeyStoreException(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR,
160 "Keystore consumed more input than provided. Provided: " + chunk.length
161 + ", consumed: " + opResult.inputConsumed);
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700162 }
163
164 if ((opResult.output != null) && (opResult.output.length > 0)) {
165 if (inputLength > 0) {
166 // More output might be produced in this loop -- buffer the current output
167 if (bufferedOutput == null) {
168 bufferedOutput = new ByteArrayOutputStream();
169 try {
170 bufferedOutput.write(opResult.output);
171 } catch (IOException e) {
Alex Klyubin53d544a2015-07-15 17:15:08 -0700172 throw new ProviderException("Failed to buffer output", e);
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700173 }
174 }
175 } else {
176 // No more output will be produced in this loop
Alex Klyubin00af27b2015-06-01 16:07:53 -0700177 byte[] result;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700178 if (bufferedOutput == null) {
179 // No previously buffered output
Alex Klyubin00af27b2015-06-01 16:07:53 -0700180 result = opResult.output;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700181 } else {
182 // There was some previously buffered output
183 try {
184 bufferedOutput.write(opResult.output);
185 } catch (IOException e) {
Alex Klyubin53d544a2015-07-15 17:15:08 -0700186 throw new ProviderException("Failed to buffer output", e);
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700187 }
Alex Klyubin00af27b2015-06-01 16:07:53 -0700188 result = bufferedOutput.toByteArray();
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700189 }
Alex Klyubin00af27b2015-06-01 16:07:53 -0700190 mProducedOutputSizeBytes += result.length;
191 return result;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700192 }
193 }
194 }
195
Alex Klyubin00af27b2015-06-01 16:07:53 -0700196 byte[] result;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700197 if (bufferedOutput == null) {
198 // No output produced
Alex Klyubin00af27b2015-06-01 16:07:53 -0700199 result = EmptyArray.BYTE;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700200 } else {
Alex Klyubin00af27b2015-06-01 16:07:53 -0700201 result = bufferedOutput.toByteArray();
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700202 }
Alex Klyubin00af27b2015-06-01 16:07:53 -0700203 mProducedOutputSizeBytes += result.length;
204 return result;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700205 }
206
Alex Klyubin4f389fd2015-05-29 14:22:54 -0700207 @Override
Alex Klyubind23dc502015-06-24 12:25:52 -0700208 public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
209 byte[] signature, byte[] additionalEntropy) throws KeyStoreException {
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700210 if (inputLength == 0) {
211 // No input provided -- simplify the rest of the code
Alex Klyubin4f389fd2015-05-29 14:22:54 -0700212 input = EmptyArray.BYTE;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700213 inputOffset = 0;
214 }
215
Alex Klyubinb406f242015-03-31 13:39:38 -0700216 // Flush all buffered input and provided input into keystore/keymaster.
217 byte[] output = update(input, inputOffset, inputLength);
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700218 output = ArrayUtils.concat(output, flush());
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700219
Alex Klyubind23dc502015-06-24 12:25:52 -0700220 OperationResult opResult = mKeyStoreStream.finish(signature, additionalEntropy);
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700221 if (opResult == null) {
222 throw new KeyStoreConnectException();
223 } else if (opResult.resultCode != KeyStore.NO_ERROR) {
Alex Klyubinb4834ae2015-04-02 15:53:46 -0700224 throw KeyStore.getKeyStoreException(opResult.resultCode);
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700225 }
Alex Klyubin00af27b2015-06-01 16:07:53 -0700226 mProducedOutputSizeBytes += opResult.output.length;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700227
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700228 return ArrayUtils.concat(output, opResult.output);
Alex Klyubinb406f242015-03-31 13:39:38 -0700229 }
230
Alex Klyubinb4834ae2015-04-02 15:53:46 -0700231 public byte[] flush() throws KeyStoreException {
Alex Klyubinb406f242015-03-31 13:39:38 -0700232 if (mBufferedLength <= 0) {
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700233 return EmptyArray.BYTE;
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700234 }
235
Alex Klyubin53d544a2015-07-15 17:15:08 -0700236 // Keep invoking the update operation with remaining buffered data until either all of the
237 // buffered data is consumed or until update fails to consume anything.
238 ByteArrayOutputStream bufferedOutput = null;
239 while (mBufferedLength > 0) {
240 byte[] chunk = ArrayUtils.subarray(mBuffered, mBufferedOffset, mBufferedLength);
241 OperationResult opResult = mKeyStoreStream.update(chunk);
242 if (opResult == null) {
243 throw new KeyStoreConnectException();
244 } else if (opResult.resultCode != KeyStore.NO_ERROR) {
245 throw KeyStore.getKeyStoreException(opResult.resultCode);
246 }
Alex Klyubinb406f242015-03-31 13:39:38 -0700247
Alex Klyubin53d544a2015-07-15 17:15:08 -0700248 if (opResult.inputConsumed <= 0) {
249 // Nothing was consumed. Break out of the loop to avoid an infinite loop.
250 break;
251 }
252
253 if (opResult.inputConsumed >= chunk.length) {
254 // All of the input was consumed
255 mBuffered = EmptyArray.BYTE;
256 mBufferedOffset = 0;
257 mBufferedLength = 0;
258 } else {
259 // Some of the input was not consumed
260 mBuffered = chunk;
261 mBufferedOffset = opResult.inputConsumed;
262 mBufferedLength = chunk.length - opResult.inputConsumed;
263 }
264
265 if (opResult.inputConsumed > chunk.length) {
266 throw new KeyStoreException(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR,
267 "Keystore consumed more input than provided. Provided: "
268 + chunk.length + ", consumed: " + opResult.inputConsumed);
269 }
270
271 if ((opResult.output != null) && (opResult.output.length > 0)) {
272 // Some output was produced by this update operation
273 if (bufferedOutput == null) {
274 // No output buffered yet.
275 if (mBufferedLength == 0) {
276 // No more output will be produced by this flush operation
277 mProducedOutputSizeBytes += opResult.output.length;
278 return opResult.output;
279 } else {
280 // More output might be produced by this flush operation -- buffer output.
281 bufferedOutput = new ByteArrayOutputStream();
282 }
283 }
284 // Buffer the output from this update operation
285 try {
286 bufferedOutput.write(opResult.output);
287 } catch (IOException e) {
288 throw new ProviderException("Failed to buffer output", e);
289 }
290 }
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700291 }
Alex Klyubinb406f242015-03-31 13:39:38 -0700292
Alex Klyubin53d544a2015-07-15 17:15:08 -0700293 if (mBufferedLength > 0) {
294 throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH,
295 "Keystore failed to consume last "
296 + ((mBufferedLength != 1) ? (mBufferedLength + " bytes") : "byte")
297 + " of input");
Alex Klyubinb406f242015-03-31 13:39:38 -0700298 }
299
Alex Klyubin53d544a2015-07-15 17:15:08 -0700300 byte[] result = (bufferedOutput != null) ? bufferedOutput.toByteArray() : EmptyArray.BYTE;
Alex Klyubin00af27b2015-06-01 16:07:53 -0700301 mProducedOutputSizeBytes += result.length;
302 return result;
303 }
304
305 @Override
306 public long getConsumedInputSizeBytes() {
307 return mConsumedInputSizeBytes;
308 }
309
310 @Override
311 public long getProducedOutputSizeBytes() {
312 return mProducedOutputSizeBytes;
Alex Klyubinb406f242015-03-31 13:39:38 -0700313 }
314
315 /**
316 * Main data stream via a KeyStore streaming operation.
317 *
318 * <p>For example, for an encryption operation, this is the stream through which plaintext is
319 * provided and ciphertext is obtained.
320 */
321 public static class MainDataStream implements Stream {
322
323 private final KeyStore mKeyStore;
324 private final IBinder mOperationToken;
325
326 public MainDataStream(KeyStore keyStore, IBinder operationToken) {
327 mKeyStore = keyStore;
328 mOperationToken = operationToken;
329 }
330
331 @Override
332 public OperationResult update(byte[] input) {
333 return mKeyStore.update(mOperationToken, null, input);
334 }
335
336 @Override
Alex Klyubind23dc502015-06-24 12:25:52 -0700337 public OperationResult finish(byte[] signature, byte[] additionalEntropy) {
338 return mKeyStore.finish(mOperationToken, null, signature, additionalEntropy);
Alex Klyubinb406f242015-03-31 13:39:38 -0700339 }
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700340 }
341}