AndroidKeyStoreSpi.java revision 3ceb1a04b44539c2b2c3afec6df487fe128911f2
1/* 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.keystore; 18 19import com.android.org.conscrypt.OpenSSLEngine; 20import com.android.org.conscrypt.OpenSSLKeyHolder; 21 22import libcore.util.EmptyArray; 23 24import android.security.Credentials; 25import android.security.KeyStore; 26import android.security.KeyStoreParameter; 27import android.security.keymaster.KeyCharacteristics; 28import android.security.keymaster.KeymasterArguments; 29import android.security.keymaster.KeymasterDefs; 30import android.security.keystore.KeyProperties; 31import android.security.keystore.KeyProtection; 32import android.util.Log; 33 34import java.io.ByteArrayInputStream; 35import java.io.IOException; 36import java.io.InputStream; 37import java.io.OutputStream; 38import java.security.InvalidKeyException; 39import java.security.Key; 40import java.security.KeyStore.Entry; 41import java.security.KeyStore.PrivateKeyEntry; 42import java.security.KeyStore.ProtectionParameter; 43import java.security.KeyStore.SecretKeyEntry; 44import java.security.KeyStoreException; 45import java.security.KeyStoreSpi; 46import java.security.NoSuchAlgorithmException; 47import java.security.PrivateKey; 48import java.security.UnrecoverableKeyException; 49import java.security.cert.Certificate; 50import java.security.cert.CertificateEncodingException; 51import java.security.cert.CertificateException; 52import java.security.cert.CertificateFactory; 53import java.security.cert.X509Certificate; 54import java.util.ArrayList; 55import java.util.Arrays; 56import java.util.Collection; 57import java.util.Collections; 58import java.util.Date; 59import java.util.Enumeration; 60import java.util.HashSet; 61import java.util.Iterator; 62import java.util.List; 63import java.util.Set; 64 65import javax.crypto.SecretKey; 66 67/** 68 * A java.security.KeyStore interface for the Android KeyStore. An instance of 69 * it can be created via the {@link java.security.KeyStore#getInstance(String) 70 * KeyStore.getInstance("AndroidKeyStore")} interface. This returns a 71 * java.security.KeyStore backed by this "AndroidKeyStore" implementation. 72 * <p> 73 * This is built on top of Android's keystore daemon. The convention of alias 74 * use is: 75 * <p> 76 * PrivateKeyEntry will have a Credentials.USER_PRIVATE_KEY as the private key, 77 * Credentials.USER_CERTIFICATE as the first certificate in the chain (the one 78 * that corresponds to the private key), and then a Credentials.CA_CERTIFICATE 79 * entry which will have the rest of the chain concatenated in BER format. 80 * <p> 81 * TrustedCertificateEntry will just have a Credentials.CA_CERTIFICATE entry 82 * with a single certificate. 83 * 84 * @hide 85 */ 86public class AndroidKeyStoreSpi extends KeyStoreSpi { 87 public static final String NAME = "AndroidKeyStore"; 88 89 private KeyStore mKeyStore; 90 91 @Override 92 public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, 93 UnrecoverableKeyException { 94 if (isPrivateKeyEntry(alias)) { 95 final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore"); 96 try { 97 return engine.getPrivateKeyById(Credentials.USER_PRIVATE_KEY + alias); 98 } catch (InvalidKeyException e) { 99 UnrecoverableKeyException t = new UnrecoverableKeyException("Can't get key"); 100 t.initCause(e); 101 throw t; 102 } 103 } else if (isSecretKeyEntry(alias)) { 104 KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); 105 String keyAliasInKeystore = Credentials.USER_SECRET_KEY + alias; 106 int errorCode = mKeyStore.getKeyCharacteristics( 107 keyAliasInKeystore, null, null, keyCharacteristics); 108 if (errorCode != KeyStore.NO_ERROR) { 109 throw (UnrecoverableKeyException) 110 new UnrecoverableKeyException("Failed to load information about key") 111 .initCause(mKeyStore.getInvalidKeyException(alias, errorCode)); 112 } 113 114 int keymasterAlgorithm = 115 keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1); 116 if (keymasterAlgorithm == -1) { 117 keymasterAlgorithm = 118 keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1); 119 } 120 if (keymasterAlgorithm == -1) { 121 throw new UnrecoverableKeyException("Key algorithm unknown"); 122 } 123 124 List<Integer> keymasterDigests = 125 keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST); 126 int keymasterDigest; 127 if (keymasterDigests.isEmpty()) { 128 keymasterDigest = -1; 129 } else { 130 // More than one digest can be permitted for this key. Use the first one to form the 131 // JCA key algorithm name. 132 keymasterDigest = keymasterDigests.get(0); 133 } 134 135 @KeyProperties.KeyAlgorithmEnum String keyAlgorithmString; 136 try { 137 keyAlgorithmString = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm( 138 keymasterAlgorithm, keymasterDigest); 139 } catch (IllegalArgumentException e) { 140 throw (UnrecoverableKeyException) 141 new UnrecoverableKeyException("Unsupported secret key type").initCause(e); 142 } 143 144 return new AndroidKeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmString); 145 } 146 147 return null; 148 } 149 150 @Override 151 public Certificate[] engineGetCertificateChain(String alias) { 152 if (alias == null) { 153 throw new NullPointerException("alias == null"); 154 } 155 156 final X509Certificate leaf = (X509Certificate) engineGetCertificate(alias); 157 if (leaf == null) { 158 return null; 159 } 160 161 final Certificate[] caList; 162 163 final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); 164 if (caBytes != null) { 165 final Collection<X509Certificate> caChain = toCertificates(caBytes); 166 167 caList = new Certificate[caChain.size() + 1]; 168 169 final Iterator<X509Certificate> it = caChain.iterator(); 170 int i = 1; 171 while (it.hasNext()) { 172 caList[i++] = it.next(); 173 } 174 } else { 175 caList = new Certificate[1]; 176 } 177 178 caList[0] = leaf; 179 180 return caList; 181 } 182 183 @Override 184 public Certificate engineGetCertificate(String alias) { 185 if (alias == null) { 186 throw new NullPointerException("alias == null"); 187 } 188 189 byte[] certificate = mKeyStore.get(Credentials.USER_CERTIFICATE + alias); 190 if (certificate != null) { 191 return toCertificate(certificate); 192 } 193 194 certificate = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); 195 if (certificate != null) { 196 return toCertificate(certificate); 197 } 198 199 return null; 200 } 201 202 private static X509Certificate toCertificate(byte[] bytes) { 203 try { 204 final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 205 return (X509Certificate) certFactory 206 .generateCertificate(new ByteArrayInputStream(bytes)); 207 } catch (CertificateException e) { 208 Log.w(NAME, "Couldn't parse certificate in keystore", e); 209 return null; 210 } 211 } 212 213 @SuppressWarnings("unchecked") 214 private static Collection<X509Certificate> toCertificates(byte[] bytes) { 215 try { 216 final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 217 return (Collection<X509Certificate>) certFactory 218 .generateCertificates(new ByteArrayInputStream(bytes)); 219 } catch (CertificateException e) { 220 Log.w(NAME, "Couldn't parse certificates in keystore", e); 221 return new ArrayList<X509Certificate>(); 222 } 223 } 224 225 private Date getModificationDate(String alias) { 226 final long epochMillis = mKeyStore.getmtime(alias); 227 if (epochMillis == -1L) { 228 return null; 229 } 230 231 return new Date(epochMillis); 232 } 233 234 @Override 235 public Date engineGetCreationDate(String alias) { 236 if (alias == null) { 237 throw new NullPointerException("alias == null"); 238 } 239 240 Date d = getModificationDate(Credentials.USER_PRIVATE_KEY + alias); 241 if (d != null) { 242 return d; 243 } 244 245 d = getModificationDate(Credentials.USER_SECRET_KEY + alias); 246 if (d != null) { 247 return d; 248 } 249 250 d = getModificationDate(Credentials.USER_CERTIFICATE + alias); 251 if (d != null) { 252 return d; 253 } 254 255 return getModificationDate(Credentials.CA_CERTIFICATE + alias); 256 } 257 258 @Override 259 public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) 260 throws KeyStoreException { 261 if ((password != null) && (password.length > 0)) { 262 throw new KeyStoreException("entries cannot be protected with passwords"); 263 } 264 265 if (key instanceof PrivateKey) { 266 setPrivateKeyEntry(alias, (PrivateKey) key, chain, null); 267 } else if (key instanceof SecretKey) { 268 setSecretKeyEntry(alias, (SecretKey) key, null); 269 } else { 270 throw new KeyStoreException("Only PrivateKey and SecretKey are supported"); 271 } 272 } 273 274 private static KeyProtection getLegacyKeyProtectionParameter(PrivateKey key) 275 throws KeyStoreException { 276 String keyAlgorithm = key.getAlgorithm(); 277 KeyProtection.Builder specBuilder; 278 if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { 279 specBuilder = 280 new KeyProtection.Builder( 281 KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY); 282 specBuilder.setDigests( 283 KeyProperties.DIGEST_NONE, 284 KeyProperties.DIGEST_MD5, 285 KeyProperties.DIGEST_SHA1, 286 KeyProperties.DIGEST_SHA224, 287 KeyProperties.DIGEST_SHA256, 288 KeyProperties.DIGEST_SHA384, 289 KeyProperties.DIGEST_SHA512); 290 } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { 291 specBuilder = 292 new KeyProtection.Builder( 293 KeyProperties.PURPOSE_ENCRYPT 294 | KeyProperties.PURPOSE_DECRYPT 295 | KeyProperties.PURPOSE_SIGN 296 | KeyProperties.PURPOSE_VERIFY); 297 specBuilder.setDigests( 298 KeyProperties.DIGEST_NONE, 299 KeyProperties.DIGEST_MD5, 300 KeyProperties.DIGEST_SHA1, 301 KeyProperties.DIGEST_SHA224, 302 KeyProperties.DIGEST_SHA256, 303 KeyProperties.DIGEST_SHA384, 304 KeyProperties.DIGEST_SHA512); 305 specBuilder.setSignaturePaddings( 306 KeyProperties.SIGNATURE_PADDING_RSA_PKCS1); 307 specBuilder.setEncryptionPaddings( 308 KeyProperties.ENCRYPTION_PADDING_NONE, 309 KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1); 310 // Disable randomized encryption requirement to support encryption padding NONE 311 // above. 312 specBuilder.setRandomizedEncryptionRequired(false); 313 } else { 314 throw new KeyStoreException("Unsupported key algorithm: " + keyAlgorithm); 315 } 316 specBuilder.setUserAuthenticationRequired(false); 317 318 return specBuilder.build(); 319 } 320 321 private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain, 322 java.security.KeyStore.ProtectionParameter param) throws KeyStoreException { 323 int flags = 0; 324 KeyProtection spec; 325 if (param == null) { 326 spec = getLegacyKeyProtectionParameter(key); 327 } else if (param instanceof KeyStoreParameter) { 328 spec = getLegacyKeyProtectionParameter(key); 329 KeyStoreParameter legacySpec = (KeyStoreParameter) param; 330 if (legacySpec.isEncryptionRequired()) { 331 flags = KeyStore.FLAG_ENCRYPTED; 332 } 333 } else if (param instanceof KeyProtection) { 334 spec = (KeyProtection) param; 335 } else { 336 throw new KeyStoreException( 337 "Unsupported protection parameter class:" + param.getClass().getName() 338 + ". Supported: " + KeyProtection.class.getName() + ", " 339 + KeyStoreParameter.class.getName()); 340 } 341 342 // Make sure the chain exists since this is a PrivateKey 343 if ((chain == null) || (chain.length == 0)) { 344 throw new KeyStoreException("Must supply at least one Certificate with PrivateKey"); 345 } 346 347 // Do chain type checking. 348 X509Certificate[] x509chain = new X509Certificate[chain.length]; 349 for (int i = 0; i < chain.length; i++) { 350 if (!"X.509".equals(chain[i].getType())) { 351 throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #" 352 + i); 353 } 354 355 if (!(chain[i] instanceof X509Certificate)) { 356 throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #" 357 + i); 358 } 359 360 x509chain[i] = (X509Certificate) chain[i]; 361 } 362 363 final byte[] userCertBytes; 364 try { 365 userCertBytes = x509chain[0].getEncoded(); 366 } catch (CertificateEncodingException e) { 367 throw new KeyStoreException("Failed to encode certificate #0", e); 368 } 369 370 /* 371 * If we have a chain, store it in the CA certificate slot for this 372 * alias as concatenated DER-encoded certificates. These can be 373 * deserialized by {@link CertificateFactory#generateCertificates}. 374 */ 375 final byte[] chainBytes; 376 if (chain.length > 1) { 377 /* 378 * The chain is passed in as {user_cert, ca_cert_1, ca_cert_2, ...} 379 * so we only need the certificates starting at index 1. 380 */ 381 final byte[][] certsBytes = new byte[x509chain.length - 1][]; 382 int totalCertLength = 0; 383 for (int i = 0; i < certsBytes.length; i++) { 384 try { 385 certsBytes[i] = x509chain[i + 1].getEncoded(); 386 totalCertLength += certsBytes[i].length; 387 } catch (CertificateEncodingException e) { 388 throw new KeyStoreException("Failed to encode certificate #" + i, e); 389 } 390 } 391 392 /* 393 * Serialize this into one byte array so we can later call 394 * CertificateFactory#generateCertificates to recover them. 395 */ 396 chainBytes = new byte[totalCertLength]; 397 int outputOffset = 0; 398 for (int i = 0; i < certsBytes.length; i++) { 399 final int certLength = certsBytes[i].length; 400 System.arraycopy(certsBytes[i], 0, chainBytes, outputOffset, certLength); 401 outputOffset += certLength; 402 certsBytes[i] = null; 403 } 404 } else { 405 chainBytes = null; 406 } 407 408 final String pkeyAlias; 409 if (key instanceof OpenSSLKeyHolder) { 410 pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias(); 411 } else if (key instanceof AndroidKeyStorePrivateKey) { 412 pkeyAlias = ((AndroidKeyStoreKey) key).getAlias(); 413 } else { 414 pkeyAlias = null; 415 } 416 417 byte[] pkcs8EncodedPrivateKeyBytes; 418 KeymasterArguments importArgs; 419 final boolean shouldReplacePrivateKey; 420 if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) { 421 final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length()); 422 if (!alias.equals(keySubalias)) { 423 throw new KeyStoreException("Can only replace keys with same alias: " + alias 424 + " != " + keySubalias); 425 } 426 shouldReplacePrivateKey = false; 427 importArgs = null; 428 pkcs8EncodedPrivateKeyBytes = null; 429 } else { 430 shouldReplacePrivateKey = true; 431 // Make sure the PrivateKey format is the one we support. 432 final String keyFormat = key.getFormat(); 433 if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) { 434 throw new KeyStoreException( 435 "Unsupported private key export format: " + keyFormat 436 + ". Only private keys which export their key material in PKCS#8 format are" 437 + " supported."); 438 } 439 440 // Make sure we can actually encode the key. 441 pkcs8EncodedPrivateKeyBytes = key.getEncoded(); 442 if (pkcs8EncodedPrivateKeyBytes == null) { 443 throw new KeyStoreException("Private key did not export any key material"); 444 } 445 446 importArgs = new KeymasterArguments(); 447 try { 448 importArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, 449 KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm( 450 key.getAlgorithm())); 451 @KeyProperties.PurposeEnum int purposes = spec.getPurposes(); 452 importArgs.addInts(KeymasterDefs.KM_TAG_PURPOSE, 453 KeyProperties.Purpose.allToKeymaster(purposes)); 454 if (spec.isDigestsSpecified()) { 455 importArgs.addInts(KeymasterDefs.KM_TAG_DIGEST, 456 KeyProperties.Digest.allToKeymaster(spec.getDigests())); 457 } 458 459 importArgs.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, 460 KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes())); 461 int[] keymasterEncryptionPaddings = 462 KeyProperties.EncryptionPadding.allToKeymaster( 463 spec.getEncryptionPaddings()); 464 if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) 465 && (spec.isRandomizedEncryptionRequired())) { 466 for (int keymasterPadding : keymasterEncryptionPaddings) { 467 if (!KeymasterUtils 468 .isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto( 469 keymasterPadding)) { 470 throw new KeyStoreException( 471 "Randomized encryption (IND-CPA) required but is violated by" 472 + " encryption padding mode: " 473 + KeyProperties.EncryptionPadding.fromKeymaster( 474 keymasterPadding) 475 + ". See KeyProtection documentation."); 476 } 477 } 478 } 479 importArgs.addInts(KeymasterDefs.KM_TAG_PADDING, keymasterEncryptionPaddings); 480 importArgs.addInts(KeymasterDefs.KM_TAG_PADDING, 481 KeyProperties.SignaturePadding.allToKeymaster(spec.getSignaturePaddings())); 482 KeymasterUtils.addUserAuthArgs(importArgs, 483 spec.isUserAuthenticationRequired(), 484 spec.getUserAuthenticationValidityDurationSeconds()); 485 importArgs.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, 486 (spec.getKeyValidityStart() != null) 487 ? spec.getKeyValidityStart() : new Date(0)); 488 importArgs.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, 489 (spec.getKeyValidityForOriginationEnd() != null) 490 ? spec.getKeyValidityForOriginationEnd() 491 : new Date(Long.MAX_VALUE)); 492 importArgs.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, 493 (spec.getKeyValidityForConsumptionEnd() != null) 494 ? spec.getKeyValidityForConsumptionEnd() 495 : new Date(Long.MAX_VALUE)); 496 } catch (IllegalArgumentException e) { 497 throw new KeyStoreException("Invalid parameter", e); 498 } 499 } 500 501 502 boolean success = false; 503 try { 504 // Store the private key, if necessary 505 if (shouldReplacePrivateKey) { 506 // Delete the stored private key and any related entries before importing the 507 // provided key 508 Credentials.deleteAllTypesForAlias(mKeyStore, alias); 509 KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); 510 int errorCode = mKeyStore.importKey( 511 Credentials.USER_PRIVATE_KEY + alias, 512 importArgs, 513 KeymasterDefs.KM_KEY_FORMAT_PKCS8, 514 pkcs8EncodedPrivateKeyBytes, 515 flags, 516 resultingKeyCharacteristics); 517 if (errorCode != KeyStore.NO_ERROR) { 518 throw new KeyStoreException("Failed to store private key", 519 KeyStore.getKeyStoreException(errorCode)); 520 } 521 } else { 522 // Keep the stored private key around -- delete all other entry types 523 Credentials.deleteCertificateTypesForAlias(mKeyStore, alias); 524 Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias); 525 } 526 527 // Store the leaf certificate 528 int errorCode = mKeyStore.insert(Credentials.USER_CERTIFICATE + alias, userCertBytes, 529 KeyStore.UID_SELF, flags); 530 if (errorCode != KeyStore.NO_ERROR) { 531 throw new KeyStoreException("Failed to store certificate #0", 532 KeyStore.getKeyStoreException(errorCode)); 533 } 534 535 // Store the certificate chain 536 errorCode = mKeyStore.insert(Credentials.CA_CERTIFICATE + alias, chainBytes, 537 KeyStore.UID_SELF, flags); 538 if (errorCode != KeyStore.NO_ERROR) { 539 throw new KeyStoreException("Failed to store certificate chain", 540 KeyStore.getKeyStoreException(errorCode)); 541 } 542 success = true; 543 } finally { 544 if (!success) { 545 if (shouldReplacePrivateKey) { 546 Credentials.deleteAllTypesForAlias(mKeyStore, alias); 547 } else { 548 Credentials.deleteCertificateTypesForAlias(mKeyStore, alias); 549 Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias); 550 } 551 } 552 } 553 } 554 555 private void setSecretKeyEntry(String entryAlias, SecretKey key, 556 java.security.KeyStore.ProtectionParameter param) 557 throws KeyStoreException { 558 if ((param != null) && (!(param instanceof KeyProtection))) { 559 throw new KeyStoreException( 560 "Unsupported protection parameter class: " + param.getClass().getName() 561 + ". Supported: " + KeyProtection.class.getName()); 562 } 563 KeyProtection params = (KeyProtection) param; 564 565 if (key instanceof AndroidKeyStoreSecretKey) { 566 // KeyStore-backed secret key. It cannot be duplicated into another entry and cannot 567 // overwrite its own entry. 568 String keyAliasInKeystore = ((AndroidKeyStoreSecretKey) key).getAlias(); 569 if (keyAliasInKeystore == null) { 570 throw new KeyStoreException("KeyStore-backed secret key does not have an alias"); 571 } 572 if (!keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) { 573 throw new KeyStoreException("KeyStore-backed secret key has invalid alias: " 574 + keyAliasInKeystore); 575 } 576 String keyEntryAlias = 577 keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length()); 578 if (!entryAlias.equals(keyEntryAlias)) { 579 throw new KeyStoreException("Can only replace KeyStore-backed keys with same" 580 + " alias: " + entryAlias + " != " + keyEntryAlias); 581 } 582 // This is the entry where this key is already stored. No need to do anything. 583 if (params != null) { 584 throw new KeyStoreException("Modifying KeyStore-backed key using protection" 585 + " parameters not supported"); 586 } 587 return; 588 } 589 590 if (params == null) { 591 throw new KeyStoreException( 592 "Protection parameters must be specified when importing a symmetric key"); 593 } 594 595 // Not a KeyStore-backed secret key -- import its key material into keystore. 596 String keyExportFormat = key.getFormat(); 597 if (keyExportFormat == null) { 598 throw new KeyStoreException( 599 "Only secret keys that export their key material are supported"); 600 } else if (!"RAW".equals(keyExportFormat)) { 601 throw new KeyStoreException( 602 "Unsupported secret key material export format: " + keyExportFormat); 603 } 604 byte[] keyMaterial = key.getEncoded(); 605 if (keyMaterial == null) { 606 throw new KeyStoreException("Key did not export its key material despite supporting" 607 + " RAW format export"); 608 } 609 610 String keyAlgorithmString = key.getAlgorithm(); 611 int keymasterAlgorithm; 612 int keymasterDigest; 613 try { 614 keymasterAlgorithm = 615 KeyProperties.KeyAlgorithm.toKeymasterSecretKeyAlgorithm(keyAlgorithmString); 616 keymasterDigest = KeyProperties.KeyAlgorithm.toKeymasterDigest(keyAlgorithmString); 617 } catch (IllegalArgumentException e) { 618 throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString); 619 } 620 621 KeymasterArguments args = new KeymasterArguments(); 622 args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, keymasterAlgorithm); 623 624 int[] keymasterDigests; 625 if (params.isDigestsSpecified()) { 626 // Digest(s) specified in parameters 627 keymasterDigests = KeyProperties.Digest.allToKeymaster(params.getDigests()); 628 if (keymasterDigest != -1) { 629 // Digest also specified in the JCA key algorithm name. 630 if (!com.android.internal.util.ArrayUtils.contains( 631 keymasterDigests, keymasterDigest)) { 632 throw new KeyStoreException("Key digest mismatch" 633 + ". Key: " + keyAlgorithmString 634 + ", parameter spec: " + Arrays.asList(params.getDigests())); 635 } 636 // When the key is read back from keystore we reconstruct the JCA key algorithm 637 // name from the KM_TAG_ALGORITHM and the first KM_TAG_DIGEST. Thus we need to 638 // ensure that the digest reflected in the JCA key algorithm name is the first 639 // KM_TAG_DIGEST tag. 640 if (keymasterDigests[0] != keymasterDigest) { 641 // The first digest is not the one implied by the JCA key algorithm name. 642 // Swap the implied digest with the first one. 643 for (int i = 0; i < keymasterDigests.length; i++) { 644 if (keymasterDigests[i] == keymasterDigest) { 645 keymasterDigests[i] = keymasterDigests[0]; 646 keymasterDigests[0] = keymasterDigest; 647 break; 648 } 649 } 650 } 651 } 652 } else { 653 // No digest specified in parameters 654 if (keymasterDigest != -1) { 655 // Digest specified in the JCA key algorithm name. 656 keymasterDigests = new int[] {keymasterDigest}; 657 } else { 658 keymasterDigests = EmptyArray.INT; 659 } 660 } 661 args.addInts(KeymasterDefs.KM_TAG_DIGEST, keymasterDigests); 662 if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { 663 if (keymasterDigests.length == 0) { 664 throw new KeyStoreException("At least one digest algorithm must be specified" 665 + " for key algorithm " + keyAlgorithmString); 666 } 667 } 668 669 @KeyProperties.PurposeEnum int purposes = params.getPurposes(); 670 int[] keymasterBlockModes = 671 KeyProperties.BlockMode.allToKeymaster(params.getBlockModes()); 672 if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) 673 && (params.isRandomizedEncryptionRequired())) { 674 for (int keymasterBlockMode : keymasterBlockModes) { 675 if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto( 676 keymasterBlockMode)) { 677 throw new KeyStoreException( 678 "Randomized encryption (IND-CPA) required but may be violated by block" 679 + " mode: " 680 + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode) 681 + ". See KeyProtection documentation."); 682 } 683 } 684 } 685 args.addInts(KeymasterDefs.KM_TAG_PURPOSE, KeyProperties.Purpose.allToKeymaster(purposes)); 686 args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes); 687 if (params.getSignaturePaddings().length > 0) { 688 throw new KeyStoreException("Signature paddings not supported for symmetric keys"); 689 } 690 int[] keymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster( 691 params.getEncryptionPaddings()); 692 args.addInts(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings); 693 KeymasterUtils.addUserAuthArgs(args, 694 params.isUserAuthenticationRequired(), 695 params.getUserAuthenticationValidityDurationSeconds()); 696 args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, 697 (params.getKeyValidityStart() != null) 698 ? params.getKeyValidityStart() : new Date(0)); 699 args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, 700 (params.getKeyValidityForOriginationEnd() != null) 701 ? params.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE)); 702 args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, 703 (params.getKeyValidityForConsumptionEnd() != null) 704 ? params.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE)); 705 706 if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) 707 && (!params.isRandomizedEncryptionRequired())) { 708 // Permit caller-provided IV when encrypting with this key 709 args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); 710 } 711 712 Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias); 713 String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias; 714 int errorCode = mKeyStore.importKey( 715 keyAliasInKeystore, 716 args, 717 KeymasterDefs.KM_KEY_FORMAT_RAW, 718 keyMaterial, 719 0, // flags 720 new KeyCharacteristics()); 721 if (errorCode != KeyStore.NO_ERROR) { 722 throw new KeyStoreException("Failed to import secret key. Keystore error code: " 723 + errorCode); 724 } 725 } 726 727 @Override 728 public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain) 729 throws KeyStoreException { 730 throw new KeyStoreException("Operation not supported because key encoding is unknown"); 731 } 732 733 @Override 734 public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException { 735 if (isKeyEntry(alias)) { 736 throw new KeyStoreException("Entry exists and is not a trusted certificate"); 737 } 738 739 // We can't set something to null. 740 if (cert == null) { 741 throw new NullPointerException("cert == null"); 742 } 743 744 final byte[] encoded; 745 try { 746 encoded = cert.getEncoded(); 747 } catch (CertificateEncodingException e) { 748 throw new KeyStoreException(e); 749 } 750 751 if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded, 752 KeyStore.UID_SELF, KeyStore.FLAG_NONE)) { 753 throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?"); 754 } 755 } 756 757 @Override 758 public void engineDeleteEntry(String alias) throws KeyStoreException { 759 if (!engineContainsAlias(alias)) { 760 return; 761 } 762 // At least one entry corresponding to this alias exists in keystore 763 764 if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) { 765 throw new KeyStoreException("Failed to delete entry: " + alias); 766 } 767 } 768 769 private Set<String> getUniqueAliases() { 770 final String[] rawAliases = mKeyStore.list(""); 771 if (rawAliases == null) { 772 return new HashSet<String>(); 773 } 774 775 final Set<String> aliases = new HashSet<String>(rawAliases.length); 776 for (String alias : rawAliases) { 777 final int idx = alias.indexOf('_'); 778 if ((idx == -1) || (alias.length() <= idx)) { 779 Log.e(NAME, "invalid alias: " + alias); 780 continue; 781 } 782 783 aliases.add(new String(alias.substring(idx + 1))); 784 } 785 786 return aliases; 787 } 788 789 @Override 790 public Enumeration<String> engineAliases() { 791 return Collections.enumeration(getUniqueAliases()); 792 } 793 794 @Override 795 public boolean engineContainsAlias(String alias) { 796 if (alias == null) { 797 throw new NullPointerException("alias == null"); 798 } 799 800 return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias) 801 || mKeyStore.contains(Credentials.USER_SECRET_KEY + alias) 802 || mKeyStore.contains(Credentials.USER_CERTIFICATE + alias) 803 || mKeyStore.contains(Credentials.CA_CERTIFICATE + alias); 804 } 805 806 @Override 807 public int engineSize() { 808 return getUniqueAliases().size(); 809 } 810 811 @Override 812 public boolean engineIsKeyEntry(String alias) { 813 return isKeyEntry(alias); 814 } 815 816 private boolean isKeyEntry(String alias) { 817 return isPrivateKeyEntry(alias) || isSecretKeyEntry(alias); 818 } 819 820 private boolean isPrivateKeyEntry(String alias) { 821 if (alias == null) { 822 throw new NullPointerException("alias == null"); 823 } 824 825 return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias); 826 } 827 828 private boolean isSecretKeyEntry(String alias) { 829 if (alias == null) { 830 throw new NullPointerException("alias == null"); 831 } 832 833 return mKeyStore.contains(Credentials.USER_SECRET_KEY + alias); 834 } 835 836 private boolean isCertificateEntry(String alias) { 837 if (alias == null) { 838 throw new NullPointerException("alias == null"); 839 } 840 841 return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias); 842 } 843 844 @Override 845 public boolean engineIsCertificateEntry(String alias) { 846 return !isKeyEntry(alias) && isCertificateEntry(alias); 847 } 848 849 @Override 850 public String engineGetCertificateAlias(Certificate cert) { 851 if (cert == null) { 852 return null; 853 } 854 855 final Set<String> nonCaEntries = new HashSet<String>(); 856 857 /* 858 * First scan the PrivateKeyEntry types. The KeyStoreSpi documentation 859 * says to only compare the first certificate in the chain which is 860 * equivalent to the USER_CERTIFICATE prefix for the Android keystore 861 * convention. 862 */ 863 final String[] certAliases = mKeyStore.list(Credentials.USER_CERTIFICATE); 864 if (certAliases != null) { 865 for (String alias : certAliases) { 866 final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias); 867 if (certBytes == null) { 868 continue; 869 } 870 871 final Certificate c = toCertificate(certBytes); 872 nonCaEntries.add(alias); 873 874 if (cert.equals(c)) { 875 return alias; 876 } 877 } 878 } 879 880 /* 881 * Look at all the TrustedCertificateEntry types. Skip all the 882 * PrivateKeyEntry we looked at above. 883 */ 884 final String[] caAliases = mKeyStore.list(Credentials.CA_CERTIFICATE); 885 if (certAliases != null) { 886 for (String alias : caAliases) { 887 if (nonCaEntries.contains(alias)) { 888 continue; 889 } 890 891 final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); 892 if (certBytes == null) { 893 continue; 894 } 895 896 final Certificate c = 897 toCertificate(mKeyStore.get(Credentials.CA_CERTIFICATE + alias)); 898 if (cert.equals(c)) { 899 return alias; 900 } 901 } 902 } 903 904 return null; 905 } 906 907 @Override 908 public void engineStore(OutputStream stream, char[] password) throws IOException, 909 NoSuchAlgorithmException, CertificateException { 910 throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream"); 911 } 912 913 @Override 914 public void engineLoad(InputStream stream, char[] password) throws IOException, 915 NoSuchAlgorithmException, CertificateException { 916 if (stream != null) { 917 throw new IllegalArgumentException("InputStream not supported"); 918 } 919 920 if (password != null) { 921 throw new IllegalArgumentException("password not supported"); 922 } 923 924 // Unfortunate name collision. 925 mKeyStore = KeyStore.getInstance(); 926 } 927 928 @Override 929 public void engineSetEntry(String alias, Entry entry, ProtectionParameter param) 930 throws KeyStoreException { 931 if (entry == null) { 932 throw new KeyStoreException("entry == null"); 933 } 934 935 Credentials.deleteAllTypesForAlias(mKeyStore, alias); 936 937 if (entry instanceof java.security.KeyStore.TrustedCertificateEntry) { 938 java.security.KeyStore.TrustedCertificateEntry trE = 939 (java.security.KeyStore.TrustedCertificateEntry) entry; 940 engineSetCertificateEntry(alias, trE.getTrustedCertificate()); 941 return; 942 } 943 944 if (entry instanceof PrivateKeyEntry) { 945 PrivateKeyEntry prE = (PrivateKeyEntry) entry; 946 setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(), param); 947 } else if (entry instanceof SecretKeyEntry) { 948 SecretKeyEntry secE = (SecretKeyEntry) entry; 949 setSecretKeyEntry(alias, secE.getSecretKey(), param); 950 } else { 951 throw new KeyStoreException( 952 "Entry must be a PrivateKeyEntry, SecretKeyEntry or TrustedCertificateEntry" 953 + "; was " + entry); 954 } 955 } 956 957} 958