diff options
author | Matej Zidek <matejz@google.com> | 2019-08-07 11:26:18 +0100 |
---|---|---|
committer | Matej Zidek <matejz@google.com> | 2019-08-22 18:51:50 +0100 |
commit | 9b85d778b01aa0d191df688eb2a85a5c759c164c (patch) | |
tree | 3805e3110450e52e41c440b6e0807256efa3a96b | |
parent | 2fe33ac3fbfb1e5d2ea7f3f65aab804f433f001b (diff) | |
download | android-key-attestation-9b85d778b01aa0d191df688eb2a85a5c759c164c.tar.gz |
Parse AttestationApplicationId
Change-Id: Ide8655c382a7fe120d5dfa8fdb6fb5ecee52cbe3
7 files changed, 300 insertions, 13 deletions
diff --git a/server/src/main/java/com/android/example/KeyAttestationExample.java b/server/src/main/java/com/android/example/KeyAttestationExample.java index ff29757..df919c6 100644 --- a/server/src/main/java/com/android/example/KeyAttestationExample.java +++ b/server/src/main/java/com/android/example/KeyAttestationExample.java @@ -18,6 +18,8 @@ package com.android.example; import static com.google.android.attestation.Constants.GOOGLE_ROOT_CERTIFICATE; import static com.google.android.attestation.ParsedAttestationRecord.createParsedAttestationRecord; +import com.google.android.attestation.AttestationApplicationId; +import com.google.android.attestation.AttestationApplicationId.AttestationPackageInfo; import com.google.android.attestation.AuthorizationList; import com.google.android.attestation.ParsedAttestationRecord; import com.google.android.attestation.RootOfTrust; @@ -70,7 +72,8 @@ import org.bouncycastle.util.encoders.Base64; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class KeyAttestationExample { - private static final String CERT_FILES_DIR = "examples/pem/algorithm_EC_SecurityLevel_StrongBox"; + private static final String DEFAULT_CERT_FILES_DIR = + "examples/pem/algorithm_EC_SecurityLevel_StrongBox"; public static void main(String[] args) throws CertificateException, IOException, NoSuchProviderException, NoSuchAlgorithmException, @@ -80,7 +83,7 @@ public class KeyAttestationExample { String certFilesDir = args[0]; certs = loadCertificates(certFilesDir); } else { - certs = loadCertificates(CERT_FILES_DIR); + certs = loadCertificates(DEFAULT_CERT_FILES_DIR); } verifyCertificateChain(certs); @@ -96,7 +99,7 @@ public class KeyAttestationExample { System.out.println( "Attestation Challenge: " + new String(parsedAttestationRecord.attestationChallenge)); - System.out.println("Unique ID: " + new String(parsedAttestationRecord.uniqueId)); + System.out.println("Unique ID: " + Arrays.toString(parsedAttestationRecord.uniqueId)); System.out.println("Software Enforced Authorization List:"); AuthorizationList softwareEnforced = parsedAttestationRecord.softwareEnforced; @@ -139,12 +142,19 @@ public class KeyAttestationExample { printOptional(authorizationList.creationDateTime, indent + "Creation DateTime"); printOptional(authorizationList.origin, indent + "Origin"); System.out.println(indent + "Rollback Resistant: " + authorizationList.rollbackResistant); - System.out.println(indent + "Root Of Trust:"); - printRootOfTrust(authorizationList.rootOfTrust, indent + "\t"); + if (authorizationList.rootOfTrust.isPresent()) { + System.out.println(indent + "Root Of Trust:"); + printRootOfTrust(authorizationList.rootOfTrust, indent + "\t"); + } printOptional(authorizationList.osVersion, indent + "OS Version"); printOptional(authorizationList.osPatchLevel, indent + "OS Patch Level"); + if (authorizationList.attestationApplicationId.isPresent()) { + System.out.println(indent + "Attestation Application ID:"); + printAttestationApplicationId(authorizationList.attestationApplicationId, indent + "\t"); + } printOptional( - authorizationList.attestationApplicationId, indent + "Attestation Application ID"); + authorizationList.attestationApplicationIdBytes, + indent + "Attestation Application ID Bytes"); printOptional(authorizationList.attestationIdBrand, indent + "Attestation ID Brand"); printOptional(authorizationList.attestationIdDevice, indent + "Attestation ID Device"); printOptional(authorizationList.attestationIdProduct, indent + "Attestation ID Product"); @@ -174,6 +184,20 @@ public class KeyAttestationExample { } } + private static void printAttestationApplicationId( + Optional<AttestationApplicationId> attestationApplicationId, String indent) { + if (attestationApplicationId.isPresent()) { + System.out.println(indent + "Package Infos (<package name>, <version>): "); + for (AttestationPackageInfo info : attestationApplicationId.get().packageInfos) { + System.out.println(indent + "\t" + info.packageName + ", " + info.version); + } + System.out.println(indent + "Signature Digests:"); + for (byte[] digest : attestationApplicationId.get().signatureDigests) { + System.out.println(indent + "\t" + Base64.toBase64String(digest)); + } + } + } + private static void printOptional(Optional optional, String caption) { if (optional.isPresent()) { if (optional.get() instanceof byte[]) { diff --git a/server/src/main/java/com/google/android/attestation/AttestationApplicationId.java b/server/src/main/java/com/google/android/attestation/AttestationApplicationId.java new file mode 100644 index 0000000..d5bfe71 --- /dev/null +++ b/server/src/main/java/com/google/android/attestation/AttestationApplicationId.java @@ -0,0 +1,171 @@ +/* Copyright 2019, The Android Open Source Project, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.attestation; + +import static com.google.android.attestation.Constants.ATTESTATION_APPLICATION_ID_PACKAGE_INFOS_INDEX; +import static com.google.android.attestation.Constants.ATTESTATION_APPLICATION_ID_SIGNATURE_DIGESTS_INDEX; +import static com.google.android.attestation.Constants.ATTESTATION_PACKAGE_INFO_PACKAGE_NAME_INDEX; +import static com.google.android.attestation.Constants.ATTESTATION_PACKAGE_INFO_VERSION_INDEX; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.ASN1OctetString; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.ASN1Set; +import org.bouncycastle.asn1.DEROctetString; + +/** + * This data structure reflects the Android platform's belief as to which apps are allowed to use + * the secret key material under attestation. The ID can comprise multiple packages if and only if + * multiple packages share the same UID. + */ +public class AttestationApplicationId implements Comparable<AttestationApplicationId> { + public final List<AttestationPackageInfo> packageInfos; + public final List<byte[]> signatureDigests; + + private AttestationApplicationId(DEROctetString attestationApplicationId) throws IOException { + ASN1Sequence attestationApplicationIdSequence = + (ASN1Sequence) ASN1Sequence.fromByteArray(attestationApplicationId.getOctets()); + ASN1Set attestationPackageInfos = + (ASN1Set) + attestationApplicationIdSequence.getObjectAt( + ATTESTATION_APPLICATION_ID_PACKAGE_INFOS_INDEX); + this.packageInfos = new ArrayList<>(); + for (ASN1Encodable packageInfo : attestationPackageInfos) { + this.packageInfos.add(new AttestationPackageInfo((ASN1Sequence) packageInfo)); + } + + ASN1Set digests = + (ASN1Set) + attestationApplicationIdSequence.getObjectAt( + ATTESTATION_APPLICATION_ID_SIGNATURE_DIGESTS_INDEX); + this.signatureDigests = new ArrayList<>(); + for (ASN1Encodable digest : digests) { + this.signatureDigests.add(((ASN1OctetString) digest).getOctets()); + } + } + + AttestationApplicationId( + List<AttestationPackageInfo> packageInfos, List<byte[]> signatureDigests) { + this.packageInfos = packageInfos; + this.signatureDigests = signatureDigests; + } + + static AttestationApplicationId createAttestationApplicationId( + DEROctetString attestationApplicationId) { + if (attestationApplicationId == null) { + return null; + } + try { + return new AttestationApplicationId(attestationApplicationId); + } catch (IOException e) { + return null; + } + } + + @Override + public int compareTo(AttestationApplicationId other) { + int res = Integer.compare(packageInfos.size(), other.packageInfos.size()); + if (res != 0) { + return res; + } + for (int i = 0; i < packageInfos.size(); ++i) { + res = packageInfos.get(i).compareTo(other.packageInfos.get(i)); + if (res != 0) { + return res; + } + } + res = Integer.compare(signatureDigests.size(), other.signatureDigests.size()); + if (res != 0) { + return res; + } + ByteArrayComparator cmp = new ByteArrayComparator(); + for (int i = 0; i < signatureDigests.size(); ++i) { + res = cmp.compare(signatureDigests.get(i), other.signatureDigests.get(i)); + if (res != 0) { + return res; + } + } + return res; + } + + @Override + public boolean equals(Object o) { + return (o instanceof AttestationApplicationId) + && (compareTo((AttestationApplicationId) o) == 0); + } + + /** Provides package's name and version number. */ + public static class AttestationPackageInfo implements Comparable<AttestationPackageInfo> { + public final String packageName; + public final long version; + + private AttestationPackageInfo(ASN1Sequence packageInfo) { + this.packageName = + new String( + ((ASN1OctetString) + packageInfo.getObjectAt(ATTESTATION_PACKAGE_INFO_PACKAGE_NAME_INDEX)) + .getOctets()); + this.version = + ((ASN1Integer) packageInfo.getObjectAt(ATTESTATION_PACKAGE_INFO_VERSION_INDEX)) + .getValue() + .longValue(); + } + + AttestationPackageInfo(String packageName, long version) { + this.packageName = packageName; + this.version = version; + } + + @Override + public int compareTo(AttestationPackageInfo other) { + int res = packageName.compareTo(other.packageName); + if (res != 0) { + return res; + } + res = Long.compare(version, other.version); + if (res != 0) { + return res; + } + return res; + } + + @Override + public boolean equals(Object o) { + return (o instanceof AttestationPackageInfo) && (0 == compareTo((AttestationPackageInfo) o)); + } + } + + private class ByteArrayComparator implements java.util.Comparator<byte[]> { + @Override + public int compare(byte[] a, byte[] b) { + int res = Integer.compare(a.length, b.length); + if (res != 0) { + return res; + } + for (int i = 0; i < a.length; ++i) { + res = Byte.compare(a[i], b[i]); + if (res != 0) { + return res; + } + } + return res; + } + } +} 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 f6a567c..012a994 100644 --- a/server/src/main/java/com/google/android/attestation/AuthorizationList.java +++ b/server/src/main/java/com/google/android/attestation/AuthorizationList.java @@ -67,6 +67,7 @@ import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1Set; import org.bouncycastle.asn1.ASN1TaggedObject; +import org.bouncycastle.asn1.DEROctetString; /** * This data structure contains the key pair's properties themselves, as defined in the Keymaster @@ -102,7 +103,8 @@ public class AuthorizationList { public final Optional<RootOfTrust> rootOfTrust; public final Optional<Integer> osVersion; public final Optional<Integer> osPatchLevel; - public final Optional<byte[]> attestationApplicationId; + public final Optional<AttestationApplicationId> attestationApplicationId; + public final Optional<byte[]> attestationApplicationIdBytes; public final Optional<byte[]> attestationIdBrand; public final Optional<byte[]> attestationIdDevice; public final Optional<byte[]> attestationIdProduct; @@ -167,6 +169,12 @@ public class AuthorizationList { this.osPatchLevel = findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_OS_PATCH_LEVEL); this.attestationApplicationId = + Optional.ofNullable( + AttestationApplicationId.createAttestationApplicationId( + (DEROctetString) + findAuthorizationListEntry( + authorizationMap, KM_TAG_ATTESTATION_APPLICATION_ID))); + this.attestationApplicationIdBytes = findOptionalByteArrayAuthorizationListEntry( authorizationMap, KM_TAG_ATTESTATION_APPLICATION_ID); this.attestationIdBrand = 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 10e9538..3464003 100644 --- a/server/src/main/java/com/google/android/attestation/Constants.java +++ b/server/src/main/java/com/google/android/attestation/Constants.java @@ -106,6 +106,10 @@ public class Constants { static final int ROOT_OF_TRUST_DEVICE_LOCKED_INDEX = 1; static final int ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX = 2; static final int ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX = 3; + static final int ATTESTATION_APPLICATION_ID_PACKAGE_INFOS_INDEX = 0; + static final int ATTESTATION_APPLICATION_ID_SIGNATURE_DIGESTS_INDEX = 1; + static final int ATTESTATION_PACKAGE_INFO_PACKAGE_NAME_INDEX = 0; + static final int ATTESTATION_PACKAGE_INFO_VERSION_INDEX = 1; // Some security values. The complete list is in this AOSP file: // hardware/libhardware/include/hardware/keymaster_defs.h static final int KM_SECURITY_LEVEL_SOFTWARE = 0; diff --git a/server/src/test/java/com/google/android/attestation/AttestationApplicationIdTest.java b/server/src/test/java/com/google/android/attestation/AttestationApplicationIdTest.java new file mode 100644 index 0000000..cbaa47b --- /dev/null +++ b/server/src/test/java/com/google/android/attestation/AttestationApplicationIdTest.java @@ -0,0 +1,81 @@ +/* Copyright 2019, The Android Open Source Project, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.attestation; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.android.attestation.AttestationApplicationId.AttestationPackageInfo; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.util.encoders.Base64; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test for {@link AttestationApplicationId} */ +@RunWith(JUnit4.class) +public class AttestationApplicationIdTest { + + // Generated from certificate with RSA Algorithm and StrongBox Security Level + private static final String ATTESTATION_APPLICATION_ID = + "MIIBszGCAYswDAQHYW5kcm9pZAIBHTAZBBRjb20uYW5kcm9pZC5rZXljaGFpbgIBHTAZBBRjb20uYW5kcm9pZC5zZXR0" + + "aW5ncwIBHTAZBBRjb20ucXRpLmRpYWdzZXJ2aWNlcwIBHTAaBBVjb20uYW5kcm9pZC5keW5zeXN0ZW0CAR0wHQ" + + "QYY29tLmFuZHJvaWQuaW5wdXRkZXZpY2VzAgEdMB8EGmNvbS5hbmRyb2lkLmxvY2FsdHJhbnNwb3J0AgEdMB8E" + + "GmNvbS5hbmRyb2lkLmxvY2F0aW9uLmZ1c2VkAgEdMB8EGmNvbS5hbmRyb2lkLnNlcnZlci50ZWxlY29tAgEdMC" + + "AEG2NvbS5hbmRyb2lkLndhbGxwYXBlcmJhY2t1cAIBHTAhBBxjb20uZ29vZ2xlLlNTUmVzdGFydERldGVjdG9y" + + "AgEdMCIEHWNvbS5nb29nbGUuYW5kcm9pZC5oaWRkZW5tZW51AgEBMCMEHmNvbS5hbmRyb2lkLnByb3ZpZGVycy" + + "5zZXR0aW5ncwIBHTEiBCAwGqPLCBE0UBxF8UIqvGbCQiT9Xe1f3I8X5pcXb9hmqg=="; + private static final DEROctetString ATTESTATION_APPLICATION_ID_OCTETS = + new DEROctetString(Base64.decode(ATTESTATION_APPLICATION_ID)); + + private static final List<AttestationPackageInfo> EXPECTED_PACKAGE_INFOS = + ImmutableList.of( + new AttestationPackageInfo("android", 29L), + new AttestationPackageInfo("com.android.keychain", 29L), + new AttestationPackageInfo("com.android.settings", 29L), + new AttestationPackageInfo("com.qti.diagservices", 29L), + new AttestationPackageInfo("com.android.dynsystem", 29L), + new AttestationPackageInfo("com.android.inputdevices", 29L), + new AttestationPackageInfo("com.android.localtransport", 29L), + new AttestationPackageInfo("com.android.location.fused", 29L), + new AttestationPackageInfo("com.android.server.telecom", 29L), + new AttestationPackageInfo("com.android.wallpaperbackup", 29L), + new AttestationPackageInfo("com.google.SSRestartDetector", 29L), + new AttestationPackageInfo("com.google.android.hiddenmenu", 1L), + new AttestationPackageInfo("com.android.providers.settings", 29L)); + private static final List<byte[]> EXPECTED_SIGNATURE_DIGESTS = + ImmutableList.of(Base64.decode("MBqjywgRNFAcRfFCKrxmwkIk/V3tX9yPF+aXF2/YZqo=\n")); + + private static final AttestationApplicationId EXPECTED_ATTESTATION_APPLICATION_ID = + new AttestationApplicationId(EXPECTED_PACKAGE_INFOS, EXPECTED_SIGNATURE_DIGESTS); + + @Test + public void testCreateAttestationApplicationId() { + AttestationApplicationId attestationApplicationId = + AttestationApplicationId.createAttestationApplicationId(ATTESTATION_APPLICATION_ID_OCTETS); + assertThat(attestationApplicationId).isEqualTo(EXPECTED_ATTESTATION_APPLICATION_ID); + } + + @Test + public void testCreateEmptyAttestationApplicationIdFromEmptyOrInvalidInput() { + assertThat(AttestationApplicationId.createAttestationApplicationId(null)).isNull(); + assertThat( + AttestationApplicationId.createAttestationApplicationId( + new DEROctetString(Base64.decode("Invalid DEROcetet String")))) + .isNull(); + } +} 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 fb617f9..b5c61d4 100644 --- a/server/src/test/java/com/google/android/attestation/AuthorizationListTest.java +++ b/server/src/test/java/com/google/android/attestation/AuthorizationListTest.java @@ -60,7 +60,7 @@ public class AuthorizationListTest { // 2019-07-15T14:56:32.972Z private static final Instant EXPECTED_SW_CREATION_DATETIME = Instant.ofEpochMilli(1563202592972L); - private static final byte[] EXPECTED_SW_ATTESTATION_APPLICATION_ID = + private static final byte[] EXPECTED_SW_ATTESTATION_APPLICATION_ID_BYTES = Base64.decode( "MIIBszGCAYswDAQHYW5kcm9pZAIBHTAZBBRjb20uYW5kcm9pZC5rZXljaGFpbgIBHTAZBBRjb20uYW5kcm9pZC5z" + "ZXR0aW5ncwIBHTAZBBRjb20ucXRpLmRpYWdzZXJ2aWNlcwIBHTAaBBVjb20uYW5kcm9pZC5keW5zeXN0ZW" @@ -70,7 +70,6 @@ public class AuthorizationListTest { + "UmVzdGFydERldGVjdG9yAgEdMCIEHWNvbS5nb29nbGUuYW5kcm9pZC5oaWRkZW5tZW51AgEBMCMEHmNvbS" + "5hbmRyb2lkLnByb3ZpZGVycy5zZXR0aW5ncwIBHTEiBCAwGqPLCBE0UBxF8UIqvGbCQiT9Xe1f3I8X5pcX" + "b9hmqg=="); - private static final ImmutableSet<Integer> EXPECTED_TEE_PURPOSE = ImmutableSet.of(PURPOSE_SIGN, PURPOSE_VERIFY); private static final Integer EXPECTED_TEE_ALGORITHM = ALGORITHM_RSA; @@ -100,8 +99,9 @@ public class AuthorizationListTest { assertThat(authorizationList.creationDateTime).hasValue(EXPECTED_SW_CREATION_DATETIME); assertThat(authorizationList.rootOfTrust).isEmpty(); - assertThat(authorizationList.attestationApplicationId) - .hasValue(EXPECTED_SW_ATTESTATION_APPLICATION_ID); + assertThat(authorizationList.attestationApplicationId).isPresent(); + assertThat(authorizationList.attestationApplicationIdBytes) + .hasValue(EXPECTED_SW_ATTESTATION_APPLICATION_ID_BYTES); } @Test diff --git a/server/src/test/java/com/google/android/attestation/RootOfTrustTest.java b/server/src/test/java/com/google/android/attestation/RootOfTrustTest.java index 31ebaf1..3f952a6 100644 --- a/server/src/test/java/com/google/android/attestation/RootOfTrustTest.java +++ b/server/src/test/java/com/google/android/attestation/RootOfTrustTest.java @@ -18,7 +18,6 @@ package com.google.android.attestation; import static com.google.common.truth.Truth.assertThat; import com.google.android.attestation.RootOfTrust.VerifiedBootState; -import com.google.common.truth.Truth; import java.io.IOException; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.util.encoders.Base64; @@ -64,6 +63,6 @@ public class RootOfTrustTest { @Test public void testCreateEmptyRootOfTrust() { - Truth.assertThat(RootOfTrust.createRootOfTrust(null, ATTESTATION_VERSION)).isNull(); + assertThat(RootOfTrust.createRootOfTrust(null, ATTESTATION_VERSION)).isNull(); } } |