blob: 1c068be7fc8fb4e9e276265dcd885bd448e772e5 [file] [log] [blame]
Kenny Roote29df162012-08-10 08:28:37 -07001/*
2 * Copyright (C) 2012 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;
18
Kenny Root12e75222013-04-23 22:34:24 -070019import com.android.org.conscrypt.OpenSSLEngine;
20import com.android.org.conscrypt.OpenSSLKeyHolder;
Kenny Roote29df162012-08-10 08:28:37 -070021
Alex Klyubin5927c9f2015-04-10 13:28:03 -070022import libcore.util.EmptyArray;
23
Alex Klyubinbaf28382015-03-26 14:46:55 -070024import android.security.keymaster.KeyCharacteristics;
25import android.security.keymaster.KeymasterArguments;
26import android.security.keymaster.KeymasterDefs;
Kenny Roote29df162012-08-10 08:28:37 -070027import android.util.Log;
28
29import java.io.ByteArrayInputStream;
30import java.io.IOException;
31import java.io.InputStream;
32import java.io.OutputStream;
33import java.security.InvalidKeyException;
34import java.security.Key;
Kenny Root2eeda722013-04-10 11:30:58 -070035import java.security.KeyStore.Entry;
36import java.security.KeyStore.PrivateKeyEntry;
37import java.security.KeyStore.ProtectionParameter;
38import java.security.KeyStore;
Alex Klyubinbaf28382015-03-26 14:46:55 -070039import java.security.KeyStore.SecretKeyEntry;
Kenny Roote29df162012-08-10 08:28:37 -070040import java.security.KeyStoreException;
41import java.security.KeyStoreSpi;
42import java.security.NoSuchAlgorithmException;
43import java.security.PrivateKey;
44import java.security.UnrecoverableKeyException;
45import java.security.cert.Certificate;
46import java.security.cert.CertificateEncodingException;
47import java.security.cert.CertificateException;
48import java.security.cert.CertificateFactory;
49import java.security.cert.X509Certificate;
50import java.util.ArrayList;
Alex Klyubin5927c9f2015-04-10 13:28:03 -070051import java.util.Arrays;
Kenny Roote29df162012-08-10 08:28:37 -070052import java.util.Collection;
53import java.util.Collections;
54import java.util.Date;
55import java.util.Enumeration;
56import java.util.HashSet;
57import java.util.Iterator;
Alex Klyubin5eacd772015-04-14 19:00:35 -070058import java.util.List;
Kenny Roote29df162012-08-10 08:28:37 -070059import java.util.Set;
60
Alex Klyubinbaf28382015-03-26 14:46:55 -070061import javax.crypto.SecretKey;
62
Kenny Roote29df162012-08-10 08:28:37 -070063/**
Kenny Rootdb026712012-08-20 10:48:46 -070064 * A java.security.KeyStore interface for the Android KeyStore. An instance of
65 * it can be created via the {@link java.security.KeyStore#getInstance(String)
Kenny Roote29df162012-08-10 08:28:37 -070066 * KeyStore.getInstance("AndroidKeyStore")} interface. This returns a
67 * java.security.KeyStore backed by this "AndroidKeyStore" implementation.
68 * <p>
69 * This is built on top of Android's keystore daemon. The convention of alias
70 * use is:
71 * <p>
72 * PrivateKeyEntry will have a Credentials.USER_PRIVATE_KEY as the private key,
73 * Credentials.USER_CERTIFICATE as the first certificate in the chain (the one
74 * that corresponds to the private key), and then a Credentials.CA_CERTIFICATE
75 * entry which will have the rest of the chain concatenated in BER format.
76 * <p>
77 * TrustedCertificateEntry will just have a Credentials.CA_CERTIFICATE entry
78 * with a single certificate.
79 *
80 * @hide
81 */
82public class AndroidKeyStore extends KeyStoreSpi {
83 public static final String NAME = "AndroidKeyStore";
84
85 private android.security.KeyStore mKeyStore;
86
87 @Override
88 public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
89 UnrecoverableKeyException {
Alex Klyubinbaf28382015-03-26 14:46:55 -070090 if (isPrivateKeyEntry(alias)) {
91 final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
92 try {
93 return engine.getPrivateKeyById(Credentials.USER_PRIVATE_KEY + alias);
94 } catch (InvalidKeyException e) {
95 UnrecoverableKeyException t = new UnrecoverableKeyException("Can't get key");
96 t.initCause(e);
97 throw t;
98 }
99 } else if (isSecretKeyEntry(alias)) {
100 KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
101 String keyAliasInKeystore = Credentials.USER_SECRET_KEY + alias;
102 int errorCode = mKeyStore.getKeyCharacteristics(
103 keyAliasInKeystore, null, null, keyCharacteristics);
104 if ((errorCode != KeymasterDefs.KM_ERROR_OK)
105 && (errorCode != android.security.KeyStore.NO_ERROR)) {
106 throw new UnrecoverableKeyException("Failed to load information about key."
107 + " Error code: " + errorCode);
108 }
109
110 int keymasterAlgorithm =
111 keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1);
112 if (keymasterAlgorithm == -1) {
113 keymasterAlgorithm =
114 keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1);
115 }
116 if (keymasterAlgorithm == -1) {
117 throw new UnrecoverableKeyException("Key algorithm unknown");
118 }
Alex Klyubinbaf28382015-03-26 14:46:55 -0700119
Alex Klyubin5eacd772015-04-14 19:00:35 -0700120 List<Integer> keymasterDigests =
121 keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST);
122 int keymasterDigest;
123 if (keymasterDigests.isEmpty()) {
124 keymasterDigest = -1;
125 } else {
126 // More than one digest can be permitted for this key. Use the first one to form the
127 // JCA key algorithm name.
128 keymasterDigest = keymasterDigests.get(0);
Alex Klyubinbaf28382015-03-26 14:46:55 -0700129 }
Alex Klyubinbaf28382015-03-26 14:46:55 -0700130
131 String keyAlgorithmString;
132 try {
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700133 keyAlgorithmString = KeymasterUtils.getJcaSecretKeyAlgorithm(
134 keymasterAlgorithm, keymasterDigest);
Alex Klyubinbaf28382015-03-26 14:46:55 -0700135 } catch (IllegalArgumentException e) {
136 throw (UnrecoverableKeyException)
137 new UnrecoverableKeyException("Unsupported secret key type").initCause(e);
138 }
139
140 return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmString);
Kenny Roote29df162012-08-10 08:28:37 -0700141 }
142
Alex Klyubinbaf28382015-03-26 14:46:55 -0700143 return null;
Kenny Roote29df162012-08-10 08:28:37 -0700144 }
145
146 @Override
147 public Certificate[] engineGetCertificateChain(String alias) {
Kenny Roota4640c02012-08-31 13:38:11 -0700148 if (alias == null) {
149 throw new NullPointerException("alias == null");
150 }
151
Kenny Roote29df162012-08-10 08:28:37 -0700152 final X509Certificate leaf = (X509Certificate) engineGetCertificate(alias);
153 if (leaf == null) {
154 return null;
155 }
156
157 final Certificate[] caList;
158
159 final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
160 if (caBytes != null) {
161 final Collection<X509Certificate> caChain = toCertificates(caBytes);
162
163 caList = new Certificate[caChain.size() + 1];
164
165 final Iterator<X509Certificate> it = caChain.iterator();
166 int i = 1;
167 while (it.hasNext()) {
168 caList[i++] = it.next();
169 }
170 } else {
171 caList = new Certificate[1];
172 }
173
174 caList[0] = leaf;
175
176 return caList;
177 }
178
179 @Override
180 public Certificate engineGetCertificate(String alias) {
Kenny Roota4640c02012-08-31 13:38:11 -0700181 if (alias == null) {
182 throw new NullPointerException("alias == null");
183 }
184
Kenny Roote29df162012-08-10 08:28:37 -0700185 byte[] certificate = mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
186 if (certificate != null) {
187 return toCertificate(certificate);
188 }
189
190 certificate = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
191 if (certificate != null) {
192 return toCertificate(certificate);
193 }
194
195 return null;
196 }
197
198 private static X509Certificate toCertificate(byte[] bytes) {
199 try {
200 final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
201 return (X509Certificate) certFactory
202 .generateCertificate(new ByteArrayInputStream(bytes));
203 } catch (CertificateException e) {
204 Log.w(NAME, "Couldn't parse certificate in keystore", e);
205 return null;
206 }
207 }
208
209 @SuppressWarnings("unchecked")
210 private static Collection<X509Certificate> toCertificates(byte[] bytes) {
211 try {
212 final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
213 return (Collection<X509Certificate>) certFactory
214 .generateCertificates(new ByteArrayInputStream(bytes));
215 } catch (CertificateException e) {
216 Log.w(NAME, "Couldn't parse certificates in keystore", e);
217 return new ArrayList<X509Certificate>();
218 }
219 }
220
221 private Date getModificationDate(String alias) {
222 final long epochMillis = mKeyStore.getmtime(alias);
223 if (epochMillis == -1L) {
224 return null;
225 }
226
227 return new Date(epochMillis);
228 }
229
230 @Override
231 public Date engineGetCreationDate(String alias) {
Kenny Roota4640c02012-08-31 13:38:11 -0700232 if (alias == null) {
233 throw new NullPointerException("alias == null");
234 }
235
Kenny Roote29df162012-08-10 08:28:37 -0700236 Date d = getModificationDate(Credentials.USER_PRIVATE_KEY + alias);
237 if (d != null) {
238 return d;
239 }
240
Alex Klyubinbaf28382015-03-26 14:46:55 -0700241 d = getModificationDate(Credentials.USER_SECRET_KEY + alias);
242 if (d != null) {
243 return d;
244 }
245
Kenny Roote29df162012-08-10 08:28:37 -0700246 d = getModificationDate(Credentials.USER_CERTIFICATE + alias);
247 if (d != null) {
248 return d;
249 }
250
251 return getModificationDate(Credentials.CA_CERTIFICATE + alias);
252 }
253
254 @Override
255 public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
256 throws KeyStoreException {
257 if ((password != null) && (password.length > 0)) {
258 throw new KeyStoreException("entries cannot be protected with passwords");
259 }
260
261 if (key instanceof PrivateKey) {
Kenny Root2eeda722013-04-10 11:30:58 -0700262 setPrivateKeyEntry(alias, (PrivateKey) key, chain, null);
Alex Klyubinbaf28382015-03-26 14:46:55 -0700263 } else if (key instanceof SecretKey) {
264 setSecretKeyEntry(alias, (SecretKey) key, null);
Kenny Roote29df162012-08-10 08:28:37 -0700265 } else {
Alex Klyubinbaf28382015-03-26 14:46:55 -0700266 throw new KeyStoreException("Only PrivateKey and SecretKey are supported");
Kenny Roote29df162012-08-10 08:28:37 -0700267 }
268 }
269
Kenny Root2eeda722013-04-10 11:30:58 -0700270 private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain,
Kenny Root1c219f62013-04-18 17:57:03 -0700271 KeyStoreParameter params) throws KeyStoreException {
Kenny Root802768d2012-08-21 15:23:35 -0700272 byte[] keyBytes = null;
273
274 final String pkeyAlias;
Kenny Rootcc1fc6b2013-01-22 13:25:38 -0800275 if (key instanceof OpenSSLKeyHolder) {
276 pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias();
Kenny Root802768d2012-08-21 15:23:35 -0700277 } else {
278 pkeyAlias = null;
Kenny Roote29df162012-08-10 08:28:37 -0700279 }
280
Kenny Root802768d2012-08-21 15:23:35 -0700281 final boolean shouldReplacePrivateKey;
282 if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) {
283 final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length());
284 if (!alias.equals(keySubalias)) {
285 throw new KeyStoreException("Can only replace keys with same alias: " + alias
286 + " != " + keySubalias);
287 }
288
289 shouldReplacePrivateKey = false;
290 } else {
291 // Make sure the PrivateKey format is the one we support.
292 final String keyFormat = key.getFormat();
293 if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) {
294 throw new KeyStoreException(
295 "Only PrivateKeys that can be encoded into PKCS#8 are supported");
296 }
297
298 // Make sure we can actually encode the key.
299 keyBytes = key.getEncoded();
300 if (keyBytes == null) {
301 throw new KeyStoreException("PrivateKey has no encoding");
302 }
303
304 shouldReplacePrivateKey = true;
Kenny Roote29df162012-08-10 08:28:37 -0700305 }
306
307 // Make sure the chain exists since this is a PrivateKey
308 if ((chain == null) || (chain.length == 0)) {
309 throw new KeyStoreException("Must supply at least one Certificate with PrivateKey");
310 }
311
312 // Do chain type checking.
313 X509Certificate[] x509chain = new X509Certificate[chain.length];
314 for (int i = 0; i < chain.length; i++) {
315 if (!"X.509".equals(chain[i].getType())) {
316 throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
317 + i);
318 }
319
320 if (!(chain[i] instanceof X509Certificate)) {
321 throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
322 + i);
323 }
324
325 x509chain[i] = (X509Certificate) chain[i];
326 }
327
328 final byte[] userCertBytes;
329 try {
330 userCertBytes = x509chain[0].getEncoded();
331 } catch (CertificateEncodingException e) {
332 throw new KeyStoreException("Couldn't encode certificate #1", e);
333 }
334
335 /*
336 * If we have a chain, store it in the CA certificate slot for this
337 * alias as concatenated DER-encoded certificates. These can be
338 * deserialized by {@link CertificateFactory#generateCertificates}.
339 */
340 final byte[] chainBytes;
341 if (chain.length > 1) {
342 /*
343 * The chain is passed in as {user_cert, ca_cert_1, ca_cert_2, ...}
344 * so we only need the certificates starting at index 1.
345 */
346 final byte[][] certsBytes = new byte[x509chain.length - 1][];
347 int totalCertLength = 0;
348 for (int i = 0; i < certsBytes.length; i++) {
349 try {
350 certsBytes[i] = x509chain[i + 1].getEncoded();
351 totalCertLength += certsBytes[i].length;
352 } catch (CertificateEncodingException e) {
353 throw new KeyStoreException("Can't encode Certificate #" + i, e);
354 }
355 }
356
357 /*
358 * Serialize this into one byte array so we can later call
359 * CertificateFactory#generateCertificates to recover them.
360 */
361 chainBytes = new byte[totalCertLength];
362 int outputOffset = 0;
363 for (int i = 0; i < certsBytes.length; i++) {
364 final int certLength = certsBytes[i].length;
365 System.arraycopy(certsBytes[i], 0, chainBytes, outputOffset, certLength);
366 outputOffset += certLength;
367 certsBytes[i] = null;
368 }
369 } else {
370 chainBytes = null;
371 }
372
373 /*
Kenny Root802768d2012-08-21 15:23:35 -0700374 * Make sure we clear out all the appropriate types before trying to
Kenny Roote29df162012-08-10 08:28:37 -0700375 * write.
376 */
Kenny Root802768d2012-08-21 15:23:35 -0700377 if (shouldReplacePrivateKey) {
378 Credentials.deleteAllTypesForAlias(mKeyStore, alias);
379 } else {
380 Credentials.deleteCertificateTypesForAlias(mKeyStore, alias);
Alex Klyubinbaf28382015-03-26 14:46:55 -0700381 Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias);
Kenny Root802768d2012-08-21 15:23:35 -0700382 }
Kenny Roote29df162012-08-10 08:28:37 -0700383
Kenny Root2eeda722013-04-10 11:30:58 -0700384 final int flags = (params == null) ? 0 : params.getFlags();
385
Kenny Root802768d2012-08-21 15:23:35 -0700386 if (shouldReplacePrivateKey
Kenny Root2eeda722013-04-10 11:30:58 -0700387 && !mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes,
388 android.security.KeyStore.UID_SELF, flags)) {
Kenny Root802768d2012-08-21 15:23:35 -0700389 Credentials.deleteAllTypesForAlias(mKeyStore, alias);
Kenny Roote29df162012-08-10 08:28:37 -0700390 throw new KeyStoreException("Couldn't put private key in keystore");
Kenny Root2eeda722013-04-10 11:30:58 -0700391 } else if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertBytes,
392 android.security.KeyStore.UID_SELF, flags)) {
Kenny Root802768d2012-08-21 15:23:35 -0700393 Credentials.deleteAllTypesForAlias(mKeyStore, alias);
Kenny Roote29df162012-08-10 08:28:37 -0700394 throw new KeyStoreException("Couldn't put certificate #1 in keystore");
395 } else if (chainBytes != null
Kenny Root2eeda722013-04-10 11:30:58 -0700396 && !mKeyStore.put(Credentials.CA_CERTIFICATE + alias, chainBytes,
397 android.security.KeyStore.UID_SELF, flags)) {
Kenny Root802768d2012-08-21 15:23:35 -0700398 Credentials.deleteAllTypesForAlias(mKeyStore, alias);
Kenny Roote29df162012-08-10 08:28:37 -0700399 throw new KeyStoreException("Couldn't put certificate chain in keystore");
400 }
401 }
402
Alex Klyubinbaf28382015-03-26 14:46:55 -0700403 private void setSecretKeyEntry(String entryAlias, SecretKey key, KeyStoreParameter params)
404 throws KeyStoreException {
405 if (key instanceof KeyStoreSecretKey) {
406 // KeyStore-backed secret key. It cannot be duplicated into another entry and cannot
407 // overwrite its own entry.
408 String keyAliasInKeystore = ((KeyStoreSecretKey) key).getAlias();
409 if (keyAliasInKeystore == null) {
410 throw new KeyStoreException("KeyStore-backed secret key does not have an alias");
411 }
412 if (!keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) {
413 throw new KeyStoreException("KeyStore-backed secret key has invalid alias: "
414 + keyAliasInKeystore);
415 }
416 String keyEntryAlias =
417 keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length());
418 if (!entryAlias.equals(keyEntryAlias)) {
419 throw new KeyStoreException("Can only replace KeyStore-backed keys with same"
420 + " alias: " + entryAlias + " != " + keyEntryAlias);
421 }
422 // This is the entry where this key is already stored. No need to do anything.
423 if (params != null) {
424 throw new KeyStoreException("Modifying KeyStore-backed key using protection"
425 + " parameters not supported");
426 }
427 return;
428 }
429
430 if (params == null) {
431 throw new KeyStoreException(
432 "Protection parameters must be specified when importing a symmetric key");
433 }
434
435 // Not a KeyStore-backed secret key -- import its key material into keystore.
436 String keyExportFormat = key.getFormat();
437 if (keyExportFormat == null) {
438 throw new KeyStoreException(
439 "Only secret keys that export their key material are supported");
440 } else if (!"RAW".equals(keyExportFormat)) {
441 throw new KeyStoreException(
442 "Unsupported secret key material export format: " + keyExportFormat);
443 }
444 byte[] keyMaterial = key.getEncoded();
445 if (keyMaterial == null) {
446 throw new KeyStoreException("Key did not export its key material despite supporting"
447 + " RAW format export");
448 }
449
450 String keyAlgorithmString = key.getAlgorithm();
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700451 int keymasterAlgorithm;
452 int keymasterDigest;
Alex Klyubinbaf28382015-03-26 14:46:55 -0700453 try {
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700454 keymasterAlgorithm = KeymasterUtils.getKeymasterAlgorithmFromJcaSecretKeyAlgorithm(
455 keyAlgorithmString);
456 keymasterDigest =
457 KeymasterUtils.getKeymasterDigestfromJcaSecretKeyAlgorithm(keyAlgorithmString);
Alex Klyubinbaf28382015-03-26 14:46:55 -0700458 } catch (IllegalArgumentException e) {
459 throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString);
460 }
461
Alex Klyubinbaf28382015-03-26 14:46:55 -0700462 KeymasterArguments args = new KeymasterArguments();
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700463 args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, keymasterAlgorithm);
Alex Klyubinbaf28382015-03-26 14:46:55 -0700464
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700465 int[] keymasterDigests;
Alex Klyubinc46e9e72015-04-06 15:36:25 -0700466 if (params.isDigestsSpecified()) {
467 // Digest(s) specified in parameters
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700468 keymasterDigests =
469 KeymasterUtils.getKeymasterDigestsFromJcaDigestAlgorithms(params.getDigests());
470 if (keymasterDigest != -1) {
Alex Klyubinc46e9e72015-04-06 15:36:25 -0700471 // Digest also specified in the JCA key algorithm name.
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700472 if (!com.android.internal.util.ArrayUtils.contains(
473 keymasterDigests, keymasterDigest)) {
Alex Klyubinc46e9e72015-04-06 15:36:25 -0700474 throw new KeyStoreException("Key digest mismatch"
475 + ". Key: " + keyAlgorithmString
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700476 + ", parameter spec: " + Arrays.asList(params.getDigests()));
Alex Klyubinbaf28382015-03-26 14:46:55 -0700477 }
478 }
479 } else {
Alex Klyubinc46e9e72015-04-06 15:36:25 -0700480 // No digest specified in parameters
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700481 if (keymasterDigest != -1) {
Alex Klyubinc46e9e72015-04-06 15:36:25 -0700482 // Digest specified in the JCA key algorithm name.
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700483 keymasterDigests = new int[] {keymasterDigest};
Alex Klyubinc46e9e72015-04-06 15:36:25 -0700484 } else {
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700485 keymasterDigests = EmptyArray.INT;
Alex Klyubinc46e9e72015-04-06 15:36:25 -0700486 }
Alex Klyubinbaf28382015-03-26 14:46:55 -0700487 }
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700488 args.addInts(KeymasterDefs.KM_TAG_DIGEST, keymasterDigests);
489 if (keymasterDigests.length > 0) {
Alex Klyubinc46e9e72015-04-06 15:36:25 -0700490 // TODO: Remove MAC length constraint once Keymaster API no longer requires it.
491 // This code will blow up if mode than one digest is specified.
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700492 int digestOutputSizeBytes =
493 KeymasterUtils.getDigestOutputSizeBytes(keymasterDigests[0]);
494 if (digestOutputSizeBytes != -1) {
Alex Klyubin4ab8ea42015-03-27 16:53:44 -0700495 // TODO: Switch to bits instead of bytes, once this is fixed in Keymaster
496 args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes);
497 }
498 }
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700499 if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) {
500 if (keymasterDigests.length == 0) {
Alex Klyubinc46e9e72015-04-06 15:36:25 -0700501 throw new KeyStoreException("At least one digest algorithm must be specified"
502 + " for key algorithm " + keyAlgorithmString);
Alex Klyubinb406f242015-03-31 13:39:38 -0700503 }
504 }
Alex Klyubinbaf28382015-03-26 14:46:55 -0700505
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700506 @KeyStoreKeyProperties.PurposeEnum int purposes = params.getPurposes();
507 int[] keymasterBlockModes = KeymasterUtils.getKeymasterBlockModesFromJcaBlockModes(
508 params.getBlockModes());
509 if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0)
Alex Klyubinf853f642015-04-08 13:36:22 -0700510 && (params.isRandomizedEncryptionRequired())) {
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700511 for (int keymasterBlockMode : keymasterBlockModes) {
512 if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible(keymasterBlockMode)) {
513 throw new KeyStoreException(
514 "Randomized encryption (IND-CPA) required but may be violated by block"
515 + " mode: "
516 + KeymasterUtils.getJcaBlockModeFromKeymasterBlockMode(
517 keymasterBlockMode)
518 + ". See KeyStoreParameter documentation.");
519 }
Alex Klyubinf853f642015-04-08 13:36:22 -0700520 }
521 }
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700522 for (int keymasterPurpose : KeyStoreKeyProperties.Purpose.allToKeymaster(purposes)) {
Alex Klyubinbaf28382015-03-26 14:46:55 -0700523 args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
524 }
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700525 args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes);
526 int[] keymasterPaddings = ArrayUtils.concat(
527 KeymasterUtils.getKeymasterPaddingsFromJcaEncryptionPaddings(
528 params.getEncryptionPaddings()),
529 KeymasterUtils.getKeymasterPaddingsFromJcaSignaturePaddings(
530 params.getSignaturePaddings()));
531 args.addInts(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings);
Alex Klyubinc46e9e72015-04-06 15:36:25 -0700532 if (params.getUserAuthenticators() == 0) {
Alex Klyubinbaf28382015-03-26 14:46:55 -0700533 args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
534 } else {
Alex Klyubinc8e55742015-03-31 19:50:13 -0700535 args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE,
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700536 KeyStoreKeyProperties.UserAuthenticator.allToKeymaster(
Alex Klyubinc8e55742015-03-31 19:50:13 -0700537 params.getUserAuthenticators()));
Alex Klyubinbaf28382015-03-26 14:46:55 -0700538 }
Alex Klyubin2ea13d42015-04-01 14:41:28 -0700539 if (params.isInvalidatedOnNewFingerprintEnrolled()) {
540 // TODO: Add the invalidate on fingerprint enrolled constraint once Keymaster supports
541 // that.
542 }
Alex Klyubinc46e9e72015-04-06 15:36:25 -0700543 if (params.getUserAuthenticationValidityDurationSeconds() != -1) {
Alex Klyubinbaf28382015-03-26 14:46:55 -0700544 args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
545 params.getUserAuthenticationValidityDurationSeconds());
546 }
Alex Klyubin5045b712015-03-31 20:19:54 -0700547 args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
548 (params.getKeyValidityStart() != null)
549 ? params.getKeyValidityStart() : new Date(0));
550 args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
551 (params.getKeyValidityForOriginationEnd() != null)
552 ? params.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE));
553 args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
554 (params.getKeyValidityForConsumptionEnd() != null)
555 ? params.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE));
Alex Klyubinbaf28382015-03-26 14:46:55 -0700556
557 // TODO: Remove this once keymaster does not require us to specify the size of imported key.
558 args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keyMaterial.length * 8);
559
Alex Klyubin5927c9f2015-04-10 13:28:03 -0700560 if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0)
Alex Klyubinf853f642015-04-08 13:36:22 -0700561 && (!params.isRandomizedEncryptionRequired())) {
562 // Permit caller-provided IV when encrypting with this key
Alex Klyubinb406f242015-03-31 13:39:38 -0700563 args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
564 }
565
Alex Klyubinbaf28382015-03-26 14:46:55 -0700566 Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias);
567 String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias;
568 int errorCode = mKeyStore.importKey(
569 keyAliasInKeystore,
570 args,
571 KeymasterDefs.KM_KEY_FORMAT_RAW,
572 keyMaterial,
573 params.getFlags(),
574 new KeyCharacteristics());
575 if (errorCode != android.security.KeyStore.NO_ERROR) {
576 throw new KeyStoreException("Failed to import secret key. Keystore error code: "
577 + errorCode);
578 }
579 }
580
Kenny Roote29df162012-08-10 08:28:37 -0700581 @Override
582 public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain)
583 throws KeyStoreException {
Kenny Roota4640c02012-08-31 13:38:11 -0700584 throw new KeyStoreException("Operation not supported because key encoding is unknown");
Kenny Roote29df162012-08-10 08:28:37 -0700585 }
586
587 @Override
588 public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
589 if (isKeyEntry(alias)) {
590 throw new KeyStoreException("Entry exists and is not a trusted certificate");
591 }
592
Kenny Roota4640c02012-08-31 13:38:11 -0700593 // We can't set something to null.
594 if (cert == null) {
595 throw new NullPointerException("cert == null");
596 }
597
Kenny Roote29df162012-08-10 08:28:37 -0700598 final byte[] encoded;
599 try {
600 encoded = cert.getEncoded();
601 } catch (CertificateEncodingException e) {
602 throw new KeyStoreException(e);
603 }
604
Kenny Root2eeda722013-04-10 11:30:58 -0700605 if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded,
606 android.security.KeyStore.UID_SELF, android.security.KeyStore.FLAG_NONE)) {
Kenny Roote29df162012-08-10 08:28:37 -0700607 throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?");
608 }
609 }
610
611 @Override
612 public void engineDeleteEntry(String alias) throws KeyStoreException {
Kenny Roota4640c02012-08-31 13:38:11 -0700613 if (!isKeyEntry(alias) && !isCertificateEntry(alias)) {
614 return;
615 }
616
Kenny Rootdb026712012-08-20 10:48:46 -0700617 if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) {
Kenny Roote29df162012-08-10 08:28:37 -0700618 throw new KeyStoreException("No such entry " + alias);
619 }
620 }
621
Kenny Roote29df162012-08-10 08:28:37 -0700622 private Set<String> getUniqueAliases() {
623 final String[] rawAliases = mKeyStore.saw("");
624 if (rawAliases == null) {
625 return new HashSet<String>();
626 }
627
628 final Set<String> aliases = new HashSet<String>(rawAliases.length);
629 for (String alias : rawAliases) {
630 final int idx = alias.indexOf('_');
631 if ((idx == -1) || (alias.length() <= idx)) {
632 Log.e(NAME, "invalid alias: " + alias);
633 continue;
634 }
635
636 aliases.add(new String(alias.substring(idx + 1)));
637 }
638
639 return aliases;
640 }
641
642 @Override
643 public Enumeration<String> engineAliases() {
644 return Collections.enumeration(getUniqueAliases());
645 }
646
647 @Override
648 public boolean engineContainsAlias(String alias) {
Kenny Roota4640c02012-08-31 13:38:11 -0700649 if (alias == null) {
650 throw new NullPointerException("alias == null");
651 }
652
Kenny Roote29df162012-08-10 08:28:37 -0700653 return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias)
Alex Klyubinbaf28382015-03-26 14:46:55 -0700654 || mKeyStore.contains(Credentials.USER_SECRET_KEY + alias)
Kenny Roote29df162012-08-10 08:28:37 -0700655 || mKeyStore.contains(Credentials.USER_CERTIFICATE + alias)
656 || mKeyStore.contains(Credentials.CA_CERTIFICATE + alias);
657 }
658
659 @Override
660 public int engineSize() {
661 return getUniqueAliases().size();
662 }
663
664 @Override
665 public boolean engineIsKeyEntry(String alias) {
666 return isKeyEntry(alias);
667 }
668
669 private boolean isKeyEntry(String alias) {
Alex Klyubinbaf28382015-03-26 14:46:55 -0700670 return isPrivateKeyEntry(alias) || isSecretKeyEntry(alias);
671 }
672
673 private boolean isPrivateKeyEntry(String alias) {
Kenny Roota4640c02012-08-31 13:38:11 -0700674 if (alias == null) {
675 throw new NullPointerException("alias == null");
676 }
677
Kenny Roote29df162012-08-10 08:28:37 -0700678 return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias);
679 }
680
Alex Klyubinbaf28382015-03-26 14:46:55 -0700681 private boolean isSecretKeyEntry(String alias) {
682 if (alias == null) {
683 throw new NullPointerException("alias == null");
684 }
685
686 return mKeyStore.contains(Credentials.USER_SECRET_KEY + alias);
687 }
688
Kenny Roota4640c02012-08-31 13:38:11 -0700689 private boolean isCertificateEntry(String alias) {
690 if (alias == null) {
691 throw new NullPointerException("alias == null");
692 }
693
694 return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias);
695 }
696
Kenny Roote29df162012-08-10 08:28:37 -0700697 @Override
698 public boolean engineIsCertificateEntry(String alias) {
Kenny Roota4640c02012-08-31 13:38:11 -0700699 return !isKeyEntry(alias) && isCertificateEntry(alias);
Kenny Roote29df162012-08-10 08:28:37 -0700700 }
701
702 @Override
703 public String engineGetCertificateAlias(Certificate cert) {
704 if (cert == null) {
705 return null;
706 }
707
708 final Set<String> nonCaEntries = new HashSet<String>();
709
710 /*
711 * First scan the PrivateKeyEntry types. The KeyStoreSpi documentation
712 * says to only compare the first certificate in the chain which is
713 * equivalent to the USER_CERTIFICATE prefix for the Android keystore
714 * convention.
715 */
716 final String[] certAliases = mKeyStore.saw(Credentials.USER_CERTIFICATE);
Kenny Root78ad8492013-02-13 17:02:57 -0800717 if (certAliases != null) {
718 for (String alias : certAliases) {
719 final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
720 if (certBytes == null) {
721 continue;
722 }
Kenny Roote29df162012-08-10 08:28:37 -0700723
Kenny Root78ad8492013-02-13 17:02:57 -0800724 final Certificate c = toCertificate(certBytes);
725 nonCaEntries.add(alias);
Kenny Roote29df162012-08-10 08:28:37 -0700726
Kenny Root78ad8492013-02-13 17:02:57 -0800727 if (cert.equals(c)) {
728 return alias;
729 }
Kenny Roote29df162012-08-10 08:28:37 -0700730 }
731 }
732
733 /*
734 * Look at all the TrustedCertificateEntry types. Skip all the
735 * PrivateKeyEntry we looked at above.
736 */
737 final String[] caAliases = mKeyStore.saw(Credentials.CA_CERTIFICATE);
Kenny Root78ad8492013-02-13 17:02:57 -0800738 if (certAliases != null) {
739 for (String alias : caAliases) {
740 if (nonCaEntries.contains(alias)) {
741 continue;
742 }
Kenny Roote29df162012-08-10 08:28:37 -0700743
Kenny Root78ad8492013-02-13 17:02:57 -0800744 final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
745 if (certBytes == null) {
746 continue;
747 }
Kenny Roote29df162012-08-10 08:28:37 -0700748
Kenny Root78ad8492013-02-13 17:02:57 -0800749 final Certificate c =
750 toCertificate(mKeyStore.get(Credentials.CA_CERTIFICATE + alias));
751 if (cert.equals(c)) {
752 return alias;
753 }
Kenny Roote29df162012-08-10 08:28:37 -0700754 }
755 }
756
757 return null;
758 }
759
760 @Override
761 public void engineStore(OutputStream stream, char[] password) throws IOException,
762 NoSuchAlgorithmException, CertificateException {
763 throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream");
764 }
765
766 @Override
767 public void engineLoad(InputStream stream, char[] password) throws IOException,
768 NoSuchAlgorithmException, CertificateException {
769 if (stream != null) {
770 throw new IllegalArgumentException("InputStream not supported");
771 }
772
773 if (password != null) {
774 throw new IllegalArgumentException("password not supported");
775 }
776
777 // Unfortunate name collision.
778 mKeyStore = android.security.KeyStore.getInstance();
779 }
780
Kenny Root2eeda722013-04-10 11:30:58 -0700781 @Override
782 public void engineSetEntry(String alias, Entry entry, ProtectionParameter param)
783 throws KeyStoreException {
784 if (entry == null) {
785 throw new KeyStoreException("entry == null");
786 }
787
788 if (engineContainsAlias(alias)) {
789 engineDeleteEntry(alias);
790 }
791
792 if (entry instanceof KeyStore.TrustedCertificateEntry) {
793 KeyStore.TrustedCertificateEntry trE = (KeyStore.TrustedCertificateEntry) entry;
794 engineSetCertificateEntry(alias, trE.getTrustedCertificate());
795 return;
796 }
797
Kenny Root1c219f62013-04-18 17:57:03 -0700798 if (param != null && !(param instanceof KeyStoreParameter)) {
799 throw new KeyStoreException(
800 "protParam should be android.security.KeyStoreParameter; was: "
Kenny Root2eeda722013-04-10 11:30:58 -0700801 + param.getClass().getName());
802 }
803
804 if (entry instanceof PrivateKeyEntry) {
805 PrivateKeyEntry prE = (PrivateKeyEntry) entry;
806 setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(),
Kenny Root1c219f62013-04-18 17:57:03 -0700807 (KeyStoreParameter) param);
Alex Klyubinbaf28382015-03-26 14:46:55 -0700808 } else if (entry instanceof SecretKeyEntry) {
809 SecretKeyEntry secE = (SecretKeyEntry) entry;
810 setSecretKeyEntry(alias, secE.getSecretKey(), (KeyStoreParameter) param);
811 } else {
812 throw new KeyStoreException(
813 "Entry must be a PrivateKeyEntry, SecretKeyEntry or TrustedCertificateEntry"
814 + "; was " + entry);
Kenny Root2eeda722013-04-10 11:30:58 -0700815 }
Kenny Root2eeda722013-04-10 11:30:58 -0700816 }
817
Kenny Roote29df162012-08-10 08:28:37 -0700818}