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