/* Copyright 2019, The Android Open Source Project, Inc. * * 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 com.google.android.attestation; import static com.google.android.attestation.AuthorizationList.UserAuthType.FINGERPRINT; import static com.google.android.attestation.AuthorizationList.UserAuthType.PASSWORD; import static com.google.android.attestation.AuthorizationList.UserAuthType.USER_AUTH_TYPE_ANY; import static com.google.android.attestation.AuthorizationList.UserAuthType.USER_AUTH_TYPE_NONE; import static com.google.android.attestation.Constants.KM_TAG_ACTIVE_DATE_TIME; import static com.google.android.attestation.Constants.KM_TAG_ALGORITHM; import static com.google.android.attestation.Constants.KM_TAG_ALLOW_WHILE_ON_BODY; import static com.google.android.attestation.Constants.KM_TAG_ALL_APPLICATIONS; import static com.google.android.attestation.Constants.KM_TAG_APPLICATION_ID; import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_APPLICATION_ID; import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_BRAND; import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_DEVICE; import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_IMEI; import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_MANUFACTURER; import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_MEID; import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_MODEL; import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_PRODUCT; import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_SERIAL; import static com.google.android.attestation.Constants.KM_TAG_AUTH_TIMEOUT; import static com.google.android.attestation.Constants.KM_TAG_BOOT_PATCH_LEVEL; import static com.google.android.attestation.Constants.KM_TAG_CREATION_DATE_TIME; import static com.google.android.attestation.Constants.KM_TAG_DIGEST; import static com.google.android.attestation.Constants.KM_TAG_EC_CURVE; import static com.google.android.attestation.Constants.KM_TAG_KEY_SIZE; import static com.google.android.attestation.Constants.KM_TAG_NO_AUTH_REQUIRED; import static com.google.android.attestation.Constants.KM_TAG_ORIGIN; import static com.google.android.attestation.Constants.KM_TAG_ORIGINATION_EXPIRE_DATE_TIME; import static com.google.android.attestation.Constants.KM_TAG_OS_PATCH_LEVEL; import static com.google.android.attestation.Constants.KM_TAG_OS_VERSION; import static com.google.android.attestation.Constants.KM_TAG_PADDING; import static com.google.android.attestation.Constants.KM_TAG_PURPOSE; import static com.google.android.attestation.Constants.KM_TAG_ROLLBACK_RESISTANCE; import static com.google.android.attestation.Constants.KM_TAG_ROLLBACK_RESISTANT; import static com.google.android.attestation.Constants.KM_TAG_ROOT_OF_TRUST; import static com.google.android.attestation.Constants.KM_TAG_RSA_PUBLIC_EXPONENT; import static com.google.android.attestation.Constants.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED; import static com.google.android.attestation.Constants.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED; import static com.google.android.attestation.Constants.KM_TAG_UNLOCKED_DEVICE_REQUIRED; import static com.google.android.attestation.Constants.KM_TAG_USAGE_EXPIRE_DATE_TIME; import static com.google.android.attestation.Constants.KM_TAG_USER_AUTH_TYPE; import static com.google.android.attestation.Constants.KM_TAG_VENDOR_PATCH_LEVEL; import static com.google.android.attestation.Constants.UINT32_MAX; import java.time.Duration; import java.time.Instant; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1Set; import org.bouncycastle.asn1.ASN1TaggedObject; import org.bouncycastle.asn1.DEROctetString; /** * This data structure contains the key pair's properties themselves, as defined in the Keymaster * hardware abstraction layer (HAL). You compare these values to the device's current state or to a * set of expected values to verify that a key pair is still valid for use in your app. */ @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class AuthorizationList { /** Specifies the types of user authenticators that may be used to authorize this key. */ public enum UserAuthType { USER_AUTH_TYPE_NONE, PASSWORD, FINGERPRINT, USER_AUTH_TYPE_ANY } public final Optional<Set<Integer>> purpose; public final Optional<Integer> algorithm; public final Optional<Integer> keySize; public final Optional<Set<Integer>> digest; public final Optional<Set<Integer>> padding; public final Optional<Integer> ecCurve; public final Optional<Long> rsaPublicExponent; public final boolean rollbackResistance; public final Optional<Instant> activeDateTime; public final Optional<Instant> originationExpireDateTime; public final Optional<Instant> usageExpireDateTime; public final boolean noAuthRequired; public final Optional<UserAuthType> userAuthType; public final Optional<Duration> authTimeout; public final boolean allowWhileOnBody; public final boolean trustedUserPresenceRequired; public final boolean trustedConfirmationRequired; public final boolean unlockedDeviceRequired; public final boolean allApplications; public final Optional<byte[]> applicationId; public final Optional<Instant> creationDateTime; public final Optional<Integer> origin; public final boolean rollbackResistant; public final Optional<RootOfTrust> rootOfTrust; public final Optional<Integer> osVersion; public final Optional<Integer> osPatchLevel; public final Optional<AttestationApplicationId> attestationApplicationId; public final Optional<byte[]> attestationApplicationIdBytes; public final Optional<byte[]> attestationIdBrand; public final Optional<byte[]> attestationIdDevice; public final Optional<byte[]> attestationIdProduct; public final Optional<byte[]> attestationIdSerial; public final Optional<byte[]> attestationIdImei; public final Optional<byte[]> attestationIdMeid; public final Optional<byte[]> attestationIdManufacturer; public final Optional<byte[]> attestationIdModel; public final Optional<Integer> vendorPatchLevel; public final Optional<Integer> bootPatchLevel; private AuthorizationList(ASN1Encodable[] authorizationList, int attestationVersion) { Map<Integer, ASN1Primitive> authorizationMap = getAuthorizationMap(authorizationList); this.purpose = findOptionalIntegerSetAuthorizationListEntry(authorizationMap, KM_TAG_PURPOSE); this.algorithm = findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_ALGORITHM); this.keySize = findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_KEY_SIZE); this.digest = findOptionalIntegerSetAuthorizationListEntry(authorizationMap, KM_TAG_DIGEST); this.padding = findOptionalIntegerSetAuthorizationListEntry(authorizationMap, KM_TAG_PADDING); this.ecCurve = findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_EC_CURVE); this.rsaPublicExponent = findOptionalLongAuthorizationListEntry(authorizationMap, KM_TAG_RSA_PUBLIC_EXPONENT); this.rollbackResistance = findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_ROLLBACK_RESISTANCE); this.activeDateTime = findOptionalInstantMillisAuthorizationListEntry(authorizationMap, KM_TAG_ACTIVE_DATE_TIME); this.originationExpireDateTime = findOptionalInstantMillisAuthorizationListEntry( authorizationMap, KM_TAG_ORIGINATION_EXPIRE_DATE_TIME); this.usageExpireDateTime = findOptionalInstantMillisAuthorizationListEntry( authorizationMap, KM_TAG_USAGE_EXPIRE_DATE_TIME); this.noAuthRequired = findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_NO_AUTH_REQUIRED); this.userAuthType = findOptionalUserAuthType(authorizationMap, KM_TAG_USER_AUTH_TYPE); this.authTimeout = findOptionalDurationSecondsAuthorizationListEntry(authorizationMap, KM_TAG_AUTH_TIMEOUT); this.allowWhileOnBody = findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_ALLOW_WHILE_ON_BODY); this.trustedUserPresenceRequired = findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED); this.trustedConfirmationRequired = findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_TRUSTED_CONFIRMATION_REQUIRED); this.unlockedDeviceRequired = findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_UNLOCKED_DEVICE_REQUIRED); this.allApplications = findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_ALL_APPLICATIONS); this.applicationId = findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_APPLICATION_ID); this.creationDateTime = findOptionalInstantMillisAuthorizationListEntry( authorizationMap, KM_TAG_CREATION_DATE_TIME); this.origin = findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_ORIGIN); this.rollbackResistant = findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_ROLLBACK_RESISTANT); this.rootOfTrust = Optional.ofNullable( RootOfTrust.createRootOfTrust( (ASN1Sequence) findAuthorizationListEntry(authorizationMap, KM_TAG_ROOT_OF_TRUST), attestationVersion)); this.osVersion = findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_OS_VERSION); this.osPatchLevel = findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_OS_PATCH_LEVEL); this.attestationApplicationId = Optional.ofNullable( AttestationApplicationId.createAttestationApplicationId( (DEROctetString) findAuthorizationListEntry( authorizationMap, KM_TAG_ATTESTATION_APPLICATION_ID))); this.attestationApplicationIdBytes = findOptionalByteArrayAuthorizationListEntry( authorizationMap, KM_TAG_ATTESTATION_APPLICATION_ID); this.attestationIdBrand = findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_ATTESTATION_ID_BRAND); this.attestationIdDevice = findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_ATTESTATION_ID_DEVICE); this.attestationIdProduct = findOptionalByteArrayAuthorizationListEntry( authorizationMap, KM_TAG_ATTESTATION_ID_PRODUCT); this.attestationIdSerial = findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_ATTESTATION_ID_SERIAL); this.attestationIdImei = findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_ATTESTATION_ID_IMEI); this.attestationIdMeid = findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_ATTESTATION_ID_MEID); this.attestationIdManufacturer = findOptionalByteArrayAuthorizationListEntry( authorizationMap, KM_TAG_ATTESTATION_ID_MANUFACTURER); this.attestationIdModel = findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_ATTESTATION_ID_MODEL); this.vendorPatchLevel = findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_VENDOR_PATCH_LEVEL); this.bootPatchLevel = findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_BOOT_PATCH_LEVEL); } static AuthorizationList createAuthorizationList( ASN1Encodable[] authorizationList, int attestationVersion) { return new AuthorizationList(authorizationList, attestationVersion); } private static Map<Integer, ASN1Primitive> getAuthorizationMap( ASN1Encodable[] authorizationList) { Map<Integer, ASN1Primitive> authorizationMap = new HashMap<>(); for (ASN1Encodable entry : authorizationList) { ASN1TaggedObject taggedEntry = (ASN1TaggedObject) entry; authorizationMap.put(taggedEntry.getTagNo(), taggedEntry.getObject()); } return authorizationMap; } private static ASN1Primitive findAuthorizationListEntry( Map<Integer, ASN1Primitive> authorizationMap, int tag) { return authorizationMap.getOrDefault(tag, null); } private static Optional<Set<Integer>> findOptionalIntegerSetAuthorizationListEntry( Map<Integer, ASN1Primitive> authorizationMap, int tag) { ASN1Set asn1Set = (ASN1Set) findAuthorizationListEntry(authorizationMap, tag); if (asn1Set == null) { return Optional.empty(); } Set<Integer> entrySet = new HashSet<>(); for (ASN1Encodable value : asn1Set) { entrySet.add(ASN1Parsing.getIntegerFromAsn1(value)); } return Optional.of(entrySet); } private static Optional<Duration> findOptionalDurationSecondsAuthorizationListEntry( Map<Integer, ASN1Primitive> authorizationMap, int tag) { Optional<Integer> seconds = findOptionalIntegerAuthorizationListEntry(authorizationMap, tag); return seconds.map(Duration::ofSeconds); } private static Optional<Integer> findOptionalIntegerAuthorizationListEntry( Map<Integer, ASN1Primitive> authorizationMap, int tag) { ASN1Primitive entry = findAuthorizationListEntry(authorizationMap, tag); return Optional.ofNullable(entry).map(ASN1Parsing::getIntegerFromAsn1); } private static Optional<Instant> findOptionalInstantMillisAuthorizationListEntry( Map<Integer, ASN1Primitive> authorizationMap, int tag) { Optional<Long> millis = findOptionalLongAuthorizationListEntry(authorizationMap, tag); return millis.map(Instant::ofEpochMilli); } private static Optional<Long> findOptionalLongAuthorizationListEntry( Map<Integer, ASN1Primitive> authorizationMap, int tag) { ASN1Integer longEntry = ((ASN1Integer) findAuthorizationListEntry(authorizationMap, tag)); return Optional.ofNullable(longEntry).map(value -> value.getValue().longValue()); } private static boolean findBooleanAuthorizationListEntry( Map<Integer, ASN1Primitive> authorizationMap, int tag) { return null != findAuthorizationListEntry(authorizationMap, tag); } private static Optional<byte[]> findOptionalByteArrayAuthorizationListEntry( Map<Integer, ASN1Primitive> authorizationMap, int tag) { ASN1OctetString entry = (ASN1OctetString) findAuthorizationListEntry(authorizationMap, tag); return Optional.ofNullable(entry).map(ASN1OctetString::getOctets); } private static Optional<UserAuthType> findOptionalUserAuthType( Map<Integer, ASN1Primitive> authorizationMap, int tag) { Optional<Long> userAuthType = findOptionalLongAuthorizationListEntry(authorizationMap, tag); return userAuthType.map(AuthorizationList::userAuthTypeToEnum); } // Visible for testing. static UserAuthType userAuthTypeToEnum(long userAuthType) { if (userAuthType == 0L) { return USER_AUTH_TYPE_NONE; } else if (userAuthType == 1L) { return PASSWORD; } else if (userAuthType == 2L) { return FINGERPRINT; } else if (userAuthType == UINT32_MAX) { return USER_AUTH_TYPE_ANY; } throw new IllegalArgumentException("Invalid User Auth Type."); } }