1 /* 2 * Copyright 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.security.identity.cts; 18 19 import static org.junit.Assert.assertArrayEquals; 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertTrue; 23 import static org.junit.Assert.assertNull; 24 import static org.junit.Assume.assumeTrue; 25 26 import android.content.Context; 27 28 import android.hardware.biometrics.CryptoObject; 29 import android.security.identity.AccessControlProfile; 30 import android.security.identity.AccessControlProfileId; 31 import android.security.identity.PersonalizationData; 32 import android.security.identity.IdentityCredential; 33 import android.security.identity.IdentityCredentialException; 34 import android.security.identity.IdentityCredentialStore; 35 import android.security.identity.InvalidReaderSignatureException; 36 import android.security.identity.ResultData; 37 import android.security.identity.WritableIdentityCredential; 38 import android.security.identity.PresentationSession; 39 import android.security.identity.CredentialDataRequest; 40 import android.security.identity.CredentialDataResult; 41 import com.android.security.identity.internal.Util; 42 import androidx.test.InstrumentationRegistry; 43 44 import org.junit.Test; 45 46 import java.io.ByteArrayOutputStream; 47 import java.security.InvalidKeyException; 48 import java.security.NoSuchAlgorithmException; 49 import java.security.NoSuchProviderException; 50 import java.security.KeyPair; 51 import java.security.PrivateKey; 52 import java.security.PublicKey; 53 import java.security.SignatureException; 54 import java.security.cert.CertificateException; 55 import java.security.cert.X509Certificate; 56 import java.time.Instant; 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.Collection; 60 import java.util.LinkedHashMap; 61 import java.util.Map; 62 63 import co.nstant.in.cbor.CborException; 64 65 import javax.crypto.SecretKey; 66 67 public class MultiDocumentPresentationTest { 68 private static final String TAG = "MultiDocumentPresentationTest"; 69 getAuthKeyUsageCount(IdentityCredentialStore store, String credentialName)70 int[] getAuthKeyUsageCount(IdentityCredentialStore store, String credentialName) 71 throws Exception { 72 IdentityCredential credential = store.getCredentialByName( 73 credentialName, 74 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256); 75 return credential.getAuthenticationDataUsageCount(); 76 } 77 createAuthKeys(IdentityCredentialStore store, String credentialName)78 static Collection<X509Certificate> createAuthKeys(IdentityCredentialStore store, String credentialName) 79 throws Exception { 80 IdentityCredential credential = store.getCredentialByName( 81 credentialName, 82 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256); 83 credential.setAvailableAuthenticationKeys(5, 3); 84 Collection<X509Certificate> certificates = credential.getAuthKeysNeedingCertification(); 85 for (X509Certificate certificate : certificates) { 86 credential.storeStaticAuthenticationData(certificate, new byte[]{42, 43, 44}); 87 } 88 return certificates; 89 } 90 91 @Test multipleDocuments()92 public void multipleDocuments() throws Exception { 93 assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented()); 94 assumeTrue("IdentityCredentialStore.createPresentationSession(int) not supported", 95 TestUtil.getFeatureVersion() >= 202201); 96 97 Context appContext = InstrumentationRegistry.getTargetContext(); 98 IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext); 99 100 store.deleteCredentialByName("credential1"); 101 assertNull(store.deleteCredentialByName("credential1")); 102 ProvisioningTest.createCredential(store, "credential1"); 103 Collection<X509Certificate> credential1AuthKeys = createAuthKeys(store, "credential1"); 104 105 store.deleteCredentialByName("credential2"); 106 assertNull(store.deleteCredentialByName("credential2")); 107 ProvisioningTest.createCredential(store, "credential2"); 108 Collection<X509Certificate> credential2AuthKeys = createAuthKeys(store, "credential2"); 109 110 PresentationSession session = store.createPresentationSession( 111 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256); 112 113 KeyPair ephemeralKeyPair = session.getEphemeralKeyPair(); 114 KeyPair readerEphemeralKeyPair = Util.createEphemeralKeyPair(); 115 session.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic()); 116 byte[] sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair); 117 session.setSessionTranscript(sessionTranscript); 118 119 checkPresentation(session, "credential1", 120 credential1AuthKeys.iterator().next().getPublicKey(), 121 readerEphemeralKeyPair.getPrivate(), sessionTranscript); 122 // We should only have used a single key here. 123 assertArrayEquals(new int[]{1, 0, 0, 0, 0}, getAuthKeyUsageCount(store, "credential1")); 124 125 checkPresentation(session, "credential2", 126 credential2AuthKeys.iterator().next().getPublicKey(), 127 readerEphemeralKeyPair.getPrivate(), sessionTranscript); 128 assertArrayEquals(new int[]{1, 0, 0, 0, 0}, getAuthKeyUsageCount(store, "credential2")); 129 130 // Since it's the same session, additional getCredentialData() calls shouldn't consume 131 // additional auth-keys. Check this. 132 checkPresentation(session, "credential1", 133 null, 134 readerEphemeralKeyPair.getPrivate(), sessionTranscript); 135 assertArrayEquals(new int[]{1, 0, 0, 0, 0}, getAuthKeyUsageCount(store, "credential1")); 136 } 137 checkPresentation(PresentationSession session, String credentialName, PublicKey expectedAuthKey, PrivateKey readerEphemeralPrivateKey, byte[] sessionTranscript)138 static void checkPresentation(PresentationSession session, 139 String credentialName, 140 PublicKey expectedAuthKey, 141 PrivateKey readerEphemeralPrivateKey, 142 byte[] sessionTranscript) throws Exception { 143 // Now use one of the keys... 144 Map<String, Collection<String>> dsEntriesToRequest = new LinkedHashMap<>(); 145 dsEntriesToRequest.put("org.iso.18013-5.2019", 146 Arrays.asList("First name", 147 "Last name", 148 "Home address", 149 "Birth date", 150 "Cryptanalyst", 151 "Portrait image", 152 "Height")); 153 CredentialDataResult rd = session.getCredentialData( 154 credentialName, 155 new CredentialDataRequest.Builder() 156 .setDeviceSignedEntriesToRequest(dsEntriesToRequest) 157 .setRequestMessage(Util.createItemsRequest(dsEntriesToRequest, null)) 158 .setAllowUsingExhaustedKeys(true) 159 .setReaderSignature(null) 160 .setIncrementUseCount(true) 161 .setAllowUsingExpiredKeys(false) 162 .build()); 163 byte[] resultCbor = rd.getDeviceNameSpaces(); 164 try { 165 String pretty = Util.cborPrettyPrint(Util.canonicalizeCbor(resultCbor)); 166 assertEquals("{\n" 167 + " 'org.iso.18013-5.2019' : {\n" 168 + " 'Height' : 180,\n" 169 + " 'Last name' : 'Turing',\n" 170 + " 'Birth date' : '19120623',\n" 171 + " 'First name' : 'Alan',\n" 172 + " 'Cryptanalyst' : true,\n" 173 + " 'Home address' : 'Maida Vale, London, England',\n" 174 + " 'Portrait image' : [0x01, 0x02]\n" 175 + " }\n" 176 + "}", 177 pretty); 178 } catch (CborException e) { 179 e.printStackTrace(); 180 assertTrue(false); 181 } 182 183 byte[] deviceAuthenticationCbor = Util.buildDeviceAuthenticationCbor( 184 "org.iso.18013-5.2019.mdl", 185 sessionTranscript, 186 resultCbor); 187 188 if (expectedAuthKey != null) { 189 // Calculate the MAC by deriving the key using ECDH and HKDF. 190 SecretKey eMacKey = Util.calcEMacKeyForReader( 191 expectedAuthKey, 192 readerEphemeralPrivateKey, 193 sessionTranscript); 194 byte[] deviceAuthenticationBytes = 195 Util.prependSemanticTagForEncodedCbor(deviceAuthenticationCbor); 196 byte[] expectedMac = Util.coseMac0(eMacKey, 197 new byte[0], // payload 198 deviceAuthenticationBytes); // detached content 199 200 // Then compare it with what the TA produced. 201 assertArrayEquals(expectedMac, rd.getDeviceMac()); 202 203 // Feature version 202301 and later also returns an ECDSA signature. Check this. 204 if (TestUtil.getFeatureVersion() >= 202301) { 205 byte[] signature = rd.getDeviceSignature(); 206 assertNotNull(signature); 207 assertEquals(0, Util.coseSign1GetData(signature).length); 208 assertTrue(Util.coseSign1CheckSignature(signature, 209 deviceAuthenticationBytes, // detached content 210 expectedAuthKey)); 211 } else { 212 try { 213 rd.getDeviceSignature(); 214 assertTrue(false); 215 } catch (UnsupportedOperationException e) { 216 // This is the expected path... 217 } 218 } 219 } 220 } 221 222 @Test cryptoObjectReturnsCorrectSession()223 public void cryptoObjectReturnsCorrectSession() throws Exception { 224 assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented()); 225 assumeTrue("IdentityCredentialStore.createPresentationSession(int) not supported", 226 TestUtil.getFeatureVersion() >= 202201); 227 228 Context appContext = InstrumentationRegistry.getTargetContext(); 229 IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext); 230 231 PresentationSession session = store.createPresentationSession( 232 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256); 233 234 CryptoObject cryptoObject = new CryptoObject(session); 235 assertEquals(session, cryptoObject.getPresentationSession()); 236 } 237 } 238