Implementing verification of PROFILE_PEER_DEVICE.

Bug: 216477071
Test: AttestationVerificationTest unit test
Change-Id: Ide254de1aaaad24a5ac9e449086192aa9f59a72b
This commit is contained in:
Justin McClain 2022-01-21 21:41:53 +00:00
parent 95db721b82
commit 193a27d287
14 changed files with 1882 additions and 6 deletions

View File

@ -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<String, Long> mApplicationPackageNameVersion = null;
private List<ByteString> 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<String, Long> getApplicationPackageNameVersion() {
return Collections.unmodifiableMap(mApplicationPackageNameVersion);
}
@Nullable
List<ByteString> 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<String, Long> 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<ByteString> 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;
}
}

View File

@ -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;

View File

@ -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<TrustAnchor> 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<TrustAnchor> 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<X509Certificate> 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<X509Certificate> getCertificates(byte[] attestation)
throws CertificateException {
List<X509Certificate> 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<X509Certificate> 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<TrustAnchor> getTrustAnchors() throws CertPathValidatorException {
Set<TrustAnchor> 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<String> getSupportedExtensions() {
return null;
}
@Override
public void check(Certificate cert, Collection<String> 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);
}
}
}

View File

@ -40,5 +40,6 @@ android_test {
"androidx.test.rules",
"androidx.test.ext.junit",
"platform-test-annotations",
"services.core",
],
}

View File

@ -24,6 +24,7 @@
<application>
<uses-library android:name="android.test.runner"/>
<activity android:name=".SystemAttestationVerificationTest$TestActivity" />
<activity android:name=".PeerDeviceSystemAttestationVerificationTest$TestActivity" />
</application>
<!-- self-instrumenting test package. -->

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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<Int>()
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<Int>()
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<Int>()
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<Int>()
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<Int>()
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<Int>()
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<Int>()
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 <T> CompletableFuture<T>.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"
}
}

View File

@ -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<Int>()
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<Int>()
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<VerificationToken>()
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)

View File

@ -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<X509Certificate> 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<X509Certificate> generateCertificates(String certificateString)
throws Exception {
Collection<? extends Certificate> certificates = mFactory.generateCertificates(
InstrumentationRegistry.getInstrumentation().getContext().getResources().getAssets()
.open(certificateString));
ArrayList<X509Certificate> x509Certs = new ArrayList<>();
for (Certificate cert : certificates) {
x509Certs.add((X509Certificate) cert);
}
return x509Certs;
}
}

View File

@ -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<TrustAnchor>
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
val rootCerts = TEST_ROOT_CERT_FILENAME.fromPEMFileToCerts()
trustAnchors = HashSet<TrustAnchor>()
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<TrustAnchor>()
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<Certificate> {
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"
}
}