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.IOException; 30 import java.time.Instant; 31 32 import org.bouncycastle.asn1.ASN1Encodable; 33 import org.bouncycastle.asn1.ASN1Sequence; 34 import org.bouncycastle.util.encoders.Base64; 35 import org.junit.Test; 36 import org.junit.runner.RunWith; 37 import org.junit.runners.JUnit4; 38 39 /** Test for {@link AuthorizationList}. */ 40 @RunWith(JUnit4.class) 41 public class AuthorizationListTest { 42 43 // Generated from certificate with RSA Algorithm and StrongBox Security Level 44 private static final String SW_ENFORCED_EXTENSION_DATA = 45 "MIIBzb+FPQgCBgFr9iKgzL+FRYIBuwSCAbcwggGzMYIBizAMBAdhbmRyb2lkAgEdMBkEFGNvbS5hbmRyb2lkLmtleWNo" 46 + "YWluAgEdMBkEFGNvbS5hbmRyb2lkLnNldHRpbmdzAgEdMBkEFGNvbS5xdGkuZGlhZ3NlcnZpY2VzAgEdMBoEFW" 47 + "NvbS5hbmRyb2lkLmR5bnN5c3RlbQIBHTAdBBhjb20uYW5kcm9pZC5pbnB1dGRldmljZXMCAR0wHwQaY29tLmFu" 48 + "ZHJvaWQubG9jYWx0cmFuc3BvcnQCAR0wHwQaY29tLmFuZHJvaWQubG9jYXRpb24uZnVzZWQCAR0wHwQaY29tLm" 49 + "FuZHJvaWQuc2VydmVyLnRlbGVjb20CAR0wIAQbY29tLmFuZHJvaWQud2FsbHBhcGVyYmFja3VwAgEdMCEEHGNv" 50 + "bS5nb29nbGUuU1NSZXN0YXJ0RGV0ZWN0b3ICAR0wIgQdY29tLmdvb2dsZS5hbmRyb2lkLmhpZGRlbm1lbnUCAQ" 51 + "EwIwQeY29tLmFuZHJvaWQucHJvdmlkZXJzLnNldHRpbmdzAgEdMSIEIDAao8sIETRQHEXxQiq8ZsJCJP1d7V/c" 52 + "jxfmlxdv2Gaq"; 53 private static final String TEE_ENFORCED_EXTENSION_DATA = 54 "MIGwoQgxBgIBAgIBA6IDAgEBowQCAggApQUxAwIBBKYIMQYCAQMCAQW/gUgFAgMBAAG/g3cCBQC/hT4DAgEAv4VATDBK" 55 + "BCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAoBAgQgco2xJ08fHPFXHeQ4CwSKVUrEo4Dnb1" 56 + "NVCDUpCEqTeAG/hUEDAgEAv4VCBQIDAxSzv4VOBgIEATQV8b+FTwYCBAE0Few="; 57 private static final int ATTESTATION_VERSION = 3; 58 59 // Some enum values, complete list can be found at: 60 // https://source.android.com/security/keystore/tags 61 private static final int PURPOSE_SIGN = 2; 62 private static final int PURPOSE_VERIFY = 3; 63 private static final int ALGORITHM_RSA = 1; 64 private static final int DIGEST_SHA_2_256 = 4; 65 private static final int PADDING_RSA_PSS = 3; 66 private static final int PADDING_RSA_1_5_SIGN = 5; 67 private static final int ORIGIN_GENERATED = 0; 68 69 // 2019-07-15T14:56:32.972Z 70 private static final Instant EXPECTED_SW_CREATION_DATETIME = Instant.ofEpochMilli(1563202592972L); 71 private static final byte[] EXPECTED_SW_ATTESTATION_APPLICATION_ID_BYTES = 72 Base64.decode( 73 "MIIBszGCAYswDAQHYW5kcm9pZAIBHTAZBBRjb20uYW5kcm9pZC5rZXljaGFpbgIBHTAZBBRjb20uYW5kcm9pZC5z" 74 + "ZXR0aW5ncwIBHTAZBBRjb20ucXRpLmRpYWdzZXJ2aWNlcwIBHTAaBBVjb20uYW5kcm9pZC5keW5zeXN0ZW" 75 + "0CAR0wHQQYY29tLmFuZHJvaWQuaW5wdXRkZXZpY2VzAgEdMB8EGmNvbS5hbmRyb2lkLmxvY2FsdHJhbnNw" 76 + "b3J0AgEdMB8EGmNvbS5hbmRyb2lkLmxvY2F0aW9uLmZ1c2VkAgEdMB8EGmNvbS5hbmRyb2lkLnNlcnZlci" 77 + "50ZWxlY29tAgEdMCAEG2NvbS5hbmRyb2lkLndhbGxwYXBlcmJhY2t1cAIBHTAhBBxjb20uZ29vZ2xlLlNT" 78 + "UmVzdGFydERldGVjdG9yAgEdMCIEHWNvbS5nb29nbGUuYW5kcm9pZC5oaWRkZW5tZW51AgEBMCMEHmNvbS" 79 + "5hbmRyb2lkLnByb3ZpZGVycy5zZXR0aW5ncwIBHTEiBCAwGqPLCBE0UBxF8UIqvGbCQiT9Xe1f3I8X5pcX" 80 + "b9hmqg=="); 81 private static final ImmutableSet<Integer> EXPECTED_TEE_PURPOSE = 82 ImmutableSet.of(PURPOSE_SIGN, PURPOSE_VERIFY); 83 private static final Integer EXPECTED_TEE_ALGORITHM = ALGORITHM_RSA; 84 private static final Integer EXPECTED_TEE_KEY_SIZE = 2048; 85 private static final ImmutableSet<Integer> EXPECTED_TEE_DIGEST = 86 ImmutableSet.of(DIGEST_SHA_2_256); 87 private static final ImmutableSet<Integer> EXPECTED_TEE_PADDING = 88 ImmutableSet.of(PADDING_RSA_PSS, PADDING_RSA_1_5_SIGN); 89 private static final Long EXPECTED_TEE_RSA_PUBLIC_COMPONENT = 65537L; 90 private static final Integer EXPECTED_TEE_ORIGIN = ORIGIN_GENERATED; 91 private static final Integer EXPECTED_TEE_OS_VERSION = 0; 92 private static final Integer EXPECTED_TEE_OS_PATCH_LEVEL = 201907; 93 private static final Integer EXPECTED_TEE_VENDOR_PATCH_LEVEL = 20190705; 94 private static final Integer EXPECTED_TEE_BOOT_PATCH_LEVEL = 20190700; 95 getEncodableAuthorizationList(String extensionData)96 private static ASN1Encodable[] getEncodableAuthorizationList(String extensionData) 97 throws IOException { 98 byte[] extensionDataBytes = Base64.decode(extensionData); 99 return ((ASN1Sequence) ASN1Sequence.fromByteArray(extensionDataBytes)).toArray(); 100 } 101 102 @Test testCanParseAuthorizationListFromSwEnforced()103 public void testCanParseAuthorizationListFromSwEnforced() throws IOException { 104 AuthorizationList authorizationList = 105 AuthorizationList.createAuthorizationList( 106 getEncodableAuthorizationList(SW_ENFORCED_EXTENSION_DATA), ATTESTATION_VERSION); 107 108 assertThat(authorizationList.creationDateTime).hasValue(EXPECTED_SW_CREATION_DATETIME); 109 assertThat(authorizationList.rootOfTrust).isEmpty(); 110 assertThat(authorizationList.attestationApplicationId).isPresent(); 111 assertThat(authorizationList.attestationApplicationIdBytes) 112 .hasValue(EXPECTED_SW_ATTESTATION_APPLICATION_ID_BYTES); 113 assertThat(authorizationList.individualAttestation).isFalse(); 114 assertThat(authorizationList.identityCredentialKey).isFalse(); 115 } 116 117 @Test testCanParseAuthorizationListFromTeeEnforced()118 public void testCanParseAuthorizationListFromTeeEnforced() throws IOException { 119 AuthorizationList authorizationList = 120 AuthorizationList.createAuthorizationList( 121 getEncodableAuthorizationList(TEE_ENFORCED_EXTENSION_DATA), ATTESTATION_VERSION); 122 123 assertThat(authorizationList.purpose).hasValue(EXPECTED_TEE_PURPOSE); 124 assertThat(authorizationList.algorithm).hasValue(EXPECTED_TEE_ALGORITHM); 125 assertThat(authorizationList.keySize).hasValue(EXPECTED_TEE_KEY_SIZE); 126 assertThat(authorizationList.digest).hasValue(EXPECTED_TEE_DIGEST); 127 assertThat(authorizationList.padding).hasValue(EXPECTED_TEE_PADDING); 128 assertThat(authorizationList.rsaPublicExponent).hasValue(EXPECTED_TEE_RSA_PUBLIC_COMPONENT); 129 assertThat(authorizationList.noAuthRequired).isTrue(); 130 assertThat(authorizationList.origin).hasValue(EXPECTED_TEE_ORIGIN); 131 assertThat(authorizationList.rootOfTrust).isPresent(); 132 assertThat(authorizationList.osVersion).hasValue(EXPECTED_TEE_OS_VERSION); 133 assertThat(authorizationList.osPatchLevel).hasValue(EXPECTED_TEE_OS_PATCH_LEVEL); 134 assertThat(authorizationList.vendorPatchLevel).hasValue(EXPECTED_TEE_VENDOR_PATCH_LEVEL); 135 assertThat(authorizationList.bootPatchLevel).hasValue(EXPECTED_TEE_BOOT_PATCH_LEVEL); 136 assertThat(authorizationList.individualAttestation).isFalse(); 137 assertThat(authorizationList.identityCredentialKey).isFalse(); 138 } 139 140 @Test testUserAuthTypeToEnum()141 public void testUserAuthTypeToEnum() { 142 assertThat(userAuthTypeToEnum(0L)).isEqualTo(ImmutableSet.of(USER_AUTH_TYPE_NONE)); 143 assertThat(userAuthTypeToEnum(1L)).isEqualTo(ImmutableSet.of(PASSWORD)); 144 assertThat(userAuthTypeToEnum(2L)).isEqualTo(ImmutableSet.of(FINGERPRINT)); 145 assertThat(userAuthTypeToEnum(3L)).isEqualTo(ImmutableSet.of(PASSWORD, FINGERPRINT)); 146 assertThat(userAuthTypeToEnum(UINT32_MAX)).isEqualTo(ImmutableSet.of(PASSWORD, FINGERPRINT, 147 USER_AUTH_TYPE_ANY)); 148 149 150 try { 151 userAuthTypeToEnum(4L); 152 fail(); 153 } catch (IllegalArgumentException expected) { 154 assertThat(expected).hasMessageThat().contains("Invalid User Auth Type."); 155 } 156 } 157 158 private static final String EXTENTION_DATA_WITH_INDIVIDUAL_ATTESTATION = 159 "MIH0oQgxBgIBAgIBA6IDAgEBowQCAggApQUxAwIBBKYIMQYCAQMCAQW/gUgFAgMBAAG/g3cCBQC/hT4DAgEAv4VATDBK" 160 + "BCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAoBAgQgEvR7Lf1t9nD6P2qyUmgiQ0mG+RixYn" 161 + "glj2TaAMZmHn2/hUEFAgMBrbC/hUIFAgMDFRi/hUYIBAZnb29nbGW/hUcHBAVzYXJnb7+FSAcEBXNhcmdvv4VM" 162 + "CAQGR29vZ2xlv4VNCgQIUGl4ZWwgM2G/hU4GAgQBND1lv4VPBgIEATQ9Zb+FUAIFAA=="; 163 164 @Test testCanParseIndividualAttestation()165 public void testCanParseIndividualAttestation() throws IOException { 166 AuthorizationList authorizationList = 167 AuthorizationList.createAuthorizationList( 168 getEncodableAuthorizationList(EXTENTION_DATA_WITH_INDIVIDUAL_ATTESTATION), 169 ATTESTATION_VERSION); 170 171 assertThat(authorizationList.individualAttestation).isTrue(); 172 } 173 174 private static final String EXTENTION_DATA_WITH_ID_CREDENTIAL_KEY = 175 "MIH0oQgxBgIBAgIBA6IDAgEBowQCAggApQUxAwIBBKYIMQYCAQMCAQW/" + 176 "gUgFAgMBAAG/g3cCBQC/hT4DAgEAv4VATDBKBCAAAAAAAAAAAAAAAAAA" + 177 "AAAAAAAAAAAAAAAAAAAAAAAAAAEBAAoBAgQgEvR7Lf1t9nD6P2qyUmgi" + 178 "Q0mG+RixYnglj2TaAMZmHn2/hUEFAgMBrbC/hUIFAgMDFRi/hUYIBAZn" + 179 "b29nbGW/hUcHBAVzYXJnb7+FSAcEBXNhcmdvv4VMCAQGR29vZ2xlv4VN" + 180 "CgQIUGl4ZWwgM2G/hU4GAgQBND1lv4VPBgIEATQ9Zb+FUQIFAA=="; 181 182 @Test testCanParseIdentityCredentialTag()183 public void testCanParseIdentityCredentialTag() throws IOException { 184 AuthorizationList authorizationList = 185 AuthorizationList.createAuthorizationList( 186 getEncodableAuthorizationList(EXTENTION_DATA_WITH_ID_CREDENTIAL_KEY), 187 ATTESTATION_VERSION); 188 189 assertThat(authorizationList.identityCredentialKey).isTrue(); 190 } 191 192 @Test testCreateAndParse()193 public void testCreateAndParse() throws IOException { 194 AuthorizationList authorizationList = 195 AuthorizationList.createAuthorizationList( 196 getEncodableAuthorizationList(EXTENTION_DATA_WITH_INDIVIDUAL_ATTESTATION), 197 ATTESTATION_VERSION); 198 ASN1Sequence seq = authorizationList.toAsn1Sequence(); 199 assertThat(seq.getEncoded("DER")) 200 .isEqualTo(Base64.decode(EXTENTION_DATA_WITH_INDIVIDUAL_ATTESTATION)); 201 } 202 } 203