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.security.Credentials;
20import android.security.GateKeeper;
21import android.security.KeyStore;
22import android.security.keymaster.KeyCharacteristics;
23import android.security.keymaster.KeymasterArguments;
24import android.security.keymaster.KeymasterDefs;
25import android.security.keystore.KeyGenParameterSpec;
26import android.security.keystore.KeyProperties;
27
28import libcore.util.EmptyArray;
29
30import java.security.InvalidAlgorithmParameterException;
31import java.security.ProviderException;
32import java.security.SecureRandom;
33import java.security.spec.AlgorithmParameterSpec;
34import java.util.Arrays;
35
36import javax.crypto.KeyGeneratorSpi;
37import javax.crypto.SecretKey;
38
39/**
40 * {@link KeyGeneratorSpi} backed by Android KeyStore.
41 *
42 * @hide
43 */
44public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
45
46    public static class AES extends AndroidKeyStoreKeyGeneratorSpi {
47        public AES() {
48            super(KeymasterDefs.KM_ALGORITHM_AES, 128);
49        }
50
51        @Override
52        protected void engineInit(AlgorithmParameterSpec params, SecureRandom random)
53                throws InvalidAlgorithmParameterException {
54            super.engineInit(params, random);
55            if ((mKeySizeBits != 128) && (mKeySizeBits != 192) && (mKeySizeBits != 256)) {
56                throw new InvalidAlgorithmParameterException(
57                        "Unsupported key size: " + mKeySizeBits
58                        + ". Supported: 128, 192, 256.");
59            }
60        }
61    }
62
63    public static class DESede extends AndroidKeyStoreKeyGeneratorSpi {
64        public DESede() {
65            super(KeymasterDefs.KM_ALGORITHM_3DES, 168);
66        }
67    }
68
69    protected static abstract class HmacBase extends AndroidKeyStoreKeyGeneratorSpi {
70        protected HmacBase(int keymasterDigest) {
71            super(KeymasterDefs.KM_ALGORITHM_HMAC,
72                    keymasterDigest,
73                    KeymasterUtils.getDigestOutputSizeBits(keymasterDigest));
74        }
75    }
76
77    public static class HmacSHA1 extends HmacBase {
78        public HmacSHA1() {
79            super(KeymasterDefs.KM_DIGEST_SHA1);
80        }
81    }
82
83    public static class HmacSHA224 extends HmacBase {
84        public HmacSHA224() {
85            super(KeymasterDefs.KM_DIGEST_SHA_2_224);
86        }
87    }
88
89    public static class HmacSHA256 extends HmacBase {
90        public HmacSHA256() {
91            super(KeymasterDefs.KM_DIGEST_SHA_2_256);
92        }
93    }
94
95    public static class HmacSHA384 extends HmacBase {
96        public HmacSHA384() {
97            super(KeymasterDefs.KM_DIGEST_SHA_2_384);
98        }
99    }
100
101    public static class HmacSHA512 extends HmacBase {
102        public HmacSHA512() {
103            super(KeymasterDefs.KM_DIGEST_SHA_2_512);
104        }
105    }
106
107    private final KeyStore mKeyStore = KeyStore.getInstance();
108    private final int mKeymasterAlgorithm;
109    private final int mKeymasterDigest;
110    private final int mDefaultKeySizeBits;
111
112    private KeyGenParameterSpec mSpec;
113    private SecureRandom mRng;
114
115    protected int mKeySizeBits;
116    private int[] mKeymasterPurposes;
117    private int[] mKeymasterBlockModes;
118    private int[] mKeymasterPaddings;
119    private int[] mKeymasterDigests;
120
121    protected AndroidKeyStoreKeyGeneratorSpi(
122            int keymasterAlgorithm,
123            int defaultKeySizeBits) {
124        this(keymasterAlgorithm, -1, defaultKeySizeBits);
125    }
126
127    protected AndroidKeyStoreKeyGeneratorSpi(
128            int keymasterAlgorithm,
129            int keymasterDigest,
130            int defaultKeySizeBits) {
131        mKeymasterAlgorithm = keymasterAlgorithm;
132        mKeymasterDigest = keymasterDigest;
133        mDefaultKeySizeBits = defaultKeySizeBits;
134        if (mDefaultKeySizeBits <= 0) {
135            throw new IllegalArgumentException("Default key size must be positive");
136        }
137
138        if ((mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) && (mKeymasterDigest == -1)) {
139            throw new IllegalArgumentException(
140                    "Digest algorithm must be specified for HMAC key");
141        }
142    }
143
144    @Override
145    protected void engineInit(SecureRandom random) {
146        throw new UnsupportedOperationException("Cannot initialize without a "
147                + KeyGenParameterSpec.class.getName() + " parameter");
148    }
149
150    @Override
151    protected void engineInit(int keySize, SecureRandom random) {
152        throw new UnsupportedOperationException("Cannot initialize without a "
153                + KeyGenParameterSpec.class.getName() + " parameter");
154    }
155
156    @Override
157    protected void engineInit(AlgorithmParameterSpec params, SecureRandom random)
158            throws InvalidAlgorithmParameterException {
159        resetAll();
160
161        boolean success = false;
162        try {
163            if ((params == null) || (!(params instanceof KeyGenParameterSpec))) {
164                throw new InvalidAlgorithmParameterException("Cannot initialize without a "
165                        + KeyGenParameterSpec.class.getName() + " parameter");
166            }
167            KeyGenParameterSpec spec = (KeyGenParameterSpec) params;
168            if (spec.getKeystoreAlias() == null) {
169                throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided");
170            }
171
172            mRng = random;
173            mSpec = spec;
174
175            mKeySizeBits = (spec.getKeySize() != -1) ? spec.getKeySize() : mDefaultKeySizeBits;
176            if (mKeySizeBits <= 0) {
177                throw new InvalidAlgorithmParameterException(
178                        "Key size must be positive: " + mKeySizeBits);
179            } else if ((mKeySizeBits % 8) != 0) {
180                throw new InvalidAlgorithmParameterException(
181                        "Key size must be a multiple of 8: " + mKeySizeBits);
182            }
183
184            try {
185                mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes());
186                mKeymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster(
187                        spec.getEncryptionPaddings());
188                if (spec.getSignaturePaddings().length > 0) {
189                    throw new InvalidAlgorithmParameterException(
190                            "Signature paddings not supported for symmetric key algorithms");
191                }
192                mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes());
193                if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0)
194                        && (spec.isRandomizedEncryptionRequired())) {
195                    for (int keymasterBlockMode : mKeymasterBlockModes) {
196                        if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto(
197                                keymasterBlockMode)) {
198                            throw new InvalidAlgorithmParameterException(
199                                    "Randomized encryption (IND-CPA) required but may be violated"
200                                    + " by block mode: "
201                                    + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode)
202                                    + ". See " + KeyGenParameterSpec.class.getName()
203                                    + " documentation.");
204                        }
205                    }
206                }
207
208                if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) {
209                    if (mKeySizeBits < 64) {
210                        throw new InvalidAlgorithmParameterException(
211                            "HMAC key size must be at least 64 bits.");
212                    }
213
214                    // JCA HMAC key algorithm implies a digest (e.g., HmacSHA256 key algorithm
215                    // implies SHA-256 digest). Because keymaster HMAC key is authorized only for
216                    // one digest, we don't let algorithm parameter spec override the digest implied
217                    // by the key. If the spec specifies digests at all, it must specify only one
218                    // digest, the only implied by key algorithm.
219                    mKeymasterDigests = new int[] {mKeymasterDigest};
220                    if (spec.isDigestsSpecified()) {
221                        // Digest(s) explicitly specified in the spec. Check that the list
222                        // consists of exactly one digest, the one implied by key algorithm.
223                        int[] keymasterDigestsFromSpec =
224                                KeyProperties.Digest.allToKeymaster(spec.getDigests());
225                        if ((keymasterDigestsFromSpec.length != 1)
226                                || (keymasterDigestsFromSpec[0] != mKeymasterDigest)) {
227                            throw new InvalidAlgorithmParameterException(
228                                    "Unsupported digests specification: "
229                                    + Arrays.asList(spec.getDigests()) + ". Only "
230                                    + KeyProperties.Digest.fromKeymaster(mKeymasterDigest)
231                                    + " supported for this HMAC key algorithm");
232                        }
233                    }
234                } else {
235                    // Key algorithm does not imply a digest.
236                    if (spec.isDigestsSpecified()) {
237                        mKeymasterDigests = KeyProperties.Digest.allToKeymaster(spec.getDigests());
238                    } else {
239                        mKeymasterDigests = EmptyArray.INT;
240                    }
241                }
242
243                // Check that user authentication related parameters are acceptable. This method
244                // will throw an IllegalStateException if there are issues (e.g., secure lock screen
245                // not set up).
246                KeymasterUtils.addUserAuthArgs(new KeymasterArguments(), spec);
247            } catch (IllegalStateException | IllegalArgumentException e) {
248                throw new InvalidAlgorithmParameterException(e);
249            }
250
251            success = true;
252        } finally {
253            if (!success) {
254                resetAll();
255            }
256        }
257    }
258
259    private void resetAll() {
260        mSpec = null;
261        mRng = null;
262        mKeySizeBits = -1;
263        mKeymasterPurposes = null;
264        mKeymasterPaddings = null;
265        mKeymasterBlockModes = null;
266    }
267
268    @Override
269    protected SecretKey engineGenerateKey() {
270        KeyGenParameterSpec spec = mSpec;
271        if (spec == null) {
272            throw new IllegalStateException("Not initialized");
273        }
274
275        KeymasterArguments args = new KeymasterArguments();
276        args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits);
277        args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm);
278        args.addEnums(KeymasterDefs.KM_TAG_PURPOSE, mKeymasterPurposes);
279        args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockModes);
280        args.addEnums(KeymasterDefs.KM_TAG_PADDING, mKeymasterPaddings);
281        args.addEnums(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests);
282        KeymasterUtils.addUserAuthArgs(args, spec);
283        KeymasterUtils.addMinMacLengthAuthorizationIfNecessary(
284                args,
285                mKeymasterAlgorithm,
286                mKeymasterBlockModes,
287                mKeymasterDigests);
288        args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart());
289        args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
290                spec.getKeyValidityForOriginationEnd());
291        args.addDateIfNotNull(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
292                spec.getKeyValidityForConsumptionEnd());
293
294        if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0)
295                && (!spec.isRandomizedEncryptionRequired())) {
296            // Permit caller-provided IV when encrypting with this key
297            args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
298        }
299
300        byte[] additionalEntropy =
301                KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
302                        mRng, (mKeySizeBits + 7) / 8);
303        int flags = 0;
304        String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + spec.getKeystoreAlias();
305        KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
306        boolean success = false;
307        try {
308            Credentials.deleteAllTypesForAlias(mKeyStore, spec.getKeystoreAlias(), spec.getUid());
309            int errorCode = mKeyStore.generateKey(
310                    keyAliasInKeystore,
311                    args,
312                    additionalEntropy,
313                    spec.getUid(),
314                    flags,
315                    resultingKeyCharacteristics);
316            if (errorCode != KeyStore.NO_ERROR) {
317                throw new ProviderException(
318                        "Keystore operation failed", KeyStore.getKeyStoreException(errorCode));
319            }
320            @KeyProperties.KeyAlgorithmEnum String keyAlgorithmJCA;
321            try {
322                keyAlgorithmJCA = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm(
323                        mKeymasterAlgorithm, mKeymasterDigest);
324            } catch (IllegalArgumentException e) {
325                throw new ProviderException("Failed to obtain JCA secret key algorithm name", e);
326            }
327            SecretKey result = new AndroidKeyStoreSecretKey(
328                    keyAliasInKeystore, spec.getUid(), keyAlgorithmJCA);
329            success = true;
330            return result;
331        } finally {
332            if (!success) {
333                Credentials.deleteAllTypesForAlias(
334                        mKeyStore, spec.getKeystoreAlias(), spec.getUid());
335            }
336        }
337    }
338}
339