From 193a27d287aed6673732ceff5810303e11e007d8 Mon Sep 17 00:00:00 2001 From: Justin McClain Date: Fri, 21 Jan 2022 21:41:53 +0000 Subject: [PATCH] Implementing verification of PROFILE_PEER_DEVICE. Bug: 216477071 Test: AttestationVerificationTest unit test Change-Id: Ide254de1aaaad24a5ac9e449086192aa9f59a72b --- ...toreAttestationVerificationAttributes.java | 468 ++++++++++++++++ ...AttestationVerificationManagerService.java | 12 +- ...stationVerificationPeerDeviceVerifier.java | 510 ++++++++++++++++++ tests/AttestationVerificationTest/Android.bp | 1 + .../AndroidManifest.xml | 1 + .../test_attestation_with_root_certs.pem | 81 +++ .../test_attestation_wrong_root_certs.pem | 30 ++ .../assets/test_no_attestation_ext_certs.pem | 33 ++ .../assets/test_root_certs.pem | 61 +++ .../test_virtual_device_attestation_certs.pem | 50 ++ ...DeviceSystemAttestationVerificationTest.kt | 161 ++++++ .../SystemAttestationVerificationTest.kt | 8 +- ...AttestationVerificationAttributesTest.java | 297 ++++++++++ ...ationVerificationPeerDeviceVerifierTest.kt | 175 ++++++ 14 files changed, 1882 insertions(+), 6 deletions(-) create mode 100644 services/core/java/com/android/server/security/AndroidKeystoreAttestationVerificationAttributes.java create mode 100644 services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java create mode 100644 tests/AttestationVerificationTest/assets/test_attestation_with_root_certs.pem create mode 100644 tests/AttestationVerificationTest/assets/test_attestation_wrong_root_certs.pem create mode 100644 tests/AttestationVerificationTest/assets/test_no_attestation_ext_certs.pem create mode 100644 tests/AttestationVerificationTest/assets/test_root_certs.pem create mode 100644 tests/AttestationVerificationTest/assets/test_virtual_device_attestation_certs.pem create mode 100644 tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt create mode 100644 tests/AttestationVerificationTest/src/com/android/server/security/AndroidKeystoreAttestationVerificationAttributesTest.java create mode 100644 tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt diff --git a/services/core/java/com/android/server/security/AndroidKeystoreAttestationVerificationAttributes.java b/services/core/java/com/android/server/security/AndroidKeystoreAttestationVerificationAttributes.java new file mode 100644 index 000000000000..3543e9319a88 --- /dev/null +++ b/services/core/java/com/android/server/security/AndroidKeystoreAttestationVerificationAttributes.java @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2022 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 com.android.server.security; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.framework.protobuf.ByteString; +import com.android.internal.org.bouncycastle.asn1.ASN1Boolean; +import com.android.internal.org.bouncycastle.asn1.ASN1Encodable; +import com.android.internal.org.bouncycastle.asn1.ASN1Enumerated; +import com.android.internal.org.bouncycastle.asn1.ASN1InputStream; +import com.android.internal.org.bouncycastle.asn1.ASN1Integer; +import com.android.internal.org.bouncycastle.asn1.ASN1ObjectIdentifier; +import com.android.internal.org.bouncycastle.asn1.ASN1OctetString; +import com.android.internal.org.bouncycastle.asn1.ASN1Sequence; +import com.android.internal.org.bouncycastle.asn1.ASN1Set; +import com.android.internal.org.bouncycastle.asn1.ASN1TaggedObject; +import com.android.internal.org.bouncycastle.asn1.x509.Certificate; + +import java.nio.charset.StandardCharsets; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Parsed {@link X509Certificate} attestation extension values for Android Keystore attestations. + * + * Pull fields out of the top-level sequence. A full description of this structure is at + * https://source.android.com/security/keystore/attestation. + * + * If a value is null or empty, then it was not set/found in the extension values. + * + */ +class AndroidKeystoreAttestationVerificationAttributes { + // The OID for the extension Android Keymaster puts into device-generated certificates. + private static final String ANDROID_KEYMASTER_KEY_DESCRIPTION_EXTENSION_OID = + "1.3.6.1.4.1.11129.2.1.17"; + + // ASN.1 sequence index values for the Android Keymaster extension. + private static final int ATTESTATION_VERSION_INDEX = 0; + private static final int ATTESTATION_SECURITY_LEVEL_INDEX = 1; + private static final int KEYMASTER_VERSION_INDEX = 2; + private static final int KEYMASTER_SECURITY_LEVEL_INDEX = 3; + private static final int ATTESTATION_CHALLENGE_INDEX = 4; + private static final int KEYMASTER_UNIQUE_ID_INDEX = 5; + private static final int SW_ENFORCED_INDEX = 6; + private static final int HW_ENFORCED_INDEX = 7; + private static final int VERIFIED_BOOT_KEY_INDEX = 0; + private static final int VERIFIED_BOOT_LOCKED_INDEX = 1; + private static final int VERIFIED_BOOT_STATE_INDEX = 2; + private static final int VERIFIED_BOOT_HASH_INDEX = 3; + + // ASN.1 sequence index values for the Android Keystore application id. + private static final int PACKAGE_INFO_SET_INDEX = 0; + private static final int PACKAGE_SIGNATURE_SET_INDEX = 1; + private static final int PACKAGE_INFO_NAME_INDEX = 0; + private static final int PACKAGE_INFO_VERSION_INDEX = 1; + + // See these AOSP files: hardware/libhardware/include/hardware/hw_auth_token.h + private static final int HW_AUTH_NONE = 0; + + // Some keymaster constants. See this AOSP file: + // hardware/libhardware/include/hardware/keymaster_defs.h + private static final int KM_TAG_NO_AUTH_REQUIRED = 503; + private static final int KM_TAG_UNLOCKED_DEVICE_REQUIRED = 509; + private static final int KM_TAG_ALL_APPLICATIONS = 600; + private static final int KM_TAG_ROOT_OF_TRUST = 704; + private static final int KM_TAG_OS_VERSION = 705; + private static final int KM_TAG_OS_PATCHLEVEL = 706; + private static final int KM_TAG_ATTESTATION_APPLICATION_ID = 709; + private static final int KM_TAG_ATTESTATION_ID_BRAND = 710; + private static final int KM_TAG_ATTESTATION_ID_DEVICE = 711; + private static final int KM_TAG_ATTESTATION_ID_PRODUCT = 712; + private static final int KM_TAG_VENDOR_PATCHLEVEL = 718; + private static final int KM_TAG_BOOT_PATCHLEVEL = 719; + + private static final int KM_SECURITY_LEVEL_SOFTWARE = 0; + private static final int KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT = 1; + private static final int KM_SECURITY_LEVEL_STRONG_BOX = 2; + private static final int KM_VERIFIED_BOOT_STATE_VERIFIED = 0; + private static final int KM_VERIFIED_BOOT_STATE_SELF_SIGNED = 1; + private static final int KM_VERIFIED_BOOT_STATE_UNVERIFIED = 2; + private static final int KM_VERIFIED_BOOT_STATE_FAILED = 3; + + private Integer mAttestationVersion = null; + private SecurityLevel mAttestationSecurityLevel = null; + private boolean mAttestationHardwareBacked = false; + private Integer mKeymasterVersion = null; + private SecurityLevel mKeymasterSecurityLevel = null; + private boolean mKeymasterHardwareBacked = false; + private ByteString mAttestationChallenge = null; + private ByteString mKeymasterUniqueId = null; + private String mDeviceBrand = null; + private String mDeviceName = null; + private String mDeviceProductName = null; + private boolean mKeyAllowedForAllApplications = false; + private Integer mKeyAuthenticatorType = null; + private Integer mKeyBootPatchLevel = null; + private Integer mKeyOsPatchLevel = null; + private Integer mKeyOsVersion = null; + private Integer mKeyVendorPatchLevel = null; + private Boolean mKeyRequiresUnlockedDevice = null; + private ByteString mVerifiedBootHash = null; + private ByteString mVerifiedBootKey = null; + private Boolean mVerifiedBootLocked = null; + private VerifiedBootState mVerifiedBootState = null; + private Map mApplicationPackageNameVersion = null; + private List mApplicationCertificateDigests = null; + + enum VerifiedBootState { + VERIFIED, + SELF_SIGNED, + UNVERIFIED, + FAILED + } + + enum SecurityLevel { + SOFTWARE, + TRUSTED_ENVIRONMENT, + STRONG_BOX + } + + /** + * Extracts attestation extension properties from {@link X509Certificate} + * and returns a {@link AndroidKeystoreAttestationVerificationAttributes} that encapsulates the + * properties. + */ + @NonNull + static AndroidKeystoreAttestationVerificationAttributes fromCertificate( + @NonNull X509Certificate x509Certificate) + throws Exception { + return new AndroidKeystoreAttestationVerificationAttributes(x509Certificate); + } + + int getAttestationVersion() { + return mAttestationVersion; + } + + @Nullable + SecurityLevel getAttestationSecurityLevel() { + return mAttestationSecurityLevel; + } + + boolean isAttestationHardwareBacked() { + return mAttestationHardwareBacked; + } + + int getKeymasterVersion() { + return mKeymasterVersion; + } + + @Nullable + SecurityLevel getKeymasterSecurityLevel() { + return mKeymasterSecurityLevel; + } + + boolean isKeymasterHardwareBacked() { + return mKeymasterHardwareBacked; + } + + @Nullable + ByteString getAttestationChallenge() { + return mAttestationChallenge; + } + + @Nullable + ByteString getKeymasterUniqueId() { + return mKeymasterUniqueId; + } + + @Nullable + String getDeviceBrand() { + return mDeviceBrand; + } + + @Nullable + String getDeviceName() { + return mDeviceName; + } + + @Nullable + String getDeviceProductName() { + return mDeviceProductName; + } + + boolean isKeyAllowedForAllApplications() { + return mKeyAllowedForAllApplications; + } + + int getKeyAuthenticatorType() { + if (mKeyAuthenticatorType == null) { + throw new IllegalStateException("KeyAuthenticatorType is not set."); + } + return mKeyAuthenticatorType; + } + + int getKeyBootPatchLevel() { + if (mKeyBootPatchLevel == null) { + throw new IllegalStateException("KeyBootPatchLevel is not set."); + } + return mKeyBootPatchLevel; + } + + int getKeyOsPatchLevel() { + if (mKeyOsPatchLevel == null) { + throw new IllegalStateException("KeyOsPatchLevel is not set."); + } + return mKeyOsPatchLevel; + } + + int getKeyVendorPatchLevel() { + if (mKeyVendorPatchLevel == null) { + throw new IllegalStateException("KeyVendorPatchLevel is not set."); + } + return mKeyVendorPatchLevel; + } + + int getKeyOsVersion() { + if (mKeyOsVersion == null) { + throw new IllegalStateException("KeyOsVersion is not set."); + } + return mKeyOsVersion; + } + + boolean isKeyRequiresUnlockedDevice() { + if (mKeyRequiresUnlockedDevice == null) { + throw new IllegalStateException("KeyRequiresUnlockedDevice is not set."); + } + return mKeyRequiresUnlockedDevice; + } + + @Nullable + ByteString getVerifiedBootHash() { + return mVerifiedBootHash; + } + + @Nullable + ByteString getVerifiedBootKey() { + return mVerifiedBootKey; + } + + boolean isVerifiedBootLocked() { + if (mVerifiedBootLocked == null) { + throw new IllegalStateException("VerifiedBootLocked is not set."); + } + return mVerifiedBootLocked; + } + + @Nullable + VerifiedBootState getVerifiedBootState() { + return mVerifiedBootState; + } + + @Nullable + Map getApplicationPackageNameVersion() { + return Collections.unmodifiableMap(mApplicationPackageNameVersion); + } + + @Nullable + List getApplicationCertificateDigests() { + return Collections.unmodifiableList(mApplicationCertificateDigests); + } + + private AndroidKeystoreAttestationVerificationAttributes(X509Certificate x509Certificate) + throws Exception { + Certificate certificate = Certificate.getInstance( + new ASN1InputStream(x509Certificate.getEncoded()).readObject()); + ASN1Sequence keyAttributes = (ASN1Sequence) certificate.getTBSCertificate().getExtensions() + .getExtensionParsedValue( + new ASN1ObjectIdentifier(ANDROID_KEYMASTER_KEY_DESCRIPTION_EXTENSION_OID)); + if (keyAttributes == null) { + throw new CertificateEncodingException( + "No attestation extension found in certificate."); + } + this.mAttestationVersion = getIntegerFromAsn1( + keyAttributes.getObjectAt(ATTESTATION_VERSION_INDEX)); + this.mAttestationSecurityLevel = getSecurityLevelEnum( + keyAttributes.getObjectAt(ATTESTATION_SECURITY_LEVEL_INDEX)); + this.mAttestationHardwareBacked = + this.mAttestationSecurityLevel == SecurityLevel.TRUSTED_ENVIRONMENT; + this.mAttestationChallenge = getOctetsFromAsn1( + keyAttributes.getObjectAt(ATTESTATION_CHALLENGE_INDEX)); + this.mKeymasterVersion = getIntegerFromAsn1( + keyAttributes.getObjectAt(KEYMASTER_VERSION_INDEX)); + this.mKeymasterUniqueId = getOctetsFromAsn1( + keyAttributes.getObjectAt(KEYMASTER_UNIQUE_ID_INDEX)); + this.mKeymasterSecurityLevel = getSecurityLevelEnum( + keyAttributes.getObjectAt(KEYMASTER_SECURITY_LEVEL_INDEX)); + this.mKeymasterHardwareBacked = + this.mKeymasterSecurityLevel == SecurityLevel.TRUSTED_ENVIRONMENT; + + ASN1Encodable[] softwareEnforced = ((ASN1Sequence) + keyAttributes.getObjectAt(SW_ENFORCED_INDEX)).toArray(); + for (ASN1Encodable entry : softwareEnforced) { + ASN1TaggedObject taggedEntry = (ASN1TaggedObject) entry; + switch (taggedEntry.getTagNo()) { + case KM_TAG_ATTESTATION_APPLICATION_ID: + parseAttestationApplicationId( + getOctetsFromAsn1(taggedEntry.getObject()).toByteArray()); + break; + case KM_TAG_UNLOCKED_DEVICE_REQUIRED: + this.mKeyRequiresUnlockedDevice = getBoolFromAsn1(taggedEntry.getObject()); + break; + default: + break; + } + } + + ASN1Encodable[] hardwareEnforced = ((ASN1Sequence) + keyAttributes.getObjectAt(HW_ENFORCED_INDEX)).toArray(); + for (ASN1Encodable entry : hardwareEnforced) { + ASN1TaggedObject taggedEntry = (ASN1TaggedObject) entry; + switch (taggedEntry.getTagNo()) { + case KM_TAG_NO_AUTH_REQUIRED: + this.mKeyAuthenticatorType = HW_AUTH_NONE; + break; + case KM_TAG_ALL_APPLICATIONS: + this.mKeyAllowedForAllApplications = true; + break; + case KM_TAG_ROOT_OF_TRUST: + ASN1Sequence rootOfTrust = (ASN1Sequence) taggedEntry.getObject(); + this.mVerifiedBootKey = + getOctetsFromAsn1(rootOfTrust.getObjectAt(VERIFIED_BOOT_KEY_INDEX)); + this.mVerifiedBootLocked = + getBoolFromAsn1(rootOfTrust.getObjectAt(VERIFIED_BOOT_LOCKED_INDEX)); + this.mVerifiedBootState = + getVerifiedBootStateEnum( + rootOfTrust.getObjectAt(VERIFIED_BOOT_STATE_INDEX)); + // The verified boot hash was added in structure version 3 (Keymaster 4.0). + if (mAttestationVersion >= 3) { + this.mVerifiedBootHash = + getOctetsFromAsn1( + rootOfTrust.getObjectAt(VERIFIED_BOOT_HASH_INDEX)); + } + break; + case KM_TAG_OS_VERSION: + this.mKeyOsVersion = getIntegerFromAsn1(taggedEntry.getObject()); + break; + case KM_TAG_OS_PATCHLEVEL: + this.mKeyOsPatchLevel = getIntegerFromAsn1(taggedEntry.getObject()); + break; + case KM_TAG_ATTESTATION_ID_BRAND: + this.mDeviceBrand = getUtf8FromOctetsFromAsn1(taggedEntry.getObject()); + break; + case KM_TAG_ATTESTATION_ID_DEVICE: + this.mDeviceName = getUtf8FromOctetsFromAsn1(taggedEntry.getObject()); + break; + case KM_TAG_ATTESTATION_ID_PRODUCT: + this.mDeviceProductName = getUtf8FromOctetsFromAsn1(taggedEntry.getObject()); + break; + case KM_TAG_VENDOR_PATCHLEVEL: + this.mKeyVendorPatchLevel = getIntegerFromAsn1(taggedEntry.getObject()); + break; + case KM_TAG_BOOT_PATCHLEVEL: + this.mKeyBootPatchLevel = getIntegerFromAsn1(taggedEntry.getObject()); + break; + default: + break; + } + } + } + + private void parseAttestationApplicationId(byte [] attestationApplicationId) + throws Exception { + ASN1Sequence outerSequence = ASN1Sequence.getInstance( + new ASN1InputStream(attestationApplicationId).readObject()); + Map packageNameVersion = new HashMap<>(); + ASN1Set packageInfoSet = (ASN1Set) outerSequence.getObjectAt(PACKAGE_INFO_SET_INDEX); + for (ASN1Encodable packageInfoEntry : packageInfoSet.toArray()) { + ASN1Sequence packageInfoSequence = (ASN1Sequence) packageInfoEntry; + packageNameVersion.put( + getUtf8FromOctetsFromAsn1( + packageInfoSequence.getObjectAt(PACKAGE_INFO_NAME_INDEX)), + getLongFromAsn1(packageInfoSequence.getObjectAt(PACKAGE_INFO_VERSION_INDEX))); + } + List certificateDigests = new ArrayList<>(); + ASN1Set certificateDigestSet = + (ASN1Set) outerSequence.getObjectAt(PACKAGE_SIGNATURE_SET_INDEX); + for (ASN1Encodable certificateDigestEntry : certificateDigestSet.toArray()) { + certificateDigests.add(getOctetsFromAsn1(certificateDigestEntry)); + } + this.mApplicationPackageNameVersion = Collections.unmodifiableMap(packageNameVersion); + this.mApplicationCertificateDigests = Collections.unmodifiableList(certificateDigests); + + } + + private VerifiedBootState getVerifiedBootStateEnum(ASN1Encodable asn1) { + int verifiedBoot = getEnumFromAsn1(asn1); + switch (verifiedBoot) { + case KM_VERIFIED_BOOT_STATE_VERIFIED: + return VerifiedBootState.VERIFIED; + case KM_VERIFIED_BOOT_STATE_SELF_SIGNED: + return VerifiedBootState.SELF_SIGNED; + case KM_VERIFIED_BOOT_STATE_UNVERIFIED: + return VerifiedBootState.UNVERIFIED; + case KM_VERIFIED_BOOT_STATE_FAILED: + return VerifiedBootState.FAILED; + default: + throw new IllegalArgumentException("Invalid verified boot state."); + } + } + + private SecurityLevel getSecurityLevelEnum(ASN1Encodable asn1) { + int securityLevel = getEnumFromAsn1(asn1); + switch (securityLevel) { + case KM_SECURITY_LEVEL_SOFTWARE: + return SecurityLevel.SOFTWARE; + case KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT: + return SecurityLevel.TRUSTED_ENVIRONMENT; + case KM_SECURITY_LEVEL_STRONG_BOX: + return SecurityLevel.STRONG_BOX; + default: + throw new IllegalArgumentException("Invalid security level."); + } + } + + @NonNull + private ByteString getOctetsFromAsn1(ASN1Encodable asn1) { + return ByteString.copyFrom(((ASN1OctetString) asn1).getOctets()); + } + + @NonNull + private String getUtf8FromOctetsFromAsn1(ASN1Encodable asn1) { + return new String(((ASN1OctetString) asn1).getOctets(), StandardCharsets.UTF_8); + } + + @NonNull + private int getIntegerFromAsn1(ASN1Encodable asn1) { + return ((ASN1Integer) asn1).getValue().intValueExact(); + } + + @NonNull + private long getLongFromAsn1(ASN1Encodable asn1) { + return ((ASN1Integer) asn1).getValue().longValueExact(); + } + + @NonNull + private int getEnumFromAsn1(ASN1Encodable asn1) { + return ((ASN1Enumerated) asn1).getValue().intValueExact(); + } + + @Nullable + private Boolean getBoolFromAsn1(ASN1Encodable asn1) { + if (asn1 instanceof ASN1Boolean) { + return ((ASN1Boolean) asn1).isTrue(); + } + return null; + } +} diff --git a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java index 243efb5e58ce..863f2d1a762d 100644 --- a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java +++ b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java @@ -16,6 +16,7 @@ package com.android.server.security; +import static android.security.attestationverification.AttestationVerificationManager.PROFILE_PEER_DEVICE; import static android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED; import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE; import static android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN; @@ -44,9 +45,11 @@ import com.android.server.SystemService; public class AttestationVerificationManagerService extends SystemService { private static final String TAG = "AVF"; + private final AttestationVerificationPeerDeviceVerifier mPeerDeviceVerifier; - public AttestationVerificationManagerService(final Context context) { + public AttestationVerificationManagerService(final Context context) throws Exception { super(context); + mPeerDeviceVerifier = new AttestationVerificationPeerDeviceVerifier(context); } private final IBinder mService = new IAttestationVerificationManagerService.Stub() { @@ -83,7 +86,7 @@ public class AttestationVerificationManagerService extends SystemService { result.token = null; switch (profile.getAttestationProfileId()) { case PROFILE_SELF_TRUSTED: - Slog.d(TAG, "Verifying Self trusted profile."); + Slog.d(TAG, "Verifying Self Trusted profile."); try { result.resultCode = AttestationVerificationSelfTrustedVerifierForTesting.getInstance() @@ -92,6 +95,11 @@ public class AttestationVerificationManagerService extends SystemService { result.resultCode = RESULT_FAILURE; } break; + case PROFILE_PEER_DEVICE: + Slog.d(TAG, "Verifying Peer Device profile."); + result.resultCode = mPeerDeviceVerifier.verifyAttestation( + localBindingType, requirements, attestation); + break; default: Slog.d(TAG, "No profile found, defaulting."); result.resultCode = RESULT_UNKNOWN; diff --git a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java new file mode 100644 index 000000000000..0f8be5a77944 --- /dev/null +++ b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2022 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 com.android.server.security; + +import static android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE; +import static android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY; +import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE; +import static android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS; +import static android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE; +import static android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY; + +import static com.android.server.security.AndroidKeystoreAttestationVerificationAttributes.VerifiedBootState.VERIFIED; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; + +import org.json.JSONObject; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URL; +import java.security.cert.CertPath; +import java.security.cert.CertPathValidator; +import java.security.cert.CertPathValidatorException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.PKIXCertPathChecker; +import java.security.cert.PKIXParameters; +import java.security.cert.TrustAnchor; +import java.security.cert.X509Certificate; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * Verifies Android key attestation according to the {@code PROFILE_PEER_DEVICE} profile. + * + * Trust anchors are vendor-defined via the vendor_required_attestation_certificates.xml resource. + * The profile is satisfied by checking all the following: + * * TrustAnchor match + * * Certificate validity + * * Android OS 10 or higher + * * Hardware backed key store + * * Verified boot locked + * * Remote Patch level must be within 1 year of local patch if local patch is less than 1 year old. + * + */ +class AttestationVerificationPeerDeviceVerifier { + private static final String TAG = "AVF"; + private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE); + private static final int MAX_PATCH_AGE_MONTHS = 12; + + private final Context mContext; + private final Set mTrustAnchors; + private final boolean mRevocationEnabled; + private final LocalDate mTestSystemDate; + private final LocalDate mTestLocalPatchDate; + private CertificateFactory mCertificateFactory; + private CertPathValidator mCertPathValidator; + + private static void debugVerboseLog(String str, Throwable t) { + if (DEBUG) { + Slog.v(TAG, str, t); + } + } + + private static void debugVerboseLog(String str) { + if (DEBUG) { + Slog.v(TAG, str); + } + } + + AttestationVerificationPeerDeviceVerifier(@NonNull Context context) throws Exception { + mContext = Objects.requireNonNull(context); + mCertificateFactory = CertificateFactory.getInstance("X.509"); + mCertPathValidator = CertPathValidator.getInstance("PKIX"); + mTrustAnchors = getTrustAnchors(); + mRevocationEnabled = true; + mTestSystemDate = null; + mTestLocalPatchDate = null; + } + + // Use ONLY for hermetic unit testing. + @VisibleForTesting + AttestationVerificationPeerDeviceVerifier(@NonNull Context context, + Set trustAnchors, boolean revocationEnabled, + LocalDate systemDate, LocalDate localPatchDate) throws Exception { + mContext = Objects.requireNonNull(context); + mCertificateFactory = CertificateFactory.getInstance("X.509"); + mCertPathValidator = CertPathValidator.getInstance("PKIX"); + mTrustAnchors = trustAnchors; + mRevocationEnabled = revocationEnabled; + mTestSystemDate = systemDate; + mTestLocalPatchDate = localPatchDate; + } + + /** + * Verifies attestation for public key or challenge local binding. + * + * The attestations must be suitable for {@link java.security.cert.CertificateFactory} + * The certificates in the attestation provided must be DER-encoded and may be supplied in + * binary or printable (Base64) encoding. If the certificate is provided in Base64 encoding, + * it must be bounded at the beginning by -----BEGIN CERTIFICATE-----, and must be bounded at + * the end by -----END CERTIFICATE-----. + * + * @param localBindingType Only {@code TYPE_PUBLIC_KEY} and {@code TYPE_CHALLENGE} supported. + * @param requirements Only {@code PARAM_PUBLIC_KEY} and {@code PARAM_CHALLENGE} supported. + * @param attestation Certificates should be DER encoded with leaf certificate appended first. + */ + int verifyAttestation( + int localBindingType, @NonNull Bundle requirements, @NonNull byte[] attestation) { + int status = RESULT_FAILURE; + + if (mCertificateFactory == null) { + debugVerboseLog("Was unable to initialize CertificateFactory onCreate."); + return status; + } + + if (mCertPathValidator == null) { + debugVerboseLog("Was unable to initialize CertPathValidator onCreate."); + return status; + } + + List certificates; + try { + certificates = getCertificates(attestation); + } catch (CertificateException e) { + debugVerboseLog("Unable to parse attestation certificates.", e); + return status; + } + + if (certificates.isEmpty()) { + debugVerboseLog("Attestation contains no certificates."); + return status; + } + + X509Certificate leafNode = certificates.get(0); + if (validateRequirements(localBindingType, requirements) + && validateCertificateChain(certificates) + && checkCertificateAttributes(leafNode, localBindingType, requirements)) { + status = RESULT_SUCCESS; + } else { + status = RESULT_FAILURE; + } + return status; + } + + @NonNull + private List getCertificates(byte[] attestation) + throws CertificateException { + List certificates = new ArrayList<>(); + ByteArrayInputStream bis = new ByteArrayInputStream(attestation); + while (bis.available() > 0) { + certificates.add((X509Certificate) mCertificateFactory.generateCertificate(bis)); + } + + return certificates; + } + + private boolean validateRequirements(int localBindingType, Bundle requirements) { + if (requirements.size() != 1) { + debugVerboseLog("Requirements does not contain exactly 1 key."); + return false; + } + + if (localBindingType != TYPE_PUBLIC_KEY && localBindingType != TYPE_CHALLENGE) { + debugVerboseLog("Binding type is not supported: " + localBindingType); + return false; + } + + if (localBindingType == TYPE_PUBLIC_KEY && !requirements.containsKey(PARAM_PUBLIC_KEY)) { + debugVerboseLog("Requirements does not contain key: " + PARAM_PUBLIC_KEY); + return false; + } + + if (localBindingType == TYPE_CHALLENGE && !requirements.containsKey(PARAM_CHALLENGE)) { + debugVerboseLog("Requirements does not contain key: " + PARAM_CHALLENGE); + return false; + } + + return true; + } + + private boolean validateCertificateChain(List certificates) { + if (certificates.size() < 2) { + debugVerboseLog("Certificate chain less than 2 in size."); + return false; + } + + try { + CertPath certificatePath = mCertificateFactory.generateCertPath(certificates); + PKIXParameters validationParams = new PKIXParameters(mTrustAnchors); + if (mRevocationEnabled) { + // Checks Revocation Status List based on + // https://developer.android.com/training/articles/security-key-attestation#certificate_status + PKIXCertPathChecker checker = new AndroidRevocationStatusListChecker(); + validationParams.addCertPathChecker(checker); + } + // Do not use built-in revocation status checker. + validationParams.setRevocationEnabled(false); + mCertPathValidator.validate(certificatePath, validationParams); + } catch (Throwable t) { + debugVerboseLog("Invalid certificate chain.", t); + return false; + } + return true; + } + + private Set getTrustAnchors() throws CertPathValidatorException { + Set modifiableSet = new HashSet<>(); + try { + for (String certString: getTrustAnchorResources()) { + modifiableSet.add( + new TrustAnchor((X509Certificate) mCertificateFactory.generateCertificate( + new ByteArrayInputStream(getCertificateBytes(certString))), null)); + } + } catch (CertificateException e) { + e.printStackTrace(); + throw new CertPathValidatorException("Invalid trust anchor certificate.", e); + } + return Collections.unmodifiableSet(modifiableSet); + } + + private byte[] getCertificateBytes(String certString) { + String formattedCertString = certString.replaceAll("\\s+", "\n"); + formattedCertString = formattedCertString.replaceAll( + "-BEGIN\\nCERTIFICATE-", "-BEGIN CERTIFICATE-"); + formattedCertString = formattedCertString.replaceAll( + "-END\\nCERTIFICATE-", "-END CERTIFICATE-"); + return formattedCertString.getBytes(UTF_8); + } + + private String[] getTrustAnchorResources() { + return mContext.getResources().getStringArray( + R.array.vendor_required_attestation_certificates); + } + + private boolean checkCertificateAttributes( + X509Certificate leafCertificate, int localBindingType, Bundle requirements) { + AndroidKeystoreAttestationVerificationAttributes attestationAttributes; + try { + attestationAttributes = + AndroidKeystoreAttestationVerificationAttributes.fromCertificate( + leafCertificate); + } catch (Throwable t) { + debugVerboseLog("Could not get ParsedAttestationAttributes from Certificate.", t); + return false; + } + + // Checks for support of Keymaster 4. + if (attestationAttributes.getAttestationVersion() < 3) { + debugVerboseLog("Attestation version is not at least 3 (Keymaster 4)."); + return false; + } + + // Checks for support of Keymaster 4. + if (attestationAttributes.getKeymasterVersion() < 4) { + debugVerboseLog("Keymaster version is not at least 4."); + return false; + } + + // First two characters are Android OS version. + if (attestationAttributes.getKeyOsVersion() < 100000) { + debugVerboseLog("Android OS version is not 10+."); + return false; + } + + if (!attestationAttributes.isAttestationHardwareBacked()) { + debugVerboseLog("Key is not HW backed."); + return false; + } + + if (!attestationAttributes.isKeymasterHardwareBacked()) { + debugVerboseLog("Keymaster is not HW backed."); + return false; + } + + if (attestationAttributes.getVerifiedBootState() != VERIFIED) { + debugVerboseLog("Boot state not Verified."); + return false; + } + + try { + if (!attestationAttributes.isVerifiedBootLocked()) { + debugVerboseLog("Verified boot state is not locked."); + return false; + } + } catch (IllegalStateException e) { + debugVerboseLog("VerifiedBootLocked is not set.", e); + return false; + } + + // Patch level integer YYYYMM is expected to be within 1 year of today. + if (!isValidPatchLevel(attestationAttributes.getKeyOsPatchLevel())) { + debugVerboseLog("OS patch level is not within valid range."); + return false; + } + + // Patch level integer YYYYMMDD is expected to be within 1 year of today. + if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) { + debugVerboseLog("Boot patch level is not within valid range."); + return false; + } + + if (!isValidPatchLevel(attestationAttributes.getKeyVendorPatchLevel())) { + debugVerboseLog("Vendor patch level is not within valid range."); + return false; + } + + if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) { + debugVerboseLog("Boot patch level is not within valid range."); + return false; + } + + // Verify leaf public key matches provided public key. + if (localBindingType == TYPE_PUBLIC_KEY + && !Arrays.equals(requirements.getByteArray(PARAM_PUBLIC_KEY), + leafCertificate.getPublicKey().getEncoded())) { + debugVerboseLog("Provided public key does not match leaf certificate public key."); + return false; + } + + // Verify challenge matches provided challenge. + if (localBindingType == TYPE_CHALLENGE + && !Arrays.equals(requirements.getByteArray(PARAM_CHALLENGE), + attestationAttributes.getAttestationChallenge().toByteArray())) { + debugVerboseLog("Provided challenge does not match leaf certificate challenge."); + return false; + } + + return true; + } + + /** + * Validates patchLevel passed is within range of the local device patch date if local patch is + * not over one year old. Since the time can be changed on device, just checking the patch date + * is not enough. Therefore, we also confirm the patch level for the remote and local device are + * similar. + */ + private boolean isValidPatchLevel(int patchLevel) { + LocalDate currentDate = mTestSystemDate != null + ? mTestSystemDate : LocalDate.now(ZoneId.systemDefault()); + + // Convert local patch date to LocalDate. + LocalDate localPatchDate; + try { + if (mTestLocalPatchDate != null) { + localPatchDate = mTestLocalPatchDate; + } else { + localPatchDate = LocalDate.parse(Build.VERSION.SECURITY_PATCH); + } + } catch (Throwable t) { + debugVerboseLog("Build.VERSION.SECURITY_PATCH: " + + Build.VERSION.SECURITY_PATCH + " is not in format YYYY-MM-DD"); + return false; + } + + // Check local patch date is not in last year of system clock. + if (ChronoUnit.MONTHS.between(localPatchDate, currentDate) > MAX_PATCH_AGE_MONTHS) { + return true; + } + + // Convert remote patch dates to LocalDate. + String remoteDeviceDateStr = String.valueOf(patchLevel); + if (remoteDeviceDateStr.length() != 6 && remoteDeviceDateStr.length() != 8) { + debugVerboseLog("Patch level is not in format YYYYMM or YYYYMMDD"); + return false; + } + + int patchYear = Integer.parseInt(remoteDeviceDateStr.substring(0, 4)); + int patchMonth = Integer.parseInt(remoteDeviceDateStr.substring(4, 6)); + LocalDate remotePatchDate = LocalDate.of(patchYear, patchMonth, 1); + + // Check patch dates are within 1 year of each other + boolean IsRemotePatchWithinOneYearOfLocalPatch; + if (remotePatchDate.compareTo(localPatchDate) > 0) { + IsRemotePatchWithinOneYearOfLocalPatch = ChronoUnit.MONTHS.between( + localPatchDate, remotePatchDate) <= MAX_PATCH_AGE_MONTHS; + } else if (remotePatchDate.compareTo(localPatchDate) < 0) { + IsRemotePatchWithinOneYearOfLocalPatch = ChronoUnit.MONTHS.between( + remotePatchDate, localPatchDate) <= MAX_PATCH_AGE_MONTHS; + } else { + IsRemotePatchWithinOneYearOfLocalPatch = true; + } + + return IsRemotePatchWithinOneYearOfLocalPatch; + } + + /** + * Checks certificate revocation status. + * + * Queries status list from android.googleapis.com/attestation/status and checks for + * the existence of certificate's serial number. If serial number exists in map, then fail. + */ + private final class AndroidRevocationStatusListChecker extends PKIXCertPathChecker { + private static final String TOP_LEVEL_JSON_PROPERTY_KEY = "entries"; + private static final String STATUS_PROPERTY_KEY = "status"; + private static final String REASON_PROPERTY_KEY = "reason"; + private String mStatusUrl; + private JSONObject mJsonStatusMap; + + @Override + public void init(boolean forward) throws CertPathValidatorException { + mStatusUrl = getRevocationListUrl(); + if (mStatusUrl == null || mStatusUrl.isEmpty()) { + throw new CertPathValidatorException( + "R.string.vendor_required_attestation_revocation_list_url is empty."); + } + // TODO(b/221067843): Update to only pull status map on non critical path and if + // out of date (24hrs). + mJsonStatusMap = getStatusMap(mStatusUrl); + } + + @Override + public boolean isForwardCheckingSupported() { + return false; + } + + @Override + public Set getSupportedExtensions() { + return null; + } + + @Override + public void check(Certificate cert, Collection unresolvedCritExts) + throws CertPathValidatorException { + X509Certificate x509Certificate = (X509Certificate) cert; + // The json key is the certificate's serial number converted to lowercase hex. + String serialNumber = x509Certificate.getSerialNumber().toString(16); + + if (serialNumber == null) { + throw new CertPathValidatorException("Certificate serial number can not be null."); + } + + if (mJsonStatusMap.has(serialNumber)) { + JSONObject revocationStatus; + String status; + String reason; + try { + revocationStatus = mJsonStatusMap.getJSONObject(serialNumber); + status = revocationStatus.getString(STATUS_PROPERTY_KEY); + reason = revocationStatus.getString(REASON_PROPERTY_KEY); + } catch (Throwable t) { + throw new CertPathValidatorException("Unable get properties for certificate " + + "with serial number " + serialNumber); + } + throw new CertPathValidatorException( + "Invalid certificate with serial number " + serialNumber + + " has status " + status + + " because reason " + reason); + } + } + + private JSONObject getStatusMap(String stringUrl) throws CertPathValidatorException { + URL url; + try { + url = new URL(stringUrl); + } catch (Throwable t) { + throw new CertPathValidatorException( + "Unable to get revocation status from " + mStatusUrl, t); + } + + try (InputStream inputStream = url.openStream()) { + JSONObject statusListJson = new JSONObject( + new String(inputStream.readAllBytes(), UTF_8)); + return statusListJson.getJSONObject(TOP_LEVEL_JSON_PROPERTY_KEY); + } catch (Throwable t) { + throw new CertPathValidatorException( + "Unable to parse revocation status from " + mStatusUrl, t); + } + } + + private String getRevocationListUrl() { + return mContext.getResources().getString( + R.string.vendor_required_attestation_revocation_list_url); + } + } +} diff --git a/tests/AttestationVerificationTest/Android.bp b/tests/AttestationVerificationTest/Android.bp index a4741eedaac0..b98f8cb0c21d 100644 --- a/tests/AttestationVerificationTest/Android.bp +++ b/tests/AttestationVerificationTest/Android.bp @@ -40,5 +40,6 @@ android_test { "androidx.test.rules", "androidx.test.ext.junit", "platform-test-annotations", + "services.core", ], } diff --git a/tests/AttestationVerificationTest/AndroidManifest.xml b/tests/AttestationVerificationTest/AndroidManifest.xml index c42bde9dca3a..37321ad80b0f 100755 --- a/tests/AttestationVerificationTest/AndroidManifest.xml +++ b/tests/AttestationVerificationTest/AndroidManifest.xml @@ -24,6 +24,7 @@ + diff --git a/tests/AttestationVerificationTest/assets/test_attestation_with_root_certs.pem b/tests/AttestationVerificationTest/assets/test_attestation_with_root_certs.pem new file mode 100644 index 000000000000..e29ff487806e --- /dev/null +++ b/tests/AttestationVerificationTest/assets/test_attestation_with_root_certs.pem @@ -0,0 +1,81 @@ +-----BEGIN CERTIFICATE----- +MIICkjCCAjmgAwIBAgIBATAKBggqhkjOPQQDAjA5MQwwCgYDVQQMDANURUUxKTAn +BgNVBAUTIDg2ZTQ0MjRhMjY2NDlhZDcyZWZhNWM0MWEwM2IyN2QxMCAXDTcwMDEw +MTAwMDAwMFoYDzIxMDYwMjA3MDYyODE1WjAfMR0wGwYDVQQDDBRBbmRyb2lkIEtl +eXN0b3JlIEtleTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIlTwcvhe+DLV45X +RCTO7HoN20Ib7IbCEhV5+YdMiYOp/0AdKk8oYvsri1XODeC4zcoPfHNdQGt/68i0 +ADbilJmjggFIMIIBRDAOBgNVHQ8BAf8EBAMCB4AwggEwBgorBgEEAdZ5AgERBIIB +IDCCARwCAQMKAQECAQQKAQEECXBsYXllcjQ1NgQAMFe/hT0IAgYBfvkgVei/hUVH +BEUwQzEdMBsEFmNvbS5nb29nbGUuYXR0ZXN0YXRpb24CAQExIgQgOqyVXRJUdAGY +/XVx8y/uRPiebqlyELt1EpqIz29h5tUwgaehCDEGAgECAgEDogMCAQOjBAICAQCl +CDEGAgEEAgEGqgMCAQG/g3cCBQC/hT4DAgEAv4VATDBKBCCEZx8qY8Ys0HC2TqPq +74eYPzh5L/agxD7Bn7zVBQHoNAEB/woBAAQguJwoDfWBjRaedzQ6TJPFJJKs+ytr ++8Vu2CSmqifFBHW/hUEFAgMB1MC/hUIFAgMDFdm/hU4GAgQBNIjJv4VPBgIEATSI +yTAKBggqhkjOPQQDAgNHADBEAiBdGxfMEx59k5+zo+hV3Q9kgjbGi0zU3WH355P5 +JZttBwIgY4FZsSreUJL8RY3JvfvD8BRw8GuXcB1OQ600hwaYYC4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB8zCCAXqgAwIBAgIRAOuuukN0OHbNQvKngECkewEwCgYIKoZIzj0EAwIwOTEM +MAoGA1UEDAwDVEVFMSkwJwYDVQQFEyA3MDkxMmRmNDYxMDRmYWFlOTQ3ODY0ZTU4 +MDRmMWY4ZDAeFw0yMDA5MjgyMDI3NTZaFw0zMDA5MjYyMDI3NTZaMDkxDDAKBgNV +BAwMA1RFRTEpMCcGA1UEBRMgODZlNDQyNGEyNjY0OWFkNzJlZmE1YzQxYTAzYjI3 +ZDEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT3Mjl05ewv6G8zAR4fXJy2iadU +yK7rNvzlECy2+nhEieL8BFXDvo0tx5fYs8qr67j/KvluFBfp2r9s+ckWz3Kzo2Mw +YTAdBgNVHQ4EFgQUsVKBzAs1lMXAauQ3mGAJZJqK5tAwHwYDVR0jBBgwFoAUEsQA +i8d2oLULSi5Ix4BTGGbvUEkwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AgQwCgYIKoZIzj0EAwIDZwAwZAIwfFziBCwuM1thLUSUNI61Xx/vnDnNkSv/aX5D +yLjxbLlgnFSzIrc+6vf6h6L/+TjYAjAq6h9GKtMn4R0286MoqYqzp/rHn6JD2sqH +iM8KZ0oA+Ut242EcmGjAoNfGZGZGddQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDkzCCAXugAwIBAgIQNTAX5z3CBac6nD3hQiMDcDANBgkqhkiG9w0BAQsFADAb +MRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MB4XDTIwMDkyODIwMjUwMloXDTMw +MDkyNjIwMjUwMlowOTEMMAoGA1UEDAwDVEVFMSkwJwYDVQQFEyA3MDkxMmRmNDYx +MDRmYWFlOTQ3ODY0ZTU4MDRmMWY4ZDB2MBAGByqGSM49AgEGBSuBBAAiA2IABA/7 +xZFlFtTjdy2B3p7E+FsrBjyhBSqY4a9FywawXMJRSja3HAK36ruzJjWlEkD+D0vq +HI2joY39FHmWoZWwm2cq9gOleFGYOSCpMr4ib7xtq/6nefvKTP5rutxudF97t6Nj +MGEwHQYDVR0OBBYEFBLEAIvHdqC1C0ouSMeAUxhm71BJMB8GA1UdIwQYMBaAFDZh +4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgIEMA0GCSqGSIb3DQEBCwUAA4ICAQAaMONDQxJz3PRn9gHQW5KP+TIoBPJZyGa1 +QFuEBcMDTtIxBxEh5Pj3ivPBc76PrdYu5U47Ve5YYCPsTpUTj7dOxbzGSZjfjvHF +fNwy24g1Lah2iAdQRVErhWKBlpnQhBnnRrrNmTTmzhl8NvSExqAPP746dqwm1kQ7 +YesC5yoEAHpxamhlZpIKAjSxSZeHWace2qV00M8qWd/7lIpqttJjFFrhCjzR0dtr +oIIpC5EtmqIWdLeg6yZjJkX+Cjv4F8mRfBtwuNuxFsfALQ3D5l8WKw3iwPebmCy1 +kEby8Eoq88FxzXQp/XgAaljlrKXyuxptrc1noRuob4g42Oh6wetueYRSCtO6Bkym +0UMnld/kG77aeiHOMVVb86wrhNuAGir1vgDGOBsclITVyuu9ka0YVQjjDm3phTpd +O8JV16gbei2Phn+FfRV1MSDsZo/wu0i2KVzgs27bfJocMHXv+GzvwfefYgMJ/rYq +Bg27lpsWzmFEPv2cyhA5PwwbG8ceswa3RZE/2eS9o7STkz93jr/KsKLcMBY6cX2C +q4CBJByKFJtVANOVj+neFNxc2sQgeTT33yYNKbe4b5bm7Ki1FbrhFVckpzUGDnKs +gL+AxvALWOoryDGwNbJiW8PRiD3HHByiMvSEQ7e7BSc2KjbsaWbCfYZAMZJEhEsc +P1l8lcUVuA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFHDCCAwSgAwIBAgIJANUP8luj8tazMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV +BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTkxMTIyMjAzNzU4WhcNMzQxMTE4MjAz +NzU4WjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS +Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7 +tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj +nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq +C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ +oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O +JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg +sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi +igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M +RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E +aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um +AGMCAwEAAaNjMGEwHQYDVR0OBBYEFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMB8GA1Ud +IwQYMBaAFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4ICAQBOMaBc8oumXb2voc7XCWnu +XKhBBK3e2KMGz39t7lA3XXRe2ZLLAkLM5y3J7tURkf5a1SutfdOyXAmeE6SRo83U +h6WszodmMkxK5GM4JGrnt4pBisu5igXEydaW7qq2CdC6DOGjG+mEkN8/TA6p3cno +L/sPyz6evdjLlSeJ8rFBH6xWyIZCbrcpYEJzXaUOEaxxXxgYz5/cTiVKN2M1G2ok +QBUIYSY6bjEL4aUN5cfo7ogP3UvliEo3Eo0YgwuzR2v0KR6C1cZqZJSTnghIC/vA +D32KdNQ+c3N+vl2OTsUVMC1GiWkngNx1OO1+kXW+YTnnTUOtOIswUP/Vqd5SYgAI +mMAfY8U9/iIgkQj6T2W6FsScy94IN9fFhE1UtzmLoBIuUFsVXJMTz+Jucth+IqoW +Fua9v1R93/k98p41pjtFX+H8DslVgfP097vju4KDlqN64xV1grw3ZLl4CiOe/A91 +oeLm2UHOq6wn3esB4r2EIQKb6jTVGu5sYCcdWpXr0AUVqcABPdgL+H7qJguBw09o +jm6xNIrw2OocrDKsudk/okr/AwqEyPKw9WnMlQgLIKw1rODG2NvU9oR3GVGdMkUB +ZutL8VuFkERQGt6vQ2OCw0sV47VMkuYbacK/xyZFiRcrPJPb41zgbQj9XAEyLKCH +ex0SdDrx+tWUDqG8At2JHA== +-----END CERTIFICATE----- diff --git a/tests/AttestationVerificationTest/assets/test_attestation_wrong_root_certs.pem b/tests/AttestationVerificationTest/assets/test_attestation_wrong_root_certs.pem new file mode 100644 index 000000000000..3d6410af042a --- /dev/null +++ b/tests/AttestationVerificationTest/assets/test_attestation_wrong_root_certs.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIGCDCCBHCgAwIBAgIBATANBgkqhkiG9w0BAQsFADApMRkwFwYDVQQFExAyZGM1OGIyZDFhMjQx +MzI2MQwwCgYDVQQMDANURUUwIBcNNzAwMTAxMDAwMDAwWhgPMjEwNjAyMDcwNjI4MTVaMB8xHTAb +BgNVBAMMFEFuZHJvaWQgS2V5c3RvcmUgS2V5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEApNVcnyN40MANMbbo2nMGNq2NNysDSjfLm0W3i6wPKf0ffCYkhWM4dCmQKKf50uAZTBeTit4c +NwXeZn3qellMlOsIN3Qc384rfN/8cikrRvUAgibz0Jy7STykjwa7x6tKwqITxbO8HqAhKo8/BQXU +xzrOdIg5ciy+UM7Vgh7a7ogen0KL2iGgrsalb1ti7Vlzb6vIJ4WzIC3TGD2sCkoPahghwqFDZZCo +/FzaLoNY0jAUX2mL+kf8aUaoxz7xA9FTvgara+1pLBR1s4c8xPS2HdZipcVXWfey0wujv1VAKs4+ +tXjKlHkYBHBBceEjxUtEmrapSQEdpHPv7Xh9Uanq4QIDAQABo4ICwTCCAr0wDgYDVR0PAQH/BAQD +AgeAMIICqQYKKwYBBAHWeQIBEQSCApkwggKVAgEDCgEBAgEECgEBBANhYmMEADCCAc2/hT0IAgYB +ZOYGEYe/hUWCAbsEggG3MIIBszGCAYswDAQHYW5kcm9pZAIBHTAZBBRjb20uYW5kcm9pZC5rZXlj +aGFpbgIBHTAZBBRjb20uYW5kcm9pZC5zZXR0aW5ncwIBHTAZBBRjb20ucXRpLmRpYWdzZXJ2aWNl +cwIBHTAaBBVjb20uYW5kcm9pZC5keW5zeXN0ZW0CAR0wHQQYY29tLmFuZHJvaWQuaW5wdXRkZXZp +Y2VzAgEdMB8EGmNvbS5hbmRyb2lkLmxvY2FsdHJhbnNwb3J0AgEdMB8EGmNvbS5hbmRyb2lkLmxv +Y2F0aW9uLmZ1c2VkAgEdMB8EGmNvbS5hbmRyb2lkLnNlcnZlci50ZWxlY29tAgEdMCAEG2NvbS5h +bmRyb2lkLndhbGxwYXBlcmJhY2t1cAIBHTAhBBxjb20uZ29vZ2xlLlNTUmVzdGFydERldGVjdG9y +AgEdMCIEHWNvbS5nb29nbGUuYW5kcm9pZC5oaWRkZW5tZW51AgEBMCMEHmNvbS5hbmRyb2lkLnBy +b3ZpZGVycy5zZXR0aW5ncwIBHTEiBCAwGqPLCBE0UBxF8UIqvGbCQiT9Xe1f3I8X5pcXb9hmqjCB +rqEIMQYCAQICAQOiAwIBAaMEAgIIAKUFMQMCAQSmCDEGAgEDAgEFv4FIBQIDAQABv4N3AgUAv4U+ +AwIBAL+FQEwwSgQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQAKAQIEIHKNsSdP +HxzxVx3kOAsEilVKxKOA529TVQg1KQhKk3gBv4VBAwIBAL+FQgUCAwMUs7+FTgUCAwMUs7+FTwUC +AwMUszANBgkqhkiG9w0BAQsFAAOCAYEAJMIuzdNUdfrE6sIdmsnMn/scSG2odbphj8FkX9JGdF2S +OT599HuDY9qhvkru2Dza4sLKK3f4ViBhuR9lpfeprKvstxbtBO7jkLYfVn0ZRzHRHVEyiW5IVKh+ +qOXVJ9S1lMShOTlsaYJytLKIlcrRAZBEXZiNbzTuVh1CH6X9Ni1dog14snm+lcOeORdL9fht2CHa +u/caRnpWiZbjoAoJp0O89uBrRkXPpln51+3jPY6AFny30grNAvKguauDcPPhNV1yR+ylSsQi2gm3 +Rs4pgtlxFLMfZLgT0cbkl+9zk/QUqlpBP8ftUBsOI0ARr8xhFN3cvq9kXGLtJ9hEP9PRaflAFREk +DK3IBIbVcAFZBFoAQOdE9zy0+F5bQrznPGaZg4Dzhcx33qMDUTgHtWoy+k3ePGQMEtmoTTLgQywW +OIkXEoFqqGi9GKJXUT1KYi5NsigaYqu7FoN4Qsvs61pMUEfZSPP2AFwkA8uNFbmb9uxcxaGHCA8i +3i9VM6yOLIrP +-----END CERTIFICATE----- diff --git a/tests/AttestationVerificationTest/assets/test_no_attestation_ext_certs.pem b/tests/AttestationVerificationTest/assets/test_no_attestation_ext_certs.pem new file mode 100644 index 000000000000..6d261fae47cf --- /dev/null +++ b/tests/AttestationVerificationTest/assets/test_no_attestation_ext_certs.pem @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFoDCCA4igAwIBAgIQTfpKgAsLZJhp2V4xUriMADANBgkqhkiG9w0BAQ0FADBp +MQswCQYDVQQGEwJVUzEUMBIGA1UECgwLR29vZ2xlIEluYy4xFzAVBgNVBAsMDkFu +ZHJvaWQgVGhpbmdzMSswKQYDVQQDDCJBbmRyb2lkIFRoaW5ncyBBdHRlc3RhdGlv +biBSb290IENBMCAXDTE3MDYyMTIwMjQzN1oYDzIwNTcwNjExMjAyNDM3WjBpMQsw +CQYDVQQGEwJVUzEUMBIGA1UECgwLR29vZ2xlIEluYy4xFzAVBgNVBAsMDkFuZHJv +aWQgVGhpbmdzMSswKQYDVQQDDCJBbmRyb2lkIFRoaW5ncyBBdHRlc3RhdGlvbiBS +b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuO82oerGivb9 +G9bWyM8Pg0y6SOnAC8/8b92dp1v4Npnc+QpjPRUKgn8lzjQ9Jo6IGY3OShRBiQYl +bbZYkfJnC5HtqbOETdPLZclErVE/G6Oda1IeZWvQVMjNImEYOLL5ct2RxiPttd8v +SLyOSNFPf5/SeFqX/La0NcmXMOvPSrTW3qO34brnC+ih7mlpJFLz6Up93N3Umxsl +IElz2wCG72t6k3+caWLyIPVgIPmsQrfTeBK/hN5dAJgAN65BsTevLHRP9J610wj3 +RagSIK1NdTuJRnr5ZyTQrwE2nA8H3IJ7/eo6IlGhXPwLKDhbdxYygPxdlCq6Rl96 +aVLjfpqDPtJ9ow+QKZuEDbYJ4z4olNXC6O5G7vqnCuULA/2E7y7DZObjzXOrdx2z +9YKd8BrIDMTN/5mmw2us8tywiaQhbl8vOtjU+A+iBBnkj/wt9TYyLKopdrDlo5mz +wy5l750HOkVZXC3VkeECnp+9duSHdS4qeUf/W1j9nPM7kY0HFLPUVX9AFVp2JXnC +iKZC32GQAVsDc1iyAZWAVTqA7E0fBHhk9jUnA0W9b5Lq06oW95ngNR1MIFY871i8 +aLHCBpIix8DuMe8NB9spCIP6WCQqGiWQQpzbeuBPtoi424xwZTO4oectTd77bs9V +Rvunn49fz308KnoWjk/by1N7gWyTb38CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMDQ1I0RKwFCI+Fy9uIIJ/HrXuqu +MA0GCSqGSIb3DQEBDQUAA4ICAQB09qkyEpEDocdN5pPeXqtjj9d0AXREUGH2LhnC +z9KZcUFR+JskwEMHCaOENOmKI3zWRmxT7d8cVywwGk+ExE7EBQoeHlh3Yo44M8aj +ZL7RHCvHRYsePhAJkYpJ02IMR60TV+1jhMqE8+BnqFivS7kft4t295EyrnLRZE3b +Nfc0t011j02RwUrioR57mdvS9EZBRnMQkobhn+jWt9O+V3mtplW+1A2n4ec6uni1 +2MMgAWHuO1sKVYd5Sp4JMUpNnfmQAMnNiOMF6VxkpaoF1lZWo4TrLxuDKJG3O8h1 +fByjCpNVY8kOvvYEadbldzh6Agy/3ppb9yfG7X7FtHr1ghNjuNT6w5VgvbRtoRja +/ZSKuJMaKm5emMWNkls/cwVSPJIvTOzPTeYK1BKSyAL2LDJ93HI7x8h79/Q7gKRi +kL8qT7GW2FqpWTK0253sJHqCJJP4A5Rxtf2+Afwqadfc6Ga4jJHb7rPXngz4j1ZB +gl5yjXgWF9wHGxqrjKWe2EA3d47BC4HG3Rf5L56KQiRPhTqTk5vtZwtwLRLFDLt7 +Hdff13O1oLhn+2z9xkASUL3rFE/qWajZP7fk3CvzcuXwKDTZomIC4nNaglx4nLdj +lHhOq+6ON8MZC46sLStD+D4a9A1HOoihJgI/yGGkwdrp4KQIveRkEBO/x9v3NNBE +bMwG9w== +-----END CERTIFICATE----- diff --git a/tests/AttestationVerificationTest/assets/test_root_certs.pem b/tests/AttestationVerificationTest/assets/test_root_certs.pem new file mode 100644 index 000000000000..c51851fe3da5 --- /dev/null +++ b/tests/AttestationVerificationTest/assets/test_root_certs.pem @@ -0,0 +1,61 @@ +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIJAOj6GWMU0voYMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV +BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTYwNTI2MTYyODUyWhcNMjYwNTI0MTYy +ODUyWjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS +Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7 +tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj +nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq +C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ +oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O +JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg +sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi +igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M +RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E +aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um +AGMCAwEAAaOBpjCBozAdBgNVHQ4EFgQUNmHhAHyIBQlRi0RsR/8aTMnqTxIwHwYD +VR0jBBgwFoAUNmHhAHyIBQlRi0RsR/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAYYwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cHM6Ly9hbmRyb2lk +Lmdvb2dsZWFwaXMuY29tL2F0dGVzdGF0aW9uL2NybC8wDQYJKoZIhvcNAQELBQAD +ggIBACDIw41L3KlXG0aMiS//cqrG+EShHUGo8HNsw30W1kJtjn6UBwRM6jnmiwfB +Pb8VA91chb2vssAtX2zbTvqBJ9+LBPGCdw/E53Rbf86qhxKaiAHOjpvAy5Y3m00m +qC0w/Zwvju1twb4vhLaJ5NkUJYsUS7rmJKHHBnETLi8GFqiEsqTWpG/6ibYCv7rY +DBJDcR9W62BW9jfIoBQcxUCUJouMPH25lLNcDc1ssqvC2v7iUgI9LeoM1sNovqPm +QUiG9rHli1vXxzCyaMTjwftkJLkf6724DFhuKug2jITV0QkXvaJWF4nUaHOTNA4u +JU9WDvZLI1j83A+/xnAJUucIv/zGJ1AMH2boHqF8CY16LpsYgBt6tKxxWH00XcyD +CdW2KlBCeqbQPcsFmWyWugxdcekhYsAWyoSf818NUsZdBWBaR/OukXrNLfkQ79Iy +ZohZbvabO/X+MVT3rriAoKc8oE2Uws6DF+60PV7/WIPjNvXySdqspImSN78mflxD +qwLqRBYkA3I75qppLGG9rp7UCdRjxMl8ZDBld+7yvHVgt1cVzJx9xnyGCC23Uaic +MDSXYrB4I4WHXPGjxhZuCuPBLTdOLU8YRvMYdEvYebWHMpvwGCF6bAx3JBpIeOQ1 +wDB5y0USicV3YgYGmi+NZfhA4URSh77Yd6uuJOJENRaNVTzk +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFHDCCAwSgAwIBAgIJANUP8luj8tazMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV +BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTkxMTIyMjAzNzU4WhcNMzQxMTE4MjAz +NzU4WjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS +Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7 +tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj +nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq +C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ +oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O +JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg +sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi +igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M +RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E +aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um +AGMCAwEAAaNjMGEwHQYDVR0OBBYEFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMB8GA1Ud +IwQYMBaAFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4ICAQBOMaBc8oumXb2voc7XCWnu +XKhBBK3e2KMGz39t7lA3XXRe2ZLLAkLM5y3J7tURkf5a1SutfdOyXAmeE6SRo83U +h6WszodmMkxK5GM4JGrnt4pBisu5igXEydaW7qq2CdC6DOGjG+mEkN8/TA6p3cno +L/sPyz6evdjLlSeJ8rFBH6xWyIZCbrcpYEJzXaUOEaxxXxgYz5/cTiVKN2M1G2ok +QBUIYSY6bjEL4aUN5cfo7ogP3UvliEo3Eo0YgwuzR2v0KR6C1cZqZJSTnghIC/vA +D32KdNQ+c3N+vl2OTsUVMC1GiWkngNx1OO1+kXW+YTnnTUOtOIswUP/Vqd5SYgAI +mMAfY8U9/iIgkQj6T2W6FsScy94IN9fFhE1UtzmLoBIuUFsVXJMTz+Jucth+IqoW +Fua9v1R93/k98p41pjtFX+H8DslVgfP097vju4KDlqN64xV1grw3ZLl4CiOe/A91 +oeLm2UHOq6wn3esB4r2EIQKb6jTVGu5sYCcdWpXr0AUVqcABPdgL+H7qJguBw09o +jm6xNIrw2OocrDKsudk/okr/AwqEyPKw9WnMlQgLIKw1rODG2NvU9oR3GVGdMkUB +ZutL8VuFkERQGt6vQ2OCw0sV47VMkuYbacK/xyZFiRcrPJPb41zgbQj9XAEyLKCH +ex0SdDrx+tWUDqG8At2JHA== +-----END CERTIFICATE----- diff --git a/tests/AttestationVerificationTest/assets/test_virtual_device_attestation_certs.pem b/tests/AttestationVerificationTest/assets/test_virtual_device_attestation_certs.pem new file mode 100644 index 000000000000..282771000bf7 --- /dev/null +++ b/tests/AttestationVerificationTest/assets/test_virtual_device_attestation_certs.pem @@ -0,0 +1,50 @@ +-----BEGIN CERTIFICATE----- +MIIC7DCCApGgAwIBAgIBATAKBggqhkjOPQQDAjCBiDELMAkGA1UEBhMCVVMxEzAR +BgNVBAgMCkNhbGlmb3JuaWExFTATBgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UE +CwwHQW5kcm9pZDE7MDkGA1UEAwwyQW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBB +dHRlc3RhdGlvbiBJbnRlcm1lZGlhdGUwHhcNNzAwMTAxMDAwMDAwWhcNNjkxMjMx +MjM1OTU5WjAfMR0wGwYDVQQDDBRBbmRyb2lkIEtleXN0b3JlIEtleTBZMBMGByqG +SM49AgEGCCqGSM49AwEHA0IABEYtCH28qu+St0F0TixVsQz0L/Y7DcRHgYAU98E6 +edwOpACFmmseYxMjvmZv/4jURSG2/Z0J1s3A/qFzIY96/tyjggFSMIIBTjALBgNV +HQ8EBAMCB4AwggEcBgorBgEEAdZ5AgERBIIBDDCCAQgCAQQKAQACASkKAQAECXBs +YXllcjQ1NgQAMIHqoQgxBgIBAgIBA6IDAgEDowQCAgEApQgxBgIBBAIBBqoDAgEB +v4N3AgUAv4U9CAIGAX8DoY9Qv4U+AwIBAL+FQEwwSgQgAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAABAQAKAQIEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAv4VBBQIDAa2wv4VCBQIDAxUbv4VFRwRFMEMxHTAbBBZjb20uZ29v +Z2xlLmF0dGVzdGF0aW9uAgEBMSIEIDqslV0SVHQBmP11cfMv7kT4nm6pchC7dRKa +iM9vYebVMAAwHwYDVR0jBBgwFoAUP/ys1hqxOp6BILjVJRzFZbsekakwCgYIKoZI +zj0EAwIDSQAwRgIhAMzs7gWWBIITpeLeEEx9B8ihdhkFqpMGlsYLRO01ZIOeAiEA +uKs9xfK3fIOpVAhDmsrp+zE8KUwyvqCU/IS13tXz7Ng= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICeDCCAh6gAwIBAgICEAEwCgYIKoZIzj0EAwIwgZgxCzAJBgNVBAYTAlVTMRMw +EQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRUwEwYD +VQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQxMzAxBgNVBAMMKkFu +ZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gUm9vdDAeFw0xNjAx +MTEwMDQ2MDlaFw0yNjAxMDgwMDQ2MDlaMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE +CAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdB +bmRyb2lkMTswOQYDVQQDDDJBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVz +dGF0aW9uIEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOue +efhCY1msyyqRTImGzHCtkGaTgqlzJhP+rMv4ISdMIXSXSir+pblNf2bU4GUQZjW8 +U7ego6ZxWD7bPhGuEBSjZjBkMB0GA1UdDgQWBBQ//KzWGrE6noEguNUlHMVlux6R +qTAfBgNVHSMEGDAWgBTIrel3TEXDo88NFhDkeUM6IVowzzASBgNVHRMBAf8ECDAG +AQH/AgEAMA4GA1UdDwEB/wQEAwIChDAKBggqhkjOPQQDAgNIADBFAiBLipt77oK8 +wDOHri/AiZi03cONqycqRZ9pDMfDktQPjgIhAO7aAV229DLp1IQ7YkyUBO86fMy9 +Xvsiu+f+uXc/WT/7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICizCCAjKgAwIBAgIJAKIFntEOQ1tXMAoGCCqGSM49BAMCMIGYMQswCQYDVQQG +EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmll +dzEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYD +VQQDDCpBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIFJvb3Qw +HhcNMTYwMTExMDA0MzUwWhcNMzYwMTA2MDA0MzUwWjCBmDELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT +BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwq +QW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290MFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAE7l1ex+HA220Dpn7mthvsTWpdamguD/9/SQ59 +dx9EIm29sa/6FsvHrcV30lacqrewLVQBXT5DKyqO107sSHVBpKNjMGEwHQYDVR0O +BBYEFMit6XdMRcOjzw0WEOR5QzohWjDPMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0W +EOR5QzohWjDPMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMAoGCCqG +SM49BAMCA0cAMEQCIDUho++LNEYenNVg8x1YiSBq3KNlQfYNns6KGYxmSGB7AiBN +C/NR2TB8fVvaNTQdqEcbY6WFZTytTySn502vQX3xvw== +-----END CERTIFICATE----- diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt new file mode 100644 index 000000000000..32c2230e4880 --- /dev/null +++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt @@ -0,0 +1,161 @@ +package android.security.attestationverification + +import android.app.Activity +import android.os.Bundle +import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE +import android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY +import android.security.attestationverification.AttestationVerificationManager.PROFILE_PEER_DEVICE +import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE +import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE +import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY +import android.security.attestationverification.AttestationVerificationManager.TYPE_UNKNOWN +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.io.ByteArrayOutputStream +import java.security.cert.CertificateFactory +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit + +/** Test for system-defined attestation verifiers. */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class PeerDeviceSystemAttestationVerificationTest { + + @get:Rule + val rule = ActivityScenarioRule(TestActivity::class.java) + + private val certifcateFactory = CertificateFactory.getInstance("X.509") + private lateinit var activity: Activity + private lateinit var avm: AttestationVerificationManager + private lateinit var invalidAttestationByteArray: ByteArray + + @Before + fun setup() { + rule.getScenario().onActivity { + avm = it.getSystemService(AttestationVerificationManager::class.java) + activity = it + } + invalidAttestationByteArray = TEST_ATTESTATION_CERT_FILENAME.fromPEMFileToByteArray() + } + + @Test + fun verifyAttestation_returnsFailureWrongBindingType() { + val future = CompletableFuture() + val profile = AttestationProfile(PROFILE_PEER_DEVICE) + avm.verifyAttestation(profile, TYPE_UNKNOWN, Bundle(), + invalidAttestationByteArray, activity.mainExecutor) { result, _ -> + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureEmptyRequirements() { + val future = CompletableFuture() + val profile = AttestationProfile(PROFILE_PEER_DEVICE) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), + invalidAttestationByteArray, activity.mainExecutor) { result, _ -> + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureMismatchBindingType() { + val future = CompletableFuture() + val profile = AttestationProfile(PROFILE_PEER_DEVICE) + val publicKeyRequirements = Bundle() + publicKeyRequirements.putByteArray(PARAM_PUBLIC_KEY, "publicKeyStr".encodeToByteArray()) + avm.verifyAttestation(profile, TYPE_CHALLENGE, publicKeyRequirements, + invalidAttestationByteArray, activity.mainExecutor) { result, _ -> + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + + val future2 = CompletableFuture() + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "challengeStr".encodeToByteArray()) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, challengeRequirements, + invalidAttestationByteArray, activity.mainExecutor) { result, _ -> + future2.complete(result) + } + + assertThat(future2.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureWrongResourceKey() { + val future = CompletableFuture() + val profile = AttestationProfile(PROFILE_PEER_DEVICE) + val wrongKeyRequirements = Bundle() + wrongKeyRequirements.putByteArray("wrongReqKey", "publicKeyStr".encodeToByteArray()) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, wrongKeyRequirements, + invalidAttestationByteArray, activity.mainExecutor) { result, _ -> + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureEmptyAttestation() { + val future = CompletableFuture() + val profile = AttestationProfile(PROFILE_PEER_DEVICE) + val requirements = Bundle() + requirements.putByteArray(PARAM_PUBLIC_KEY, "publicKeyStr".encodeToByteArray()) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, requirements, ByteArray(0), + activity.mainExecutor) { result, _ -> + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureTrustAnchorMismatch() { + val future = CompletableFuture() + val profile = AttestationProfile(PROFILE_PEER_DEVICE) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) + avm.verifyAttestation(profile, TYPE_CHALLENGE, challengeRequirements, + invalidAttestationByteArray, activity.mainExecutor) { result, _ -> + future.complete(result) + } + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + private fun CompletableFuture.getSoon(): T { + return this.get(1, TimeUnit.SECONDS) + } + + private fun String.fromPEMFileToByteArray(): ByteArray { + val certs = certifcateFactory.generateCertificates( + InstrumentationRegistry.getInstrumentation().getContext().getResources().getAssets() + .open(this)) + val bos = ByteArrayOutputStream() + certs.forEach { + bos.write(it.encoded) + } + return bos.toByteArray() + } + + class TestActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + } + + companion object { + private const val TEST_ATTESTATION_CERT_FILENAME = "test_attestation_wrong_root_certs.pem" + } +} diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt index 62902929fcd5..169effaa45ca 100644 --- a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt +++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt @@ -12,8 +12,8 @@ import org.junit.Test import org.junit.runner.RunWith import com.google.common.truth.Truth.assertThat import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE -import android.security.attestationverification.AttestationVerificationManager.PROFILE_PEER_DEVICE import android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED +import android.security.attestationverification.AttestationVerificationManager.PROFILE_UNKNOWN import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS import android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN @@ -52,7 +52,7 @@ class SystemAttestationVerificationTest { @Test fun verifyAttestation_returnsUnknown() { val future = CompletableFuture() - val profile = AttestationProfile(PROFILE_PEER_DEVICE) + val profile = AttestationProfile(PROFILE_UNKNOWN) avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), activity.mainExecutor) { result, _ -> future.complete(result) @@ -137,7 +137,7 @@ class SystemAttestationVerificationTest { @Test fun verifyToken_returnsUnknown() { val future = CompletableFuture() - val profile = AttestationProfile(PROFILE_PEER_DEVICE) + val profile = AttestationProfile(PROFILE_SELF_TRUSTED) avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), activity.mainExecutor) { _, token -> val result = avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), token, null) @@ -150,7 +150,7 @@ class SystemAttestationVerificationTest { @Test fun verifyToken_tooBigMaxAgeThrows() { val future = CompletableFuture() - val profile = AttestationProfile(PROFILE_PEER_DEVICE) + val profile = AttestationProfile(PROFILE_SELF_TRUSTED) avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), activity.mainExecutor) { _, token -> future.complete(token) diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/AndroidKeystoreAttestationVerificationAttributesTest.java b/tests/AttestationVerificationTest/src/com/android/server/security/AndroidKeystoreAttestationVerificationAttributesTest.java new file mode 100644 index 000000000000..0d15fe72920a --- /dev/null +++ b/tests/AttestationVerificationTest/src/com/android/server/security/AndroidKeystoreAttestationVerificationAttributesTest.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2022 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 com.android.server.security; + +import static com.google.common.truth.Truth.assertThat; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.hamcrest.CoreMatchers; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** Test for data class holding parsed X509Certificate attestation attributes. */ +@RunWith(AndroidJUnit4.class) +public class AndroidKeystoreAttestationVerificationAttributesTest { + @Rule public ExpectedException mException = ExpectedException.none(); + private static final String TEST_PHYSCIAL_DEVICE_CERTS = + "test_attestation_wrong_root_certs.pem"; + private static final String TEST_PHYSICAL_DEVICE_CERTS_2 = + "test_attestation_with_root_certs.pem"; + private static final String TEST_VIRTUAL_DEVICE_CERTS = + "test_virtual_device_attestation_certs.pem"; + private static final String TEST_CERT_NO_ATTESTATION_EXTENSION = + "test_no_attestation_ext_certs.pem"; + private static final String TEST_CERTS_NO_ATTESTATION_EXTENSION_2 = + "test_root_certs.pem"; + + + private CertificateFactory mFactory; + private AndroidKeystoreAttestationVerificationAttributes mPhysicalDeviceAttributes; + private AndroidKeystoreAttestationVerificationAttributes mPhysicalDeviceAttributes2; + private AndroidKeystoreAttestationVerificationAttributes mVirtualDeviceAttributes; + + @Before + public void setUp() throws Exception { + mFactory = CertificateFactory.getInstance("X.509"); + mPhysicalDeviceAttributes = + AndroidKeystoreAttestationVerificationAttributes.fromCertificate( + generateCertificate(TEST_PHYSCIAL_DEVICE_CERTS)); + mPhysicalDeviceAttributes2 = + AndroidKeystoreAttestationVerificationAttributes.fromCertificate( + generateCertificates(TEST_PHYSICAL_DEVICE_CERTS_2).get(0)); + mVirtualDeviceAttributes = + AndroidKeystoreAttestationVerificationAttributes.fromCertificate( + generateCertificates(TEST_VIRTUAL_DEVICE_CERTS).get(0)); + } + + @Test + public void parseCertificate_noAttestationExtension() throws Exception { + List certsNoAttestation = + generateCertificates(TEST_CERTS_NO_ATTESTATION_EXTENSION_2); + certsNoAttestation.add(generateCertificate(TEST_CERT_NO_ATTESTATION_EXTENSION)); + for (X509Certificate cert: certsNoAttestation) { + mException.expect(CertificateEncodingException.class); + mException.expectMessage( + CoreMatchers.containsString("No attestation extension found in certificate.")); + + AndroidKeystoreAttestationVerificationAttributes.fromCertificate(cert); + } + } + + @Test + public void parseCertificate_attestationLevel() { + assertThat(mPhysicalDeviceAttributes.getAttestationVersion()).isEqualTo(3); + assertThat(mPhysicalDeviceAttributes2.getAttestationVersion()).isEqualTo(3); + assertThat(mVirtualDeviceAttributes.getAttestationVersion()).isEqualTo(4); + } + + @Test + public void parseCertificate_attestationSecurityLevel() { + assertThat(mPhysicalDeviceAttributes.getAttestationSecurityLevel()).isEqualTo( + AndroidKeystoreAttestationVerificationAttributes.SecurityLevel.TRUSTED_ENVIRONMENT); + assertThat(mPhysicalDeviceAttributes2.getAttestationSecurityLevel()).isEqualTo( + AndroidKeystoreAttestationVerificationAttributes.SecurityLevel.TRUSTED_ENVIRONMENT); + assertThat(mVirtualDeviceAttributes.getAttestationSecurityLevel()).isEqualTo( + AndroidKeystoreAttestationVerificationAttributes.SecurityLevel.SOFTWARE); + } + + @Test + public void parseCertificate_isAttestationHardwareBacked() { + assertThat(mPhysicalDeviceAttributes.isAttestationHardwareBacked()).isTrue(); + assertThat(mPhysicalDeviceAttributes2.isAttestationHardwareBacked()).isTrue(); + assertThat(mVirtualDeviceAttributes.isAttestationHardwareBacked()).isFalse(); + } + + @Test + public void parseCertificate_keymasterLevel() { + assertThat(mPhysicalDeviceAttributes.getKeymasterVersion()).isEqualTo(4); + assertThat(mPhysicalDeviceAttributes2.getKeymasterVersion()).isEqualTo(4); + assertThat(mVirtualDeviceAttributes.getKeymasterVersion()).isEqualTo(41); + } + + @Test + public void parseCertificate_keymasterSecurityLevel() { + assertThat(mPhysicalDeviceAttributes.getKeymasterSecurityLevel()).isEqualTo( + AndroidKeystoreAttestationVerificationAttributes.SecurityLevel.TRUSTED_ENVIRONMENT); + assertThat(mPhysicalDeviceAttributes2.getKeymasterSecurityLevel()).isEqualTo( + AndroidKeystoreAttestationVerificationAttributes.SecurityLevel.TRUSTED_ENVIRONMENT); + assertThat(mVirtualDeviceAttributes.getKeymasterSecurityLevel()).isEqualTo( + AndroidKeystoreAttestationVerificationAttributes.SecurityLevel.SOFTWARE); + } + + @Test + public void parseCertificate_isKeymasterHardwareBacked() { + assertThat(mPhysicalDeviceAttributes.isKeymasterHardwareBacked()).isTrue(); + assertThat(mPhysicalDeviceAttributes2.isKeymasterHardwareBacked()).isTrue(); + assertThat(mVirtualDeviceAttributes.isKeymasterHardwareBacked()).isFalse(); + } + + @Test + public void parseCertificate_attestationChallenge() { + assertThat(mPhysicalDeviceAttributes.getAttestationChallenge().toByteArray()).isEqualTo( + "abc".getBytes(UTF_8)); + assertThat(mPhysicalDeviceAttributes2.getAttestationChallenge().toByteArray()).isEqualTo( + "player456".getBytes(UTF_8)); + assertThat(mVirtualDeviceAttributes.getAttestationChallenge().toByteArray()).isEqualTo( + "player456".getBytes(UTF_8)); + } + + @Test + public void parseCertificate_verifiedBootState() { + assertThat(mPhysicalDeviceAttributes.getVerifiedBootState()).isEqualTo( + AndroidKeystoreAttestationVerificationAttributes.VerifiedBootState.UNVERIFIED); + assertThat(mPhysicalDeviceAttributes2.getVerifiedBootState()).isEqualTo( + AndroidKeystoreAttestationVerificationAttributes.VerifiedBootState.VERIFIED); + assertThat(mVirtualDeviceAttributes.getVerifiedBootState()).isNull(); + } + + @Test + public void parseCertificate_keyBootPatchLevel() { + assertThat(mPhysicalDeviceAttributes.getKeyBootPatchLevel()).isEqualTo(201907); + assertThat(mPhysicalDeviceAttributes2.getKeyBootPatchLevel()).isEqualTo(20220105); + } + + @Test + public void parseCertificate_keyBootPatchLevelNotSetException() { + mException.expect(IllegalStateException.class); + mException.expectMessage( + CoreMatchers.containsString("KeyBootPatchLevel is not set.")); + + mVirtualDeviceAttributes.getKeyBootPatchLevel(); + } + + @Test + public void parseCertificate_keyOsPatchLevel() { + assertThat(mPhysicalDeviceAttributes.getKeyOsPatchLevel()).isEqualTo(201907); + assertThat(mPhysicalDeviceAttributes2.getKeyOsPatchLevel()).isEqualTo(202201); + } + + @Test + public void parseCertificate_keyOsPatchLevelNotSetException() { + mException.expect(IllegalStateException.class); + mException.expectMessage( + CoreMatchers.containsString("KeyOsPatchLevel is not set.")); + + mVirtualDeviceAttributes.getKeyOsPatchLevel(); + } + + @Test + public void parseCertificate_keyVendorPatchLevel() { + assertThat(mPhysicalDeviceAttributes.getKeyVendorPatchLevel()).isEqualTo(201907); + assertThat(mPhysicalDeviceAttributes2.getKeyVendorPatchLevel()).isEqualTo(20220105); + } + + @Test + public void parseCertificate_keyVendorPatchLevelNotSetException() { + mException.expect(IllegalStateException.class); + mException.expectMessage( + CoreMatchers.containsString("KeyVendorPatchLevel is not set.")); + + mVirtualDeviceAttributes.getKeyVendorPatchLevel(); + } + + @Test + public void parseCertificate_keyAuthenticatorType() { + assertThat(mPhysicalDeviceAttributes.getKeyAuthenticatorType()).isEqualTo(0); + assertThat(mPhysicalDeviceAttributes2.getKeyAuthenticatorType()).isEqualTo(0); + } + + @Test + public void parseCertificate_keyOsVersion() { + assertThat(mPhysicalDeviceAttributes.getKeyOsVersion()).isEqualTo(0); + assertThat(mPhysicalDeviceAttributes2.getKeyOsVersion()).isEqualTo(120000); + } + + @Test + public void parseCertificate_keyOsVersionNotSetException() { + mException.expect(IllegalStateException.class); + mException.expectMessage( + CoreMatchers.containsString("KeyOsVersion is not set.")); + + mVirtualDeviceAttributes.getKeyOsVersion(); + } + + @Test + public void parseCertificate_verifiedBootHash() { + assertThat(mPhysicalDeviceAttributes.getVerifiedBootHash()).isNotEmpty(); + assertThat(mPhysicalDeviceAttributes2.getVerifiedBootHash()).isNotEmpty(); + } + + @Test + public void parseCertificate_verifiedBootKey() { + assertThat(mPhysicalDeviceAttributes.getVerifiedBootKey()).isNotEmpty(); + assertThat(mPhysicalDeviceAttributes2.getVerifiedBootKey()).isNotEmpty(); + } + + @Test + public void parseCertificate_isVerifiedBootLocked() { + assertThat(mPhysicalDeviceAttributes.isVerifiedBootLocked()).isFalse(); + assertThat(mPhysicalDeviceAttributes2.isVerifiedBootLocked()).isTrue(); + } + + @Test + public void parseCertificate_isVerifiedBootLockedNotSetException() { + mException.expect(IllegalStateException.class); + mException.expectMessage( + CoreMatchers.containsString("VerifiedBootLocked is not set.")); + + mVirtualDeviceAttributes.isVerifiedBootLocked(); + } + + @Test + public void parseCertificate_applicationPackageNameVersion() { + assertThat(mPhysicalDeviceAttributes.getApplicationPackageNameVersion()).isNotEmpty(); + } + + @Test + public void parseCertificate_applicationCertificateDigests() { + assertThat(mPhysicalDeviceAttributes.getApplicationCertificateDigests()).isNotEmpty(); + } + + @Test + public void parseCertificate_valuesNotSet() { + assertThat(mPhysicalDeviceAttributes.getDeviceBrand()).isNull(); + assertThat(mPhysicalDeviceAttributes.getDeviceName()).isNull(); + assertThat(mPhysicalDeviceAttributes.getDeviceProductName()).isNull(); + assertThat(mPhysicalDeviceAttributes.isKeyAllowedForAllApplications()).isFalse(); + assertThat(mPhysicalDeviceAttributes2.getDeviceBrand()).isNull(); + assertThat(mPhysicalDeviceAttributes2.getDeviceName()).isNull(); + assertThat(mPhysicalDeviceAttributes2.getDeviceProductName()).isNull(); + assertThat(mPhysicalDeviceAttributes2.isKeyAllowedForAllApplications()).isFalse(); + } + + @Test + public void parseCertificate_keyRequiresUnlockedDeviceNotSetException() { + mException.expect(IllegalStateException.class); + mException.expectMessage( + CoreMatchers.containsString("KeyRequiresUnlockedDevice is not set.")); + + mPhysicalDeviceAttributes.isKeyRequiresUnlockedDevice(); + } + + private X509Certificate generateCertificate(String certificateString) + throws Exception { + return generateCertificates(certificateString).get(0); + } + + private List generateCertificates(String certificateString) + throws Exception { + Collection certificates = mFactory.generateCertificates( + InstrumentationRegistry.getInstrumentation().getContext().getResources().getAssets() + .open(certificateString)); + + ArrayList x509Certs = new ArrayList<>(); + for (Certificate cert : certificates) { + x509Certs.add((X509Certificate) cert); + } + return x509Certs; + } +} diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt new file mode 100644 index 000000000000..45f2e5c6fdf7 --- /dev/null +++ b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt @@ -0,0 +1,175 @@ +package com.android.server.security + +import android.app.Activity +import android.content.Context +import android.os.Bundle +import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE +import android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY +import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE +import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS +import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE +import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import java.io.ByteArrayOutputStream +import java.security.cert.Certificate +import java.security.cert.CertificateFactory +import java.security.cert.TrustAnchor +import java.security.cert.X509Certificate +import java.time.LocalDate + +/** Test for Peer Device attestation verifier. */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class AttestationVerificationPeerDeviceVerifierTest { + private val certificateFactory = CertificateFactory.getInstance("X.509") + @Mock private lateinit var context: Context + private lateinit var trustAnchors: HashSet + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + val rootCerts = TEST_ROOT_CERT_FILENAME.fromPEMFileToCerts() + trustAnchors = HashSet() + rootCerts.forEach { + trustAnchors.add(TrustAnchor(it as X509Certificate, null)) + } + } + + @Test + fun verifyAttestation_returnsSuccessTypeChallenge() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 8, 1)) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) + + val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + assertThat(result).isEqualTo(RESULT_SUCCESS) + } + + @Test + fun verifyAttestation_returnsSuccessLocalPatchOlderThanOneYear() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 1, 1)) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) + + val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + assertThat(result).isEqualTo(RESULT_SUCCESS) + } + + @Test + fun verifyAttestation_returnsSuccessTypePublicKey() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 8, 1)) + + val leafCert = + (TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToCerts() as List)[0] + as X509Certificate + val pkRequirements = Bundle() + pkRequirements.putByteArray(PARAM_PUBLIC_KEY, leafCert.publicKey.encoded) + + val result = verifier.verifyAttestation( + TYPE_PUBLIC_KEY, pkRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + assertThat(result).isEqualTo(RESULT_SUCCESS) + } + + @Test + fun verifyAttestation_returnsFailurePatchDateNotWithinOneYearLocalPatch() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, trustAnchors, false, LocalDate.of(2023, 3, 1), + LocalDate.of(2023, 2, 1)) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) + + val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + assertThat(result).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureTrustedAnchorEmpty() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, HashSet(), false, LocalDate.of(2022, 1, 1), + LocalDate.of(2022, 1, 1)) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) + + val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + assertThat(result).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureTrustedAnchorMismatch() { + val badTrustAnchorsCerts = TEST_ATTESTATION_CERT_FILENAME.fromPEMFileToCerts() + val badTrustAnchors = HashSet() + badTrustAnchorsCerts.forEach { + badTrustAnchors.add(TrustAnchor(it as X509Certificate, null)) + } + + val verifier = AttestationVerificationPeerDeviceVerifier( + context, badTrustAnchors, false, LocalDate.of(2022, 1, 1), + LocalDate.of(2022, 1, 1)) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) + + val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + assertThat(result).isEqualTo(RESULT_FAILURE) + } + + fun verifyAttestation_returnsFailureChallenge() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, trustAnchors, false, LocalDate.of(2022, 1, 1), + LocalDate.of(2022, 1, 1)) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "wrong".encodeToByteArray()) + + val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + assertThat(result).isEqualTo(RESULT_FAILURE) + } + + private fun String.fromPEMFileToCerts(): Collection { + return certificateFactory.generateCertificates( + InstrumentationRegistry.getInstrumentation().getContext().getResources().getAssets() + .open(this)) + } + + private fun String.fromPEMFileToByteArray(): ByteArray { + val certs = this.fromPEMFileToCerts() + val bos = ByteArrayOutputStream() + certs.forEach { + bos.write(it.encoded) + } + return bos.toByteArray() + } + + class TestActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + } + + companion object { + private const val TEST_ROOT_CERT_FILENAME = "test_root_certs.pem" + private const val TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME = + "test_attestation_with_root_certs.pem" + private const val TEST_ATTESTATION_CERT_FILENAME = "test_attestation_wrong_root_certs.pem" + } +}