1 /* Copyright 2019, The Android Open Source Project, Inc. 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package com.google.android.attestation; 17 18 import static com.google.android.attestation.AuthorizationList.UserAuthType.FINGERPRINT; 19 import static com.google.android.attestation.AuthorizationList.UserAuthType.PASSWORD; 20 import static com.google.android.attestation.AuthorizationList.UserAuthType.USER_AUTH_TYPE_ANY; 21 import static com.google.android.attestation.AuthorizationList.UserAuthType.USER_AUTH_TYPE_NONE; 22 import static com.google.android.attestation.AuthorizationList.userAuthTypeToEnum; 23 import static com.google.android.attestation.Constants.UINT32_MAX; 24 import static com.google.common.truth.Truth.assertThat; 25 import static com.google.common.truth.Truth8.assertThat; 26 import static org.junit.Assert.fail; 27 28 import com.google.common.collect.ImmutableSet; 29 import java.io.ByteArrayOutputStream; 30 import java.io.IOException; 31 import java.time.Instant; 32 import java.util.Set; 33 34 import org.bouncycastle.asn1.ASN1Encodable; 35 import org.bouncycastle.asn1.ASN1Sequence; 36 import org.bouncycastle.util.encoders.Base64; 37 import org.junit.Test; 38 import org.junit.runner.RunWith; 39 import org.junit.runners.JUnit4; 40 41 /** Test for {@link AuthorizationList}. */ 42 @RunWith(JUnit4.class) 43 public class AuthorizationListTest { 44 45 // Generated from certificate with RSA Algorithm and StrongBox Security Level 46 private static final String SW_ENFORCED_EXTENSION_DATA = 47 "MIIBzb+FPQgCBgFr9iKgzL+FRYIBuwSCAbcwggGzMYIBizAMBAdhbmRyb2lkAgEdMBkEFGNvbS5hbmRyb2lkLmtleWNo" 48 + "YWluAgEdMBkEFGNvbS5hbmRyb2lkLnNldHRpbmdzAgEdMBkEFGNvbS5xdGkuZGlhZ3NlcnZpY2VzAgEdMBoEFW" 49 + "NvbS5hbmRyb2lkLmR5bnN5c3RlbQIBHTAdBBhjb20uYW5kcm9pZC5pbnB1dGRldmljZXMCAR0wHwQaY29tLmFu" 50 + "ZHJvaWQubG9jYWx0cmFuc3BvcnQCAR0wHwQaY29tLmFuZHJvaWQubG9jYXRpb24uZnVzZWQCAR0wHwQaY29tLm" 51 + "FuZHJvaWQuc2VydmVyLnRlbGVjb20CAR0wIAQbY29tLmFuZHJvaWQud2FsbHBhcGVyYmFja3VwAgEdMCEEHGNv" 52 + "bS5nb29nbGUuU1NSZXN0YXJ0RGV0ZWN0b3ICAR0wIgQdY29tLmdvb2dsZS5hbmRyb2lkLmhpZGRlbm1lbnUCAQ" 53 + "EwIwQeY29tLmFuZHJvaWQucHJvdmlkZXJzLnNldHRpbmdzAgEdMSIEIDAao8sIETRQHEXxQiq8ZsJCJP1d7V/c" 54 + "jxfmlxdv2Gaq"; 55 private static final String TEE_ENFORCED_EXTENSION_DATA = 56 "MIGwoQgxBgIBAgIBA6IDAgEBowQCAggApQUxAwIBBKYIMQYCAQMCAQW/gUgFAgMBAAG/g3cCBQC/hT4DAgEAv4VATDBK" 57 + "BCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAoBAgQgco2xJ08fHPFXHeQ4CwSKVUrEo4Dnb1" 58 + "NVCDUpCEqTeAG/hUEDAgEAv4VCBQIDAxSzv4VOBgIEATQV8b+FTwYCBAE0Few="; 59 private static final int ATTESTATION_VERSION = 3; 60 61 // Some enum values, complete list can be found at: 62 // https://source.android.com/security/keystore/tags 63 private static final int PURPOSE_SIGN = 2; 64 private static final int PURPOSE_VERIFY = 3; 65 private static final int ALGORITHM_RSA = 1; 66 private static final int DIGEST_SHA_2_256 = 4; 67 private static final int PADDING_RSA_PSS = 3; 68 private static final int PADDING_RSA_1_5_SIGN = 5; 69 private static final int ORIGIN_GENERATED = 0; 70 71 // 2019-07-15T14:56:32.972Z 72 private static final Instant EXPECTED_SW_CREATION_DATETIME = Instant.ofEpochMilli(1563202592972L); 73 private static final byte[] EXPECTED_SW_ATTESTATION_APPLICATION_ID_BYTES = 74 Base64.decode( 75 "MIIBszGCAYswDAQHYW5kcm9pZAIBHTAZBBRjb20uYW5kcm9pZC5rZXljaGFpbgIBHTAZBBRjb20uYW5kcm9pZC5z" 76 + "ZXR0aW5ncwIBHTAZBBRjb20ucXRpLmRpYWdzZXJ2aWNlcwIBHTAaBBVjb20uYW5kcm9pZC5keW5zeXN0ZW" 77 + "0CAR0wHQQYY29tLmFuZHJvaWQuaW5wdXRkZXZpY2VzAgEdMB8EGmNvbS5hbmRyb2lkLmxvY2FsdHJhbnNw" 78 + "b3J0AgEdMB8EGmNvbS5hbmRyb2lkLmxvY2F0aW9uLmZ1c2VkAgEdMB8EGmNvbS5hbmRyb2lkLnNlcnZlci" 79 + "50ZWxlY29tAgEdMCAEG2NvbS5hbmRyb2lkLndhbGxwYXBlcmJhY2t1cAIBHTAhBBxjb20uZ29vZ2xlLlNT" 80 + "UmVzdGFydERldGVjdG9yAgEdMCIEHWNvbS5nb29nbGUuYW5kcm9pZC5oaWRkZW5tZW51AgEBMCMEHmNvbS" 81 + "5hbmRyb2lkLnByb3ZpZGVycy5zZXR0aW5ncwIBHTEiBCAwGqPLCBE0UBxF8UIqvGbCQiT9Xe1f3I8X5pcX" 82 + "b9hmqg=="); 83 private static final ImmutableSet<Integer> EXPECTED_TEE_PURPOSE = 84 ImmutableSet.of(PURPOSE_SIGN, PURPOSE_VERIFY); 85 private static final Integer EXPECTED_TEE_ALGORITHM = ALGORITHM_RSA; 86 private static final Integer EXPECTED_TEE_KEY_SIZE = 2048; 87 private static final ImmutableSet<Integer> EXPECTED_TEE_DIGEST = 88 ImmutableSet.of(DIGEST_SHA_2_256); 89 private static final ImmutableSet<Integer> EXPECTED_TEE_PADDING = 90 ImmutableSet.of(PADDING_RSA_PSS, PADDING_RSA_1_5_SIGN); 91 private static final Long EXPECTED_TEE_RSA_PUBLIC_COMPONENT = 65537L; 92 private static final Integer EXPECTED_TEE_ORIGIN = ORIGIN_GENERATED; 93 private static final Integer EXPECTED_TEE_OS_VERSION = 0; 94 private static final Integer EXPECTED_TEE_OS_PATCH_LEVEL = 201907; 95 private static final Integer EXPECTED_TEE_VENDOR_PATCH_LEVEL = 20190705; 96 private static final Integer EXPECTED_TEE_BOOT_PATCH_LEVEL = 20190700; 97 getEncodableAuthorizationList(String extensionData)98 private static ASN1Encodable[] getEncodableAuthorizationList(String extensionData) 99 throws IOException { 100 byte[] extensionDataBytes = Base64.decode(extensionData); 101 return ((ASN1Sequence) ASN1Sequence.fromByteArray(extensionDataBytes)).toArray(); 102 } 103 104 @Test testCanParseAuthorizationListFromSwEnforced()105 public void testCanParseAuthorizationListFromSwEnforced() throws IOException { 106 AuthorizationList authorizationList = 107 AuthorizationList.createAuthorizationList( 108 getEncodableAuthorizationList(SW_ENFORCED_EXTENSION_DATA), ATTESTATION_VERSION); 109 110 assertThat(authorizationList.creationDateTime).hasValue(EXPECTED_SW_CREATION_DATETIME); 111 assertThat(authorizationList.rootOfTrust).isEmpty(); 112 assertThat(authorizationList.attestationApplicationId).isPresent(); 113 assertThat(authorizationList.attestationApplicationIdBytes) 114 .hasValue(EXPECTED_SW_ATTESTATION_APPLICATION_ID_BYTES); 115 assertThat(authorizationList.individualAttestation).isFalse(); 116 } 117 118 @Test testCanParseAuthorizationListFromTeeEnforced()119 public void testCanParseAuthorizationListFromTeeEnforced() throws IOException { 120 AuthorizationList authorizationList = 121 AuthorizationList.createAuthorizationList( 122 getEncodableAuthorizationList(TEE_ENFORCED_EXTENSION_DATA), ATTESTATION_VERSION); 123 124 assertThat(authorizationList.purpose).hasValue(EXPECTED_TEE_PURPOSE); 125 assertThat(authorizationList.algorithm).hasValue(EXPECTED_TEE_ALGORITHM); 126 assertThat(authorizationList.keySize).hasValue(EXPECTED_TEE_KEY_SIZE); 127 assertThat(authorizationList.digest).hasValue(EXPECTED_TEE_DIGEST); 128 assertThat(authorizationList.padding).hasValue(EXPECTED_TEE_PADDING); 129 assertThat(authorizationList.rsaPublicExponent).hasValue(EXPECTED_TEE_RSA_PUBLIC_COMPONENT); 130 assertThat(authorizationList.noAuthRequired).isTrue(); 131 assertThat(authorizationList.origin).hasValue(EXPECTED_TEE_ORIGIN); 132 assertThat(authorizationList.rootOfTrust).isPresent(); 133 assertThat(authorizationList.osVersion).hasValue(EXPECTED_TEE_OS_VERSION); 134 assertThat(authorizationList.osPatchLevel).hasValue(EXPECTED_TEE_OS_PATCH_LEVEL); 135 assertThat(authorizationList.vendorPatchLevel).hasValue(EXPECTED_TEE_VENDOR_PATCH_LEVEL); 136 assertThat(authorizationList.bootPatchLevel).hasValue(EXPECTED_TEE_BOOT_PATCH_LEVEL); 137 assertThat(authorizationList.individualAttestation).isFalse(); 138 } 139 140 @Test testUserAuthTypeToEnum()141 public void testUserAuthTypeToEnum() { 142 assertThat(userAuthTypeToEnum(0L)).isEqualTo(Set.of(USER_AUTH_TYPE_NONE)); 143 assertThat(userAuthTypeToEnum(1L)).isEqualTo(Set.of(PASSWORD)); 144 assertThat(userAuthTypeToEnum(2L)).isEqualTo(Set.of(FINGERPRINT)); 145 assertThat(userAuthTypeToEnum(3L)).isEqualTo(Set.of(PASSWORD, FINGERPRINT)); 146 assertThat(userAuthTypeToEnum(UINT32_MAX)).isEqualTo(Set.of(PASSWORD, FINGERPRINT, USER_AUTH_TYPE_ANY)); 147 148 149 try { 150 userAuthTypeToEnum(4L); 151 fail(); 152 } catch (IllegalArgumentException expected) { 153 assertThat(expected).hasMessageThat().contains("Invalid User Auth Type."); 154 } 155 } 156 157 private static final String EXTENTION_DATA_WITH_INDIVIDUAL_ATTESTATION = 158 "MIH0oQgxBgIBAgIBA6IDAgEBowQCAggApQUxAwIBBKYIMQYCAQMCAQW/gUgFAgMBAAG/g3cCBQC/hT4DAgEAv4VATDBK" 159 + "BCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAoBAgQgEvR7Lf1t9nD6P2qyUmgiQ0mG+RixYn" 160 + "glj2TaAMZmHn2/hUEFAgMBrbC/hUIFAgMDFRi/hUYIBAZnb29nbGW/hUcHBAVzYXJnb7+FSAcEBXNhcmdvv4VM" 161 + "CAQGR29vZ2xlv4VNCgQIUGl4ZWwgM2G/hU4GAgQBND1lv4VPBgIEATQ9Zb+FUAIFAA=="; 162 163 @Test testCanParseIndividualAttestation()164 public void testCanParseIndividualAttestation() throws IOException { 165 AuthorizationList authorizationList = 166 AuthorizationList.createAuthorizationList( 167 getEncodableAuthorizationList(EXTENTION_DATA_WITH_INDIVIDUAL_ATTESTATION), 168 ATTESTATION_VERSION); 169 170 assertThat(authorizationList.individualAttestation).isTrue(); 171 } 172 173 @Test testCreateAndParse()174 public void testCreateAndParse() throws IOException { 175 AuthorizationList authorizationList = 176 AuthorizationList.createAuthorizationList( 177 getEncodableAuthorizationList(EXTENTION_DATA_WITH_INDIVIDUAL_ATTESTATION), 178 ATTESTATION_VERSION); 179 ASN1Sequence seq = authorizationList.toAsn1Sequence(); 180 assertThat(seq.getEncoded("DER")) 181 .isEqualTo(Base64.decode(EXTENTION_DATA_WITH_INDIVIDUAL_ATTESTATION)); 182 } 183 } 184