AndroidKeyStoreAuthenticatedAESCipherSpi.java revision 00af27b7d9010eb41e45959dab7c4ff6de119897
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        private 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            GCMParameterSpec spec;
133            try {
134                spec = params.getParameterSpec(GCMParameterSpec.class);
135            } catch (InvalidParameterSpecException e) {
136                if (!isEncrypting()) {
137                    // IV must be provided by the caller
138                    throw new InvalidAlgorithmParameterException("IV and tag length required when"
139                            + " decrypting, but not found in parameters: " + params, e);
140                }
141                setIv(null);
142                return;
143            }
144            initAlgorithmSpecificParameters(spec);
145        }
146
147        @Nullable
148        @Override
149        protected final AlgorithmParameters engineGetParameters() {
150            byte[] iv = getIv();
151            if ((iv != null) && (iv.length > 0)) {
152                try {
153                    AlgorithmParameters params = AlgorithmParameters.getInstance("GCM");
154                    params.init(new GCMParameterSpec(mTagLengthBits, iv));
155                    return params;
156                } catch (NoSuchAlgorithmException e) {
157                    throw new ProviderException(
158                            "Failed to obtain GCM AlgorithmParameters", e);
159                } catch (InvalidParameterSpecException e) {
160                    throw new ProviderException(
161                            "Failed to initialize GCM AlgorithmParameters", e);
162                }
163            }
164            return null;
165        }
166
167        @NonNull
168        @Override
169        protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
170                KeyStore keyStore, IBinder operationToken) {
171            KeyStoreCryptoOperationStreamer streamer = new KeyStoreCryptoOperationChunkedStreamer(
172                    new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
173                            keyStore, operationToken));
174            if (isEncrypting()) {
175                return streamer;
176            } else {
177                // When decrypting, to avoid leaking unauthenticated plaintext, do not return any
178                // plaintext before ciphertext is authenticated by KeyStore.finish.
179                return new BufferAllOutputUntilDoFinalStreamer(streamer);
180            }
181        }
182
183        @NonNull
184        @Override
185        protected final KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer(
186                KeyStore keyStore, IBinder operationToken) {
187            return new KeyStoreCryptoOperationChunkedStreamer(
188                    new AdditionalAuthenticationDataStream(keyStore, operationToken));
189        }
190
191        @Override
192        protected final int getAdditionalEntropyAmountForBegin() {
193            if ((getIv() == null) && (isEncrypting())) {
194                // IV will need to be generated
195                return IV_LENGTH_BYTES;
196            }
197
198            return 0;
199        }
200
201        @Override
202        protected final int getAdditionalEntropyAmountForFinish() {
203            return 0;
204        }
205
206        @Override
207        protected final void addAlgorithmSpecificParametersToBegin(
208                @NonNull KeymasterArguments keymasterArgs) {
209            super.addAlgorithmSpecificParametersToBegin(keymasterArgs);
210            keymasterArgs.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mTagLengthBits);
211        }
212
213        protected final int getTagLengthBits() {
214            return mTagLengthBits;
215        }
216
217        public static final class NoPadding extends GCM {
218            public NoPadding() {
219                super(KeymasterDefs.KM_PAD_NONE);
220            }
221
222            @Override
223            protected final int engineGetOutputSize(int inputLen) {
224                int tagLengthBytes = (getTagLengthBits() + 7) / 8;
225                long result;
226                if (isEncrypting()) {
227                    result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen
228                            + tagLengthBytes;
229                } else {
230                    result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen
231                            - tagLengthBytes;
232                }
233                if (result < 0) {
234                    return 0;
235                } else if (result > Integer.MAX_VALUE) {
236                    return Integer.MAX_VALUE;
237                }
238                return (int) result;
239            }
240        }
241    }
242
243    private static final int BLOCK_SIZE_BYTES = 16;
244
245    private final int mKeymasterBlockMode;
246    private final int mKeymasterPadding;
247
248    private byte[] mIv;
249
250    /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
251    private boolean mIvHasBeenUsed;
252
253    AndroidKeyStoreAuthenticatedAESCipherSpi(
254            int keymasterBlockMode,
255            int keymasterPadding) {
256        mKeymasterBlockMode = keymasterBlockMode;
257        mKeymasterPadding = keymasterPadding;
258    }
259
260    @Override
261    protected void resetAll() {
262        mIv = null;
263        mIvHasBeenUsed = false;
264        super.resetAll();
265    }
266
267    @Override
268    protected final void initKey(int opmode, Key key) throws InvalidKeyException {
269        if (!(key instanceof AndroidKeyStoreSecretKey)) {
270            throw new InvalidKeyException(
271                    "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
272        }
273        if (!KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(key.getAlgorithm())) {
274            throw new InvalidKeyException(
275                    "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " +
276                    KeyProperties.KEY_ALGORITHM_AES + " supported");
277        }
278        setKey((AndroidKeyStoreSecretKey) key);
279    }
280
281    @Override
282    protected void addAlgorithmSpecificParametersToBegin(
283            @NonNull KeymasterArguments keymasterArgs) {
284        if ((isEncrypting()) && (mIvHasBeenUsed)) {
285            // IV is being reused for encryption: this violates security best practices.
286            throw new IllegalStateException(
287                    "IV has already been used. Reusing IV in encryption mode violates security best"
288                    + " practices.");
289        }
290
291        keymasterArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
292        keymasterArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
293        keymasterArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
294        if (mIv != null) {
295            keymasterArgs.addBlob(KeymasterDefs.KM_TAG_NONCE, mIv);
296        }
297    }
298
299    @Override
300    protected final void loadAlgorithmSpecificParametersFromBeginResult(
301            @NonNull KeymasterArguments keymasterArgs) {
302        mIvHasBeenUsed = true;
303
304        // NOTE: Keymaster doesn't always return an IV, even if it's used.
305        byte[] returnedIv = keymasterArgs.getBlob(KeymasterDefs.KM_TAG_NONCE, null);
306        if ((returnedIv != null) && (returnedIv.length == 0)) {
307            returnedIv = null;
308        }
309
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    }
316
317    @Override
318    protected final int engineGetBlockSize() {
319        return BLOCK_SIZE_BYTES;
320    }
321
322    @Override
323    protected final byte[] engineGetIV() {
324        return ArrayUtils.cloneIfNotEmpty(mIv);
325    }
326
327    protected void setIv(byte[] iv) {
328        mIv = iv;
329    }
330
331    protected byte[] getIv() {
332        return mIv;
333    }
334
335    /**
336     * {@link KeyStoreCryptoOperationStreamer} which buffers all output until {@code doFinal} from
337     * which it returns all output in one go, provided {@code doFinal} succeeds.
338     */
339    private static class BufferAllOutputUntilDoFinalStreamer
340        implements KeyStoreCryptoOperationStreamer {
341
342        private final KeyStoreCryptoOperationStreamer mDelegate;
343        private ByteArrayOutputStream mBufferedOutput = new ByteArrayOutputStream();
344        private long mProducedOutputSizeBytes;
345
346        private BufferAllOutputUntilDoFinalStreamer(KeyStoreCryptoOperationStreamer delegate) {
347            mDelegate = delegate;
348        }
349
350        @Override
351        public byte[] update(byte[] input, int inputOffset, int inputLength)
352                throws KeyStoreException {
353            byte[] output = mDelegate.update(input, inputOffset, inputLength);
354            if (output != null) {
355                try {
356                    mBufferedOutput.write(output);
357                } catch (IOException e) {
358                    throw new ProviderException("Failed to buffer output", e);
359                }
360            }
361            return EmptyArray.BYTE;
362        }
363
364        @Override
365        public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
366                byte[] additionalEntropy) throws KeyStoreException {
367            byte[] output = mDelegate.doFinal(input, inputOffset, inputLength, additionalEntropy);
368            if (output != null) {
369                try {
370                    mBufferedOutput.write(output);
371                } catch (IOException e) {
372                    throw new ProviderException("Failed to buffer output", e);
373                }
374            }
375            byte[] result = mBufferedOutput.toByteArray();
376            mBufferedOutput.reset();
377            mProducedOutputSizeBytes += result.length;
378            return result;
379        }
380
381        @Override
382        public long getConsumedInputSizeBytes() {
383            return mDelegate.getConsumedInputSizeBytes();
384        }
385
386        @Override
387        public long getProducedOutputSizeBytes() {
388            return mProducedOutputSizeBytes;
389        }
390    }
391
392    /**
393     * Additional Authentication Data (AAD) stream via a KeyStore streaming operation. This stream
394     * sends AAD into the KeyStore.
395     */
396    private static class AdditionalAuthenticationDataStream implements Stream {
397
398        private final KeyStore mKeyStore;
399        private final IBinder mOperationToken;
400
401        private AdditionalAuthenticationDataStream(KeyStore keyStore, IBinder operationToken) {
402            mKeyStore = keyStore;
403            mOperationToken = operationToken;
404        }
405
406        @Override
407        public OperationResult update(byte[] input) {
408            KeymasterArguments keymasterArgs = new KeymasterArguments();
409            keymasterArgs.addBlob(KeymasterDefs.KM_TAG_ASSOCIATED_DATA, input);
410
411            // KeyStore does not reflect AAD in inputConsumed, but users of Stream rely on this
412            // field. We fix this discrepancy here. KeyStore.update contract is that all of AAD
413            // has been consumed if the method succeeds.
414            OperationResult result = mKeyStore.update(mOperationToken, keymasterArgs, null);
415            if (result.resultCode == KeyStore.NO_ERROR) {
416                result = new OperationResult(
417                        result.resultCode,
418                        result.token,
419                        result.operationHandle,
420                        input.length, // inputConsumed
421                        result.output,
422                        result.outParams);
423            }
424            return result;
425        }
426
427        @Override
428        public OperationResult finish(byte[] additionalEntropy) {
429            if ((additionalEntropy != null) && (additionalEntropy.length > 0)) {
430                throw new ProviderException("AAD stream does not support additional entropy");
431            }
432            return new OperationResult(
433                    KeyStore.NO_ERROR,
434                    mOperationToken,
435                    0, // operation handle -- nobody cares about this being returned from finish
436                    0, // inputConsumed
437                    EmptyArray.BYTE, // output
438                    new KeymasterArguments() // additional params returned by finish
439                    );
440        }
441    }
442}