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