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.os.IBinder;
22import android.security.KeyStore;
23import android.security.KeyStoreException;
24import android.security.keymaster.KeymasterArguments;
25import android.security.keymaster.KeymasterDefs;
26import android.security.keymaster.OperationResult;
27import android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.Stream;
28
29import libcore.util.EmptyArray;
30
31import java.io.ByteArrayOutputStream;
32import java.io.IOException;
33import java.security.AlgorithmParameters;
34import java.security.InvalidAlgorithmParameterException;
35import java.security.InvalidKeyException;
36import java.security.Key;
37import java.security.NoSuchAlgorithmException;
38import java.security.ProviderException;
39import java.security.spec.AlgorithmParameterSpec;
40import java.security.spec.InvalidParameterSpecException;
41import java.util.Arrays;
42
43import javax.crypto.CipherSpi;
44import javax.crypto.spec.GCMParameterSpec;
45
46/**
47 * Base class for Android Keystore authenticated AES {@link CipherSpi} implementations.
48 *
49 * @hide
50 */
51abstract 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
61        GCM(int keymasterPadding) {
62            super(KeymasterDefs.KM_MODE_GCM, keymasterPadding);
63        }
64
65        @Override
66        protected final void resetAll() {
67            mTagLengthBits = DEFAULT_TAG_LENGTH_BITS;
68            super.resetAll();
69        }
70
71        @Override
72        protected final void resetWhilePreservingInitState() {
73            super.resetWhilePreservingInitState();
74        }
75
76        @Override
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
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
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
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
175        protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
176                KeyStore keyStore, IBinder operationToken) {
177            KeyStoreCryptoOperationStreamer streamer = new KeyStoreCryptoOperationChunkedStreamer(
178                    new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
179                            keyStore, operationToken));
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
191        protected final KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer(
192                KeyStore keyStore, IBinder operationToken) {
193            return new KeyStoreCryptoOperationChunkedStreamer(
194                    new AdditionalAuthenticationDataStream(keyStore, operationToken));
195        }
196
197        @Override
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
208        protected final int getAdditionalEntropyAmountForFinish() {
209            return 0;
210        }
211
212        @Override
213        protected final void addAlgorithmSpecificParametersToBegin(
214                @NonNull KeymasterArguments keymasterArgs) {
215            super.addAlgorithmSpecificParametersToBegin(keymasterArgs);
216            keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mTagLengthBits);
217        }
218
219        protected final int getTagLengthBits() {
220            return mTagLengthBits;
221        }
222
223        public static final class NoPadding extends GCM {
224            public NoPadding() {
225                super(KeymasterDefs.KM_PAD_NONE);
226            }
227
228            @Override
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
259    AndroidKeyStoreAuthenticatedAESCipherSpi(
260            int keymasterBlockMode,
261            int keymasterPadding) {
262        mKeymasterBlockMode = keymasterBlockMode;
263        mKeymasterPadding = keymasterPadding;
264    }
265
266    @Override
267    protected void resetAll() {
268        mIv = null;
269        mIvHasBeenUsed = false;
270        super.resetAll();
271    }
272
273    @Override
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
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
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
324    protected final int engineGetBlockSize() {
325        return BLOCK_SIZE_BYTES;
326    }
327
328    @Override
329    protected final byte[] engineGetIV() {
330        return ArrayUtils.cloneIfNotEmpty(mIv);
331    }
332
333    protected void setIv(byte[] iv) {
334        mIv = iv;
335    }
336
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
352        private BufferAllOutputUntilDoFinalStreamer(KeyStoreCryptoOperationStreamer delegate) {
353            mDelegate = delegate;
354        }
355
356        @Override
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
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
389        public long getConsumedInputSizeBytes() {
390            return mDelegate.getConsumedInputSizeBytes();
391        }
392
393        @Override
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
408        private AdditionalAuthenticationDataStream(KeyStore keyStore, IBinder operationToken) {
409            mKeyStore = keyStore;
410            mOperationToken = operationToken;
411        }
412
413        @Override
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
435        public OperationResult finish(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}