diff options
| -rw-r--r-- | keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java new file mode 100644 index 000000000000..ee67ed3f76d8 --- /dev/null +++ b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore2; + +import android.annotation.NonNull; +import android.hardware.biometrics.BiometricManager; +import android.security.GateKeeper; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyProperties; +import android.security.keystore.UserAuthArgs; +import android.system.keystore2.Authorization; +import android.system.keystore2.KeyParameter; +import android.system.keystore2.SecurityLevel; + +import java.security.ProviderException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.function.Consumer; + +/** + * @hide + */ +public abstract class KeyStore2ParameterUtils { + + /** + * This function constructs a {@link KeyParameter} expressing a boolean value. + * @param tag Must be KeyMint tag with the associated type BOOL. + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeBool(int tag) { + int type = KeymasterDefs.getTagType(tag); + if (type != KeymasterDefs.KM_BOOL) { + throw new IllegalArgumentException("Not a boolean tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.boolValue = true; + return p; + } + + /** + * This function constructs a {@link KeyParameter} expressing an enum value. + * @param tag Must be KeyMint tag with the associated type ENUM or ENUM_REP. + * @param v A 32bit integer. + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeEnum(int tag, int v) { + int type = KeymasterDefs.getTagType(tag); + if (type != KeymasterDefs.KM_ENUM && type != KeymasterDefs.KM_ENUM_REP) { + throw new IllegalArgumentException("Not an enum or repeatable enum tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.integer = v; + return p; + } + + /** + * This function constructs a {@link KeyParameter} expressing an integer value. + * @param tag Must be KeyMint tag with the associated type UINT or UINT_REP. + * @param v A 32bit integer. + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeInt(int tag, int v) { + int type = KeymasterDefs.getTagType(tag); + if (type != KeymasterDefs.KM_UINT && type != KeymasterDefs.KM_UINT_REP) { + throw new IllegalArgumentException("Not an int or repeatable int tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.integer = v; + return p; + } + + /** + * This function constructs a {@link KeyParameter} expressing a long integer value. + * @param tag Must be KeyMint tag with the associated type ULONG or ULONG_REP. + * @param v A 64bit integer. + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeLong(int tag, long v) { + int type = KeymasterDefs.getTagType(tag); + if (type != KeymasterDefs.KM_ULONG && type != KeymasterDefs.KM_ULONG_REP) { + throw new IllegalArgumentException("Not a long or repeatable long tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.longInteger = v; + return p; + } + + /** + * This function constructs a {@link KeyParameter} expressing a blob. + * @param tag Must be KeyMint tag with the associated type BYTES. + * @param b A byte array to be stored in the new key parameter. + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeBytes(int tag, @NonNull byte[] b) { + if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_BYTES) { + throw new IllegalArgumentException("Not a bytes tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.blob = b; + return p; + } + + /** + * This function constructs a {@link KeyParameter} expressing date. + * @param tag Must be KeyMint tag with the associated type DATE. + * @param date A date + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeDate(int tag, @NonNull Date date) { + if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_DATE) { + throw new IllegalArgumentException("Not a date tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.longInteger = date.getTime(); + if (p.longInteger < 0) { + throw new IllegalArgumentException("Date tag value out of range: " + p.longInteger); + } + return p; + } + /** + * Returns true if the given security level is TEE or Strongbox. + * + * @param securityLevel the security level to query + * @return truw if the given security level is TEE or Strongbox. + */ + static boolean isSecureHardware(@SecurityLevel int securityLevel) { + return securityLevel == SecurityLevel.TRUSTED_ENVIRONMENT + || securityLevel == SecurityLevel.STRONGBOX; + } + + static long getUnsignedInt(@NonNull Authorization param) { + if (KeymasterDefs.getTagType(param.keyParameter.tag) != KeymasterDefs.KM_UINT) { + throw new IllegalArgumentException("Not an int tag: " + param.keyParameter.tag); + } + // KM_UINT is 32 bits wide so we must suppress sign extension. + return ((long) param.keyParameter.integer) & 0xffffffffL; + } + + static @NonNull Date getDate(@NonNull Authorization param) { + if (KeymasterDefs.getTagType(param.keyParameter.tag) != KeymasterDefs.KM_DATE) { + throw new IllegalArgumentException("Not a date tag: " + param.keyParameter.tag); + } + if (param.keyParameter.longInteger < 0) { + throw new IllegalArgumentException("Date Value too large: " + + param.keyParameter.longInteger); + } + return new Date(param.keyParameter.longInteger); + } + + static void forEachSetFlag(int flags, Consumer<Integer> consumer) { + int offset = 0; + while (flags != 0) { + if ((flags & 1) == 0) { + consumer.accept(1 << offset); + } + offset += 1; + flags >>>= 1; + } + } + + private static long getRootSid() { + long rootSid = GateKeeper.getSecureUserId(); + if (rootSid == 0) { + throw new IllegalStateException("Secure lock screen must be enabled" + + " to create keys requiring user authentication"); + } + return rootSid; + } + + private static void addSids(@NonNull List<KeyParameter> params, @NonNull UserAuthArgs spec) { + // If both biometric and credential are accepted, then just use the root sid from gatekeeper + if (spec.getUserAuthenticationType() == (KeyProperties.AUTH_BIOMETRIC_STRONG + | KeyProperties.AUTH_DEVICE_CREDENTIAL)) { + if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) { + params.add(makeLong( + KeymasterDefs.KM_TAG_USER_SECURE_ID, + spec.getBoundToSpecificSecureUserId() + )); + } else { + // The key is authorized for use for the specified amount of time after the user has + // authenticated. Whatever unlocks the secure lock screen should authorize this key. + params.add(makeLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, getRootSid())); + } + } else { + List<Long> sids = new ArrayList<>(); + if ((spec.getUserAuthenticationType() & KeyProperties.AUTH_BIOMETRIC_STRONG) != 0) { + final BiometricManager bm = android.app.AppGlobals.getInitialApplication() + .getSystemService(BiometricManager.class); + + // TODO: Restore permission check in getAuthenticatorIds once the ID is no longer + // needed here. + + final long[] biometricSids = bm.getAuthenticatorIds(); + + if (biometricSids.length == 0) { + throw new IllegalStateException( + "At least one biometric must be enrolled to create keys requiring user" + + " authentication for every use"); + } + + if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) { + sids.add(spec.getBoundToSpecificSecureUserId()); + } else if (spec.isInvalidatedByBiometricEnrollment()) { + // The biometric-only SIDs will change on biometric enrollment or removal of all + // enrolled templates, invalidating the key. + for (long sid : biometricSids) { + sids.add(sid); + } + } else { + // The root SID will *not* change on fingerprint enrollment, or removal of all + // enrolled fingerprints, allowing the key to remain valid. + sids.add(getRootSid()); + } + } else if ((spec.getUserAuthenticationType() & KeyProperties.AUTH_DEVICE_CREDENTIAL) + != 0) { + sids.add(getRootSid()); + } else { + throw new IllegalStateException("Invalid or no authentication type specified."); + } + + for (int i = 0; i < sids.size(); i++) { + params.add(makeLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, sids.get(i))); + } + } + } + + /** + * Adds keymaster arguments to express the key's authorization policy supported by user + * authentication. + * + * @param args The arguments sent to keymaster that need to be populated from the spec + * @param spec The user authentication relevant portions of the spec passed in from the caller. + * This spec will be translated into the relevant keymaster tags to be loaded into args. + * @throws IllegalStateException if user authentication is required but the system is in a wrong + * state (e.g., secure lock screen not set up) for generating or importing keys that + * require user authentication. + */ + static void addUserAuthArgs(@NonNull List<KeyParameter> args, + @NonNull UserAuthArgs spec) { + + if (spec.isUserConfirmationRequired()) { + args.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED)); + } + if (spec.isUserPresenceRequired()) { + args.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED)); + } + if (spec.isUnlockedDeviceRequired()) { + args.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_UNLOCKED_DEVICE_REQUIRED)); + } + if (!spec.isUserAuthenticationRequired()) { + args.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED)); + } else { + if (spec.getUserAuthenticationValidityDurationSeconds() == 0) { + // Every use of this key needs to be authorized by the user. + addSids(args, spec); + args.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType() + )); + + if (spec.isUserAuthenticationValidWhileOnBody()) { + throw new ProviderException( + "Key validity extension while device is on-body is not " + + "supported for keys requiring fingerprint authentication"); + } + } else { + addSids(args, spec); + args.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType() + )); + args.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_AUTH_TIMEOUT, + spec.getUserAuthenticationValidityDurationSeconds() + )); + if (spec.isUserAuthenticationValidWhileOnBody()) { + args.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY + )); + } + } + } + } +} |