Implementing verification of PROFILE_PEER_DEVICE.
Bug: 216477071 Test: AttestationVerificationTest unit test Change-Id: Ide254de1aaaad24a5ac9e449086192aa9f59a72b
This commit is contained in:
parent
95db721b82
commit
193a27d287
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.server.security;
|
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.PROFILE_SELF_TRUSTED;
|
||||||
import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE;
|
import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE;
|
||||||
import static android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN;
|
import static android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN;
|
||||||
@ -44,9 +45,11 @@ import com.android.server.SystemService;
|
|||||||
public class AttestationVerificationManagerService extends SystemService {
|
public class AttestationVerificationManagerService extends SystemService {
|
||||||
|
|
||||||
private static final String TAG = "AVF";
|
private static final String TAG = "AVF";
|
||||||
|
private final AttestationVerificationPeerDeviceVerifier mPeerDeviceVerifier;
|
||||||
|
|
||||||
public AttestationVerificationManagerService(final Context context) {
|
public AttestationVerificationManagerService(final Context context) throws Exception {
|
||||||
super(context);
|
super(context);
|
||||||
|
mPeerDeviceVerifier = new AttestationVerificationPeerDeviceVerifier(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final IBinder mService = new IAttestationVerificationManagerService.Stub() {
|
private final IBinder mService = new IAttestationVerificationManagerService.Stub() {
|
||||||
@ -83,7 +86,7 @@ public class AttestationVerificationManagerService extends SystemService {
|
|||||||
result.token = null;
|
result.token = null;
|
||||||
switch (profile.getAttestationProfileId()) {
|
switch (profile.getAttestationProfileId()) {
|
||||||
case PROFILE_SELF_TRUSTED:
|
case PROFILE_SELF_TRUSTED:
|
||||||
Slog.d(TAG, "Verifying Self trusted profile.");
|
Slog.d(TAG, "Verifying Self Trusted profile.");
|
||||||
try {
|
try {
|
||||||
result.resultCode =
|
result.resultCode =
|
||||||
AttestationVerificationSelfTrustedVerifierForTesting.getInstance()
|
AttestationVerificationSelfTrustedVerifierForTesting.getInstance()
|
||||||
@ -92,6 +95,11 @@ public class AttestationVerificationManagerService extends SystemService {
|
|||||||
result.resultCode = RESULT_FAILURE;
|
result.resultCode = RESULT_FAILURE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case PROFILE_PEER_DEVICE:
|
||||||
|
Slog.d(TAG, "Verifying Peer Device profile.");
|
||||||
|
result.resultCode = mPeerDeviceVerifier.verifyAttestation(
|
||||||
|
localBindingType, requirements, attestation);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Slog.d(TAG, "No profile found, defaulting.");
|
Slog.d(TAG, "No profile found, defaulting.");
|
||||||
result.resultCode = RESULT_UNKNOWN;
|
result.resultCode = RESULT_UNKNOWN;
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -40,5 +40,6 @@ android_test {
|
|||||||
"androidx.test.rules",
|
"androidx.test.rules",
|
||||||
"androidx.test.ext.junit",
|
"androidx.test.ext.junit",
|
||||||
"platform-test-annotations",
|
"platform-test-annotations",
|
||||||
|
"services.core",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
<application>
|
<application>
|
||||||
<uses-library android:name="android.test.runner"/>
|
<uses-library android:name="android.test.runner"/>
|
||||||
<activity android:name=".SystemAttestationVerificationTest$TestActivity" />
|
<activity android:name=".SystemAttestationVerificationTest$TestActivity" />
|
||||||
|
<activity android:name=".PeerDeviceSystemAttestationVerificationTest$TestActivity" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<!-- self-instrumenting test package. -->
|
<!-- self-instrumenting test package. -->
|
||||||
|
@ -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-----
|
||||||
|
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
|
||||||
|
ex0SdDrx+tWUDqG8At2JHA==
|
||||||
|
-----END CERTIFICATE-----
|
@ -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-----
|
@ -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-----
|
61
tests/AttestationVerificationTest/assets/test_root_certs.pem
Normal file
61
tests/AttestationVerificationTest/assets/test_root_certs.pem
Normal 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-----
|
||||||
|
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
|
||||||
|
ex0SdDrx+tWUDqG8At2JHA==
|
||||||
|
-----END CERTIFICATE-----
|
@ -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-----
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
@ -12,8 +12,8 @@ import org.junit.Test
|
|||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE
|
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_SELF_TRUSTED
|
||||||
|
import android.security.attestationverification.AttestationVerificationManager.PROFILE_UNKNOWN
|
||||||
import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE
|
import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE
|
||||||
import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS
|
import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS
|
||||||
import android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN
|
import android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN
|
||||||
@ -52,7 +52,7 @@ class SystemAttestationVerificationTest {
|
|||||||
@Test
|
@Test
|
||||||
fun verifyAttestation_returnsUnknown() {
|
fun verifyAttestation_returnsUnknown() {
|
||||||
val future = CompletableFuture<Int>()
|
val future = CompletableFuture<Int>()
|
||||||
val profile = AttestationProfile(PROFILE_PEER_DEVICE)
|
val profile = AttestationProfile(PROFILE_UNKNOWN)
|
||||||
avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0),
|
avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0),
|
||||||
activity.mainExecutor) { result, _ ->
|
activity.mainExecutor) { result, _ ->
|
||||||
future.complete(result)
|
future.complete(result)
|
||||||
@ -137,7 +137,7 @@ class SystemAttestationVerificationTest {
|
|||||||
@Test
|
@Test
|
||||||
fun verifyToken_returnsUnknown() {
|
fun verifyToken_returnsUnknown() {
|
||||||
val future = CompletableFuture<Int>()
|
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),
|
avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0),
|
||||||
activity.mainExecutor) { _, token ->
|
activity.mainExecutor) { _, token ->
|
||||||
val result = avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), token, null)
|
val result = avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), token, null)
|
||||||
@ -150,7 +150,7 @@ class SystemAttestationVerificationTest {
|
|||||||
@Test
|
@Test
|
||||||
fun verifyToken_tooBigMaxAgeThrows() {
|
fun verifyToken_tooBigMaxAgeThrows() {
|
||||||
val future = CompletableFuture<VerificationToken>()
|
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),
|
avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0),
|
||||||
activity.mainExecutor) { _, token ->
|
activity.mainExecutor) { _, token ->
|
||||||
future.complete(token)
|
future.complete(token)
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user