diff options
Diffstat (limited to 'keystore/java')
8 files changed, 913 insertions, 354 deletions
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index ad348f8..893771a 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -181,11 +181,15 @@ public class KeyStore { } public boolean put(String key, byte[] value, int uid, int flags) { + return insert(key, value, uid, flags) == NO_ERROR; + } + + public int insert(String key, byte[] value, int uid, int flags) { try { - return mBinder.insert(key, value, uid, flags) == NO_ERROR; + return mBinder.insert(key, value, uid, flags); } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); - return false; + return SYSTEM_ERROR; } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java index e555cc0..f37cf07 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java @@ -245,4 +245,12 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider { put("Signature." + algorithm + " SupportedKeyClasses", KEYSTORE_PRIVATE_KEY_CLASS_NAME + "|" + KEYSTORE_PUBLIC_KEY_CLASS_NAME); } + + public static String[] getSupportedEcdsaSignatureDigests() { + return new String[] {"NONE", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"}; + } + + public static String[] getSupportedRsaSignatureWithPkcs1PaddingDigests() { + return new String[] {"NONE", "MD5", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"}; + } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java index 4d6178f..688936c 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java @@ -179,11 +179,15 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes()); mKeymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster( spec.getEncryptionPaddings()); + if (spec.getSignaturePaddings().length > 0) { + throw new InvalidAlgorithmParameterException( + "Signature paddings not supported for symmetric key algorithms"); + } mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()); if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) && (spec.isRandomizedEncryptionRequired())) { for (int keymasterBlockMode : mKeymasterBlockModes) { - if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible( + if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto( keymasterBlockMode)) { throw new InvalidAlgorithmParameterException( "Randomized encryption (IND-CPA) required but may be violated" diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java index c5ea0f7..69155a8 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -16,17 +16,39 @@ package android.security.keystore; -import android.annotation.NonNull; +import android.annotation.Nullable; import android.security.Credentials; import android.security.KeyPairGeneratorSpec; import android.security.KeyStore; import android.security.keymaster.ExportResult; +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; +import com.android.org.bouncycastle.asn1.ASN1EncodableVector; +import com.android.org.bouncycastle.asn1.ASN1InputStream; +import com.android.org.bouncycastle.asn1.ASN1Integer; +import com.android.org.bouncycastle.asn1.ASN1ObjectIdentifier; +import com.android.org.bouncycastle.asn1.DERBitString; +import com.android.org.bouncycastle.asn1.DERInteger; +import com.android.org.bouncycastle.asn1.DERNull; +import com.android.org.bouncycastle.asn1.DERSequence; +import com.android.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import com.android.org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import com.android.org.bouncycastle.asn1.x509.Certificate; +import com.android.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import com.android.org.bouncycastle.asn1.x509.TBSCertificate; +import com.android.org.bouncycastle.asn1.x509.Time; +import com.android.org.bouncycastle.asn1.x509.V3TBSCertificateGenerator; +import com.android.org.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import com.android.org.bouncycastle.jce.X509Principal; +import com.android.org.bouncycastle.jce.provider.X509CertificateObject; import com.android.org.bouncycastle.x509.X509V3CertificateGenerator; -import com.android.org.conscrypt.NativeConstants; import com.android.org.conscrypt.OpenSSLEngine; +import libcore.util.EmptyArray; + +import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; @@ -41,10 +63,19 @@ import java.security.SecureRandom; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECGenParameterSpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAKeyGenParameterSpec; import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.Set; /** * Provides a way to create instances of a KeyPair which will be placed in the @@ -63,13 +94,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato public static class RSA extends AndroidKeyStoreKeyPairGeneratorSpi { public RSA() { - super(KeyProperties.KEY_ALGORITHM_RSA); + super(KeymasterDefs.KM_ALGORITHM_RSA); } } public static class EC extends AndroidKeyStoreKeyPairGeneratorSpi { public EC() { - super(KeyProperties.KEY_ALGORITHM_EC); + super(KeymasterDefs.KM_ALGORITHM_EC); } } @@ -87,39 +118,296 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato private static final int RSA_MIN_KEY_SIZE = 512; private static final int RSA_MAX_KEY_SIZE = 8192; - private final String mAlgorithm; + private static final Map<String, Integer> SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE = + new HashMap<String, Integer>(); + private static final List<String> SUPPORTED_EC_NIST_CURVE_NAMES = new ArrayList<String>(); + static { + // Aliases for NIST P-192 + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-192", 192); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp192r1", 192); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("prime192v1", 192); + + // Aliases for NIST P-224 + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-224", 224); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp224r1", 224); + + // Aliases for NIST P-256 + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-256", 256); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp256r1", 256); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("prime256v1", 256); + + // Aliases for NIST P-384 + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-384", 384); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp384r1", 384); + + // Aliases for NIST P-521 + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-521", 521); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp521r1", 521); + + SUPPORTED_EC_NIST_CURVE_NAMES.addAll(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.keySet()); + Collections.sort(SUPPORTED_EC_NIST_CURVE_NAMES); + } + private final int mOriginalKeymasterAlgorithm; private KeyStore mKeyStore; private KeyGenParameterSpec mSpec; + + private String mEntryAlias; private boolean mEncryptionAtRestRequired; - private @KeyProperties.KeyAlgorithmEnum String mKeyAlgorithm; - private int mKeyType; - private int mKeySize; + private @KeyProperties.KeyAlgorithmEnum String mJcaKeyAlgorithm; + private int mKeymasterAlgorithm = -1; + private int mKeySizeBits; + private SecureRandom mRng; + + private int[] mKeymasterPurposes; + private int[] mKeymasterBlockModes; + private int[] mKeymasterEncryptionPaddings; + private int[] mKeymasterSignaturePaddings; + private int[] mKeymasterDigests; - protected AndroidKeyStoreKeyPairGeneratorSpi(@KeyProperties.KeyAlgorithmEnum String algorithm) { - mAlgorithm = algorithm; + private long mRSAPublicExponent; + + protected AndroidKeyStoreKeyPairGeneratorSpi(int keymasterAlgorithm) { + mOriginalKeymasterAlgorithm = keymasterAlgorithm; } - @KeyProperties.KeyAlgorithmEnum String getAlgorithm() { - return mAlgorithm; + @Override + public void initialize(int keysize, SecureRandom random) { + throw new IllegalArgumentException( + KeyGenParameterSpec.class.getName() + " or " + KeyPairGeneratorSpec.class.getName() + + " required to initialize this KeyPairGenerator"); + } + + @Override + public void initialize(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + if (params == null) { + throw new InvalidAlgorithmParameterException( + "Must supply params of type " + KeyGenParameterSpec.class.getName() + + " or " + KeyPairGeneratorSpec.class.getName()); + } + + KeyGenParameterSpec spec; + boolean encryptionAtRestRequired = false; + int keymasterAlgorithm = mOriginalKeymasterAlgorithm; + if (params instanceof KeyGenParameterSpec) { + spec = (KeyGenParameterSpec) params; + } else if (params instanceof KeyPairGeneratorSpec) { + // Legacy/deprecated spec + KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params; + try { + KeyGenParameterSpec.Builder specBuilder; + String specKeyAlgorithm = legacySpec.getKeyType(); + if (specKeyAlgorithm != null) { + // Spec overrides the generator's default key algorithm + try { + keymasterAlgorithm = + KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm( + specKeyAlgorithm); + } catch (IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException( + "Invalid key type in parameters", e); + } + } + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + specBuilder = new KeyGenParameterSpec.Builder( + legacySpec.getKeystoreAlias(), + KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_MD5, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + break; + case KeymasterDefs.KM_ALGORITHM_RSA: + specBuilder = new KeyGenParameterSpec.Builder( + legacySpec.getKeystoreAlias(), + KeyProperties.PURPOSE_ENCRYPT + | KeyProperties.PURPOSE_DECRYPT + | KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_MD5, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + specBuilder.setSignaturePaddings( + KeyProperties.SIGNATURE_PADDING_RSA_PKCS1); + specBuilder.setEncryptionPaddings( + KeyProperties.ENCRYPTION_PADDING_NONE, + KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1); + // Disable randomized encryption requirement to support encryption + // padding NONE above. + specBuilder.setRandomizedEncryptionRequired(false); + break; + default: + throw new ProviderException( + "Unsupported algorithm: " + mKeymasterAlgorithm); + } + + if (legacySpec.getKeySize() != -1) { + specBuilder.setKeySize(legacySpec.getKeySize()); + } + if (legacySpec.getAlgorithmParameterSpec() != null) { + specBuilder.setAlgorithmParameterSpec( + legacySpec.getAlgorithmParameterSpec()); + } + specBuilder.setCertificateSubject(legacySpec.getSubjectDN()); + specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber()); + specBuilder.setCertificateNotBefore(legacySpec.getStartDate()); + specBuilder.setCertificateNotAfter(legacySpec.getEndDate()); + encryptionAtRestRequired = legacySpec.isEncryptionRequired(); + specBuilder.setUserAuthenticationRequired(false); + + spec = specBuilder.build(); + } catch (NullPointerException | IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException(e); + } + } else { + throw new InvalidAlgorithmParameterException( + "Unsupported params class: " + params.getClass().getName() + + ". Supported: " + KeyGenParameterSpec.class.getName() + + ", " + KeyPairGeneratorSpec.class.getName()); + } + + mEntryAlias = spec.getKeystoreAlias(); + mSpec = spec; + mKeymasterAlgorithm = keymasterAlgorithm; + mEncryptionAtRestRequired = encryptionAtRestRequired; + mKeySizeBits = spec.getKeySize(); + initAlgorithmSpecificParameters(); + if (mKeySizeBits == -1) { + mKeySizeBits = getDefaultKeySize(keymasterAlgorithm); + } + checkValidKeySize(keymasterAlgorithm, mKeySizeBits); + + if (spec.getKeystoreAlias() == null) { + throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided"); + } + + String jcaKeyAlgorithm; + try { + jcaKeyAlgorithm = KeyProperties.KeyAlgorithm.fromKeymasterAsymmetricKeyAlgorithm( + keymasterAlgorithm); + mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes()); + mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()); + mKeymasterEncryptionPaddings = KeyProperties.EncryptionPadding.allToKeymaster( + spec.getEncryptionPaddings()); + mKeymasterSignaturePaddings = KeyProperties.SignaturePadding.allToKeymaster( + spec.getSignaturePaddings()); + if (spec.isDigestsSpecified()) { + mKeymasterDigests = KeyProperties.Digest.allToKeymaster(spec.getDigests()); + } else { + mKeymasterDigests = EmptyArray.INT; + } + } catch (IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException(e); + } + + mJcaKeyAlgorithm = jcaKeyAlgorithm; + mRng = random; + mKeyStore = KeyStore.getInstance(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + private void resetAll() { + mEntryAlias = null; + mJcaKeyAlgorithm = null; + mKeymasterAlgorithm = -1; + mKeymasterPurposes = null; + mKeymasterBlockModes = null; + mKeymasterEncryptionPaddings = null; + mKeymasterSignaturePaddings = null; + mKeymasterDigests = null; + mKeySizeBits = 0; + mSpec = null; + mRSAPublicExponent = -1; + mEncryptionAtRestRequired = false; + mRng = null; + mKeyStore = null; + } + + private void initAlgorithmSpecificParameters() throws InvalidAlgorithmParameterException { + AlgorithmParameterSpec algSpecificSpec = mSpec.getAlgorithmParameterSpec(); + switch (mKeymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_RSA: + { + BigInteger publicExponent = null; + if (algSpecificSpec instanceof RSAKeyGenParameterSpec) { + RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) algSpecificSpec; + if (mKeySizeBits == -1) { + mKeySizeBits = rsaSpec.getKeysize(); + } else if (mKeySizeBits != rsaSpec.getKeysize()) { + throw new InvalidAlgorithmParameterException("RSA key size must match " + + " between " + mSpec + " and " + algSpecificSpec + + ": " + mKeySizeBits + " vs " + rsaSpec.getKeysize()); + } + publicExponent = rsaSpec.getPublicExponent(); + } else if (algSpecificSpec != null) { + throw new InvalidAlgorithmParameterException( + "RSA may only use RSAKeyGenParameterSpec"); + } + if (publicExponent == null) { + publicExponent = RSAKeyGenParameterSpec.F4; + } + if (publicExponent.compareTo(BigInteger.ZERO) < 1) { + throw new InvalidAlgorithmParameterException( + "RSA public exponent must be positive: " + publicExponent); + } + if (publicExponent.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) { + throw new InvalidAlgorithmParameterException( + "Unsupported RSA public exponent: " + publicExponent + + ". Only exponents <= " + Long.MAX_VALUE + " supported"); + } + mRSAPublicExponent = publicExponent.longValue(); + break; + } + case KeymasterDefs.KM_ALGORITHM_EC: + if (algSpecificSpec instanceof ECGenParameterSpec) { + ECGenParameterSpec ecSpec = (ECGenParameterSpec) algSpecificSpec; + String curveName = ecSpec.getName(); + Integer ecSpecKeySizeBits = SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.get( + curveName.toLowerCase(Locale.US)); + if (ecSpecKeySizeBits == null) { + throw new InvalidAlgorithmParameterException( + "Unsupported EC curve name: " + curveName + + ". Supported: " + SUPPORTED_EC_NIST_CURVE_NAMES); + } + if (mKeySizeBits == -1) { + mKeySizeBits = ecSpecKeySizeBits; + } else if (mKeySizeBits != ecSpecKeySizeBits) { + throw new InvalidAlgorithmParameterException("EC key size must match " + + " between " + mSpec + " and " + algSpecificSpec + + ": " + mKeySizeBits + " vs " + ecSpecKeySizeBits); + } + } else if (algSpecificSpec != null) { + throw new InvalidAlgorithmParameterException( + "EC may only use ECGenParameterSpec"); + } + break; + default: + throw new ProviderException("Unsupported algorithm: " + mKeymasterAlgorithm); + } } - /** - * Generate a KeyPair which is backed by the Android keystore service. You - * must call {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)} - * with an {@link KeyPairGeneratorSpec} as the {@code params} - * argument before calling this otherwise an {@code IllegalStateException} - * will be thrown. - * <p> - * This will create an entry in the Android keystore service with a - * self-signed certificate using the {@code params} specified in the - * {@code initialize(params)} call. - * - * @throws IllegalStateException when called before calling - * {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)} - * @see java.security.KeyPairGeneratorSpi#generateKeyPair() - */ @Override public KeyPair generateKeyPair() { if (mKeyStore == null || mSpec == null) { @@ -134,18 +422,65 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato + ", but the user has not yet entered the credential"); } - final String alias = mSpec.getKeystoreAlias(); - - byte[][] args = getArgsForKeyType(mKeyType, mSpec.getAlgorithmParameterSpec()); - - final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; + KeymasterArguments args = new KeymasterArguments(); + args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits); + args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); + args.addInts(KeymasterDefs.KM_TAG_PURPOSE, mKeymasterPurposes); + args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockModes); + args.addInts(KeymasterDefs.KM_TAG_PADDING, mKeymasterEncryptionPaddings); + args.addInts(KeymasterDefs.KM_TAG_PADDING, mKeymasterSignaturePaddings); + args.addInts(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests); + + // TODO: Remove the digest and padding NONE workaround below once Android Keystore returns + // keys which are backed by AndroidKeyStoreBCWorkaround provider instead of Conscrypt. The + // workaround is needed because Conscrypt (via keystore-engine) uses old KeyStore API which + // translates into digest NONE and padding NONE in the new API. keystore-engine cannot be + // updated to pass in the correct padding and digest values because it uses + // OpenSSL/BoringSSL engine which performs digesting and padding prior before invoking + // KeyStore API. + if (!com.android.internal.util.ArrayUtils.contains( + mKeymasterDigests, KeymasterDefs.KM_DIGEST_NONE)) { + args.addInt(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE); + } + if ((!com.android.internal.util.ArrayUtils.contains( + mKeymasterSignaturePaddings, KeymasterDefs.KM_PAD_NONE)) + && (!com.android.internal.util.ArrayUtils.contains( + mKeymasterEncryptionPaddings, KeymasterDefs.KM_PAD_NONE))) { + args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE); + } + KeymasterUtils.addUserAuthArgs(args, + mSpec.isUserAuthenticationRequired(), + mSpec.getUserAuthenticationValidityDurationSeconds()); + args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, + (mSpec.getKeyValidityStart() != null) + ? mSpec.getKeyValidityStart() : new Date(0)); + args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + (mSpec.getKeyValidityForOriginationEnd() != null) + ? mSpec.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE)); + args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + (mSpec.getKeyValidityForConsumptionEnd() != null) + ? mSpec.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE)); + addAlgorithmSpecificParameters(args); + + byte[] additionalEntropy = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, (mKeySizeBits + 7) / 8); + + final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + mEntryAlias; boolean success = false; try { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); - if (!mKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, mKeyType, mKeySize, - flags, args)) { - throw new IllegalStateException("could not generate key in keystore"); + Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias); + KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); + int errorCode = mKeyStore.generateKey( + privateKeyAlias, + args, + additionalEntropy, + flags, + resultingKeyCharacteristics); + if (errorCode != KeyStore.NO_ERROR) { + throw new ProviderException( + "Failed to generate key pair", KeyStore.getKeyStoreException(errorCode)); } final PrivateKey privKey; @@ -153,7 +488,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato try { privKey = engine.getPrivateKeyById(privateKeyAlias); } catch (InvalidKeyException e) { - throw new RuntimeException("Can't get key", e); + throw new ProviderException("Failed to obtain generated private key", e); } ExportResult exportResult = @@ -163,39 +498,45 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato throw new KeyStoreConnectException(); } else if (exportResult.resultCode != KeyStore.NO_ERROR) { throw new ProviderException( - "Failed to obtain public key in X.509 format", + "Failed to obtain X.509 form of generated public key", KeyStore.getKeyStoreException(exportResult.resultCode)); } final byte[] pubKeyBytes = exportResult.exportData; - final PublicKey pubKey; try { - final KeyFactory keyFact = KeyFactory.getInstance(mKeyAlgorithm); + final KeyFactory keyFact = KeyFactory.getInstance(mJcaKeyAlgorithm); pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes)); } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("Can't instantiate key generator", e); + throw new ProviderException( + "Failed to obtain " + mJcaKeyAlgorithm + " KeyFactory", e); } catch (InvalidKeySpecException e) { - throw new IllegalStateException("keystore returned invalid key encoding", e); + throw new ProviderException("Invalid X.509 encoding of generated public key", e); } final X509Certificate cert; try { - cert = generateCertificate(privKey, pubKey); + cert = generateSelfSignedCertificate(privKey, pubKey); } catch (Exception e) { - throw new IllegalStateException("Can't generate certificate", e); + throw new ProviderException("Failed to generate self-signed certificate", e); } byte[] certBytes; try { certBytes = cert.getEncoded(); } catch (CertificateEncodingException e) { - throw new IllegalStateException("Can't get encoding of certificate", e); + throw new ProviderException( + "Failed to obtain encoded form of self-signed certificate", e); } - if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, certBytes, KeyStore.UID_SELF, - flags)) { - throw new IllegalStateException("Can't store certificate in AndroidKeyStore"); + int insertErrorCode = mKeyStore.insert( + Credentials.USER_CERTIFICATE + mEntryAlias, + certBytes, + KeyStore.UID_SELF, + flags); + if (insertErrorCode != KeyStore.NO_ERROR) { + throw new ProviderException("Failed to store self-signed certificate", + KeyStore.getKeyStoreException(insertErrorCode)); } KeyPair result = new KeyPair(pubKey, privKey); @@ -203,14 +544,41 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato return result; } finally { if (!success) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); + Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias); } } } + private void addAlgorithmSpecificParameters(KeymasterArguments keymasterArgs) { + switch (mKeymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_RSA: + keymasterArgs.addLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, mRSAPublicExponent); + break; + case KeymasterDefs.KM_ALGORITHM_EC: + break; + default: + throw new ProviderException("Unsupported algorithm: " + mKeymasterAlgorithm); + } + } + + private X509Certificate generateSelfSignedCertificate( + PrivateKey privateKey, PublicKey publicKey) throws Exception { + String signatureAlgorithm = + getCertificateSignatureAlgorithm(mKeymasterAlgorithm, mKeySizeBits, mSpec); + if (signatureAlgorithm == null) { + // Key cannot be used to sign a certificate + return generateSelfSignedCertificateWithFakeSignature(publicKey); + } else { + // Key can be used to sign a certificate + return generateSelfSignedCertificateWithValidSignature( + privateKey, publicKey, signatureAlgorithm); + } + } + @SuppressWarnings("deprecation") - private X509Certificate generateCertificate(PrivateKey privateKey, PublicKey publicKey) - throws Exception { + private X509Certificate generateSelfSignedCertificateWithValidSignature( + PrivateKey privateKey, PublicKey publicKey, String signatureAlgorithm) + throws Exception { final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); certGen.setPublicKey(publicKey); certGen.setSerialNumber(mSpec.getCertificateSerialNumber()); @@ -218,198 +586,223 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato certGen.setIssuerDN(mSpec.getCertificateSubject()); certGen.setNotBefore(mSpec.getCertificateNotBefore()); certGen.setNotAfter(mSpec.getCertificateNotAfter()); - certGen.setSignatureAlgorithm(getDefaultSignatureAlgorithmForKeyAlgorithm(mKeyAlgorithm)); + certGen.setSignatureAlgorithm(signatureAlgorithm); return certGen.generate(privateKey); } - @NonNull - private @KeyProperties.KeyAlgorithmEnum String getKeyAlgorithm(KeyPairGeneratorSpec spec) { - String result = spec.getKeyType(); - if (result != null) { - return result; + @SuppressWarnings("deprecation") + private X509Certificate generateSelfSignedCertificateWithFakeSignature( + PublicKey publicKey) throws Exception { + V3TBSCertificateGenerator tbsGenerator = new V3TBSCertificateGenerator(); + ASN1ObjectIdentifier sigAlgOid; + AlgorithmIdentifier sigAlgId; + byte[] signature; + switch (mKeymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + sigAlgOid = X9ObjectIdentifiers.ecdsa_with_SHA256; + sigAlgId = new AlgorithmIdentifier(sigAlgOid); + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(new DERInteger(0)); + v.add(new DERInteger(0)); + signature = new DERSequence().getEncoded(); + break; + case KeymasterDefs.KM_ALGORITHM_RSA: + sigAlgOid = PKCSObjectIdentifiers.sha256WithRSAEncryption; + sigAlgId = new AlgorithmIdentifier(sigAlgOid, DERNull.INSTANCE); + signature = new byte[1]; + break; + default: + throw new ProviderException("Unsupported key algorithm: " + mKeymasterAlgorithm); } - return getAlgorithm(); - } - private static int getDefaultKeySize(int keyType) { - if (keyType == NativeConstants.EVP_PKEY_EC) { - return EC_DEFAULT_KEY_SIZE; - } else if (keyType == NativeConstants.EVP_PKEY_RSA) { - return RSA_DEFAULT_KEY_SIZE; + try (ASN1InputStream publicKeyInfoIn = new ASN1InputStream(publicKey.getEncoded())) { + tbsGenerator.setSubjectPublicKeyInfo( + SubjectPublicKeyInfo.getInstance(publicKeyInfoIn.readObject())); } - return -1; + tbsGenerator.setSerialNumber(new ASN1Integer(mSpec.getCertificateSerialNumber())); + X509Principal subject = + new X509Principal(mSpec.getCertificateSubject().getEncoded()); + tbsGenerator.setSubject(subject); + tbsGenerator.setIssuer(subject); + tbsGenerator.setStartDate(new Time(mSpec.getCertificateNotBefore())); + tbsGenerator.setEndDate(new Time(mSpec.getCertificateNotAfter())); + tbsGenerator.setSignature(sigAlgId); + TBSCertificate tbsCertificate = tbsGenerator.generateTBSCertificate(); + + ASN1EncodableVector result = new ASN1EncodableVector(); + result.add(tbsCertificate); + result.add(sigAlgId); + result.add(new DERBitString(signature)); + return new X509CertificateObject(Certificate.getInstance(new DERSequence(result))); } - private static void checkValidKeySize(String keyAlgorithm, int keyType, int keySize) - throws InvalidAlgorithmParameterException { - if (keyType == NativeConstants.EVP_PKEY_EC) { - if (keySize < EC_MIN_KEY_SIZE || keySize > EC_MAX_KEY_SIZE) { - throw new InvalidAlgorithmParameterException("EC keys must be >= " - + EC_MIN_KEY_SIZE + " and <= " + EC_MAX_KEY_SIZE); - } - } else if (keyType == NativeConstants.EVP_PKEY_RSA) { - if (keySize < RSA_MIN_KEY_SIZE || keySize > RSA_MAX_KEY_SIZE) { - throw new InvalidAlgorithmParameterException("RSA keys must be >= " - + RSA_MIN_KEY_SIZE + " and <= " + RSA_MAX_KEY_SIZE); - } - } else { - throw new InvalidAlgorithmParameterException( - "Unsupported key algorithm: " + keyAlgorithm); + private static int getDefaultKeySize(int keymasterAlgorithm) { + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + return EC_DEFAULT_KEY_SIZE; + case KeymasterDefs.KM_ALGORITHM_RSA: + return RSA_DEFAULT_KEY_SIZE; + default: + throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm); } } - private static void checkCorrectParametersSpec(int keyType, int keySize, - AlgorithmParameterSpec spec) throws InvalidAlgorithmParameterException { - if (keyType == NativeConstants.EVP_PKEY_RSA && spec != null) { - if (spec instanceof RSAKeyGenParameterSpec) { - RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) spec; - if (keySize != -1 && keySize != rsaSpec.getKeysize()) { - throw new InvalidAlgorithmParameterException("RSA key size must match: " - + keySize + " vs " + rsaSpec.getKeysize()); + private static void checkValidKeySize(int keymasterAlgorithm, int keySize) + throws InvalidAlgorithmParameterException { + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + if (keySize < EC_MIN_KEY_SIZE || keySize > EC_MAX_KEY_SIZE) { + throw new InvalidAlgorithmParameterException("EC key size must be >= " + + EC_MIN_KEY_SIZE + " and <= " + EC_MAX_KEY_SIZE); } - } else { - throw new InvalidAlgorithmParameterException( - "RSA may only use RSAKeyGenParameterSpec"); - } - } - } - - private static String getDefaultSignatureAlgorithmForKeyAlgorithm( - @KeyProperties.KeyAlgorithmEnum String algorithm) { - if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) { - return "sha256WithRSA"; - } else if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) { - return "sha256WithECDSA"; - } else { - throw new IllegalArgumentException("Unsupported key type " + algorithm); - } - } - - private static byte[][] getArgsForKeyType(int keyType, AlgorithmParameterSpec spec) { - switch (keyType) { - case NativeConstants.EVP_PKEY_RSA: - if (spec instanceof RSAKeyGenParameterSpec) { - RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) spec; - return new byte[][] { rsaSpec.getPublicExponent().toByteArray() }; + break; + case KeymasterDefs.KM_ALGORITHM_RSA: + if (keySize < RSA_MIN_KEY_SIZE || keySize > RSA_MAX_KEY_SIZE) { + throw new InvalidAlgorithmParameterException("RSA key size must be >= " + + RSA_MIN_KEY_SIZE + " and <= " + RSA_MAX_KEY_SIZE); } break; + default: + throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm); } - return null; } - @Override - public void initialize(int keysize, SecureRandom random) { - throw new IllegalArgumentException( - "cannot specify keysize with AndroidKeyStore KeyPairGenerator"); - } - - @Override - public void initialize(AlgorithmParameterSpec params, SecureRandom random) - throws InvalidAlgorithmParameterException { - if (params == null) { - throw new InvalidAlgorithmParameterException( - "Must supply params of type " + KeyGenParameterSpec.class.getName() - + " or " + KeyPairGeneratorSpec.class.getName()); + /** + * Returns the {@code Signature} algorithm to be used for signing a certificate using the + * specified key or {@code null} if the key cannot be used for signing a certificate. + */ + @Nullable + private static String getCertificateSignatureAlgorithm( + int keymasterAlgorithm, + int keySizeBits, + KeyGenParameterSpec spec) { + // Constraints: + // 1. Key must be authorized for signing. + // 2. Signature digest must be one of key's authorized digests. + // 3. For RSA keys, the digest output size must not exceed modulus size minus space needed + // for RSA PKCS#1 signature padding (about 29 bytes: minimum 10 bytes of padding + 15--19 + // bytes overhead for encoding digest OID and digest value in DER). + // 4. For EC keys, the there is no point in using a digest whose output size is longer than + // key/field size because the digest will be truncated to that size. + + if ((spec.getPurposes() & KeyProperties.PURPOSE_SIGN) == 0) { + // Key not authorized for signing + return null; } - - String keyAlgorithm; - KeyGenParameterSpec spec; - boolean encryptionAtRestRequired = false; - if (params instanceof KeyPairGeneratorSpec) { - KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params; - try { - KeyGenParameterSpec.Builder specBuilder; - keyAlgorithm = getKeyAlgorithm(legacySpec).toUpperCase(Locale.US); - if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { - specBuilder = new KeyGenParameterSpec.Builder( - legacySpec.getKeystoreAlias(), - KeyProperties.PURPOSE_SIGN - | KeyProperties.PURPOSE_VERIFY); - specBuilder.setDigests( - KeyProperties.DIGEST_NONE, - KeyProperties.DIGEST_MD5, - KeyProperties.DIGEST_SHA1, - KeyProperties.DIGEST_SHA224, - KeyProperties.DIGEST_SHA256, - KeyProperties.DIGEST_SHA384, - KeyProperties.DIGEST_SHA512); - } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { - specBuilder = new KeyGenParameterSpec.Builder( - legacySpec.getKeystoreAlias(), - KeyProperties.PURPOSE_ENCRYPT - | KeyProperties.PURPOSE_DECRYPT - | KeyProperties.PURPOSE_SIGN - | KeyProperties.PURPOSE_VERIFY); - specBuilder.setDigests( - KeyProperties.DIGEST_NONE, - KeyProperties.DIGEST_MD5, - KeyProperties.DIGEST_SHA1, - KeyProperties.DIGEST_SHA224, - KeyProperties.DIGEST_SHA256, - KeyProperties.DIGEST_SHA384, - KeyProperties.DIGEST_SHA512); - specBuilder.setSignaturePaddings( - KeyProperties.SIGNATURE_PADDING_RSA_PKCS1); - specBuilder.setBlockModes(KeyProperties.BLOCK_MODE_ECB); - specBuilder.setEncryptionPaddings( - KeyProperties.ENCRYPTION_PADDING_NONE, - KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1); - // Disable randomized encryption requirement to support encryption padding NONE - // above. - specBuilder.setRandomizedEncryptionRequired(false); - } else { - throw new InvalidAlgorithmParameterException( - "Unsupported key algorithm: " + keyAlgorithm); + if (!spec.isDigestsSpecified()) { + // Key not authorized for any digests -- can't sign + return null; + } + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + { + Set<Integer> availableKeymasterDigests = getAvailableKeymasterSignatureDigests( + spec.getDigests(), + AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests()); + + int bestKeymasterDigest = -1; + int bestDigestOutputSizeBits = -1; + for (int keymasterDigest : availableKeymasterDigests) { + int outputSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest); + if (outputSizeBits == keySizeBits) { + // Perfect match -- use this digest + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + break; + } + // Not a perfect match -- check against the best digest so far + if (bestKeymasterDigest == -1) { + // First digest tested -- definitely the best so far + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } else { + // Prefer output size to be as close to key size as possible, with output + // sizes larger than key size preferred to those smaller than key size. + if (bestDigestOutputSizeBits < keySizeBits) { + // Output size of the best digest so far is smaller than key size. + // Anything larger is a win. + if (outputSizeBits > bestDigestOutputSizeBits) { + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } + } else { + // Output size of the best digest so far is larger than key size. + // Anything smaller is a win, as long as it's not smaller than key size. + if ((outputSizeBits < bestDigestOutputSizeBits) + && (outputSizeBits >= keySizeBits)) { + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } + } + } } - - if (legacySpec.getKeySize() != -1) { - specBuilder.setKeySize(legacySpec.getKeySize()); + if (bestKeymasterDigest == -1) { + return null; + } + return KeyProperties.Digest.fromKeymasterToSignatureAlgorithmDigest( + bestKeymasterDigest) + "WithECDSA"; + } + case KeymasterDefs.KM_ALGORITHM_RSA: + { + Set<Integer> availableKeymasterDigests = getAvailableKeymasterSignatureDigests( + spec.getDigests(), + AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests()); + + // The amount of space available for the digest is less than modulus size because + // padding must be at least 10 bytes long, and then there's also the 15--19 + // bytes overhead for encoding digest OID and digest value in DER. + int maxDigestOutputSizeBits = keySizeBits - 29 * 8; + int bestKeymasterDigest = -1; + int bestDigestOutputSizeBits = -1; + for (int keymasterDigest : availableKeymasterDigests) { + int outputSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest); + if (outputSizeBits > maxDigestOutputSizeBits) { + // Digest too long (signature generation will fail) -- skip + continue; + } + if (bestKeymasterDigest == -1) { + // First digest tested -- definitely the best so far + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } else { + // The longer the better + if (outputSizeBits > bestDigestOutputSizeBits) { + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } + } } - if (legacySpec.getAlgorithmParameterSpec() != null) { - specBuilder.setAlgorithmParameterSpec(legacySpec.getAlgorithmParameterSpec()); + if (bestKeymasterDigest == -1) { + return null; } - specBuilder.setCertificateSubject(legacySpec.getSubjectDN()); - specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber()); - specBuilder.setCertificateNotBefore(legacySpec.getStartDate()); - specBuilder.setCertificateNotAfter(legacySpec.getEndDate()); - encryptionAtRestRequired = legacySpec.isEncryptionRequired(); - specBuilder.setUserAuthenticationRequired(false); - - spec = specBuilder.build(); - } catch (NullPointerException | IllegalArgumentException e) { - throw new InvalidAlgorithmParameterException(e); + return KeyProperties.Digest.fromKeymasterToSignatureAlgorithmDigest( + bestKeymasterDigest) + "WithRSA"; } - } else if (params instanceof KeyGenParameterSpec) { - spec = (KeyGenParameterSpec) params; - keyAlgorithm = getAlgorithm(); - } else { - throw new InvalidAlgorithmParameterException( - "Unsupported params class: " + params.getClass().getName() - + ". Supported: " + KeyGenParameterSpec.class.getName() - + ", " + KeyPairGeneratorSpec.class); + default: + throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm); } + } - int keyType = KeyStore.getKeyTypeForAlgorithm(keyAlgorithm); - if (keyType == -1) { - throw new InvalidAlgorithmParameterException( - "Unsupported key algorithm: " + keyAlgorithm); + private static Set<Integer> getAvailableKeymasterSignatureDigests( + @KeyProperties.DigestEnum String[] authorizedKeyDigests, + @KeyProperties.DigestEnum String[] supportedSignatureDigests) { + Set<Integer> authorizedKeymasterKeyDigests = new HashSet<Integer>(); + for (int keymasterDigest : KeyProperties.Digest.allToKeymaster(authorizedKeyDigests)) { + authorizedKeymasterKeyDigests.add(keymasterDigest); } - int keySize = spec.getKeySize(); - if (keySize == -1) { - keySize = getDefaultKeySize(keyType); - if (keySize == -1) { - throw new InvalidAlgorithmParameterException( - "Unsupported key algorithm: " + keyAlgorithm); - } + Set<Integer> supportedKeymasterSignatureDigests = new HashSet<Integer>(); + for (int keymasterDigest + : KeyProperties.Digest.allToKeymaster(supportedSignatureDigests)) { + supportedKeymasterSignatureDigests.add(keymasterDigest); + } + if (authorizedKeymasterKeyDigests.contains(KeymasterDefs.KM_DIGEST_NONE)) { + // Key is authorized to be used with any digest + return supportedKeymasterSignatureDigests; + } else { + // Key is authorized to be used only with specific digests. + Set<Integer> result = new HashSet<Integer>(supportedKeymasterSignatureDigests); + result.retainAll(authorizedKeymasterKeyDigests); + return result; } - checkCorrectParametersSpec(keyType, keySize, spec.getAlgorithmParameterSpec()); - checkValidKeySize(keyAlgorithm, keyType, keySize); - - mKeyAlgorithm = keyAlgorithm; - mKeyType = keyType; - mKeySize = keySize; - mSpec = spec; - mEncryptionAtRestRequired = encryptionAtRestRequired; - mKeyStore = KeyStore.getInstance(); } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java index 7c9c0cf..c03be63 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java @@ -22,6 +22,7 @@ import com.android.org.conscrypt.OpenSSLKeyHolder; import libcore.util.EmptyArray; import android.security.Credentials; +import android.security.KeyStore; import android.security.KeyStoreParameter; import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterArguments; @@ -39,7 +40,6 @@ import java.security.Key; import java.security.KeyStore.Entry; import java.security.KeyStore.PrivateKeyEntry; import java.security.KeyStore.ProtectionParameter; -import java.security.KeyStore; import java.security.KeyStore.SecretKeyEntry; import java.security.KeyStoreException; import java.security.KeyStoreSpi; @@ -86,7 +86,7 @@ import javax.crypto.SecretKey; public class AndroidKeyStoreSpi extends KeyStoreSpi { public static final String NAME = "AndroidKeyStore"; - private android.security.KeyStore mKeyStore; + private KeyStore mKeyStore; @Override public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, @@ -105,8 +105,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { String keyAliasInKeystore = Credentials.USER_SECRET_KEY + alias; int errorCode = mKeyStore.getKeyCharacteristics( keyAliasInKeystore, null, null, keyCharacteristics); - if ((errorCode != KeymasterDefs.KM_ERROR_OK) - && (errorCode != android.security.KeyStore.NO_ERROR)) { + if (errorCode != KeyStore.NO_ERROR) { throw (UnrecoverableKeyException) new UnrecoverableKeyException("Failed to load information about key") .initCause(mKeyStore.getInvalidKeyException(alias, errorCode)); @@ -272,107 +271,72 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } } + private static KeyProtection getLegacyKeyProtectionParameter(PrivateKey key) + throws KeyStoreException { + String keyAlgorithm = key.getAlgorithm(); + KeyProtection.Builder specBuilder; + if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { + specBuilder = + new KeyProtection.Builder( + KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY); + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_MD5, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { + specBuilder = + new KeyProtection.Builder( + KeyProperties.PURPOSE_ENCRYPT + | KeyProperties.PURPOSE_DECRYPT + | KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_MD5, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + specBuilder.setSignaturePaddings( + KeyProperties.SIGNATURE_PADDING_RSA_PKCS1); + specBuilder.setEncryptionPaddings( + KeyProperties.ENCRYPTION_PADDING_NONE, + KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1); + // Disable randomized encryption requirement to support encryption padding NONE + // above. + specBuilder.setRandomizedEncryptionRequired(false); + } else { + throw new KeyStoreException("Unsupported key algorithm: " + keyAlgorithm); + } + specBuilder.setUserAuthenticationRequired(false); + + return specBuilder.build(); + } + private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain, java.security.KeyStore.ProtectionParameter param) throws KeyStoreException { int flags = 0; KeyProtection spec; - if (param instanceof KeyStoreParameter) { + if (param == null) { + spec = getLegacyKeyProtectionParameter(key); + } else if (param instanceof KeyStoreParameter) { + spec = getLegacyKeyProtectionParameter(key); KeyStoreParameter legacySpec = (KeyStoreParameter) param; - try { - String keyAlgorithm = key.getAlgorithm(); - KeyProtection.Builder specBuilder; - if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { - specBuilder = - new KeyProtection.Builder( - KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY); - specBuilder.setDigests( - KeyProperties.DIGEST_NONE, - KeyProperties.DIGEST_MD5, - KeyProperties.DIGEST_SHA1, - KeyProperties.DIGEST_SHA224, - KeyProperties.DIGEST_SHA256, - KeyProperties.DIGEST_SHA384, - KeyProperties.DIGEST_SHA512); - } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { - specBuilder = - new KeyProtection.Builder( - KeyProperties.PURPOSE_ENCRYPT - | KeyProperties.PURPOSE_DECRYPT - | KeyProperties.PURPOSE_SIGN - | KeyProperties.PURPOSE_VERIFY); - specBuilder.setDigests( - KeyProperties.DIGEST_NONE, - KeyProperties.DIGEST_MD5, - KeyProperties.DIGEST_SHA1, - KeyProperties.DIGEST_SHA224, - KeyProperties.DIGEST_SHA256, - KeyProperties.DIGEST_SHA384, - KeyProperties.DIGEST_SHA512); - specBuilder.setSignaturePaddings( - KeyProperties.SIGNATURE_PADDING_RSA_PKCS1); - specBuilder.setBlockModes(KeyProperties.BLOCK_MODE_ECB); - specBuilder.setEncryptionPaddings( - KeyProperties.ENCRYPTION_PADDING_NONE, - KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1); - // Disable randomized encryption requirement to support encryption padding NONE - // above. - specBuilder.setRandomizedEncryptionRequired(false); - } else { - throw new KeyStoreException("Unsupported key algorithm: " + keyAlgorithm); - } - if (legacySpec.isEncryptionRequired()) { - flags = android.security.KeyStore.FLAG_ENCRYPTED; - } - specBuilder.setUserAuthenticationRequired(false); - - spec = specBuilder.build(); - } catch (NullPointerException | IllegalArgumentException e) { - throw new KeyStoreException("Unsupported protection parameter", e); + if (legacySpec.isEncryptionRequired()) { + flags = KeyStore.FLAG_ENCRYPTED; } } else if (param instanceof KeyProtection) { spec = (KeyProtection) param; - } else if (param != null) { + } else { throw new KeyStoreException( "Unsupported protection parameter class:" + param.getClass().getName() - + ". Supported: " + KeyStoreParameter.class.getName() + ", " - + KeyProtection.class.getName()); - } else { - spec = null; - } - - byte[] keyBytes = null; - - final String pkeyAlias; - if (key instanceof OpenSSLKeyHolder) { - pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias(); - } else { - pkeyAlias = null; - } - - final boolean shouldReplacePrivateKey; - if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) { - final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length()); - if (!alias.equals(keySubalias)) { - throw new KeyStoreException("Can only replace keys with same alias: " + alias - + " != " + keySubalias); - } - - shouldReplacePrivateKey = false; - } else { - // Make sure the PrivateKey format is the one we support. - final String keyFormat = key.getFormat(); - if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) { - throw new KeyStoreException( - "Only PrivateKeys that can be encoded into PKCS#8 are supported"); - } - - // Make sure we can actually encode the key. - keyBytes = key.getEncoded(); - if (keyBytes == null) { - throw new KeyStoreException("PrivateKey has no encoding"); - } - - shouldReplacePrivateKey = true; + + ". Supported: " + KeyProtection.class.getName() + ", " + + KeyStoreParameter.class.getName()); } // Make sure the chain exists since this is a PrivateKey @@ -400,7 +364,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { try { userCertBytes = x509chain[0].getEncoded(); } catch (CertificateEncodingException e) { - throw new KeyStoreException("Couldn't encode certificate #1", e); + throw new KeyStoreException("Failed to encode certificate #0", e); } /* @@ -421,7 +385,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { certsBytes[i] = x509chain[i + 1].getEncoded(); totalCertLength += certsBytes[i].length; } catch (CertificateEncodingException e) { - throw new KeyStoreException("Can't encode Certificate #" + i, e); + throw new KeyStoreException("Failed to encode certificate #" + i, e); } } @@ -441,31 +405,150 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { chainBytes = null; } - /* - * Make sure we clear out all the appropriate types before trying to - * write. - */ - if (shouldReplacePrivateKey) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); + final String pkeyAlias; + if (key instanceof OpenSSLKeyHolder) { + pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias(); + } else if (key instanceof AndroidKeyStorePrivateKey) { + pkeyAlias = ((AndroidKeyStoreKey) key).getAlias(); } else { - Credentials.deleteCertificateTypesForAlias(mKeyStore, alias); - Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias); - } - - if (shouldReplacePrivateKey - && !mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes, - android.security.KeyStore.UID_SELF, flags)) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); - throw new KeyStoreException("Couldn't put private key in keystore"); - } else if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertBytes, - android.security.KeyStore.UID_SELF, flags)) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); - throw new KeyStoreException("Couldn't put certificate #1 in keystore"); - } else if (chainBytes != null - && !mKeyStore.put(Credentials.CA_CERTIFICATE + alias, chainBytes, - android.security.KeyStore.UID_SELF, flags)) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); - throw new KeyStoreException("Couldn't put certificate chain in keystore"); + pkeyAlias = null; + } + + byte[] pkcs8EncodedPrivateKeyBytes; + KeymasterArguments importArgs; + final boolean shouldReplacePrivateKey; + if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) { + final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length()); + if (!alias.equals(keySubalias)) { + throw new KeyStoreException("Can only replace keys with same alias: " + alias + + " != " + keySubalias); + } + shouldReplacePrivateKey = false; + importArgs = null; + pkcs8EncodedPrivateKeyBytes = null; + } else { + shouldReplacePrivateKey = true; + // Make sure the PrivateKey format is the one we support. + final String keyFormat = key.getFormat(); + if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) { + throw new KeyStoreException( + "Unsupported private key export format: " + keyFormat + + ". Only private keys which export their key material in PKCS#8 format are" + + " supported."); + } + + // Make sure we can actually encode the key. + pkcs8EncodedPrivateKeyBytes = key.getEncoded(); + if (pkcs8EncodedPrivateKeyBytes == null) { + throw new KeyStoreException("Private key did not export any key material"); + } + + importArgs = new KeymasterArguments(); + try { + importArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, + KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm( + key.getAlgorithm())); + @KeyProperties.PurposeEnum int purposes = spec.getPurposes(); + importArgs.addInts(KeymasterDefs.KM_TAG_PURPOSE, + KeyProperties.Purpose.allToKeymaster(purposes)); + if (spec.isDigestsSpecified()) { + importArgs.addInts(KeymasterDefs.KM_TAG_DIGEST, + KeyProperties.Digest.allToKeymaster(spec.getDigests())); + } + + importArgs.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, + KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes())); + int[] keymasterEncryptionPaddings = + KeyProperties.EncryptionPadding.allToKeymaster( + spec.getEncryptionPaddings()); + if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) + && (spec.isRandomizedEncryptionRequired())) { + for (int keymasterPadding : keymasterEncryptionPaddings) { + if (!KeymasterUtils + .isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto( + keymasterPadding)) { + throw new KeyStoreException( + "Randomized encryption (IND-CPA) required but is violated by" + + " encryption padding mode: " + + KeyProperties.EncryptionPadding.fromKeymaster( + keymasterPadding) + + ". See KeyProtection documentation."); + } + } + } + importArgs.addInts(KeymasterDefs.KM_TAG_PADDING, keymasterEncryptionPaddings); + importArgs.addInts(KeymasterDefs.KM_TAG_PADDING, + KeyProperties.SignaturePadding.allToKeymaster(spec.getSignaturePaddings())); + KeymasterUtils.addUserAuthArgs(importArgs, + spec.isUserAuthenticationRequired(), + spec.getUserAuthenticationValidityDurationSeconds()); + importArgs.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, + (spec.getKeyValidityStart() != null) + ? spec.getKeyValidityStart() : new Date(0)); + importArgs.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + (spec.getKeyValidityForOriginationEnd() != null) + ? spec.getKeyValidityForOriginationEnd() + : new Date(Long.MAX_VALUE)); + importArgs.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + (spec.getKeyValidityForConsumptionEnd() != null) + ? spec.getKeyValidityForConsumptionEnd() + : new Date(Long.MAX_VALUE)); + } catch (IllegalArgumentException e) { + throw new KeyStoreException("Invalid parameter", e); + } + } + + + boolean success = false; + try { + // Store the private key, if necessary + if (shouldReplacePrivateKey) { + // Delete the stored private key and any related entries before importing the + // provided key + Credentials.deleteAllTypesForAlias(mKeyStore, alias); + KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); + int errorCode = mKeyStore.importKey( + Credentials.USER_PRIVATE_KEY + alias, + importArgs, + KeymasterDefs.KM_KEY_FORMAT_PKCS8, + pkcs8EncodedPrivateKeyBytes, + flags, + resultingKeyCharacteristics); + if (errorCode != KeyStore.NO_ERROR) { + throw new KeyStoreException("Failed to store private key", + KeyStore.getKeyStoreException(errorCode)); + } + } else { + // Keep the stored private key around -- delete all other entry types + Credentials.deleteCertificateTypesForAlias(mKeyStore, alias); + Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias); + } + + // Store the leaf certificate + int errorCode = mKeyStore.insert(Credentials.USER_CERTIFICATE + alias, userCertBytes, + KeyStore.UID_SELF, flags); + if (errorCode != KeyStore.NO_ERROR) { + throw new KeyStoreException("Failed to store certificate #0", + KeyStore.getKeyStoreException(errorCode)); + } + + // Store the certificate chain + errorCode = mKeyStore.insert(Credentials.CA_CERTIFICATE + alias, chainBytes, + KeyStore.UID_SELF, flags); + if (errorCode != KeyStore.NO_ERROR) { + throw new KeyStoreException("Failed to store certificate chain", + KeyStore.getKeyStoreException(errorCode)); + } + success = true; + } finally { + if (!success) { + if (shouldReplacePrivateKey) { + Credentials.deleteAllTypesForAlias(mKeyStore, alias); + } else { + Credentials.deleteCertificateTypesForAlias(mKeyStore, alias); + Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias); + } + } } } @@ -589,7 +672,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) && (params.isRandomizedEncryptionRequired())) { for (int keymasterBlockMode : keymasterBlockModes) { - if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible(keymasterBlockMode)) { + if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto( + keymasterBlockMode)) { throw new KeyStoreException( "Randomized encryption (IND-CPA) required but may be violated by block" + " mode: " @@ -598,9 +682,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } } } - for (int keymasterPurpose : KeyProperties.Purpose.allToKeymaster(purposes)) { - args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose); - } + args.addInts(KeymasterDefs.KM_TAG_PURPOSE, KeyProperties.Purpose.allToKeymaster(purposes)); args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes); if (params.getSignaturePaddings().length > 0) { throw new KeyStoreException("Signature paddings not supported for symmetric keys"); @@ -636,7 +718,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { keyMaterial, 0, // flags new KeyCharacteristics()); - if (errorCode != android.security.KeyStore.NO_ERROR) { + if (errorCode != KeyStore.NO_ERROR) { throw new KeyStoreException("Failed to import secret key. Keystore error code: " + errorCode); } @@ -667,7 +749,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded, - android.security.KeyStore.UID_SELF, android.security.KeyStore.FLAG_NONE)) { + KeyStore.UID_SELF, KeyStore.FLAG_NONE)) { throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?"); } } @@ -840,7 +922,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } // Unfortunate name collision. - mKeyStore = android.security.KeyStore.getInstance(); + mKeyStore = KeyStore.getInstance(); } @Override @@ -852,8 +934,9 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { Credentials.deleteAllTypesForAlias(mKeyStore, alias); - if (entry instanceof KeyStore.TrustedCertificateEntry) { - KeyStore.TrustedCertificateEntry trE = (KeyStore.TrustedCertificateEntry) entry; + if (entry instanceof java.security.KeyStore.TrustedCertificateEntry) { + java.security.KeyStore.TrustedCertificateEntry trE = + (java.security.KeyStore.TrustedCertificateEntry) entry; engineSetCertificateEntry(alias, trE.getTrustedCertificate()); return; } diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index d861302..19ff9c7 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -214,7 +214,9 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { } /** - * Returns the requested key size or {@code -1} if default size should be used. + * Returns the requested key size. If {@code -1}, the size should be looked up from + * {@link #getAlgorithmParameterSpec()}, if provided, otherwise an algorithm-specific default + * size should be used. */ public int getKeySize() { return mKeySize; @@ -465,7 +467,10 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * the modulus size, for EC keys this selects a curve with a matching field size, and for * symmetric keys this sets the size of the bitstring which is their key material. * - * <p>The default key size is specific to each key algorithm. + * <p>The default key size is specific to each key algorithm. If key size is not set + * via this method, it should be looked up from the algorithm-specific parameters (if any) + * provided via + * {@link #setAlgorithmParameterSpec(AlgorithmParameterSpec) setAlgorithmParameterSpec}. */ @NonNull public Builder setKeySize(int keySize) { diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index e3c2d1d..5af4181 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -168,6 +168,31 @@ public abstract class KeyProperties { public static abstract class KeyAlgorithm { private KeyAlgorithm() {} + public static int toKeymasterAsymmetricKeyAlgorithm( + @NonNull @KeyAlgorithmEnum String algorithm) { + if (KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) { + return KeymasterDefs.KM_ALGORITHM_EC; + } else if (KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) { + return KeymasterDefs.KM_ALGORITHM_RSA; + } else { + throw new IllegalArgumentException("Unsupported key algorithm: " + algorithm); + } + } + + @NonNull + public static @KeyAlgorithmEnum String fromKeymasterAsymmetricKeyAlgorithm( + int keymasterAlgorithm) { + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + return KEY_ALGORITHM_EC; + case KeymasterDefs.KM_ALGORITHM_RSA: + return KEY_ALGORITHM_RSA; + default: + throw new IllegalArgumentException( + "Unsupported key algorithm: " + keymasterAlgorithm); + } + } + public static int toKeymasterSecretKeyAlgorithm( @NonNull @KeyAlgorithmEnum String algorithm) { if (KEY_ALGORITHM_AES.equalsIgnoreCase(algorithm)) { @@ -572,6 +597,28 @@ public abstract class KeyProperties { } @NonNull + public static @DigestEnum String fromKeymasterToSignatureAlgorithmDigest(int digest) { + switch (digest) { + case KeymasterDefs.KM_DIGEST_NONE: + return "NONE"; + case KeymasterDefs.KM_DIGEST_MD5: + return "MD5"; + case KeymasterDefs.KM_DIGEST_SHA1: + return "SHA1"; + case KeymasterDefs.KM_DIGEST_SHA_2_224: + return "SHA224"; + case KeymasterDefs.KM_DIGEST_SHA_2_256: + return "SHA256"; + case KeymasterDefs.KM_DIGEST_SHA_2_384: + return "SHA384"; + case KeymasterDefs.KM_DIGEST_SHA_2_512: + return "SHA512"; + default: + throw new IllegalArgumentException("Unsupported digest algorithm: " + digest); + } + } + + @NonNull public static @DigestEnum String[] allFromKeymaster(@NonNull Collection<Integer> digests) { if (digests.isEmpty()) { return EmptyArray.STRING; diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java index e7529e1..0639d49 100644 --- a/keystore/java/android/security/keystore/KeymasterUtils.java +++ b/keystore/java/android/security/keystore/KeymasterUtils.java @@ -50,7 +50,8 @@ public abstract class KeymasterUtils { } } - public static boolean isKeymasterBlockModeIndCpaCompatible(int keymasterBlockMode) { + public static boolean isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto( + int keymasterBlockMode) { switch (keymasterBlockMode) { case KeymasterDefs.KM_MODE_ECB: return false; @@ -63,6 +64,20 @@ public abstract class KeymasterUtils { } } + public static boolean isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto( + int keymasterPadding) { + switch (keymasterPadding) { + case KeymasterDefs.KM_PAD_NONE: + return false; + case KeymasterDefs.KM_PAD_RSA_OAEP: + case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT: + return true; + default: + throw new IllegalArgumentException( + "Unsupported encryption padding scheme: " + keymasterPadding); + } + } + /** * Adds keymaster arguments to express the key's authorization policy supported by user * authentication. |