aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-04-04 20:26:58 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-04-04 20:26:58 +0000
commit0571ed03e9c95c42b8f922934cf1e873b32bdd93 (patch)
tree7e9d8c10acdcdb6b1e01426d609c4db191ed21ec
parent357c1efb44968cdcb78785bc79cf7795fd47dc33 (diff)
parentab31729dff8b1a44f193c27d2411ffcd2b1c56bb (diff)
downloadandroid-key-attestation-android14-tests-release.tar.gz
Snap for 11672127 from ab31729dff8b1a44f193c27d2411ffcd2b1c56bb to android14-tests-releaseandroid-vts-14.0_r4android-cts-14.0_r4android14-tests-release
Change-Id: I78eb63183df955bcb443a7fc46ede588d2bd9a7d
-rw-r--r--.github/workflows/bazel.yml35
-rw-r--r--.github/workflows/gradle.yml31
-rw-r--r--Android.bp4
-rw-r--r--METADATA15
-rw-r--r--WORKSPACE5
-rw-r--r--server/build.gradle2
-rw-r--r--server/src/main/java/com/android/example/KeyAttestationExample.java53
-rw-r--r--server/src/main/java/com/google/android/attestation/ASN1Parsing.java2
-rw-r--r--server/src/main/java/com/google/android/attestation/AuthorizationList.java76
-rw-r--r--server/src/main/java/com/google/android/attestation/BUILD2
-rw-r--r--server/src/main/java/com/google/android/attestation/Constants.java55
-rw-r--r--server/src/main/java/com/google/android/attestation/ParsedAttestationRecord.java27
-rw-r--r--server/src/test/java/com/google/android/attestation/AuthorizationListTest.java33
-rw-r--r--server/src/test/java/com/google/android/attestation/ParsedAttestationRecordTest.java37
14 files changed, 289 insertions, 88 deletions
diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml
new file mode 100644
index 0000000..abe6fd2
--- /dev/null
+++ b/.github/workflows/bazel.yml
@@ -0,0 +1,35 @@
+name: Java CI with Bazel
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v1
+
+ - name: Mount bazel cache
+ uses: actions/cache@v1
+ with:
+ path: "/home/runner/.cache/bazel"
+ key: bazel
+
+ - name: Install bazelisk
+ run: |
+ curl -LO "https://github.com/bazelbuild/bazelisk/releases/download/v1.1.0/bazelisk-linux-amd64"
+ mkdir -p "${GITHUB_WORKSPACE}/bin/"
+ mv bazelisk-linux-amd64 "${GITHUB_WORKSPACE}/bin/bazel"
+ chmod +x "${GITHUB_WORKSPACE}/bin/bazel"
+
+ - name: Build
+ run: |
+ "${GITHUB_WORKSPACE}/bin/bazel" build //...
+
+ - name: Test
+ run: |
+ "${GITHUB_WORKSPACE}/bin/bazel" test //...
diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
new file mode 100644
index 0000000..f38668f
--- /dev/null
+++ b/.github/workflows/gradle.yml
@@ -0,0 +1,31 @@
+# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
+# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
+
+name: Java CI with Gradle
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up JDK 11
+ uses: actions/setup-java@v3
+ with:
+ java-version: '11'
+ distribution: 'temurin'
+ - name: Build with Gradle
+ uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
+ with:
+ arguments: build
+ build-root-directory: server/
diff --git a/Android.bp b/Android.bp
index 2867a0d..6a6e518 100644
--- a/Android.bp
+++ b/Android.bp
@@ -45,8 +45,10 @@ java_library_static {
sdk_version: "current",
static_libs: [
"bouncycastle-unbundled",
+ "error_prone_annotations",
+ "guava",
],
libs: [
- "wycheproof-gson",
+ "gson",
],
}
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..e1c123d
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,15 @@
+name: "android-key-attestation"
+description: "Android Key Attestation validation library"
+third_party {
+ url {
+ type: GIT
+ value: "https://github.com/google/android-key-attestation"
+ }
+ version: "e3a09702acdc332ef5a6496c5b78a2ca6d2713f8"
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2023
+ month: 4
+ day: 18
+ }
+}
diff --git a/WORKSPACE b/WORKSPACE
index 4012114..916d112 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -34,6 +34,11 @@ maven_install(
# Gson used for decoding certificate status list
"com.google.code.gson:gson:2.8.5",
+ "com.google.guava:guava:27.0.1-android",
+ "com.google.errorprone:error_prone_annotations:2.3.1",
+
+ "com.squareup.okhttp3:okhttp:4.10.0",
+
# Test libraries
"junit:junit:4.12",
"com.google.truth:truth:1.0",
diff --git a/server/build.gradle b/server/build.gradle
index d5cb1cc..fff1b6a 100644
--- a/server/build.gradle
+++ b/server/build.gradle
@@ -27,6 +27,8 @@ repositories {
dependencies {
// Bouncy Castle Cryptography APIs used for certificate verification
compile 'org.bouncycastle:bcpkix-jdk15on:1.61'
+ compile 'com.google.guava:guava:27.0.1-android'
+ compile 'com.google.errorprone:error_prone_annotations:2.3.1'
// Gson used for decoding certificate status list
compile 'com.google.code.gson:gson:2.8.5'
// JUnit, Truth and Truth8 used for testing
diff --git a/server/src/main/java/com/android/example/KeyAttestationExample.java b/server/src/main/java/com/android/example/KeyAttestationExample.java
index a503dc7..5167c12 100644
--- a/server/src/main/java/com/android/example/KeyAttestationExample.java
+++ b/server/src/main/java/com/android/example/KeyAttestationExample.java
@@ -15,7 +15,7 @@
package com.android.example;
-import static com.google.android.attestation.Constants.GOOGLE_ROOT_CERTIFICATE;
+import static com.google.android.attestation.Constants.GOOGLE_ROOT_CA_PUB_KEY;
import static com.google.android.attestation.ParsedAttestationRecord.createParsedAttestationRecord;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -25,6 +25,8 @@ import com.google.android.attestation.CertificateRevocationStatus;
import com.google.android.attestation.AuthorizationList;
import com.google.android.attestation.ParsedAttestationRecord;
import com.google.android.attestation.RootOfTrust;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.file.Files;
@@ -78,7 +80,7 @@ public class KeyAttestationExample {
public static void main(String[] args)
throws CertificateException, IOException, NoSuchProviderException, NoSuchAlgorithmException,
InvalidKeyException, SignatureException {
- X509Certificate[] certs;
+ List<X509Certificate> certs;
if (args.length == 1) {
String certFilesDir = args[0];
certs = loadCertificates(certFilesDir);
@@ -88,7 +90,7 @@ public class KeyAttestationExample {
verifyCertificateChain(certs);
- ParsedAttestationRecord parsedAttestationRecord = createParsedAttestationRecord(certs[0]);
+ ParsedAttestationRecord parsedAttestationRecord = createParsedAttestationRecord(certs);
System.out.println("Attestation version: " + parsedAttestationRecord.attestationVersion);
System.out.println(
@@ -161,12 +163,16 @@ public class KeyAttestationExample {
printOptional(authorizationList.attestationIdProduct, indent + "Attestation ID Product");
printOptional(authorizationList.attestationIdSerial, indent + "Attestation ID Serial");
printOptional(authorizationList.attestationIdImei, indent + "Attestation ID IMEI");
+ printOptional(
+ authorizationList.attestationIdSecondImei, indent + "Attestation ID SECOND IMEI");
printOptional(authorizationList.attestationIdMeid, indent + "Attestation ID MEID");
printOptional(
authorizationList.attestationIdManufacturer, indent + "Attestation ID Manufacturer");
printOptional(authorizationList.attestationIdModel, indent + "Attestation ID Model");
printOptional(authorizationList.vendorPatchLevel, indent + "Vendor Patch Level");
printOptional(authorizationList.bootPatchLevel, indent + "Boot Patch Level");
+ System.out.println(
+ indent + "Identity Credential Key: " + authorizationList.identityCredentialKey);
}
private static void printRootOfTrust(Optional<RootOfTrust> rootOfTrust, String indent) {
@@ -209,12 +215,12 @@ public class KeyAttestationExample {
}
}
- private static void verifyCertificateChain(X509Certificate[] certs)
+ private static void verifyCertificateChain(List<X509Certificate> certs)
throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
NoSuchProviderException, SignatureException, IOException {
- X509Certificate parent = certs[certs.length - 1];
- for (int i = certs.length - 1; i >= 0; i--) {
- X509Certificate cert = certs[i];
+ X509Certificate parent = certs.get(certs.size() - 1);
+ for (int i = certs.size() - 1; i >= 0; i--) {
+ X509Certificate cert = certs.get(i);
// Verify that the certificate has not expired.
cert.checkValidity();
cert.verify(parent.getPublicKey());
@@ -232,47 +238,40 @@ public class KeyAttestationExample {
}
// If the attestation is trustworthy and the device ships with hardware-
- // level key attestation, Android 7.0 (API level 24) or higher, and
+ // backed key attestation, Android 7.0 (API level 24) or higher, and
// Google Play services, the root certificate should be signed with the
// Google attestation root key.
- X509Certificate secureRoot =
- (X509Certificate)
- CertificateFactory.getInstance("X.509")
- .generateCertificate(
- new ByteArrayInputStream(GOOGLE_ROOT_CERTIFICATE.getBytes(UTF_8)));
+ byte[] googleRootCaPubKey = Base64.decode(GOOGLE_ROOT_CA_PUB_KEY);
if (Arrays.equals(
- secureRoot.getPublicKey().getEncoded(),
- certs[certs.length - 1].getPublicKey().getEncoded())) {
+ googleRootCaPubKey,
+ certs.get(certs.size() - 1).getPublicKey().getEncoded())) {
System.out.println(
"The root certificate is correct, so this attestation is trustworthy, as long as none of"
- + " the certificates in the chain have been revoked. A production-level system"
- + " should check the certificate revocation lists using the distribution points that"
- + " are listed in the intermediate and root certificates.");
+ + " the certificates in the chain have been revoked.");
} else {
System.out.println(
"The root certificate is NOT correct. The attestation was probably generated by"
- + " software, not in secure hardware. This means that, although the attestation"
- + " contents are probably valid and correct, there is no proof that they are in fact"
- + " correct. If you're using a production-level system, you should now treat the"
- + " properties of this attestation certificate as advisory only, and you shouldn't"
- + " rely on this attestation certificate to provide security guarantees.");
+ + " software, not in secure hardware. This means that there is no guarantee that the"
+ + " claims within the attestation are correct. If you're using a production-level"
+ + " system, you should disregard any claims made within this attestation certificate"
+ + " as there is no authority backing them up.");
}
}
- private static X509Certificate[] loadCertificates(String certFilesDir)
+ private static ImmutableList<X509Certificate> loadCertificates(String certFilesDir)
throws CertificateException, IOException {
// Load the attestation certificates from the directory in alphabetic order.
List<Path> records;
try (Stream<Path> pathStream = Files.walk(Paths.get(certFilesDir))) {
records = pathStream.filter(Files::isRegularFile).sorted().collect(Collectors.toList());
}
- X509Certificate[] certs = new X509Certificate[records.size()];
+ ImmutableList.Builder<X509Certificate> certs = new ImmutableList.Builder<>();
CertificateFactory factory = CertificateFactory.getInstance("X.509");
for (int i = 0; i < records.size(); ++i) {
byte[] encodedCert = Files.readAllBytes(records.get(i));
ByteArrayInputStream inputStream = new ByteArrayInputStream(encodedCert);
- certs[i] = (X509Certificate) factory.generateCertificate(inputStream);
+ certs.add((X509Certificate) factory.generateCertificate(inputStream));
}
- return certs;
+ return certs.build();
}
}
diff --git a/server/src/main/java/com/google/android/attestation/ASN1Parsing.java b/server/src/main/java/com/google/android/attestation/ASN1Parsing.java
index 1735822..d08f9ad 100644
--- a/server/src/main/java/com/google/android/attestation/ASN1Parsing.java
+++ b/server/src/main/java/com/google/android/attestation/ASN1Parsing.java
@@ -27,7 +27,7 @@ class ASN1Parsing {
if (asn1Value instanceof ASN1Boolean) {
return ((ASN1Boolean) asn1Value).isTrue();
} else {
- throw new RuntimeException(
+ throw new IllegalArgumentException(
"Boolean value expected; found " + asn1Value.getClass().getName() + " instead.");
}
}
diff --git a/server/src/main/java/com/google/android/attestation/AuthorizationList.java b/server/src/main/java/com/google/android/attestation/AuthorizationList.java
index d86922d..2ae2b50 100644
--- a/server/src/main/java/com/google/android/attestation/AuthorizationList.java
+++ b/server/src/main/java/com/google/android/attestation/AuthorizationList.java
@@ -32,11 +32,13 @@ import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_MAN
import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_MEID;
import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_MODEL;
import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_PRODUCT;
+import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_SECOND_IMEI;
import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_SERIAL;
import static com.google.android.attestation.Constants.KM_TAG_AUTH_TIMEOUT;
import static com.google.android.attestation.Constants.KM_TAG_BOOT_PATCH_LEVEL;
import static com.google.android.attestation.Constants.KM_TAG_CREATION_DATE_TIME;
import static com.google.android.attestation.Constants.KM_TAG_DEVICE_UNIQUE_ATTESTATION;
+import static com.google.android.attestation.Constants.KM_TAG_IDENTITY_CREDENTIAL_KEY;
import static com.google.android.attestation.Constants.KM_TAG_DIGEST;
import static com.google.android.attestation.Constants.KM_TAG_EC_CURVE;
import static com.google.android.attestation.Constants.KM_TAG_KEY_SIZE;
@@ -66,6 +68,8 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import com.google.common.collect.ImmutableSet;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
@@ -128,12 +132,14 @@ public class AuthorizationList {
public final Optional<byte[]> attestationIdProduct;
public final Optional<byte[]> attestationIdSerial;
public final Optional<byte[]> attestationIdImei;
+ public final Optional<byte[]> attestationIdSecondImei;
public final Optional<byte[]> attestationIdMeid;
public final Optional<byte[]> attestationIdManufacturer;
public final Optional<byte[]> attestationIdModel;
public final Optional<Integer> vendorPatchLevel;
public final Optional<Integer> bootPatchLevel;
public final boolean individualAttestation;
+ public final boolean identityCredentialKey;
private AuthorizationList(ASN1Encodable[] authorizationList, int attestationVersion) {
Map<Integer, ASN1Primitive> authorizationMap = getAuthorizationMap(authorizationList);
@@ -206,6 +212,8 @@ public class AuthorizationList {
findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_ATTESTATION_ID_SERIAL);
this.attestationIdImei =
findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_ATTESTATION_ID_IMEI);
+ this.attestationIdSecondImei =
+ findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_ATTESTATION_ID_SECOND_IMEI);
this.attestationIdMeid =
findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_ATTESTATION_ID_MEID);
this.attestationIdManufacturer =
@@ -219,6 +227,9 @@ public class AuthorizationList {
findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_BOOT_PATCH_LEVEL);
this.individualAttestation =
findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_DEVICE_UNIQUE_ATTESTATION);
+ this.identityCredentialKey =
+ findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_IDENTITY_CREDENTIAL_KEY);
+
}
private AuthorizationList(Builder builder) {
@@ -255,12 +266,14 @@ public class AuthorizationList {
this.attestationIdProduct = Optional.ofNullable(builder.attestationIdProduct);
this.attestationIdSerial = Optional.ofNullable(builder.attestationIdSerial);
this.attestationIdImei = Optional.ofNullable(builder.attestationIdImei);
+ this.attestationIdSecondImei = Optional.ofNullable(builder.attestationIdSecondImei);
this.attestationIdMeid = Optional.ofNullable(builder.attestationIdMeid);
this.attestationIdManufacturer = Optional.ofNullable(builder.attestationIdManufacturer);
this.attestationIdModel = Optional.ofNullable(builder.attestationIdModel);
this.vendorPatchLevel = Optional.ofNullable(builder.vendorPatchLevel);
this.bootPatchLevel = Optional.ofNullable(builder.bootPatchLevel);
this.individualAttestation = builder.individualAttestation;
+ this.identityCredentialKey = builder.identityCredentialKey;
}
static AuthorizationList createAuthorizationList(
@@ -340,7 +353,7 @@ public class AuthorizationList {
// Visible for testing.
static Set<UserAuthType> userAuthTypeToEnum(long userAuthType) {
if (userAuthType == 0) {
- return Set.of(USER_AUTH_TYPE_NONE);
+ return ImmutableSet.of(USER_AUTH_TYPE_NONE);
}
Set<UserAuthType> result = new HashSet<>();
@@ -367,7 +380,7 @@ public class AuthorizationList {
return 0L;
}
- Long result = 0L;
+ long result = 0L;
for (UserAuthType type : userAuthType) {
switch (type) {
@@ -427,6 +440,7 @@ public class AuthorizationList {
addOptionalOctetString(KM_TAG_ATTESTATION_ID_PRODUCT, this.attestationIdProduct, vector);
addOptionalOctetString(KM_TAG_ATTESTATION_ID_SERIAL, this.attestationIdSerial, vector);
addOptionalOctetString(KM_TAG_ATTESTATION_ID_IMEI, this.attestationIdImei, vector);
+ addOptionalOctetString(KM_TAG_ATTESTATION_ID_SECOND_IMEI, this.attestationIdSecondImei, vector);
addOptionalOctetString(KM_TAG_ATTESTATION_ID_MEID, this.attestationIdMeid, vector);
addOptionalOctetString(
KM_TAG_ATTESTATION_ID_MANUFACTURER, this.attestationIdManufacturer, vector);
@@ -543,203 +557,261 @@ public class AuthorizationList {
byte[] attestationIdProduct;
byte[] attestationIdSerial;
byte[] attestationIdImei;
+ byte[] attestationIdSecondImei;
byte[] attestationIdMeid;
byte[] attestationIdManufacturer;
byte[] attestationIdModel;
Integer vendorPatchLevel;
Integer bootPatchLevel;
boolean individualAttestation;
+ boolean identityCredentialKey;
+ @CanIgnoreReturnValue
public Builder setPurpose(Set<Integer> purpose) {
this.purpose = purpose;
return this;
}
+ @CanIgnoreReturnValue
public Builder setAlgorithm(Integer algorithm) {
this.algorithm = algorithm;
return this;
}
+ @CanIgnoreReturnValue
public Builder setKeySize(Integer keySize) {
this.keySize = keySize;
return this;
}
+ @CanIgnoreReturnValue
public Builder setDigest(Set<Integer> digest) {
this.digest = digest;
return this;
}
+ @CanIgnoreReturnValue
public Builder setPadding(Set<Integer> padding) {
this.padding = padding;
return this;
}
+ @CanIgnoreReturnValue
public Builder setEcCurve(Integer ecCurve) {
this.ecCurve = ecCurve;
return this;
}
+ @CanIgnoreReturnValue
public Builder setRsaPublicExponent(Long rsaPublicExponent) {
this.rsaPublicExponent = rsaPublicExponent;
return this;
}
+ @CanIgnoreReturnValue
public Builder setRollbackResistance(boolean rollbackResistance) {
this.rollbackResistance = rollbackResistance;
return this;
}
+ @CanIgnoreReturnValue
public Builder setActiveDateTime(Instant activeDateTime) {
this.activeDateTime = activeDateTime;
return this;
}
+ @CanIgnoreReturnValue
public Builder setOriginationExpireDateTime(Instant originationExpireDateTime) {
this.originationExpireDateTime = originationExpireDateTime;
return this;
}
+ @CanIgnoreReturnValue
public Builder setUsageExpireDateTime(Instant usageExpireDateTime) {
this.usageExpireDateTime = usageExpireDateTime;
return this;
}
+ @CanIgnoreReturnValue
public Builder setNoAuthRequired(boolean noAuthRequired) {
this.noAuthRequired = noAuthRequired;
return this;
}
+ @CanIgnoreReturnValue
public Builder setUserAuthType(Set<UserAuthType> userAuthType) {
this.userAuthType = userAuthType;
return this;
}
+ @CanIgnoreReturnValue
public Builder setAuthTimeout(Duration authTimeout) {
this.authTimeout = authTimeout;
return this;
}
+ @CanIgnoreReturnValue
public Builder setAllowWhileOnBody(boolean allowWhileOnBody) {
this.allowWhileOnBody = allowWhileOnBody;
return this;
}
+ @CanIgnoreReturnValue
public Builder setTrustedUserPresenceRequired(boolean trustedUserPresenceRequired) {
this.trustedUserPresenceRequired = trustedUserPresenceRequired;
return this;
}
+ @CanIgnoreReturnValue
public Builder setTrustedConfirmationRequired(boolean trustedConfirmationRequired) {
this.trustedConfirmationRequired = trustedConfirmationRequired;
return this;
}
+ @CanIgnoreReturnValue
public Builder setUnlockedDeviceRequired(boolean unlockedDeviceRequired) {
this.unlockedDeviceRequired = unlockedDeviceRequired;
return this;
}
+ @CanIgnoreReturnValue
public Builder setAllApplications(boolean allApplications) {
this.allApplications = allApplications;
return this;
}
+ @CanIgnoreReturnValue
public Builder setApplicationId(byte[] applicationId) {
this.applicationId = applicationId;
return this;
}
+ @CanIgnoreReturnValue
public Builder setCreationDateTime(Instant creationDateTime) {
this.creationDateTime = creationDateTime;
return this;
}
+ @CanIgnoreReturnValue
public Builder setOrigin(Integer origin) {
this.origin = origin;
return this;
}
+ @CanIgnoreReturnValue
public Builder setRollbackResistant(boolean rollbackResistant) {
this.rollbackResistant = rollbackResistant;
return this;
}
+ @CanIgnoreReturnValue
public Builder setRootOfTrust(RootOfTrust rootOfTrust) {
this.rootOfTrust = rootOfTrust;
return this;
}
+ @CanIgnoreReturnValue
public Builder setOsVersion(Integer osVersion) {
this.osVersion = osVersion;
return this;
}
+ @CanIgnoreReturnValue
public Builder setOsPatchLevel(Integer osPatchLevel) {
this.osPatchLevel = osPatchLevel;
return this;
}
+ @CanIgnoreReturnValue
public Builder setAttestationApplicationId(AttestationApplicationId attestationApplicationId) {
this.attestationApplicationId = attestationApplicationId;
return this;
}
+ @CanIgnoreReturnValue
public Builder setAttestationApplicationIdBytes(byte[] attestationApplicationIdBytes) {
this.attestationApplicationIdBytes = attestationApplicationIdBytes;
return this;
}
+ @CanIgnoreReturnValue
public Builder setAttestationIdBrand(byte[] attestationIdBrand) {
this.attestationIdBrand = attestationIdBrand;
return this;
}
+ @CanIgnoreReturnValue
+ public Builder setAttestationIdDevice(byte[] attestationIdDevice) {
+ this.attestationIdDevice = attestationIdDevice;
+ return this;
+ }
+
+ @CanIgnoreReturnValue
public Builder setAttestationIdProduct(byte[] attestationIdProduct) {
this.attestationIdProduct = attestationIdProduct;
return this;
}
+ @CanIgnoreReturnValue
public Builder setAttestationIdSerial(byte[] attestationIdSerial) {
this.attestationIdSerial = attestationIdSerial;
return this;
}
+ @CanIgnoreReturnValue
public Builder setAttestationIdImei(byte[] attestationIdImei) {
this.attestationIdImei = attestationIdImei;
return this;
}
+ @CanIgnoreReturnValue
+ public Builder setAttestationIdSecondImei(byte[] attestationIdSecondImei) {
+ this.attestationIdSecondImei = attestationIdSecondImei;
+ return this;
+ }
+
+ @CanIgnoreReturnValue
public Builder setAttestationIdMeid(byte[] attestationIdMeid) {
this.attestationIdMeid = attestationIdMeid;
return this;
}
+ @CanIgnoreReturnValue
public Builder setAttestationIdManufacturer(byte[] attestationIdManufacturer) {
this.attestationIdManufacturer = attestationIdManufacturer;
return this;
}
+ @CanIgnoreReturnValue
public Builder setAttestationIdModel(byte[] attestationIdModel) {
this.attestationIdModel = attestationIdModel;
return this;
}
+ @CanIgnoreReturnValue
public Builder setVendorPatchLevel(Integer vendorPatchLevel) {
this.vendorPatchLevel = vendorPatchLevel;
return this;
}
+ @CanIgnoreReturnValue
public Builder setBootPatchLevel(Integer bootPatchLevel) {
this.bootPatchLevel = bootPatchLevel;
return this;
}
+ @CanIgnoreReturnValue
public Builder setIndividualAttestation(boolean individualAttestation) {
this.individualAttestation = individualAttestation;
return this;
}
+ @CanIgnoreReturnValue
+ public Builder setIdentityCredentialKey(boolean identityCredentialKey) {
+ this.identityCredentialKey = identityCredentialKey;
+ return this;
+ }
+
public AuthorizationList build() {
return new AuthorizationList(this);
}
diff --git a/server/src/main/java/com/google/android/attestation/BUILD b/server/src/main/java/com/google/android/attestation/BUILD
index d2c2ae2..f1023c8 100644
--- a/server/src/main/java/com/google/android/attestation/BUILD
+++ b/server/src/main/java/com/google/android/attestation/BUILD
@@ -13,6 +13,8 @@ java_library(
],
deps = [
"@maven//:com_google_code_gson_gson",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:com_google_guava_guava",
"@maven//:org_bouncycastle_bcpkix_jdk15on",
"@maven//:org_bouncycastle_bcprov_jdk15on",
],
diff --git a/server/src/main/java/com/google/android/attestation/Constants.java b/server/src/main/java/com/google/android/attestation/Constants.java
index 7db2744..a69d364 100644
--- a/server/src/main/java/com/google/android/attestation/Constants.java
+++ b/server/src/main/java/com/google/android/attestation/Constants.java
@@ -18,42 +18,23 @@ package com.google.android.attestation;
/** Key Attestation constants */
public class Constants {
- // The Google root certificate that must have been used to sign the root
- // certificate in a real attestation certificate chain from a compliant
- // device.
- // (Note, the sample chain used here is not signed with this certificate.)
- public static final String GOOGLE_ROOT_CERTIFICATE =
- "-----BEGIN CERTIFICATE-----\n"
- + "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\n"
- + "-----END CERTIFICATE-----";
+ // The Google root public key corresponding to the private key that must
+ // have been used to self-sign the root of a real attestation certificate
+ // chain from a compliant device.
+ // (Note, the sample chain used here is not signed with the Google root CA.)
+ public static final String GOOGLE_ROOT_CA_PUB_KEY =
+ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xU"
+ + "FmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5j"
+ + "lRfdnJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y"
+ + "//0rb+T+W8a9nsNL/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73X"
+ + "pXyTqRxB/M0n1n/W9nGqC4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYI"
+ + "mQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB"
+ + "+TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7q"
+ + "uvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgp"
+ + "Zrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7"
+ + "gLiMm0jhO2B6tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82"
+ + "ixPvZtXQpUpuL12ab+9EaDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+"
+ + "NpUFgNPN9PvQi8WEg5UmAGMCAwEAAQ==";
static final String KEY_DESCRIPTION_OID = "1.3.6.1.4.1.11129.2.1.17";
static final int ATTESTATION_VERSION_INDEX = 0;
static final int ATTESTATION_SECURITY_LEVEL_INDEX = 1;
@@ -103,6 +84,8 @@ public class Constants {
static final int KM_TAG_VENDOR_PATCH_LEVEL = 718;
static final int KM_TAG_BOOT_PATCH_LEVEL = 719;
static final int KM_TAG_DEVICE_UNIQUE_ATTESTATION = 720;
+ static final int KM_TAG_IDENTITY_CREDENTIAL_KEY = 721;
+ static final int KM_TAG_ATTESTATION_ID_SECOND_IMEI = 723;
static final int ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX = 0;
static final int ROOT_OF_TRUST_DEVICE_LOCKED_INDEX = 1;
static final int ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX = 2;
diff --git a/server/src/main/java/com/google/android/attestation/ParsedAttestationRecord.java b/server/src/main/java/com/google/android/attestation/ParsedAttestationRecord.java
index 053baae..f1aa7e8 100644
--- a/server/src/main/java/com/google/android/attestation/ParsedAttestationRecord.java
+++ b/server/src/main/java/com/google/android/attestation/ParsedAttestationRecord.java
@@ -30,6 +30,7 @@ import static com.google.android.attestation.Constants.UNIQUE_ID_INDEX;
import java.io.IOException;
import java.security.cert.X509Certificate;
+import java.util.List;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Enumerated;
import org.bouncycastle.asn1.ASN1InputStream;
@@ -96,10 +97,23 @@ public class ParsedAttestationRecord {
this.teeEnforced = teeEnforced;
}
- public static ParsedAttestationRecord createParsedAttestationRecord(X509Certificate cert)
+ public static ParsedAttestationRecord createParsedAttestationRecord(List<X509Certificate> certs)
throws IOException {
- ASN1Sequence extensionData = extractAttestationSequence(cert);
- return new ParsedAttestationRecord(extensionData);
+
+ // Parse the attestation record that is closest to the root. This prevents an adversary from
+ // attesting an attestation record of their choice with an otherwise trusted chain using the
+ // following attack:
+ // 1) having the TEE attest a key under the adversary's control,
+ // 2) using that key to sign a new leaf certificate with an attestation extension that has their chosen attestation record, then
+ // 3) appending that certificate to the original certificate chain.
+ for (int i = certs.size() - 1; i >= 0; i--) {
+ byte[] attestationExtensionBytes = certs.get(i).getExtensionValue(KEY_DESCRIPTION_OID);
+ if (attestationExtensionBytes != null && attestationExtensionBytes.length != 0) {
+ return new ParsedAttestationRecord(extractAttestationSequence(attestationExtensionBytes));
+ }
+ }
+
+ throw new IllegalArgumentException("Couldn't find the keystore attestation extension data.");
}
public static ParsedAttestationRecord create(ASN1Sequence extensionData) {
@@ -151,13 +165,8 @@ public class ParsedAttestationRecord {
throw new IllegalArgumentException("Invalid security level.");
}
- private static ASN1Sequence extractAttestationSequence(X509Certificate attestationCert)
+ private static ASN1Sequence extractAttestationSequence(byte[] attestationExtensionBytes)
throws IOException {
- byte[] attestationExtensionBytes = attestationCert.getExtensionValue(KEY_DESCRIPTION_OID);
- if (attestationExtensionBytes == null || attestationExtensionBytes.length == 0) {
- throw new IllegalArgumentException("Couldn't find the keystore attestation extension data.");
- }
-
ASN1Sequence decodedSequence;
try (ASN1InputStream asn1InputStream = new ASN1InputStream(attestationExtensionBytes)) {
// The extension contains one object, a sequence, in the
diff --git a/server/src/test/java/com/google/android/attestation/AuthorizationListTest.java b/server/src/test/java/com/google/android/attestation/AuthorizationListTest.java
index 637e8ca..37e3319 100644
--- a/server/src/test/java/com/google/android/attestation/AuthorizationListTest.java
+++ b/server/src/test/java/com/google/android/attestation/AuthorizationListTest.java
@@ -26,10 +26,8 @@ import static com.google.common.truth.Truth8.assertThat;
import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableSet;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Instant;
-import java.util.Set;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Sequence;
@@ -113,6 +111,7 @@ public class AuthorizationListTest {
assertThat(authorizationList.attestationApplicationIdBytes)
.hasValue(EXPECTED_SW_ATTESTATION_APPLICATION_ID_BYTES);
assertThat(authorizationList.individualAttestation).isFalse();
+ assertThat(authorizationList.identityCredentialKey).isFalse();
}
@Test
@@ -135,15 +134,17 @@ public class AuthorizationListTest {
assertThat(authorizationList.vendorPatchLevel).hasValue(EXPECTED_TEE_VENDOR_PATCH_LEVEL);
assertThat(authorizationList.bootPatchLevel).hasValue(EXPECTED_TEE_BOOT_PATCH_LEVEL);
assertThat(authorizationList.individualAttestation).isFalse();
+ assertThat(authorizationList.identityCredentialKey).isFalse();
}
@Test
public void testUserAuthTypeToEnum() {
- assertThat(userAuthTypeToEnum(0L)).isEqualTo(Set.of(USER_AUTH_TYPE_NONE));
- assertThat(userAuthTypeToEnum(1L)).isEqualTo(Set.of(PASSWORD));
- assertThat(userAuthTypeToEnum(2L)).isEqualTo(Set.of(FINGERPRINT));
- assertThat(userAuthTypeToEnum(3L)).isEqualTo(Set.of(PASSWORD, FINGERPRINT));
- assertThat(userAuthTypeToEnum(UINT32_MAX)).isEqualTo(Set.of(PASSWORD, FINGERPRINT, USER_AUTH_TYPE_ANY));
+ assertThat(userAuthTypeToEnum(0L)).isEqualTo(ImmutableSet.of(USER_AUTH_TYPE_NONE));
+ assertThat(userAuthTypeToEnum(1L)).isEqualTo(ImmutableSet.of(PASSWORD));
+ assertThat(userAuthTypeToEnum(2L)).isEqualTo(ImmutableSet.of(FINGERPRINT));
+ assertThat(userAuthTypeToEnum(3L)).isEqualTo(ImmutableSet.of(PASSWORD, FINGERPRINT));
+ assertThat(userAuthTypeToEnum(UINT32_MAX)).isEqualTo(ImmutableSet.of(PASSWORD, FINGERPRINT,
+ USER_AUTH_TYPE_ANY));
try {
@@ -170,6 +171,24 @@ public class AuthorizationListTest {
assertThat(authorizationList.individualAttestation).isTrue();
}
+ private static final String EXTENTION_DATA_WITH_ID_CREDENTIAL_KEY =
+ "MIH0oQgxBgIBAgIBA6IDAgEBowQCAggApQUxAwIBBKYIMQYCAQMCAQW/" +
+ "gUgFAgMBAAG/g3cCBQC/hT4DAgEAv4VATDBKBCAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAEBAAoBAgQgEvR7Lf1t9nD6P2qyUmgi" +
+ "Q0mG+RixYnglj2TaAMZmHn2/hUEFAgMBrbC/hUIFAgMDFRi/hUYIBAZn" +
+ "b29nbGW/hUcHBAVzYXJnb7+FSAcEBXNhcmdvv4VMCAQGR29vZ2xlv4VN" +
+ "CgQIUGl4ZWwgM2G/hU4GAgQBND1lv4VPBgIEATQ9Zb+FUQIFAA==";
+
+ @Test
+ public void testCanParseIdentityCredentialTag() throws IOException {
+ AuthorizationList authorizationList =
+ AuthorizationList.createAuthorizationList(
+ getEncodableAuthorizationList(EXTENTION_DATA_WITH_ID_CREDENTIAL_KEY),
+ ATTESTATION_VERSION);
+
+ assertThat(authorizationList.identityCredentialKey).isTrue();
+ }
+
@Test
public void testCreateAndParse() throws IOException {
AuthorizationList authorizationList =
diff --git a/server/src/test/java/com/google/android/attestation/ParsedAttestationRecordTest.java b/server/src/test/java/com/google/android/attestation/ParsedAttestationRecordTest.java
index 678ea9a..678d08e 100644
--- a/server/src/test/java/com/google/android/attestation/ParsedAttestationRecordTest.java
+++ b/server/src/test/java/com/google/android/attestation/ParsedAttestationRecordTest.java
@@ -25,7 +25,9 @@ import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
-import java.util.Set;
+import java.util.Arrays;
+
+import com.google.common.collect.ImmutableSet;
import org.bouncycastle.asn1.ASN1Sequence;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -67,6 +69,31 @@ public class ParsedAttestationRecordTest {
+ "OIkXEoFqqGi9GKJXUT1KYi5NsigaYqu7FoN4Qsvs61pMUEfZSPP2AFwkA8uNFbmb9uxcxaGHCA8i"
+ "3i9VM6yOLIrP\n"
+ "-----END CERTIFICATE-----";
+ private static final String CERT2 =
+ "-----BEGIN CERTIFICATE-----\n"
+ + "MIIEFDCCAnygAwIBAgIVAKZFQPAXr5VWrosuqx4C8tai2XbHMA0GCSqGSIb3DQEB\n"
+ + "CwUAMBgxFjAUBgNVBAMMDVVua25vd25Jc3N1ZXIwHhcNMjMwMjE1MTU0MzIwWhcN\n"
+ + "MjMwMjE1MTU0ODIwWjAdMRswGQYDVQQDDBJBbmRyb2lkQXR0ZXN0ZWRLZXkwggGi\n"
+ + "MA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDJAVfP/7F1bUbDqxMnOVXpSjt5\n"
+ + "NJwYemBJkN7l7TTbAhTfMW91006Si/snd79Y6bsJklVoiEN9LGL7tQrJEf5lSSLX\n"
+ + "ZeppjsbLqKnogFHhDJy2vaSiypV2wZdX+kO0qqIKjRvgSqHuTz3gemI1rWilrG3C\n"
+ + "vd3iHGlkw/4X5PpHQKz99/20p85HP6f/jydMHewFDRQCbkbo2pJ5WrJsyPe9me3o\n"
+ + "QE0O3lgij7jJ/UBHyb9iH0w13yi+1yZ/jgyojL4QNUeWZnxW656zfHCB8weePD+l\n"
+ + "tX4AAztZTziJQwk3zVClw4xIPTeztQV6ddRQgjSjGvWanpXqhJx8mq11gWaVJoCl\n"
+ + "q/I0KOguVsKq42M25uhF7/iAQjC+6lOUUfi2+aPwyTUfGHc5Bw/rTSw2LzvZDnUW\n"
+ + "8/yw4OUTyDravVcQLeoBES4+O5cVL0yTKDY0THG+ymgsFNgFS7PXUnAbXczYzvg8\n"
+ + "ldXKOXxnF5nWgg55n2iSQ6mqtHDEUsjcxjmuFcMCAwEAAaNQME4wTAYKKwYBBAHW\n"
+ + "eQIBEQQ+MDwCAQEKAQECAQIKAQEEEkEgcmFuZG9tIGNoYWxsZW5nZQQAMAAwFr+F\n"
+ + "SQgEBlNFUklBTL+FSgYEBElNRUkwDQYJKoZIhvcNAQELBQADggGBAHSms4IBjkc8\n"
+ + "1ZLHu5l70Ih2RrNU4XAc2E/oJX8OsBte9ZRwDT3TdcfLeg0rSneS+aB4xN1BGfmL\n"
+ + "DPZ1epRzMY4RagVhzBEauHpTaM2imRT9RN5TxbFvuMC4ELICYr5qHfqeALIlMET3\n"
+ + "TbCAo3njpNh5ids6qdlmpZRoYBQNMKfWJn8SUtCmVMk87FA7RZZCqCiRk+PBnciT\n"
+ + "O3LLbwT4aBlMinQ84gBfVXRqOvGAeGOgojDqGyK3tDMjIS7itpGb23vGogxHiHjA\n"
+ + "i8hiQhsHA+C89duCdeGyWZGmxwln7QRsosFI7G4ZOufXPLZt/DauNAC2Mb2OPcDw\n"
+ + "4tSKQvzQiL9UG4X3Cck0JnATxjT5sLttshJl98V6jQHcWSnjg8+oa3B8WgcePX8E\n"
+ + "QgcLhYaEGo9WDYJQvHfuUE5AquTxdTRbeiDbV7W+FAOQ5zi/wiGit86gF26120OQ\n"
+ + "KzQHP94/ORuAT/lkv3Fp3HytF4n3scur1nI0WqrfKpbUuPkmndCIbg==\n"
+ + "-----END CERTIFICATE-----\n";
private static final int EXPECTED_ATTESTATION_VERSION = 3;
private static final SecurityLevel EXPECTED_ATTESTATION_SECURITY_LEVEL =
@@ -82,15 +109,15 @@ public class ParsedAttestationRecordTest {
X509Certificate cert =
(X509Certificate)
factory.generateCertificate(new ByteArrayInputStream(certStr.getBytes(UTF_8)));
- cert.checkValidity();
return cert;
}
@Test
public void testParseAttestationRecord() throws CertificateException, IOException {
X509Certificate x509Certificate = getAttestationRecord(CERT);
+ X509Certificate x509Certificate2 = getAttestationRecord(CERT2);
ParsedAttestationRecord attestationRecord =
- ParsedAttestationRecord.createParsedAttestationRecord(x509Certificate);
+ ParsedAttestationRecord.createParsedAttestationRecord(Arrays.asList(x509Certificate2, x509Certificate));
assertThat(attestationRecord.attestationVersion).isEqualTo(EXPECTED_ATTESTATION_VERSION);
assertThat(attestationRecord.attestationSecurityLevel)
@@ -107,7 +134,7 @@ public class ParsedAttestationRecordTest {
@Test
public void testCreateAndParseAttestationRecord() {
AuthorizationList.Builder teeEnforcedBuilder = AuthorizationList.builder();
- teeEnforcedBuilder.userAuthType = Set.of(UserAuthType.FINGERPRINT);
+ teeEnforcedBuilder.userAuthType = ImmutableSet.of(UserAuthType.FINGERPRINT);
teeEnforcedBuilder.attestationIdBrand = "free food".getBytes(UTF_8);
ParsedAttestationRecord expected =
ParsedAttestationRecord.create(
@@ -119,7 +146,7 @@ public class ParsedAttestationRecordTest {
/* uniqueId= */ "foodplease".getBytes(UTF_8),
/* softwareEnforced= */ AuthorizationList.builder().build(),
/* teeEnforced= */ AuthorizationList.builder()
- .setUserAuthType(Set.of(UserAuthType.FINGERPRINT))
+ .setUserAuthType(ImmutableSet.of(UserAuthType.FINGERPRINT))
.setAttestationIdBrand("free food".getBytes(UTF_8)).build());
ASN1Sequence seq = expected.toAsn1Sequence();
ParsedAttestationRecord actual = ParsedAttestationRecord.create(seq);