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 17 package android.security.keystore2; 18 19 import android.annotation.NonNull; 20 import android.hardware.biometrics.BiometricManager; 21 import android.hardware.security.keymint.HardwareAuthenticatorType; 22 import android.hardware.security.keymint.KeyParameter; 23 import android.hardware.security.keymint.SecurityLevel; 24 import android.security.GateKeeper; 25 import android.security.KeyStore2; 26 import android.security.KeyStoreParameter; 27 import android.security.KeyStoreSecurityLevel; 28 import android.security.keymaster.KeymasterDefs; 29 import android.security.keystore.KeyGenParameterSpec; 30 import android.security.keystore.KeyPermanentlyInvalidatedException; 31 import android.security.keystore.KeyProperties; 32 import android.security.keystore.KeyProtection; 33 import android.security.keystore.SecureKeyImportUnavailableException; 34 import android.security.keystore.WrappedKeyEntry; 35 import android.system.keystore2.AuthenticatorSpec; 36 import android.system.keystore2.Domain; 37 import android.system.keystore2.IKeystoreSecurityLevel; 38 import android.system.keystore2.KeyDescriptor; 39 import android.system.keystore2.KeyEntryResponse; 40 import android.system.keystore2.KeyMetadata; 41 import android.system.keystore2.ResponseCode; 42 import android.util.Log; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 46 import java.io.ByteArrayInputStream; 47 import java.io.IOException; 48 import java.io.InputStream; 49 import java.io.OutputStream; 50 import java.security.Key; 51 import java.security.KeyStore.Entry; 52 import java.security.KeyStore.LoadStoreParameter; 53 import java.security.KeyStore.PrivateKeyEntry; 54 import java.security.KeyStore.ProtectionParameter; 55 import java.security.KeyStore.SecretKeyEntry; 56 import java.security.KeyStoreException; 57 import java.security.KeyStoreSpi; 58 import java.security.NoSuchAlgorithmException; 59 import java.security.PrivateKey; 60 import java.security.ProviderException; 61 import java.security.UnrecoverableKeyException; 62 import java.security.cert.Certificate; 63 import java.security.cert.CertificateEncodingException; 64 import java.security.cert.CertificateException; 65 import java.security.cert.CertificateFactory; 66 import java.security.cert.X509Certificate; 67 import java.util.ArrayList; 68 import java.util.Arrays; 69 import java.util.Collection; 70 import java.util.Collections; 71 import java.util.Date; 72 import java.util.Enumeration; 73 import java.util.HashSet; 74 import java.util.Iterator; 75 import java.util.List; 76 import java.util.Set; 77 78 import javax.crypto.SecretKey; 79 80 /** 81 * A java.security.KeyStore interface for the Android KeyStore. An instance of 82 * it can be created via the {@link java.security.KeyStore#getInstance(String) 83 * KeyStore.getInstance("AndroidKeyStore")} interface. This returns a 84 * java.security.KeyStore backed by this "AndroidKeyStore" implementation. 85 * <p> 86 * This is built on top of Android's keystore daemon. The convention of alias 87 * use is: 88 * <p> 89 * PrivateKeyEntry will have a Credentials.USER_PRIVATE_KEY as the private key, 90 * Credentials.USER_CERTIFICATE as the first certificate in the chain (the one 91 * that corresponds to the private key), and then a Credentials.CA_CERTIFICATE 92 * entry which will have the rest of the chain concatenated in BER format. 93 * <p> 94 * TrustedCertificateEntry will just have a Credentials.CA_CERTIFICATE entry 95 * with a single certificate. 96 * 97 * @hide 98 */ 99 public class AndroidKeyStoreSpi extends KeyStoreSpi { 100 public static final String TAG = "AndroidKeyStoreSpi"; 101 public static final String NAME = "AndroidKeyStore"; 102 103 private KeyStore2 mKeyStore; 104 private @KeyProperties.Namespace int mNamespace = KeyProperties.NAMESPACE_APPLICATION; 105 106 @Override engineGetKey(String alias, char[] password)107 public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, 108 UnrecoverableKeyException { 109 try { 110 return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(mKeyStore, 111 alias, 112 mNamespace); 113 } catch (KeyPermanentlyInvalidatedException e) { 114 throw new UnrecoverableKeyException(e.getMessage()); 115 } catch (UnrecoverableKeyException e) { 116 Throwable cause = e.getCause(); 117 if (cause instanceof android.security.KeyStoreException) { 118 if (((android.security.KeyStoreException) cause).getErrorCode() 119 == ResponseCode.KEY_NOT_FOUND) { 120 return null; 121 } 122 } 123 throw e; 124 } 125 } 126 127 /** 128 * Make a key descriptor from the given alias and the mNamespace member. 129 * If mNamespace is -1 it sets the domain field to {@link Domain#APP} and {@link Domain#SELINUX} 130 * otherwise. The blob field is always set to null and the alias field to {@code alias} 131 * @param alias The alias of the new key descriptor. 132 * @return A new key descriptor. 133 */ makeKeyDescriptor(@onNull String alias)134 private KeyDescriptor makeKeyDescriptor(@NonNull String alias) { 135 KeyDescriptor descriptor = new KeyDescriptor(); 136 descriptor.domain = getTargetDomain(); 137 descriptor.nspace = mNamespace; // ignored if Domain.App; 138 descriptor.alias = alias; 139 descriptor.blob = null; 140 return descriptor; 141 } 142 getTargetDomain()143 private @Domain int getTargetDomain() { 144 return mNamespace == KeyProperties.NAMESPACE_APPLICATION 145 ? Domain.APP 146 : Domain.SELINUX; 147 } getKeyMetadata(String alias)148 private KeyEntryResponse getKeyMetadata(String alias) { 149 if (alias == null) { 150 throw new NullPointerException("alias == null"); 151 } 152 153 KeyDescriptor descriptor = makeKeyDescriptor(alias); 154 155 try { 156 return mKeyStore.getKeyEntry(descriptor); 157 } catch (android.security.KeyStoreException e) { 158 if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) { 159 Log.w(TAG, "Could not get key metadata from Keystore.", e); 160 } 161 return null; 162 } 163 } 164 165 @Override engineGetCertificateChain(String alias)166 public Certificate[] engineGetCertificateChain(String alias) { 167 KeyEntryResponse response = getKeyMetadata(alias); 168 169 if (response == null || response.metadata.certificate == null) { 170 return null; 171 } 172 173 final X509Certificate leaf = (X509Certificate) toCertificate(response.metadata.certificate); 174 if (leaf == null) { 175 return null; 176 } 177 178 final Certificate[] caList; 179 180 final byte[] caBytes = response.metadata.certificateChain; 181 182 if (caBytes != null) { 183 final Collection<X509Certificate> caChain = toCertificates(caBytes); 184 185 caList = new Certificate[caChain.size() + 1]; 186 187 final Iterator<X509Certificate> it = caChain.iterator(); 188 int i = 1; 189 while (it.hasNext()) { 190 caList[i++] = it.next(); 191 } 192 } else { 193 caList = new Certificate[1]; 194 } 195 196 caList[0] = leaf; 197 198 return caList; 199 } 200 201 @Override engineGetCertificate(String alias)202 public Certificate engineGetCertificate(String alias) { 203 KeyEntryResponse response = getKeyMetadata(alias); 204 205 if (response == null) { 206 return null; 207 } 208 209 byte[] encodedCert = response.metadata.certificate; 210 if (encodedCert != null) { 211 return toCertificate(encodedCert); 212 } 213 214 encodedCert = response.metadata.certificateChain; 215 if (encodedCert != null) { 216 return toCertificate(encodedCert); 217 } 218 219 // This entry/alias does not contain a certificate. 220 return null; 221 } 222 toCertificate(byte[] bytes)223 static X509Certificate toCertificate(byte[] bytes) { 224 try { 225 final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 226 return (X509Certificate) certFactory.generateCertificate( 227 new ByteArrayInputStream(bytes)); 228 } catch (CertificateException e) { 229 Log.w(NAME, "Couldn't parse certificate in keystore", e); 230 return null; 231 } 232 } 233 234 @SuppressWarnings("unchecked") toCertificates(byte[] bytes)235 private static Collection<X509Certificate> toCertificates(byte[] bytes) { 236 try { 237 final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 238 return (Collection<X509Certificate>) certFactory.generateCertificates( 239 new ByteArrayInputStream(bytes)); 240 } catch (CertificateException e) { 241 Log.w(NAME, "Couldn't parse certificates in keystore", e); 242 return new ArrayList<X509Certificate>(); 243 } 244 } 245 246 @Override engineGetCreationDate(String alias)247 public Date engineGetCreationDate(String alias) { 248 KeyEntryResponse response = getKeyMetadata(alias); 249 250 if (response == null) { 251 return null; 252 } 253 254 if (response.metadata.modificationTimeMs == -1) { 255 return null; 256 } 257 return new Date(response.metadata.modificationTimeMs); 258 } 259 260 @Override engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)261 public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) 262 throws KeyStoreException { 263 if ((password != null) && (password.length > 0)) { 264 throw new KeyStoreException("entries cannot be protected with passwords"); 265 } 266 267 if (key instanceof PrivateKey) { 268 setPrivateKeyEntry(alias, (PrivateKey) key, chain, null); 269 } else if (key instanceof SecretKey) { 270 setSecretKeyEntry(alias, (SecretKey) key, null); 271 } else { 272 throw new KeyStoreException("Only PrivateKey and SecretKey are supported"); 273 } 274 } 275 getLegacyKeyProtectionParameter(PrivateKey key)276 private static KeyProtection getLegacyKeyProtectionParameter(PrivateKey key) 277 throws KeyStoreException { 278 String keyAlgorithm = key.getAlgorithm(); 279 KeyProtection.Builder specBuilder; 280 if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { 281 specBuilder = 282 new KeyProtection.Builder( 283 KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY); 284 // Authorized to be used with any digest (including no digest). 285 // MD5 was never offered for Android Keystore for ECDSA. 286 specBuilder.setDigests( 287 KeyProperties.DIGEST_NONE, 288 KeyProperties.DIGEST_SHA1, 289 KeyProperties.DIGEST_SHA224, 290 KeyProperties.DIGEST_SHA256, 291 KeyProperties.DIGEST_SHA384, 292 KeyProperties.DIGEST_SHA512); 293 } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { 294 specBuilder = 295 new KeyProtection.Builder( 296 KeyProperties.PURPOSE_ENCRYPT 297 | KeyProperties.PURPOSE_DECRYPT 298 | KeyProperties.PURPOSE_SIGN 299 | KeyProperties.PURPOSE_VERIFY); 300 // Authorized to be used with any digest (including no digest). 301 specBuilder.setDigests( 302 KeyProperties.DIGEST_NONE, 303 KeyProperties.DIGEST_MD5, 304 KeyProperties.DIGEST_SHA1, 305 KeyProperties.DIGEST_SHA224, 306 KeyProperties.DIGEST_SHA256, 307 KeyProperties.DIGEST_SHA384, 308 KeyProperties.DIGEST_SHA512); 309 // Authorized to be used with any encryption and signature padding 310 // schemes (including no padding). 311 specBuilder.setEncryptionPaddings( 312 KeyProperties.ENCRYPTION_PADDING_NONE, 313 KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1, 314 KeyProperties.ENCRYPTION_PADDING_RSA_OAEP); 315 specBuilder.setSignaturePaddings( 316 KeyProperties.SIGNATURE_PADDING_RSA_PKCS1, 317 KeyProperties.SIGNATURE_PADDING_RSA_PSS); 318 // Disable randomized encryption requirement to support encryption 319 // padding NONE above. 320 specBuilder.setRandomizedEncryptionRequired(false); 321 } else { 322 throw new KeyStoreException("Unsupported key algorithm: " + keyAlgorithm); 323 } 324 specBuilder.setUserAuthenticationRequired(false); 325 326 return specBuilder.build(); 327 } 328 setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain, java.security.KeyStore.ProtectionParameter param)329 private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain, 330 java.security.KeyStore.ProtectionParameter param) throws KeyStoreException { 331 @SecurityLevel int securitylevel = SecurityLevel.TRUSTED_ENVIRONMENT; 332 int flags = 0; 333 KeyProtection spec; 334 if (param == null) { 335 spec = getLegacyKeyProtectionParameter(key); 336 } else if (param instanceof KeyStoreParameter) { 337 spec = getLegacyKeyProtectionParameter(key); 338 KeyStoreParameter legacySpec = (KeyStoreParameter) param; 339 } else if (param instanceof KeyProtection) { 340 spec = (KeyProtection) param; 341 if (spec.isCriticalToDeviceEncryption()) { 342 // This key is should not be bound to the LSKF even if it is auth bound. 343 // This indicates that this key is used in the derivation for of the 344 // master key, that is used for the LSKF binding of other auth bound 345 // keys. This breaks up a circular dependency while retaining logical 346 // authentication binding of the key. 347 flags |= IKeystoreSecurityLevel 348 .KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING; 349 } 350 351 if (spec.isStrongBoxBacked()) { 352 securitylevel = SecurityLevel.STRONGBOX; 353 } 354 } else { 355 throw new KeyStoreException( 356 "Unsupported protection parameter class:" + param.getClass().getName() 357 + ". Supported: " + KeyProtection.class.getName() + ", " 358 + KeyStoreParameter.class.getName()); 359 } 360 361 // Make sure the chain exists since this is a PrivateKey 362 if ((chain == null) || (chain.length == 0)) { 363 throw new KeyStoreException("Must supply at least one Certificate with PrivateKey"); 364 } 365 366 // Do chain type checking. 367 X509Certificate[] x509chain = new X509Certificate[chain.length]; 368 for (int i = 0; i < chain.length; i++) { 369 if (!"X.509".equals(chain[i].getType())) { 370 throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #" 371 + i); 372 } 373 374 if (!(chain[i] instanceof X509Certificate)) { 375 throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #" 376 + i); 377 } 378 379 x509chain[i] = (X509Certificate) chain[i]; 380 } 381 382 final byte[] userCertBytes; 383 try { 384 userCertBytes = x509chain[0].getEncoded(); 385 } catch (CertificateEncodingException e) { 386 throw new KeyStoreException("Failed to encode certificate #0", e); 387 } 388 389 /* 390 * If we have a chain, store it in the CA certificate slot for this 391 * alias as concatenated DER-encoded certificates. These can be 392 * deserialized by {@link CertificateFactory#generateCertificates}. 393 */ 394 final byte[] chainBytes; 395 if (chain.length > 1) { 396 /* 397 * The chain is passed in as {user_cert, ca_cert_1, ca_cert_2, ...} 398 * so we only need the certificates starting at index 1. 399 */ 400 final byte[][] certsBytes = new byte[x509chain.length - 1][]; 401 int totalCertLength = 0; 402 for (int i = 0; i < certsBytes.length; i++) { 403 try { 404 certsBytes[i] = x509chain[i + 1].getEncoded(); 405 totalCertLength += certsBytes[i].length; 406 } catch (CertificateEncodingException e) { 407 throw new KeyStoreException("Failed to encode certificate #" + i, e); 408 } 409 } 410 411 /* 412 * Serialize this into one byte array so we can later call 413 * CertificateFactory#generateCertificates to recover them. 414 */ 415 chainBytes = new byte[totalCertLength]; 416 int outputOffset = 0; 417 for (int i = 0; i < certsBytes.length; i++) { 418 final int certLength = certsBytes[i].length; 419 System.arraycopy(certsBytes[i], 0, chainBytes, outputOffset, certLength); 420 outputOffset += certLength; 421 certsBytes[i] = null; 422 } 423 } else { 424 chainBytes = null; 425 } 426 427 @Domain int targetDomain = getTargetDomain(); 428 429 // If the given key is an AndroidKeyStorePrivateKey, we attempt to update 430 // its subcomponents with the given certificate and certificate chain. 431 if (key instanceof AndroidKeyStorePrivateKey) { 432 AndroidKeyStoreKey ksKey = (AndroidKeyStoreKey) key; 433 KeyDescriptor descriptor = ksKey.getUserKeyDescriptor(); 434 435 // This throws if the request cannot replace the entry. 436 assertCanReplace(alias, targetDomain, mNamespace, descriptor); 437 438 try { 439 mKeyStore.updateSubcomponents( 440 ((AndroidKeyStorePrivateKey) key).getKeyIdDescriptor(), 441 userCertBytes, chainBytes); 442 } catch (android.security.KeyStoreException e) { 443 throw new KeyStoreException("Failed to store certificate and certificate chain", e); 444 } 445 return; 446 } 447 448 // Make sure the PrivateKey format is the one we support. 449 final String keyFormat = key.getFormat(); 450 if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) { 451 throw new KeyStoreException( 452 "Unsupported private key export format: " + keyFormat 453 + ". Only private keys which export their key material in PKCS#8 format are" 454 + " supported."); 455 } 456 457 // Make sure we can actually encode the key. 458 byte[] pkcs8EncodedPrivateKeyBytes = key.getEncoded(); 459 if (pkcs8EncodedPrivateKeyBytes == null) { 460 throw new KeyStoreException("Private key did not export any key material"); 461 } 462 463 final List<KeyParameter> importArgs = new ArrayList<>(); 464 465 try { 466 importArgs.add(KeyStore2ParameterUtils.makeEnum( 467 KeymasterDefs.KM_TAG_ALGORITHM, 468 KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm( 469 key.getAlgorithm())) 470 ); 471 KeyStore2ParameterUtils.forEachSetFlag(spec.getPurposes(), (purpose) -> { 472 importArgs.add(KeyStore2ParameterUtils.makeEnum( 473 KeymasterDefs.KM_TAG_PURPOSE, 474 KeyProperties.Purpose.toKeymaster(purpose) 475 )); 476 }); 477 if (spec.isDigestsSpecified()) { 478 for (String digest : spec.getDigests()) { 479 importArgs.add(KeyStore2ParameterUtils.makeEnum( 480 KeymasterDefs.KM_TAG_DIGEST, 481 KeyProperties.Digest.toKeymaster(digest) 482 )); 483 } 484 } 485 for (String blockMode : spec.getBlockModes()) { 486 importArgs.add(KeyStore2ParameterUtils.makeEnum( 487 KeymasterDefs.KM_TAG_BLOCK_MODE, 488 KeyProperties.BlockMode.toKeymaster(blockMode) 489 )); 490 } 491 int[] keymasterEncryptionPaddings = 492 KeyProperties.EncryptionPadding.allToKeymaster( 493 spec.getEncryptionPaddings()); 494 if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) 495 && (spec.isRandomizedEncryptionRequired())) { 496 for (int keymasterPadding : keymasterEncryptionPaddings) { 497 if (!KeymasterUtils 498 .isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto( 499 keymasterPadding)) { 500 throw new KeyStoreException( 501 "Randomized encryption (IND-CPA) required but is violated by" 502 + " encryption padding mode: " 503 + KeyProperties.EncryptionPadding.fromKeymaster( 504 keymasterPadding) 505 + ". See KeyProtection documentation."); 506 } 507 } 508 } 509 for (int padding : keymasterEncryptionPaddings) { 510 importArgs.add(KeyStore2ParameterUtils.makeEnum( 511 KeymasterDefs.KM_TAG_PADDING, 512 padding 513 )); 514 } 515 for (String padding : spec.getSignaturePaddings()) { 516 importArgs.add(KeyStore2ParameterUtils.makeEnum( 517 KeymasterDefs.KM_TAG_PADDING, 518 KeyProperties.SignaturePadding.toKeymaster(padding) 519 )); 520 } 521 KeyStore2ParameterUtils.addUserAuthArgs(importArgs, spec); 522 if (spec.getKeyValidityStart() != null) { 523 importArgs.add(KeyStore2ParameterUtils.makeDate( 524 KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart() 525 )); 526 } 527 if (spec.getKeyValidityForOriginationEnd() != null) { 528 importArgs.add(KeyStore2ParameterUtils.makeDate( 529 KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, 530 spec.getKeyValidityForOriginationEnd() 531 )); 532 } 533 if (spec.getKeyValidityForConsumptionEnd() != null) { 534 importArgs.add(KeyStore2ParameterUtils.makeDate( 535 KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, 536 spec.getKeyValidityForConsumptionEnd() 537 )); 538 } 539 if (spec.getMaxUsageCount() != KeyProperties.UNRESTRICTED_USAGE_COUNT) { 540 importArgs.add(KeyStore2ParameterUtils.makeInt( 541 KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT, 542 spec.getMaxUsageCount() 543 )); 544 } 545 } catch (IllegalArgumentException | IllegalStateException e) { 546 throw new KeyStoreException(e); 547 } 548 549 try { 550 KeyStoreSecurityLevel securityLevelInterface = mKeyStore.getSecurityLevel( 551 securitylevel); 552 553 KeyDescriptor descriptor = makeKeyDescriptor(alias); 554 555 KeyMetadata metadata = securityLevelInterface.importKey(descriptor, null, 556 importArgs, flags, pkcs8EncodedPrivateKeyBytes); 557 558 try { 559 mKeyStore.updateSubcomponents(metadata.key, userCertBytes, chainBytes); 560 } catch (android.security.KeyStoreException e) { 561 mKeyStore.deleteKey(metadata.key); 562 throw new KeyStoreException("Failed to store certificate and certificate chain", e); 563 } 564 565 } catch (android.security.KeyStoreException e) { 566 throw new KeyStoreException("Failed to store private key", e); 567 } 568 } 569 assertCanReplace(String alias, @Domain int targetDomain, int targetNamespace, KeyDescriptor descriptor)570 private static void assertCanReplace(String alias, @Domain int targetDomain, 571 int targetNamespace, KeyDescriptor descriptor) 572 throws KeyStoreException { 573 // If 574 // * the alias does not match, or 575 // * the domain does not match, or 576 // * the domain is Domain.SELINUX and the namespaces don not match, 577 // then the designated key location is not equivalent to the location of the 578 // given key parameter and cannot be updated. 579 // 580 // Note: mNamespace == KeyProperties.NAMESPACE_APPLICATION implies that the target domain 581 // is Domain.APP and Domain.SELINUX is the target domain otherwise. 582 if (!alias.equals(descriptor.alias) 583 || descriptor.domain != targetDomain 584 || (descriptor.domain == Domain.SELINUX && descriptor.nspace != targetNamespace)) { 585 throw new KeyStoreException("Can only replace keys with same alias: " + alias 586 + " != " + descriptor.alias + " in the same target domain: " + targetDomain 587 + " != " + descriptor.domain 588 + (targetDomain == Domain.SELINUX ? " in the same target namespace: " 589 + targetNamespace + " != " + descriptor.nspace : "") 590 ); 591 } 592 } 593 setSecretKeyEntry(String alias, SecretKey key, java.security.KeyStore.ProtectionParameter param)594 private void setSecretKeyEntry(String alias, SecretKey key, 595 java.security.KeyStore.ProtectionParameter param) 596 throws KeyStoreException { 597 if ((param != null) && (!(param instanceof KeyProtection))) { 598 throw new KeyStoreException( 599 "Unsupported protection parameter class: " + param.getClass().getName() 600 + ". Supported: " + KeyProtection.class.getName()); 601 } 602 KeyProtection params = (KeyProtection) param; 603 604 @Domain int targetDomain = (getTargetDomain()); 605 606 if (key instanceof AndroidKeyStoreSecretKey) { 607 String keyAliasInKeystore = 608 ((AndroidKeyStoreSecretKey) key).getUserKeyDescriptor().alias; 609 610 KeyDescriptor descriptor = ((AndroidKeyStoreSecretKey) key).getUserKeyDescriptor(); 611 612 // This throws if the request cannot replace the existing key. 613 assertCanReplace(alias, targetDomain, mNamespace, descriptor); 614 615 // This is the entry where this key is already stored. No need to do anything. 616 if (params != null) { 617 throw new KeyStoreException("Modifying KeyStore-backed key using protection" 618 + " parameters not supported"); 619 } 620 return; 621 } 622 623 if (params == null) { 624 throw new KeyStoreException( 625 "Protection parameters must be specified when importing a symmetric key"); 626 } 627 628 // Not a KeyStore-backed secret key -- import its key material into keystore. 629 String keyExportFormat = key.getFormat(); 630 if (keyExportFormat == null) { 631 throw new KeyStoreException( 632 "Only secret keys that export their key material are supported"); 633 } else if (!"RAW".equals(keyExportFormat)) { 634 throw new KeyStoreException( 635 "Unsupported secret key material export format: " + keyExportFormat); 636 } 637 byte[] keyMaterial = key.getEncoded(); 638 if (keyMaterial == null) { 639 throw new KeyStoreException("Key did not export its key material despite supporting" 640 + " RAW format export"); 641 } 642 643 final List<KeyParameter> importArgs = new ArrayList<>(); 644 645 try { 646 int keymasterAlgorithm = 647 KeyProperties.KeyAlgorithm.toKeymasterSecretKeyAlgorithm( 648 key.getAlgorithm()); 649 650 importArgs.add(KeyStore2ParameterUtils.makeEnum( 651 KeymasterDefs.KM_TAG_ALGORITHM, 652 keymasterAlgorithm 653 )); 654 655 if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { 656 // JCA HMAC key algorithm implies a digest (e.g., HmacSHA256 key algorithm 657 // implies SHA-256 digest). Because keymaster HMAC key is authorized only for one 658 // digest, we don't let import parameters override the digest implied by the key. 659 // If the parameters specify digests at all, they must specify only one digest, the 660 // only implied by key algorithm. 661 int keymasterImpliedDigest = 662 KeyProperties.KeyAlgorithm.toKeymasterDigest(key.getAlgorithm()); 663 if (keymasterImpliedDigest == -1) { 664 throw new ProviderException( 665 "HMAC key algorithm digest unknown for key algorithm " 666 + key.getAlgorithm()); 667 } 668 669 if (params.isDigestsSpecified()) { 670 // Digest(s) explicitly specified in params -- check that the list consists of 671 // exactly one digest, the one implied by key algorithm. 672 int[] keymasterDigestsFromParams = 673 KeyProperties.Digest.allToKeymaster(params.getDigests()); 674 if ((keymasterDigestsFromParams.length != 1) 675 || (keymasterDigestsFromParams[0] != keymasterImpliedDigest)) { 676 throw new KeyStoreException( 677 "Unsupported digests specification: " 678 + Arrays.asList(params.getDigests()) + ". Only " 679 + KeyProperties.Digest.fromKeymaster(keymasterImpliedDigest) 680 + " supported for HMAC key algorithm " 681 + key.getAlgorithm()); 682 } 683 } 684 int outputBits = KeymasterUtils.getDigestOutputSizeBits(keymasterImpliedDigest); 685 if (outputBits == -1) { 686 throw new ProviderException( 687 "HMAC key authorized for unsupported digest: " 688 + KeyProperties.Digest.fromKeymaster(keymasterImpliedDigest)); 689 } 690 importArgs.add(KeyStore2ParameterUtils.makeEnum( 691 KeymasterDefs.KM_TAG_DIGEST, keymasterImpliedDigest 692 )); 693 importArgs.add(KeyStore2ParameterUtils.makeInt( 694 KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, outputBits 695 )); 696 } else { 697 if (params.isDigestsSpecified()) { 698 for (String digest : params.getDigests()) { 699 importArgs.add(KeyStore2ParameterUtils.makeEnum( 700 KeymasterDefs.KM_TAG_DIGEST, 701 KeyProperties.Digest.toKeymaster(digest) 702 )); 703 } 704 } 705 } 706 707 KeyStore2ParameterUtils.forEachSetFlag(params.getPurposes(), (purpose) -> { 708 importArgs.add(KeyStore2ParameterUtils.makeEnum( 709 KeymasterDefs.KM_TAG_PURPOSE, 710 KeyProperties.Purpose.toKeymaster(purpose) 711 )); 712 }); 713 714 boolean indCpa = false; 715 if ((params.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) { 716 if (((KeyProtection) param).isRandomizedEncryptionRequired()) { 717 indCpa = true; 718 } else { 719 importArgs.add(KeyStore2ParameterUtils.makeBool( 720 KeymasterDefs.KM_TAG_CALLER_NONCE 721 )); 722 } 723 } 724 725 for (String blockMode : params.getBlockModes()) { 726 int keymasterBlockMode = KeyProperties.BlockMode.toKeymaster(blockMode); 727 if (indCpa 728 && !KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto( 729 keymasterBlockMode)) { 730 throw new KeyStoreException( 731 "Randomized encryption (IND-CPA) required but may be violated by" 732 + " block mode: " + blockMode 733 + ". See KeyProtection documentation."); 734 735 } 736 if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES 737 && keymasterBlockMode == KeymasterDefs.KM_MODE_GCM) { 738 importArgs.add(KeyStore2ParameterUtils.makeInt( 739 KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, 740 AndroidKeyStoreAuthenticatedAESCipherSpi.GCM 741 .MIN_SUPPORTED_TAG_LENGTH_BITS 742 )); 743 } 744 importArgs.add(KeyStore2ParameterUtils.makeEnum( 745 KeymasterDefs.KM_TAG_BLOCK_MODE, 746 keymasterBlockMode 747 )); 748 } 749 750 if (params.getSignaturePaddings().length > 0) { 751 throw new KeyStoreException("Signature paddings not supported for symmetric keys"); 752 } 753 754 for (String padding : params.getEncryptionPaddings()) { 755 importArgs.add(KeyStore2ParameterUtils.makeEnum( 756 KeymasterDefs.KM_TAG_PADDING, 757 KeyProperties.EncryptionPadding.toKeymaster(padding) 758 )); 759 } 760 761 KeyStore2ParameterUtils.addUserAuthArgs(importArgs, params); 762 763 if (params.getKeyValidityStart() != null) { 764 importArgs.add(KeyStore2ParameterUtils.makeDate( 765 KeymasterDefs.KM_TAG_ACTIVE_DATETIME, params.getKeyValidityStart() 766 )); 767 } 768 if (params.getKeyValidityForOriginationEnd() != null) { 769 importArgs.add(KeyStore2ParameterUtils.makeDate( 770 KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, 771 params.getKeyValidityForOriginationEnd() 772 )); 773 } 774 if (params.getKeyValidityForConsumptionEnd() != null) { 775 importArgs.add(KeyStore2ParameterUtils.makeDate( 776 KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, 777 params.getKeyValidityForConsumptionEnd() 778 )); 779 } 780 if (params.getMaxUsageCount() != KeyProperties.UNRESTRICTED_USAGE_COUNT) { 781 importArgs.add(KeyStore2ParameterUtils.makeInt( 782 KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT, 783 params.getMaxUsageCount() 784 )); 785 } 786 } catch (IllegalArgumentException | IllegalStateException e) { 787 throw new KeyStoreException(e); 788 } 789 790 int flags = 0; 791 if (params.isCriticalToDeviceEncryption()) { 792 flags |= IKeystoreSecurityLevel.KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING; 793 } 794 795 @SecurityLevel int securityLevel = params.isStrongBoxBacked() ? SecurityLevel.STRONGBOX : 796 SecurityLevel.TRUSTED_ENVIRONMENT; 797 798 try { 799 KeyStoreSecurityLevel securityLevelInterface = mKeyStore.getSecurityLevel( 800 securityLevel); 801 802 KeyDescriptor descriptor = makeKeyDescriptor(alias); 803 804 securityLevelInterface.importKey(descriptor, null /* TODO attestationKey */, 805 importArgs, flags, keyMaterial); 806 } catch (android.security.KeyStoreException e) { 807 throw new KeyStoreException("Failed to import secret key.", e); 808 } 809 } 810 setWrappedKeyEntry(String alias, WrappedKeyEntry entry, java.security.KeyStore.ProtectionParameter param)811 private void setWrappedKeyEntry(String alias, WrappedKeyEntry entry, 812 java.security.KeyStore.ProtectionParameter param) throws KeyStoreException { 813 if (param != null) { 814 throw new KeyStoreException("Protection parameters are specified inside wrapped keys"); 815 } 816 817 byte[] maskingKey = new byte[32]; 818 819 String[] parts = entry.getTransformation().split("/"); 820 821 List<KeyParameter> args = new ArrayList<>(); 822 823 String algorithm = parts[0]; 824 if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) { 825 args.add(KeyStore2ParameterUtils.makeEnum( 826 KeymasterDefs.KM_TAG_ALGORITHM, 827 KeymasterDefs.KM_ALGORITHM_RSA 828 )); 829 } else { 830 throw new KeyStoreException("Algorithm \"" + algorithm + "\" not supported for " 831 + "wrapping. Only RSA wrapping keys are supported."); 832 } 833 834 if (parts.length > 1) { 835 String mode = parts[1]; 836 args.add(KeyStore2ParameterUtils.makeEnum( 837 KeymasterDefs.KM_TAG_BLOCK_MODE, 838 KeyProperties.BlockMode.toKeymaster(mode) 839 )); 840 } 841 842 if (parts.length > 2) { 843 @KeyProperties.EncryptionPaddingEnum int padding = 844 KeyProperties.EncryptionPadding.toKeymaster(parts[2]); 845 if (padding != KeymasterDefs.KM_PAD_NONE) { 846 args.add(KeyStore2ParameterUtils.makeEnum( 847 KeymasterDefs.KM_TAG_PADDING, 848 padding 849 )); 850 } 851 } 852 853 KeyGenParameterSpec spec = (KeyGenParameterSpec) entry.getAlgorithmParameterSpec(); 854 if (spec.isDigestsSpecified()) { 855 @KeyProperties.DigestEnum int digest = 856 KeyProperties.Digest.toKeymaster(spec.getDigests()[0]); 857 if (digest != KeymasterDefs.KM_DIGEST_NONE) { 858 args.add(KeyStore2ParameterUtils.makeEnum( 859 KeymasterDefs.KM_TAG_DIGEST, 860 digest 861 )); 862 } 863 } 864 865 KeyDescriptor wrappingkey = makeKeyDescriptor(entry.getWrappingKeyAlias()); 866 867 KeyEntryResponse response = null; 868 try { 869 response = mKeyStore.getKeyEntry(wrappingkey); 870 } catch (android.security.KeyStoreException e) { 871 throw new KeyStoreException("Failed to import wrapped key. Keystore error code: " 872 + e.getErrorCode(), e); 873 } 874 875 KeyDescriptor wrappedKey = makeKeyDescriptor(alias); 876 877 KeyStoreSecurityLevel securityLevel = new KeyStoreSecurityLevel(response.iSecurityLevel); 878 879 final BiometricManager bm = android.app.AppGlobals.getInitialApplication() 880 .getSystemService(BiometricManager.class); 881 882 long[] biometricSids = bm.getAuthenticatorIds(); 883 884 List<AuthenticatorSpec> authenticatorSpecs = new ArrayList<>(); 885 886 AuthenticatorSpec authenticatorSpec = new AuthenticatorSpec(); 887 authenticatorSpec.authenticatorType = HardwareAuthenticatorType.PASSWORD; 888 authenticatorSpec.authenticatorId = GateKeeper.getSecureUserId(); 889 authenticatorSpecs.add(authenticatorSpec); 890 891 for (long sid : biometricSids) { 892 AuthenticatorSpec authSpec = new AuthenticatorSpec(); 893 authSpec.authenticatorType = HardwareAuthenticatorType.FINGERPRINT; 894 authSpec.authenticatorId = sid; 895 authenticatorSpecs.add(authSpec); 896 } 897 898 try { 899 securityLevel.importWrappedKey( 900 wrappedKey, wrappingkey, 901 entry.getWrappedKeyBytes(), 902 null /* masking key is set to 32 bytes if null is given here */, 903 args, 904 authenticatorSpecs.toArray(new AuthenticatorSpec[0])); 905 } catch (android.security.KeyStoreException e) { 906 switch (e.getErrorCode()) { 907 case KeymasterDefs.KM_ERROR_UNIMPLEMENTED: { 908 throw new SecureKeyImportUnavailableException("Could not import wrapped key"); 909 } 910 default: 911 throw new KeyStoreException("Failed to import wrapped key. Keystore error " 912 + "code: " + e.getErrorCode(), e); 913 } 914 } 915 } 916 917 @Override engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain)918 public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain) 919 throws KeyStoreException { 920 throw new KeyStoreException("Operation not supported because key encoding is unknown"); 921 } 922 923 /** 924 * This function sets a trusted certificate entry. It fails if the given 925 * alias is already taken by an actual key entry. However, if the entry is a 926 * trusted certificate it will get silently replaced. 927 * @param alias the alias name 928 * @param cert the certificate 929 * 930 * @throws KeyStoreException if the alias is already taken by a secret or private 931 * key entry. 932 * @throws KeyStoreException with a nested {@link CertificateEncodingException} 933 * if the {@code cert.getEncoded()} throws. 934 * @throws KeyStoreException with a nested {@link android.security.KeyStoreException} if 935 * something went wrong while inserting the certificate into keystore. 936 * @throws NullPointerException if cert or alias is null. 937 * 938 * @hide 939 */ 940 @Override engineSetCertificateEntry(String alias, Certificate cert)941 public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException { 942 if (isKeyEntry(alias)) { 943 throw new KeyStoreException("Entry exists and is not a trusted certificate"); 944 } 945 946 // We can't set something to null. 947 if (cert == null) { 948 throw new NullPointerException("cert == null"); 949 } 950 951 final byte[] encoded; 952 try { 953 encoded = cert.getEncoded(); 954 } catch (CertificateEncodingException e) { 955 throw new KeyStoreException(e); 956 } 957 958 try { 959 mKeyStore.updateSubcomponents(makeKeyDescriptor(alias), 960 null /* publicCert - unused when used as pure certificate store. */, 961 encoded); 962 } catch (android.security.KeyStoreException e) { 963 throw new KeyStoreException("Couldn't insert certificate.", e); 964 } 965 } 966 967 @Override engineDeleteEntry(String alias)968 public void engineDeleteEntry(String alias) throws KeyStoreException { 969 KeyDescriptor descriptor = makeKeyDescriptor(alias); 970 try { 971 mKeyStore.deleteKey(descriptor); 972 } catch (android.security.KeyStoreException e) { 973 if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) { 974 throw new KeyStoreException("Failed to delete entry: " + alias, e); 975 } 976 } 977 } 978 getUniqueAliases()979 private Set<String> getUniqueAliases() { 980 try { 981 final KeyDescriptor[] keys = mKeyStore.list( 982 getTargetDomain(), 983 mNamespace 984 ); 985 final Set<String> aliases = new HashSet<>(keys.length); 986 for (KeyDescriptor d : keys) { 987 aliases.add(d.alias); 988 } 989 return aliases; 990 } catch (android.security.KeyStoreException e) { 991 Log.e(TAG, "Failed to list keystore entries.", e); 992 return new HashSet<>(); 993 } 994 } 995 996 @Override engineAliases()997 public Enumeration<String> engineAliases() { 998 return Collections.enumeration(getUniqueAliases()); 999 } 1000 1001 @Override engineContainsAlias(String alias)1002 public boolean engineContainsAlias(String alias) { 1003 if (alias == null) { 1004 throw new NullPointerException("alias == null"); 1005 } 1006 1007 return getKeyMetadata(alias) != null; 1008 } 1009 1010 @Override engineSize()1011 public int engineSize() { 1012 return getUniqueAliases().size(); 1013 } 1014 1015 @Override engineIsKeyEntry(String alias)1016 public boolean engineIsKeyEntry(String alias) { 1017 return isKeyEntry(alias); 1018 } 1019 isKeyEntry(String alias)1020 private boolean isKeyEntry(String alias) { 1021 if (alias == null) { 1022 throw new NullPointerException("alias == null"); 1023 } 1024 1025 KeyEntryResponse response = getKeyMetadata(alias); 1026 // If response is null, there is no such entry. 1027 // If response.iSecurityLevel is null, there is no private or secret key material stored. 1028 return response != null && response.iSecurityLevel != null; 1029 } 1030 1031 1032 @Override engineIsCertificateEntry(String alias)1033 public boolean engineIsCertificateEntry(String alias) { 1034 if (alias == null) { 1035 throw new NullPointerException("alias == null"); 1036 } 1037 KeyEntryResponse response = getKeyMetadata(alias); 1038 // If response == null there is no such entry. 1039 // If there is no certificateChain, then this is not a certificate entry. 1040 // If there is a private key entry, this is the certificate chain for that 1041 // key entry and not a CA certificate entry. 1042 return response != null 1043 && response.metadata.certificateChain != null 1044 && response.iSecurityLevel == null; 1045 } 1046 1047 @Override engineGetCertificateAlias(Certificate cert)1048 public String engineGetCertificateAlias(Certificate cert) { 1049 if (cert == null) { 1050 return null; 1051 } 1052 if (!"X.509".equalsIgnoreCase(cert.getType())) { 1053 Log.e(TAG, "In engineGetCertificateAlias: only X.509 certificates are supported."); 1054 return null; 1055 } 1056 byte[] targetCertBytes; 1057 try { 1058 targetCertBytes = cert.getEncoded(); 1059 } catch (CertificateEncodingException e) { 1060 Log.e(TAG, "While trying to get the alias for a certificate.", e); 1061 return null; 1062 } 1063 if (targetCertBytes == null) { 1064 return null; 1065 } 1066 1067 KeyDescriptor[] keyDescriptors = null; 1068 try { 1069 keyDescriptors = mKeyStore.list( 1070 getTargetDomain(), 1071 mNamespace 1072 ); 1073 } catch (android.security.KeyStoreException e) { 1074 Log.w(TAG, "Failed to get list of keystore entries.", e); 1075 } 1076 1077 String caAlias = null; 1078 for (KeyDescriptor d : keyDescriptors) { 1079 KeyEntryResponse response = getKeyMetadata(d.alias); 1080 if (response == null) { 1081 continue; 1082 } 1083 /* 1084 * The KeyStoreSpi documentation says to only compare the first certificate in the 1085 * chain which is equivalent to the {@code response.metadata.certificate} field. 1086 * So we look for a hit in this field first. For pure CA certificate entries, 1087 * we check the {@code response.metadata.certificateChain} field. But we only 1088 * return a CA alias if there was no hit in the certificate field of any other 1089 * entry. 1090 */ 1091 if (response.metadata.certificate != null) { 1092 if (Arrays.equals(response.metadata.certificate, targetCertBytes)) { 1093 return d.alias; 1094 } 1095 } else if (response.metadata.certificateChain != null && caAlias == null) { 1096 if (Arrays.equals(response.metadata.certificateChain, targetCertBytes)) { 1097 caAlias = d.alias; 1098 } 1099 } 1100 } 1101 return caAlias; 1102 } 1103 1104 /** 1105 * Used by Tests to initialize with a fake KeyStore2. 1106 * @hide 1107 * @param keystore 1108 */ 1109 @VisibleForTesting initForTesting(KeyStore2 keystore)1110 public void initForTesting(KeyStore2 keystore) { 1111 mKeyStore = keystore; 1112 mNamespace = KeyProperties.NAMESPACE_APPLICATION; 1113 } 1114 1115 @Override engineStore(OutputStream stream, char[] password)1116 public void engineStore(OutputStream stream, char[] password) throws IOException, 1117 NoSuchAlgorithmException, CertificateException { 1118 throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream"); 1119 } 1120 1121 @Override engineLoad(InputStream stream, char[] password)1122 public void engineLoad(InputStream stream, char[] password) throws IOException, 1123 NoSuchAlgorithmException, CertificateException { 1124 if (stream != null) { 1125 throw new IllegalArgumentException("InputStream not supported"); 1126 } 1127 1128 if (password != null) { 1129 throw new IllegalArgumentException("password not supported"); 1130 } 1131 1132 // Unfortunate name collision. 1133 mKeyStore = KeyStore2.getInstance(); 1134 mNamespace = KeyProperties.NAMESPACE_APPLICATION; 1135 } 1136 1137 @Override engineLoad(LoadStoreParameter param)1138 public void engineLoad(LoadStoreParameter param) throws IOException, 1139 NoSuchAlgorithmException, CertificateException { 1140 @KeyProperties.Namespace int namespace = KeyProperties.NAMESPACE_APPLICATION; 1141 if (param != null) { 1142 if (param instanceof AndroidKeyStoreLoadStoreParameter) { 1143 namespace = ((AndroidKeyStoreLoadStoreParameter) param).getNamespace(); 1144 } else { 1145 throw new IllegalArgumentException( 1146 "Unsupported param type: " + param.getClass()); 1147 } 1148 } 1149 mKeyStore = KeyStore2.getInstance(); 1150 mNamespace = namespace; 1151 } 1152 1153 @Override engineSetEntry(String alias, Entry entry, ProtectionParameter param)1154 public void engineSetEntry(String alias, Entry entry, ProtectionParameter param) 1155 throws KeyStoreException { 1156 if (entry == null) { 1157 throw new KeyStoreException("entry == null"); 1158 } 1159 1160 if (entry instanceof java.security.KeyStore.TrustedCertificateEntry) { 1161 java.security.KeyStore.TrustedCertificateEntry trE = 1162 (java.security.KeyStore.TrustedCertificateEntry) entry; 1163 // engineSetCertificateEntry does not overwrite if the existing entry 1164 // is a key entry, but the semantic of engineSetEntry is such that it 1165 // overwrites any existing entry. Thus we delete any possible existing 1166 // entry by this alias. 1167 engineDeleteEntry(alias); 1168 engineSetCertificateEntry(alias, trE.getTrustedCertificate()); 1169 return; 1170 } 1171 1172 if (entry instanceof PrivateKeyEntry) { 1173 PrivateKeyEntry prE = (PrivateKeyEntry) entry; 1174 setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(), param); 1175 } else if (entry instanceof SecretKeyEntry) { 1176 SecretKeyEntry secE = (SecretKeyEntry) entry; 1177 setSecretKeyEntry(alias, secE.getSecretKey(), param); 1178 } else if (entry instanceof WrappedKeyEntry) { 1179 WrappedKeyEntry wke = (WrappedKeyEntry) entry; 1180 setWrappedKeyEntry(alias, wke, param); 1181 } else { 1182 throw new KeyStoreException( 1183 "Entry must be a PrivateKeyEntry, SecretKeyEntry, WrappedKeyEntry " 1184 + "or TrustedCertificateEntry; was " + entry); 1185 } 1186 } 1187 } 1188