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