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.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.IBinder;
22 import android.security.KeyStore;
23 import android.security.KeyStoreException;
24 import android.security.keymaster.KeymasterArguments;
25 import android.security.keymaster.KeymasterDefs;
26 import android.security.keymaster.OperationResult;
27 import android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.Stream;
28 
29 import libcore.util.EmptyArray;
30 
31 import java.io.ByteArrayOutputStream;
32 import java.io.IOException;
33 import java.security.AlgorithmParameters;
34 import java.security.InvalidAlgorithmParameterException;
35 import java.security.InvalidKeyException;
36 import java.security.Key;
37 import java.security.NoSuchAlgorithmException;
38 import java.security.ProviderException;
39 import java.security.spec.AlgorithmParameterSpec;
40 import java.security.spec.InvalidParameterSpecException;
41 import java.util.Arrays;
42 
43 import javax.crypto.CipherSpi;
44 import javax.crypto.spec.GCMParameterSpec;
45 
46 /**
47  * Base class for Android Keystore authenticated AES {@link CipherSpi} implementations.
48  *
49  * @hide
50  */
51 abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase {
52 
53     abstract static class GCM extends AndroidKeyStoreAuthenticatedAESCipherSpi {
54         static final int MIN_SUPPORTED_TAG_LENGTH_BITS = 96;
55         private static final int MAX_SUPPORTED_TAG_LENGTH_BITS = 128;
56         private static final int DEFAULT_TAG_LENGTH_BITS = 128;
57         private static final int IV_LENGTH_BYTES = 12;
58 
59         private int mTagLengthBits = DEFAULT_TAG_LENGTH_BITS;
60 
GCM(int keymasterPadding)61         GCM(int keymasterPadding) {
62             super(KeymasterDefs.KM_MODE_GCM, keymasterPadding);
63         }
64 
65         @Override
resetAll()66         protected final void resetAll() {
67             mTagLengthBits = DEFAULT_TAG_LENGTH_BITS;
68             super.resetAll();
69         }
70 
71         @Override
resetWhilePreservingInitState()72         protected final void resetWhilePreservingInitState() {
73             super.resetWhilePreservingInitState();
74         }
75 
76         @Override
initAlgorithmSpecificParameters()77         protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {
78             if (!isEncrypting()) {
79                 throw new InvalidKeyException("IV required when decrypting"
80                         + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
81             }
82         }
83 
84         @Override
initAlgorithmSpecificParameters(AlgorithmParameterSpec params)85         protected final void initAlgorithmSpecificParameters(AlgorithmParameterSpec params)
86                 throws InvalidAlgorithmParameterException {
87             // IV is used
88             if (params == null) {
89                 if (!isEncrypting()) {
90                     // IV must be provided by the caller
91                     throw new InvalidAlgorithmParameterException(
92                             "GCMParameterSpec must be provided when decrypting");
93                 }
94                 return;
95             }
96             if (!(params instanceof GCMParameterSpec)) {
97                 throw new InvalidAlgorithmParameterException("Only GCMParameterSpec supported");
98             }
99             GCMParameterSpec spec = (GCMParameterSpec) params;
100             byte[] iv = spec.getIV();
101             if (iv == null) {
102                 throw new InvalidAlgorithmParameterException("Null IV in GCMParameterSpec");
103             } else if (iv.length != IV_LENGTH_BYTES) {
104                 throw new InvalidAlgorithmParameterException("Unsupported IV length: "
105                         + iv.length + " bytes. Only " + IV_LENGTH_BYTES
106                         + " bytes long IV supported");
107             }
108             int tagLengthBits = spec.getTLen();
109             if ((tagLengthBits < MIN_SUPPORTED_TAG_LENGTH_BITS)
110                     || (tagLengthBits > MAX_SUPPORTED_TAG_LENGTH_BITS)
111                     || ((tagLengthBits % 8) != 0)) {
112                 throw new InvalidAlgorithmParameterException(
113                         "Unsupported tag length: " + tagLengthBits + " bits"
114                         + ". Supported lengths: 96, 104, 112, 120, 128");
115             }
116             setIv(iv);
117             mTagLengthBits = tagLengthBits;
118         }
119 
120         @Override
initAlgorithmSpecificParameters(AlgorithmParameters params)121         protected final void initAlgorithmSpecificParameters(AlgorithmParameters params)
122                 throws InvalidAlgorithmParameterException {
123             if (params == null) {
124                 if (!isEncrypting()) {
125                     // IV must be provided by the caller
126                     throw new InvalidAlgorithmParameterException("IV required when decrypting"
127                             + ". Use GCMParameterSpec or GCM AlgorithmParameters to provide it.");
128                 }
129                 return;
130             }
131 
132             if (!"GCM".equalsIgnoreCase(params.getAlgorithm())) {
133                 throw new InvalidAlgorithmParameterException(
134                         "Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm()
135                         + ". Supported: GCM");
136             }
137 
138             GCMParameterSpec spec;
139             try {
140                 spec = params.getParameterSpec(GCMParameterSpec.class);
141             } catch (InvalidParameterSpecException e) {
142                 if (!isEncrypting()) {
143                     // IV must be provided by the caller
144                     throw new InvalidAlgorithmParameterException("IV and tag length required when"
145                             + " decrypting, but not found in parameters: " + params, e);
146                 }
147                 setIv(null);
148                 return;
149             }
150             initAlgorithmSpecificParameters(spec);
151         }
152 
153         @Nullable
154         @Override
engineGetParameters()155         protected final AlgorithmParameters engineGetParameters() {
156             byte[] iv = getIv();
157             if ((iv != null) && (iv.length > 0)) {
158                 try {
159                     AlgorithmParameters params = AlgorithmParameters.getInstance("GCM");
160                     params.init(new GCMParameterSpec(mTagLengthBits, iv));
161                     return params;
162                 } catch (NoSuchAlgorithmException e) {
163                     throw new ProviderException(
164                             "Failed to obtain GCM AlgorithmParameters", e);
165                 } catch (InvalidParameterSpecException e) {
166                     throw new ProviderException(
167                             "Failed to initialize GCM AlgorithmParameters", e);
168                 }
169             }
170             return null;
171         }
172 
173         @NonNull
174         @Override
createMainDataStreamer( KeyStore keyStore, IBinder operationToken)175         protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
176                 KeyStore keyStore, IBinder operationToken) {
177             KeyStoreCryptoOperationStreamer streamer = new KeyStoreCryptoOperationChunkedStreamer(
178                     new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
179                             keyStore, operationToken), 0);
180             if (isEncrypting()) {
181                 return streamer;
182             } else {
183                 // When decrypting, to avoid leaking unauthenticated plaintext, do not return any
184                 // plaintext before ciphertext is authenticated by KeyStore.finish.
185                 return new BufferAllOutputUntilDoFinalStreamer(streamer);
186             }
187         }
188 
189         @NonNull
190         @Override
createAdditionalAuthenticationDataStreamer( KeyStore keyStore, IBinder operationToken)191         protected final KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer(
192                 KeyStore keyStore, IBinder operationToken) {
193             return new KeyStoreCryptoOperationChunkedStreamer(
194                     new AdditionalAuthenticationDataStream(keyStore, operationToken), 0);
195         }
196 
197         @Override
getAdditionalEntropyAmountForBegin()198         protected final int getAdditionalEntropyAmountForBegin() {
199             if ((getIv() == null) && (isEncrypting())) {
200                 // IV will need to be generated
201                 return IV_LENGTH_BYTES;
202             }
203 
204             return 0;
205         }
206 
207         @Override
getAdditionalEntropyAmountForFinish()208         protected final int getAdditionalEntropyAmountForFinish() {
209             return 0;
210         }
211 
212         @Override
addAlgorithmSpecificParametersToBegin( @onNull KeymasterArguments keymasterArgs)213         protected final void addAlgorithmSpecificParametersToBegin(
214                 @NonNull KeymasterArguments keymasterArgs) {
215             super.addAlgorithmSpecificParametersToBegin(keymasterArgs);
216             keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mTagLengthBits);
217         }
218 
getTagLengthBits()219         protected final int getTagLengthBits() {
220             return mTagLengthBits;
221         }
222 
223         public static final class NoPadding extends GCM {
NoPadding()224             public NoPadding() {
225                 super(KeymasterDefs.KM_PAD_NONE);
226             }
227 
228             @Override
engineGetOutputSize(int inputLen)229             protected final int engineGetOutputSize(int inputLen) {
230                 int tagLengthBytes = (getTagLengthBits() + 7) / 8;
231                 long result;
232                 if (isEncrypting()) {
233                     result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen
234                             + tagLengthBytes;
235                 } else {
236                     result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen
237                             - tagLengthBytes;
238                 }
239                 if (result < 0) {
240                     return 0;
241                 } else if (result > Integer.MAX_VALUE) {
242                     return Integer.MAX_VALUE;
243                 }
244                 return (int) result;
245             }
246         }
247     }
248 
249     private static final int BLOCK_SIZE_BYTES = 16;
250 
251     private final int mKeymasterBlockMode;
252     private final int mKeymasterPadding;
253 
254     private byte[] mIv;
255 
256     /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
257     private boolean mIvHasBeenUsed;
258 
AndroidKeyStoreAuthenticatedAESCipherSpi( int keymasterBlockMode, int keymasterPadding)259     AndroidKeyStoreAuthenticatedAESCipherSpi(
260             int keymasterBlockMode,
261             int keymasterPadding) {
262         mKeymasterBlockMode = keymasterBlockMode;
263         mKeymasterPadding = keymasterPadding;
264     }
265 
266     @Override
resetAll()267     protected void resetAll() {
268         mIv = null;
269         mIvHasBeenUsed = false;
270         super.resetAll();
271     }
272 
273     @Override
initKey(int opmode, Key key)274     protected final void initKey(int opmode, Key key) throws InvalidKeyException {
275         if (!(key instanceof AndroidKeyStoreSecretKey)) {
276             throw new InvalidKeyException(
277                     "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
278         }
279         if (!KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(key.getAlgorithm())) {
280             throw new InvalidKeyException(
281                     "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " +
282                     KeyProperties.KEY_ALGORITHM_AES + " supported");
283         }
284         setKey((AndroidKeyStoreSecretKey) key);
285     }
286 
287     @Override
addAlgorithmSpecificParametersToBegin( @onNull KeymasterArguments keymasterArgs)288     protected void addAlgorithmSpecificParametersToBegin(
289             @NonNull KeymasterArguments keymasterArgs) {
290         if ((isEncrypting()) && (mIvHasBeenUsed)) {
291             // IV is being reused for encryption: this violates security best practices.
292             throw new IllegalStateException(
293                     "IV has already been used. Reusing IV in encryption mode violates security best"
294                     + " practices.");
295         }
296 
297         keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
298         keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
299         keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
300         if (mIv != null) {
301             keymasterArgs.addBytes(KeymasterDefs.KM_TAG_NONCE, mIv);
302         }
303     }
304 
305     @Override
loadAlgorithmSpecificParametersFromBeginResult( @onNull KeymasterArguments keymasterArgs)306     protected final void loadAlgorithmSpecificParametersFromBeginResult(
307             @NonNull KeymasterArguments keymasterArgs) {
308         mIvHasBeenUsed = true;
309 
310         // NOTE: Keymaster doesn't always return an IV, even if it's used.
311         byte[] returnedIv = keymasterArgs.getBytes(KeymasterDefs.KM_TAG_NONCE, null);
312         if ((returnedIv != null) && (returnedIv.length == 0)) {
313             returnedIv = null;
314         }
315 
316         if (mIv == null) {
317             mIv = returnedIv;
318         } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
319             throw new ProviderException("IV in use differs from provided IV");
320         }
321     }
322 
323     @Override
engineGetBlockSize()324     protected final int engineGetBlockSize() {
325         return BLOCK_SIZE_BYTES;
326     }
327 
328     @Override
engineGetIV()329     protected final byte[] engineGetIV() {
330         return ArrayUtils.cloneIfNotEmpty(mIv);
331     }
332 
setIv(byte[] iv)333     protected void setIv(byte[] iv) {
334         mIv = iv;
335     }
336 
getIv()337     protected byte[] getIv() {
338         return mIv;
339     }
340 
341     /**
342      * {@link KeyStoreCryptoOperationStreamer} which buffers all output until {@code doFinal} from
343      * which it returns all output in one go, provided {@code doFinal} succeeds.
344      */
345     private static class BufferAllOutputUntilDoFinalStreamer
346         implements KeyStoreCryptoOperationStreamer {
347 
348         private final KeyStoreCryptoOperationStreamer mDelegate;
349         private ByteArrayOutputStream mBufferedOutput = new ByteArrayOutputStream();
350         private long mProducedOutputSizeBytes;
351 
BufferAllOutputUntilDoFinalStreamer(KeyStoreCryptoOperationStreamer delegate)352         private BufferAllOutputUntilDoFinalStreamer(KeyStoreCryptoOperationStreamer delegate) {
353             mDelegate = delegate;
354         }
355 
356         @Override
update(byte[] input, int inputOffset, int inputLength)357         public byte[] update(byte[] input, int inputOffset, int inputLength)
358                 throws KeyStoreException {
359             byte[] output = mDelegate.update(input, inputOffset, inputLength);
360             if (output != null) {
361                 try {
362                     mBufferedOutput.write(output);
363                 } catch (IOException e) {
364                     throw new ProviderException("Failed to buffer output", e);
365                 }
366             }
367             return EmptyArray.BYTE;
368         }
369 
370         @Override
doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature, byte[] additionalEntropy)371         public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
372                 byte[] signature, byte[] additionalEntropy) throws KeyStoreException {
373             byte[] output = mDelegate.doFinal(input, inputOffset, inputLength, signature,
374                     additionalEntropy);
375             if (output != null) {
376                 try {
377                     mBufferedOutput.write(output);
378                 } catch (IOException e) {
379                     throw new ProviderException("Failed to buffer output", e);
380                 }
381             }
382             byte[] result = mBufferedOutput.toByteArray();
383             mBufferedOutput.reset();
384             mProducedOutputSizeBytes += result.length;
385             return result;
386         }
387 
388         @Override
getConsumedInputSizeBytes()389         public long getConsumedInputSizeBytes() {
390             return mDelegate.getConsumedInputSizeBytes();
391         }
392 
393         @Override
getProducedOutputSizeBytes()394         public long getProducedOutputSizeBytes() {
395             return mProducedOutputSizeBytes;
396         }
397     }
398 
399     /**
400      * Additional Authentication Data (AAD) stream via a KeyStore streaming operation. This stream
401      * sends AAD into the KeyStore.
402      */
403     private static class AdditionalAuthenticationDataStream implements Stream {
404 
405         private final KeyStore mKeyStore;
406         private final IBinder mOperationToken;
407 
AdditionalAuthenticationDataStream(KeyStore keyStore, IBinder operationToken)408         private AdditionalAuthenticationDataStream(KeyStore keyStore, IBinder operationToken) {
409             mKeyStore = keyStore;
410             mOperationToken = operationToken;
411         }
412 
413         @Override
update(byte[] input)414         public OperationResult update(byte[] input) {
415             KeymasterArguments keymasterArgs = new KeymasterArguments();
416             keymasterArgs.addBytes(KeymasterDefs.KM_TAG_ASSOCIATED_DATA, input);
417 
418             // KeyStore does not reflect AAD in inputConsumed, but users of Stream rely on this
419             // field. We fix this discrepancy here. KeyStore.update contract is that all of AAD
420             // has been consumed if the method succeeds.
421             OperationResult result = mKeyStore.update(mOperationToken, keymasterArgs, null);
422             if (result.resultCode == KeyStore.NO_ERROR) {
423                 result = new OperationResult(
424                         result.resultCode,
425                         result.token,
426                         result.operationHandle,
427                         input.length, // inputConsumed
428                         result.output,
429                         result.outParams);
430             }
431             return result;
432         }
433 
434         @Override
finish(byte[] input, byte[] signature, byte[] additionalEntropy)435         public OperationResult finish(byte[] input, byte[] signature, byte[] additionalEntropy) {
436             if ((additionalEntropy != null) && (additionalEntropy.length > 0)) {
437                 throw new ProviderException("AAD stream does not support additional entropy");
438             }
439             return new OperationResult(
440                     KeyStore.NO_ERROR,
441                     mOperationToken,
442                     0, // operation handle -- nobody cares about this being returned from finish
443                     0, // inputConsumed
444                     EmptyArray.BYTE, // output
445                     new KeymasterArguments() // additional params returned by finish
446                     );
447         }
448     }
449 }