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