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 != 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 @SecurityLevel int securityLevel = params.isStrongBoxBacked() ? SecurityLevel.STRONGBOX : 605 SecurityLevel.TRUSTED_ENVIRONMENT; 606 @Domain int targetDomain = (getTargetDomain()); 607 608 if (key instanceof AndroidKeyStoreSecretKey) { 609 String keyAliasInKeystore = 610 ((AndroidKeyStoreSecretKey) key).getUserKeyDescriptor().alias; 611 612 KeyDescriptor descriptor = ((AndroidKeyStoreSecretKey) key).getUserKeyDescriptor(); 613 614 // This throws if the request cannot replace the existing key. 615 assertCanReplace(alias, targetDomain, mNamespace, descriptor); 616 617 // This is the entry where this key is already stored. No need to do anything. 618 if (params != null) { 619 throw new KeyStoreException("Modifying KeyStore-backed key using protection" 620 + " parameters not supported"); 621 } 622 return; 623 } 624 625 if (params == null) { 626 throw new KeyStoreException( 627 "Protection parameters must be specified when importing a symmetric key"); 628 } 629 630 // Not a KeyStore-backed secret key -- import its key material into keystore. 631 String keyExportFormat = key.getFormat(); 632 if (keyExportFormat == null) { 633 throw new KeyStoreException( 634 "Only secret keys that export their key material are supported"); 635 } else if (!"RAW".equals(keyExportFormat)) { 636 throw new KeyStoreException( 637 "Unsupported secret key material export format: " + keyExportFormat); 638 } 639 byte[] keyMaterial = key.getEncoded(); 640 if (keyMaterial == null) { 641 throw new KeyStoreException("Key did not export its key material despite supporting" 642 + " RAW format export"); 643 } 644 645 final List<KeyParameter> importArgs = new ArrayList<>(); 646 647 try { 648 int keymasterAlgorithm = 649 KeyProperties.KeyAlgorithm.toKeymasterSecretKeyAlgorithm( 650 key.getAlgorithm()); 651 652 importArgs.add(KeyStore2ParameterUtils.makeEnum( 653 KeymasterDefs.KM_TAG_ALGORITHM, 654 keymasterAlgorithm 655 )); 656 657 if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { 658 // JCA HMAC key algorithm implies a digest (e.g., HmacSHA256 key algorithm 659 // implies SHA-256 digest). Because keymaster HMAC key is authorized only for one 660 // digest, we don't let import parameters override the digest implied by the key. 661 // If the parameters specify digests at all, they must specify only one digest, the 662 // only implied by key algorithm. 663 int keymasterImpliedDigest = 664 KeyProperties.KeyAlgorithm.toKeymasterDigest(key.getAlgorithm()); 665 if (keymasterImpliedDigest == -1) { 666 throw new ProviderException( 667 "HMAC key algorithm digest unknown for key algorithm " 668 + key.getAlgorithm()); 669 } 670 671 if (params.isDigestsSpecified()) { 672 // Digest(s) explicitly specified in params -- check that the list consists of 673 // exactly one digest, the one implied by key algorithm. 674 int[] keymasterDigestsFromParams = 675 KeyProperties.Digest.allToKeymaster(params.getDigests()); 676 if ((keymasterDigestsFromParams.length != 1) 677 || (keymasterDigestsFromParams[0] != keymasterImpliedDigest)) { 678 throw new KeyStoreException( 679 "Unsupported digests specification: " 680 + Arrays.asList(params.getDigests()) + ". Only " 681 + KeyProperties.Digest.fromKeymaster(keymasterImpliedDigest) 682 + " supported for HMAC key algorithm " 683 + key.getAlgorithm()); 684 } 685 } 686 int outputBits = KeymasterUtils.getDigestOutputSizeBits(keymasterImpliedDigest); 687 if (outputBits == -1) { 688 throw new ProviderException( 689 "HMAC key authorized for unsupported digest: " 690 + KeyProperties.Digest.fromKeymaster(keymasterImpliedDigest)); 691 } 692 importArgs.add(KeyStore2ParameterUtils.makeEnum( 693 KeymasterDefs.KM_TAG_DIGEST, keymasterImpliedDigest 694 )); 695 importArgs.add(KeyStore2ParameterUtils.makeInt( 696 KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, outputBits 697 )); 698 } else { 699 if (params.isDigestsSpecified()) { 700 for (String digest : params.getDigests()) { 701 importArgs.add(KeyStore2ParameterUtils.makeEnum( 702 KeymasterDefs.KM_TAG_DIGEST, 703 KeyProperties.Digest.toKeymaster(digest) 704 )); 705 } 706 } 707 } 708 709 KeyStore2ParameterUtils.forEachSetFlag(params.getPurposes(), (purpose) -> { 710 importArgs.add(KeyStore2ParameterUtils.makeEnum( 711 KeymasterDefs.KM_TAG_PURPOSE, 712 KeyProperties.Purpose.toKeymaster(purpose) 713 )); 714 }); 715 716 boolean indCpa = false; 717 if ((params.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) { 718 if (((KeyProtection) param).isRandomizedEncryptionRequired()) { 719 indCpa = true; 720 } else { 721 importArgs.add(KeyStore2ParameterUtils.makeBool( 722 KeymasterDefs.KM_TAG_CALLER_NONCE 723 )); 724 } 725 } 726 727 for (String blockMode : params.getBlockModes()) { 728 int keymasterBlockMode = KeyProperties.BlockMode.toKeymaster(blockMode); 729 if (indCpa 730 && !KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto( 731 keymasterBlockMode)) { 732 throw new KeyStoreException( 733 "Randomized encryption (IND-CPA) required but may be violated by" 734 + " block mode: " + blockMode 735 + ". See KeyProtection documentation."); 736 737 } 738 if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES 739 && keymasterBlockMode == KeymasterDefs.KM_MODE_GCM) { 740 importArgs.add(KeyStore2ParameterUtils.makeInt( 741 KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, 742 AndroidKeyStoreAuthenticatedAESCipherSpi.GCM 743 .MIN_SUPPORTED_TAG_LENGTH_BITS 744 )); 745 } 746 importArgs.add(KeyStore2ParameterUtils.makeEnum( 747 KeymasterDefs.KM_TAG_BLOCK_MODE, 748 keymasterBlockMode 749 )); 750 } 751 752 if (params.getSignaturePaddings().length > 0) { 753 throw new KeyStoreException("Signature paddings not supported for symmetric keys"); 754 } 755 756 for (String padding : params.getEncryptionPaddings()) { 757 importArgs.add(KeyStore2ParameterUtils.makeEnum( 758 KeymasterDefs.KM_TAG_PADDING, 759 KeyProperties.EncryptionPadding.toKeymaster(padding) 760 )); 761 } 762 763 KeyStore2ParameterUtils.addUserAuthArgs(importArgs, params); 764 765 if (params.getKeyValidityStart() != null) { 766 importArgs.add(KeyStore2ParameterUtils.makeDate( 767 KeymasterDefs.KM_TAG_ACTIVE_DATETIME, params.getKeyValidityStart() 768 )); 769 } 770 if (params.getKeyValidityForOriginationEnd() != null) { 771 importArgs.add(KeyStore2ParameterUtils.makeDate( 772 KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, 773 params.getKeyValidityForOriginationEnd() 774 )); 775 } 776 if (params.getKeyValidityForConsumptionEnd() != null) { 777 importArgs.add(KeyStore2ParameterUtils.makeDate( 778 KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, 779 params.getKeyValidityForConsumptionEnd() 780 )); 781 } 782 if (params.getMaxUsageCount() != KeyProperties.UNRESTRICTED_USAGE_COUNT) { 783 importArgs.add(KeyStore2ParameterUtils.makeInt( 784 KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT, 785 params.getMaxUsageCount() 786 )); 787 } 788 } catch (IllegalArgumentException | IllegalStateException e) { 789 throw new KeyStoreException(e); 790 } 791 792 int flags = 0; 793 if (params.isCriticalToDeviceEncryption()) { 794 flags |= IKeystoreSecurityLevel.KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING; 795 } 796 797 try { 798 KeyStoreSecurityLevel securityLevelInterface = mKeyStore.getSecurityLevel( 799 securityLevel); 800 801 KeyDescriptor descriptor = makeKeyDescriptor(alias); 802 803 securityLevelInterface.importKey(descriptor, null /* TODO attestationKey */, 804 importArgs, flags, keyMaterial); 805 } catch (android.security.KeyStoreException e) { 806 throw new KeyStoreException("Failed to import secret key.", e); 807 } 808 } 809 setWrappedKeyEntry(String alias, WrappedKeyEntry entry, java.security.KeyStore.ProtectionParameter param)810 private void setWrappedKeyEntry(String alias, WrappedKeyEntry entry, 811 java.security.KeyStore.ProtectionParameter param) throws KeyStoreException { 812 if (param != null) { 813 throw new KeyStoreException("Protection parameters are specified inside wrapped keys"); 814 } 815 816 byte[] maskingKey = new byte[32]; 817 818 String[] parts = entry.getTransformation().split("/"); 819 820 List<KeyParameter> args = new ArrayList<>(); 821 822 String algorithm = parts[0]; 823 if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) { 824 args.add(KeyStore2ParameterUtils.makeEnum( 825 KeymasterDefs.KM_TAG_ALGORITHM, 826 KeymasterDefs.KM_ALGORITHM_RSA 827 )); 828 } else { 829 throw new KeyStoreException("Algorithm \"" + algorithm + "\" not supported for " 830 + "wrapping. Only RSA wrapping keys are supported."); 831 } 832 833 if (parts.length > 1) { 834 String mode = parts[1]; 835 args.add(KeyStore2ParameterUtils.makeEnum( 836 KeymasterDefs.KM_TAG_BLOCK_MODE, 837 KeyProperties.BlockMode.toKeymaster(mode) 838 )); 839 } 840 841 if (parts.length > 2) { 842 @KeyProperties.EncryptionPaddingEnum int padding = 843 KeyProperties.EncryptionPadding.toKeymaster(parts[2]); 844 if (padding != KeymasterDefs.KM_PAD_NONE) { 845 args.add(KeyStore2ParameterUtils.makeEnum( 846 KeymasterDefs.KM_TAG_PADDING, 847 padding 848 )); 849 } 850 } 851 852 KeyGenParameterSpec spec = (KeyGenParameterSpec) entry.getAlgorithmParameterSpec(); 853 if (spec.isDigestsSpecified()) { 854 @KeyProperties.DigestEnum int digest = 855 KeyProperties.Digest.toKeymaster(spec.getDigests()[0]); 856 if (digest != KeymasterDefs.KM_DIGEST_NONE) { 857 args.add(KeyStore2ParameterUtils.makeEnum( 858 KeymasterDefs.KM_TAG_DIGEST, 859 digest 860 )); 861 } 862 } 863 864 KeyDescriptor wrappingkey = makeKeyDescriptor(entry.getWrappingKeyAlias()); 865 866 KeyEntryResponse response = null; 867 try { 868 response = mKeyStore.getKeyEntry(wrappingkey); 869 } catch (android.security.KeyStoreException e) { 870 throw new KeyStoreException("Failed to import wrapped key. Keystore error code: " 871 + e.getErrorCode(), e); 872 } 873 874 KeyDescriptor wrappedKey = makeKeyDescriptor(alias); 875 876 KeyStoreSecurityLevel securityLevel = new KeyStoreSecurityLevel(response.iSecurityLevel); 877 878 final BiometricManager bm = android.app.AppGlobals.getInitialApplication() 879 .getSystemService(BiometricManager.class); 880 881 long[] biometricSids = bm.getAuthenticatorIds(); 882 883 List<AuthenticatorSpec> authenticatorSpecs = new ArrayList<>(); 884 885 AuthenticatorSpec authenticatorSpec = new AuthenticatorSpec(); 886 authenticatorSpec.authenticatorType = HardwareAuthenticatorType.PASSWORD; 887 authenticatorSpec.authenticatorId = GateKeeper.getSecureUserId(); 888 authenticatorSpecs.add(authenticatorSpec); 889 890 for (long sid : biometricSids) { 891 AuthenticatorSpec authSpec = new AuthenticatorSpec(); 892 authSpec.authenticatorType = HardwareAuthenticatorType.FINGERPRINT; 893 authSpec.authenticatorId = sid; 894 authenticatorSpecs.add(authSpec); 895 } 896 897 try { 898 securityLevel.importWrappedKey( 899 wrappedKey, wrappingkey, 900 entry.getWrappedKeyBytes(), 901 null /* masking key is set to 32 bytes if null is given here */, 902 args, 903 authenticatorSpecs.toArray(new AuthenticatorSpec[0])); 904 } catch (android.security.KeyStoreException e) { 905 switch (e.getErrorCode()) { 906 case KeymasterDefs.KM_ERROR_UNIMPLEMENTED: { 907 throw new SecureKeyImportUnavailableException("Could not import wrapped key"); 908 } 909 default: 910 throw new KeyStoreException("Failed to import wrapped key. Keystore error " 911 + "code: " + e.getErrorCode(), e); 912 } 913 } 914 } 915 916 @Override engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain)917 public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain) 918 throws KeyStoreException { 919 throw new KeyStoreException("Operation not supported because key encoding is unknown"); 920 } 921 922 /** 923 * This function sets a trusted certificate entry. It fails if the given 924 * alias is already taken by an actual key entry. However, if the entry is a 925 * trusted certificate it will get silently replaced. 926 * @param alias the alias name 927 * @param cert the certificate 928 * 929 * @throws KeyStoreException if the alias is already taken by a secret or private 930 * key entry. 931 * @throws KeyStoreException with a nested {@link CertificateEncodingException} 932 * if the {@code cert.getEncoded()} throws. 933 * @throws KeyStoreException with a nested {@link android.security.KeyStoreException} if 934 * something went wrong while inserting the certificate into keystore. 935 * @throws NullPointerException if cert or alias is null. 936 * 937 * @hide 938 */ 939 @Override engineSetCertificateEntry(String alias, Certificate cert)940 public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException { 941 if (isKeyEntry(alias)) { 942 throw new KeyStoreException("Entry exists and is not a trusted certificate"); 943 } 944 945 // We can't set something to null. 946 if (cert == null) { 947 throw new NullPointerException("cert == null"); 948 } 949 950 final byte[] encoded; 951 try { 952 encoded = cert.getEncoded(); 953 } catch (CertificateEncodingException e) { 954 throw new KeyStoreException(e); 955 } 956 957 try { 958 mKeyStore.updateSubcomponents(makeKeyDescriptor(alias), 959 null /* publicCert - unused when used as pure certificate store. */, 960 encoded); 961 } catch (android.security.KeyStoreException e) { 962 throw new KeyStoreException("Couldn't insert certificate.", e); 963 } 964 } 965 966 @Override engineDeleteEntry(String alias)967 public void engineDeleteEntry(String alias) throws KeyStoreException { 968 KeyDescriptor descriptor = makeKeyDescriptor(alias); 969 try { 970 mKeyStore.deleteKey(descriptor); 971 } catch (android.security.KeyStoreException e) { 972 if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) { 973 throw new KeyStoreException("Failed to delete entry: " + alias, e); 974 } 975 } 976 } 977 getUniqueAliases()978 private Set<String> getUniqueAliases() { 979 try { 980 final KeyDescriptor[] keys = mKeyStore.list( 981 getTargetDomain(), 982 mNamespace 983 ); 984 final Set<String> aliases = new HashSet<>(keys.length); 985 for (KeyDescriptor d : keys) { 986 aliases.add(d.alias); 987 } 988 return aliases; 989 } catch (android.security.KeyStoreException e) { 990 Log.e(TAG, "Failed to list keystore entries.", e); 991 return new HashSet<>(); 992 } 993 } 994 995 @Override engineAliases()996 public Enumeration<String> engineAliases() { 997 return Collections.enumeration(getUniqueAliases()); 998 } 999 1000 @Override engineContainsAlias(String alias)1001 public boolean engineContainsAlias(String alias) { 1002 if (alias == null) { 1003 throw new NullPointerException("alias == null"); 1004 } 1005 1006 return getKeyMetadata(alias) != null; 1007 } 1008 1009 @Override engineSize()1010 public int engineSize() { 1011 return getUniqueAliases().size(); 1012 } 1013 1014 @Override engineIsKeyEntry(String alias)1015 public boolean engineIsKeyEntry(String alias) { 1016 return isKeyEntry(alias); 1017 } 1018 isKeyEntry(String alias)1019 private boolean isKeyEntry(String alias) { 1020 if (alias == null) { 1021 throw new NullPointerException("alias == null"); 1022 } 1023 1024 KeyEntryResponse response = getKeyMetadata(alias); 1025 // If response is null, there is no such entry. 1026 // If response.iSecurityLevel is null, there is no private or secret key material stored. 1027 return response != null && response.iSecurityLevel != null; 1028 } 1029 1030 1031 @Override engineIsCertificateEntry(String alias)1032 public boolean engineIsCertificateEntry(String alias) { 1033 if (alias == null) { 1034 throw new NullPointerException("alias == null"); 1035 } 1036 KeyEntryResponse response = getKeyMetadata(alias); 1037 // If response == null there is no such entry. 1038 // If there is no certificateChain, then this is not a certificate entry. 1039 // If there is a private key entry, this is the certificate chain for that 1040 // key entry and not a CA certificate entry. 1041 return response != null 1042 && response.metadata.certificateChain != null 1043 && response.iSecurityLevel == null; 1044 } 1045 1046 @Override engineGetCertificateAlias(Certificate cert)1047 public String engineGetCertificateAlias(Certificate cert) { 1048 if (cert == null) { 1049 return null; 1050 } 1051 if (!"X.509".equalsIgnoreCase(cert.getType())) { 1052 Log.e(TAG, "In engineGetCertificateAlias: only X.509 certificates are supported."); 1053 return null; 1054 } 1055 byte[] targetCertBytes; 1056 try { 1057 targetCertBytes = cert.getEncoded(); 1058 } catch (CertificateEncodingException e) { 1059 Log.e(TAG, "While trying to get the alias for a certificate.", e); 1060 return null; 1061 } 1062 if (targetCertBytes == null) { 1063 return null; 1064 } 1065 1066 KeyDescriptor[] keyDescriptors = null; 1067 try { 1068 keyDescriptors = mKeyStore.list( 1069 getTargetDomain(), 1070 mNamespace 1071 ); 1072 } catch (android.security.KeyStoreException e) { 1073 Log.w(TAG, "Failed to get list of keystore entries.", e); 1074 } 1075 1076 String caAlias = null; 1077 for (KeyDescriptor d : keyDescriptors) { 1078 KeyEntryResponse response = getKeyMetadata(d.alias); 1079 if (response == null) { 1080 continue; 1081 } 1082 /* 1083 * The KeyStoreSpi documentation says to only compare the first certificate in the 1084 * chain which is equivalent to the {@code response.metadata.certificate} field. 1085 * So we look for a hit in this field first. For pure CA certificate entries, 1086 * we check the {@code response.metadata.certificateChain} field. But we only 1087 * return a CA alias if there was no hit in the certificate field of any other 1088 * entry. 1089 */ 1090 if (response.metadata.certificate != null) { 1091 if (Arrays.equals(response.metadata.certificate, targetCertBytes)) { 1092 return d.alias; 1093 } 1094 } else if (response.metadata.certificateChain != null && caAlias == null) { 1095 if (Arrays.equals(response.metadata.certificateChain, targetCertBytes)) { 1096 caAlias = d.alias; 1097 } 1098 } 1099 } 1100 return caAlias; 1101 } 1102 1103 /** 1104 * Used by Tests to initialize with a fake KeyStore2. 1105 * @hide 1106 * @param keystore 1107 */ 1108 @VisibleForTesting initForTesting(KeyStore2 keystore)1109 public void initForTesting(KeyStore2 keystore) { 1110 mKeyStore = keystore; 1111 mNamespace = KeyProperties.NAMESPACE_APPLICATION; 1112 } 1113 1114 @Override engineStore(OutputStream stream, char[] password)1115 public void engineStore(OutputStream stream, char[] password) throws IOException, 1116 NoSuchAlgorithmException, CertificateException { 1117 throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream"); 1118 } 1119 1120 @Override engineLoad(InputStream stream, char[] password)1121 public void engineLoad(InputStream stream, char[] password) throws IOException, 1122 NoSuchAlgorithmException, CertificateException { 1123 if (stream != null) { 1124 throw new IllegalArgumentException("InputStream not supported"); 1125 } 1126 1127 if (password != null) { 1128 throw new IllegalArgumentException("password not supported"); 1129 } 1130 1131 // Unfortunate name collision. 1132 mKeyStore = KeyStore2.getInstance(); 1133 mNamespace = KeyProperties.NAMESPACE_APPLICATION; 1134 } 1135 1136 @Override engineLoad(LoadStoreParameter param)1137 public void engineLoad(LoadStoreParameter param) throws IOException, 1138 NoSuchAlgorithmException, CertificateException { 1139 @KeyProperties.Namespace int namespace = KeyProperties.NAMESPACE_APPLICATION; 1140 if (param != null) { 1141 if (param instanceof AndroidKeyStoreLoadStoreParameter) { 1142 namespace = ((AndroidKeyStoreLoadStoreParameter) param).getNamespace(); 1143 } else { 1144 throw new IllegalArgumentException( 1145 "Unsupported param type: " + param.getClass()); 1146 } 1147 } 1148 mKeyStore = KeyStore2.getInstance(); 1149 mNamespace = namespace; 1150 } 1151 1152 @Override engineSetEntry(String alias, Entry entry, ProtectionParameter param)1153 public void engineSetEntry(String alias, Entry entry, ProtectionParameter param) 1154 throws KeyStoreException { 1155 if (entry == null) { 1156 throw new KeyStoreException("entry == null"); 1157 } 1158 1159 if (entry instanceof java.security.KeyStore.TrustedCertificateEntry) { 1160 java.security.KeyStore.TrustedCertificateEntry trE = 1161 (java.security.KeyStore.TrustedCertificateEntry) entry; 1162 // engineSetCertificateEntry does not overwrite if the existing entry 1163 // is a key entry, but the semantic of engineSetEntry is such that it 1164 // overwrites any existing entry. Thus we delete any possible existing 1165 // entry by this alias. 1166 engineDeleteEntry(alias); 1167 engineSetCertificateEntry(alias, trE.getTrustedCertificate()); 1168 return; 1169 } 1170 1171 if (entry instanceof PrivateKeyEntry) { 1172 PrivateKeyEntry prE = (PrivateKeyEntry) entry; 1173 setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(), param); 1174 } else if (entry instanceof SecretKeyEntry) { 1175 SecretKeyEntry secE = (SecretKeyEntry) entry; 1176 setSecretKeyEntry(alias, secE.getSecretKey(), param); 1177 } else if (entry instanceof WrappedKeyEntry) { 1178 WrappedKeyEntry wke = (WrappedKeyEntry) entry; 1179 setWrappedKeyEntry(alias, wke, param); 1180 } else { 1181 throw new KeyStoreException( 1182 "Entry must be a PrivateKeyEntry, SecretKeyEntry, WrappedKeyEntry " 1183 + "or TrustedCertificateEntry; was " + entry); 1184 } 1185 } 1186 } 1187