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.annotation.Nullable;
21 import android.hardware.security.keymint.KeyParameter;
22 import android.security.keymaster.KeymasterDefs;
23 import android.security.keystore.ArrayUtils;
24 import android.security.keystore.KeyProperties;
25 
26 import java.security.AlgorithmParameters;
27 import java.security.InvalidAlgorithmParameterException;
28 import java.security.InvalidKeyException;
29 import java.security.Key;
30 import java.security.NoSuchAlgorithmException;
31 import java.security.ProviderException;
32 import java.security.spec.AlgorithmParameterSpec;
33 import java.security.spec.InvalidParameterSpecException;
34 import java.util.Arrays;
35 import java.util.List;
36 
37 import javax.crypto.CipherSpi;
38 import javax.crypto.spec.IvParameterSpec;
39 
40 /**
41  * Base class for Android Keystore unauthenticated AES {@link CipherSpi} implementations.
42  *
43  * @hide
44  */
45 abstract class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase {
46 
47     abstract static class ECB extends AndroidKeyStoreUnauthenticatedAESCipherSpi {
ECB(int keymasterPadding)48         protected ECB(int keymasterPadding) {
49             super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false);
50         }
51 
52         public static class NoPadding extends ECB {
NoPadding()53             public NoPadding() {
54                 super(KeymasterDefs.KM_PAD_NONE);
55             }
56 
57             @Override
getTransform()58             protected final String getTransform() {
59                 return "AES/ECB/NoPadding";
60             }
61         }
62 
63         public static class PKCS7Padding extends ECB {
PKCS7Padding()64             public PKCS7Padding() {
65                 super(KeymasterDefs.KM_PAD_PKCS7);
66             }
67 
68             @Override
getTransform()69             protected final String getTransform() {
70                 return "AES/ECB/PKCS7Padding";
71             }
72         }
73     }
74 
75     abstract static class CBC extends AndroidKeyStoreUnauthenticatedAESCipherSpi {
CBC(int keymasterPadding)76         protected CBC(int keymasterPadding) {
77             super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true);
78         }
79 
80         public static class NoPadding extends CBC {
NoPadding()81             public NoPadding() {
82                 super(KeymasterDefs.KM_PAD_NONE);
83             }
84 
85             @Override
getTransform()86             protected final String getTransform() {
87                 return "AES/CBC/NoPadding";
88             }
89         }
90 
91         public static class PKCS7Padding extends CBC {
PKCS7Padding()92             public PKCS7Padding() {
93                 super(KeymasterDefs.KM_PAD_PKCS7);
94             }
95 
96             @Override
getTransform()97             protected final String getTransform() {
98                 return "AES/CBC/PKCS7Padding";
99             }
100         }
101     }
102 
103     abstract static class CTR extends AndroidKeyStoreUnauthenticatedAESCipherSpi {
CTR(int keymasterPadding)104         protected CTR(int keymasterPadding) {
105             super(KeymasterDefs.KM_MODE_CTR, keymasterPadding, true);
106         }
107 
108         public static class NoPadding extends CTR {
NoPadding()109             public NoPadding() {
110                 super(KeymasterDefs.KM_PAD_NONE);
111             }
112 
113             @Override
getTransform()114             protected final String getTransform() {
115                 return "AES/CTR/NoPadding";
116             }
117         }
118     }
119 
120     private static final int BLOCK_SIZE_BYTES = 16;
121 
122     private final int mKeymasterBlockMode;
123     private final int mKeymasterPadding;
124     /** Whether this transformation requires an IV. */
125     private final boolean mIvRequired;
126 
127     private byte[] mIv;
128 
129     /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
130     private boolean mIvHasBeenUsed;
131 
AndroidKeyStoreUnauthenticatedAESCipherSpi( int keymasterBlockMode, int keymasterPadding, boolean ivRequired)132     AndroidKeyStoreUnauthenticatedAESCipherSpi(
133             int keymasterBlockMode,
134             int keymasterPadding,
135             boolean ivRequired) {
136         mKeymasterBlockMode = keymasterBlockMode;
137         mKeymasterPadding = keymasterPadding;
138         mIvRequired = ivRequired;
139     }
140 
141     @Override
resetAll()142     protected final void resetAll() {
143         mIv = null;
144         mIvHasBeenUsed = false;
145         super.resetAll();
146     }
147 
148     @Override
resetWhilePreservingInitState()149     protected final void resetWhilePreservingInitState() {
150         super.resetWhilePreservingInitState();
151     }
152 
153     @Override
initKey(int opmode, Key key)154     protected final void initKey(int opmode, Key key) throws InvalidKeyException {
155         if (!(key instanceof AndroidKeyStoreSecretKey)) {
156             throw new InvalidKeyException(
157                     "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
158         }
159         if (!KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(key.getAlgorithm())) {
160             throw new InvalidKeyException(
161                     "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " +
162                     KeyProperties.KEY_ALGORITHM_AES + " supported");
163         }
164         setKey((AndroidKeyStoreSecretKey) key);
165     }
166 
167     @Override
initAlgorithmSpecificParameters()168     protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {
169         if (!mIvRequired) {
170             return;
171         }
172 
173         // IV is used
174         if (!isEncrypting()) {
175             throw new InvalidKeyException("IV required when decrypting"
176                     + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
177         }
178     }
179 
180     @Override
initAlgorithmSpecificParameters(AlgorithmParameterSpec params)181     protected final void initAlgorithmSpecificParameters(AlgorithmParameterSpec params)
182             throws InvalidAlgorithmParameterException {
183         if (!mIvRequired) {
184             if (params != null) {
185                 throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
186             }
187             return;
188         }
189 
190         // IV is used
191         if (params == null) {
192             if (!isEncrypting()) {
193                 // IV must be provided by the caller
194                 throw new InvalidAlgorithmParameterException(
195                         "IvParameterSpec must be provided when decrypting");
196             }
197             return;
198         }
199         if (!(params instanceof IvParameterSpec)) {
200             throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported");
201         }
202         mIv = ((IvParameterSpec) params).getIV();
203         if (mIv == null) {
204             throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec");
205         }
206     }
207 
208     @Override
initAlgorithmSpecificParameters(AlgorithmParameters params)209     protected final void initAlgorithmSpecificParameters(AlgorithmParameters params)
210             throws InvalidAlgorithmParameterException {
211         if (!mIvRequired) {
212             if (params != null) {
213                 throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
214             }
215             return;
216         }
217 
218         // IV is used
219         if (params == null) {
220             if (!isEncrypting()) {
221                 // IV must be provided by the caller
222                 throw new InvalidAlgorithmParameterException("IV required when decrypting"
223                         + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
224             }
225             return;
226         }
227 
228         if (!"AES".equalsIgnoreCase(params.getAlgorithm())) {
229             throw new InvalidAlgorithmParameterException(
230                     "Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm()
231                     + ". Supported: AES");
232         }
233 
234         IvParameterSpec ivSpec;
235         try {
236             ivSpec = params.getParameterSpec(IvParameterSpec.class);
237         } catch (InvalidParameterSpecException e) {
238             if (!isEncrypting()) {
239                 // IV must be provided by the caller
240                 throw new InvalidAlgorithmParameterException("IV required when decrypting"
241                         + ", but not found in parameters: " + params, e);
242             }
243             mIv = null;
244             return;
245         }
246         mIv = ivSpec.getIV();
247         if (mIv == null) {
248             throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters");
249         }
250     }
251 
252     @Override
getAdditionalEntropyAmountForBegin()253     protected final int getAdditionalEntropyAmountForBegin() {
254         if ((mIvRequired) && (mIv == null) && (isEncrypting())) {
255             // IV will need to be generated
256             return BLOCK_SIZE_BYTES;
257         }
258 
259         return 0;
260     }
261 
262     @Override
getAdditionalEntropyAmountForFinish()263     protected final int getAdditionalEntropyAmountForFinish() {
264         return 0;
265     }
266 
267     @Override
addAlgorithmSpecificParametersToBegin( @onNull List<KeyParameter> parameters)268     protected final void addAlgorithmSpecificParametersToBegin(
269             @NonNull List<KeyParameter> parameters) {
270         if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) {
271             // IV is being reused for encryption: this violates security best practices.
272             throw new IllegalStateException(
273                     "IV has already been used. Reusing IV in encryption mode violates security best"
274                     + " practices.");
275         }
276 
277         parameters.add(KeyStore2ParameterUtils.makeEnum(
278                 KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES
279         ));
280         parameters.add(KeyStore2ParameterUtils.makeEnum(
281                 KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode
282         ));
283         parameters.add(KeyStore2ParameterUtils.makeEnum(
284                 KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding
285         ));
286         if ((mIvRequired) && (mIv != null)) {
287             parameters.add(KeyStore2ParameterUtils.makeBytes(
288                     KeymasterDefs.KM_TAG_NONCE, mIv
289             ));
290         }
291     }
292 
293     @Override
loadAlgorithmSpecificParametersFromBeginResult( KeyParameter[] parameters)294     protected final void loadAlgorithmSpecificParametersFromBeginResult(
295             KeyParameter[] parameters) {
296         mIvHasBeenUsed = true;
297 
298         // NOTE: Keymaster doesn't always return an IV, even if it's used.
299         byte[] returnedIv = null;
300         if (parameters != null) {
301             for (KeyParameter p : parameters) {
302                 if (p.tag == KeymasterDefs.KM_TAG_NONCE) {
303                     returnedIv = p.value.getBlob();
304                     break;
305                 }
306             }
307         }
308 
309         if (mIvRequired) {
310             if (mIv == null) {
311                 mIv = returnedIv;
312             } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
313                 throw new ProviderException("IV in use differs from provided IV");
314             }
315         } else {
316             if (returnedIv != null) {
317                 throw new ProviderException(
318                         "IV in use despite IV not being used by this transformation");
319             }
320         }
321     }
322 
323     @Override
engineGetBlockSize()324     protected final int engineGetBlockSize() {
325         return BLOCK_SIZE_BYTES;
326     }
327 
328     @Override
engineGetOutputSize(int inputLen)329     protected final int engineGetOutputSize(int inputLen) {
330         return inputLen + 3 * BLOCK_SIZE_BYTES;
331     }
332 
333     @Override
engineGetIV()334     protected final byte[] engineGetIV() {
335         return ArrayUtils.cloneIfNotEmpty(mIv);
336     }
337 
338     @Nullable
339     @Override
engineGetParameters()340     protected final AlgorithmParameters engineGetParameters() {
341         if (!mIvRequired) {
342             return null;
343         }
344         if ((mIv != null) && (mIv.length > 0)) {
345             try {
346                 AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
347                 params.init(new IvParameterSpec(mIv));
348                 return params;
349             } catch (NoSuchAlgorithmException e) {
350                 throw new ProviderException(
351                         "Failed to obtain AES AlgorithmParameters", e);
352             } catch (InvalidParameterSpecException e) {
353                 throw new ProviderException(
354                         "Failed to initialize AES AlgorithmParameters with an IV",
355                         e);
356             }
357         }
358         return null;
359     }
360 }
361