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 android.security.identity.ResultData.STATUS_NOT_REQUESTED;
20 import static android.security.identity.ResultData.STATUS_NO_SUCH_ENTRY;
21 import static android.security.identity.ResultData.STATUS_OK;
22 import static android.security.identity.ResultData.STATUS_NOT_IN_REQUEST_MESSAGE;
23 import static android.security.identity.ResultData.STATUS_NO_ACCESS_CONTROL_PROFILES;
24 
25 import static org.junit.Assert.assertArrayEquals;
26 import static org.junit.Assert.assertEquals;
27 import static org.junit.Assert.assertNull;
28 import static org.junit.Assert.assertNotNull;
29 import static org.junit.Assert.assertTrue;
30 import static org.junit.Assume.assumeTrue;
31 
32 import android.content.Context;
33 
34 import android.security.identity.AccessControlProfile;
35 import android.security.identity.AccessControlProfileId;
36 import android.security.identity.AlreadyPersonalizedException;
37 import android.security.identity.PersonalizationData;
38 import android.security.identity.IdentityCredential;
39 import android.security.identity.IdentityCredentialException;
40 import android.security.identity.IdentityCredentialStore;
41 import android.security.identity.ResultData;
42 import android.security.identity.WritableIdentityCredential;
43 import android.security.identity.SessionTranscriptMismatchException;
44 import androidx.test.InstrumentationRegistry;
45 
46 import org.junit.Test;
47 
48 import java.io.ByteArrayOutputStream;
49 import java.security.KeyPair;
50 import java.security.InvalidKeyException;
51 import java.security.MessageDigest;
52 import java.security.NoSuchAlgorithmException;
53 import java.security.cert.CertificateEncodingException;
54 import java.security.cert.X509Certificate;
55 import java.util.Arrays;
56 import java.util.ArrayList;
57 import java.util.Collection;
58 import java.util.Iterator;
59 import java.util.LinkedList;
60 import java.util.LinkedHashMap;
61 import java.util.Map;
62 
63 import co.nstant.in.cbor.CborBuilder;
64 import co.nstant.in.cbor.CborEncoder;
65 import co.nstant.in.cbor.CborException;
66 import co.nstant.in.cbor.model.UnicodeString;
67 import co.nstant.in.cbor.model.UnsignedInteger;
68 
69 /**
70  * Instrumented test, which will execute on an Android device.
71  *
72  * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
73  */
74 public class ProvisioningTest {
75     private static final String TAG = "ProvisioningTest";
76 
getExampleDrivingPrivilegesCbor()77     private static byte[] getExampleDrivingPrivilegesCbor() {
78         // As per 7.4.4 of ISO 18013-5, driving privileges are defined with the following CDDL:
79         //
80         // driving_privileges = [
81         //     * driving_privilege
82         // ]
83         //
84         // driving_privilege = {
85         //     vehicle_category_code: tstr ; Vehicle category code as per ISO 18013-2 Annex A
86         //     ? issue_date: #6.0(tstr)    ; Date of issue encoded as full-date per RFC 3339
87         //     ? expiry_date: #6.0(tstr)   ; Date of expiry encoded as full-date per RFC 3339
88         //     ? code: tstr                ; Code as per ISO 18013-2 Annex A
89         //     ? sign: tstr                ; Sign as per ISO 18013-2 Annex A
90         //     ? value: int                ; Value as per ISO 18013-2 Annex A
91         // }
92         //
93         ByteArrayOutputStream baos = new ByteArrayOutputStream();
94         try {
95             new CborEncoder(baos).encode(new CborBuilder()
96                     .addArray()
97                     .addMap()
98                     .put(new UnicodeString("vehicle_category_code"), new UnicodeString("TODO"))
99                     .put(new UnicodeString("value"), new UnsignedInteger(42))
100                     .end()
101                     .end()
102                     .build());
103         } catch (CborException e) {
104             assertTrue(false);
105         }
106         return baos.toByteArray();
107     }
108 
createCredential(IdentityCredentialStore store, String credentialName)109     static Collection<X509Certificate> createCredential(IdentityCredentialStore store,
110             String credentialName) throws IdentityCredentialException {
111         return createCredentialWithChallengeAndAcpId(store, credentialName, "SomeChallenge".getBytes(), 0);
112     }
113 
createCredentialWithAcpId(IdentityCredentialStore store, String credentialName, int accessControlProfileId)114     static Collection<X509Certificate> createCredentialWithAcpId(IdentityCredentialStore store,
115             String credentialName,
116             int accessControlProfileId) throws IdentityCredentialException {
117         return createCredentialWithChallengeAndAcpId(store, credentialName, "SomeChallenge".getBytes(),
118                                                      accessControlProfileId);
119     }
120 
createCredentialWithChallenge(IdentityCredentialStore store, String credentialName, byte[] challenge)121     static Collection<X509Certificate> createCredentialWithChallenge(IdentityCredentialStore store,
122             String credentialName,
123             byte[] challenge) throws IdentityCredentialException {
124         return createCredentialWithChallengeAndAcpId(store, credentialName, challenge, 0);
125     }
126 
createCredentialWithChallengeAndAcpId(IdentityCredentialStore store, String credentialName, byte[] challenge, int id)127     static Collection<X509Certificate> createCredentialWithChallengeAndAcpId(IdentityCredentialStore store,
128             String credentialName,
129             byte[] challenge,
130             int id) throws IdentityCredentialException {
131         WritableIdentityCredential wc = null;
132         wc = store.createCredential(credentialName, "org.iso.18013-5.2019.mdl");
133 
134         Collection<X509Certificate> certificateChain =
135                 wc.getCredentialKeyCertificateChain(challenge);
136         // TODO: inspect cert-chain
137 
138         // Profile X (no authentication)
139         AccessControlProfile noAuthProfile =
140                 new AccessControlProfile.Builder(new AccessControlProfileId(id))
141                         .setUserAuthenticationRequired(false)
142                         .build();
143 
144         byte[] drivingPrivileges = getExampleDrivingPrivilegesCbor();
145 
146         Collection<AccessControlProfileId> idsNoAuth = new ArrayList<AccessControlProfileId>();
147         idsNoAuth.add(new AccessControlProfileId(id));
148         Collection<AccessControlProfileId> idsNoAcp = new ArrayList<AccessControlProfileId>();
149         String mdlNs = "org.iso.18013-5.2019";
150         PersonalizationData personalizationData =
151                 new PersonalizationData.Builder()
152                         .addAccessControlProfile(noAuthProfile)
153                         .putEntry(mdlNs, "First name", idsNoAuth, Util.cborEncodeString("Alan"))
154                         .putEntry(mdlNs, "Last name", idsNoAuth, Util.cborEncodeString("Turing"))
155                         .putEntry(mdlNs, "Home address", idsNoAuth,
156                                 Util.cborEncodeString("Maida Vale, London, England"))
157                         .putEntry(mdlNs, "Birth date", idsNoAuth, Util.cborEncodeString("19120623"))
158                         .putEntry(mdlNs, "Cryptanalyst", idsNoAuth, Util.cborEncodeBoolean(true))
159                         .putEntry(mdlNs, "Portrait image", idsNoAuth, Util.cborEncodeBytestring(
160                             new byte[]{0x01, 0x02}))
161                         .putEntry(mdlNs, "Height", idsNoAuth, Util.cborEncodeInt(180))
162                         .putEntry(mdlNs, "Neg Item", idsNoAuth, Util.cborEncodeInt(-42))
163                         .putEntry(mdlNs, "Int Two Bytes", idsNoAuth, Util.cborEncodeInt(0x101))
164                         .putEntry(mdlNs, "Int Four Bytes", idsNoAuth, Util.cborEncodeInt(0x10001))
165                         .putEntry(mdlNs, "Int Eight Bytes", idsNoAuth,
166                                 Util.cborEncodeInt(0x100000001L))
167                         .putEntry(mdlNs, "driving_privileges", idsNoAuth, drivingPrivileges)
168                         .putEntry(mdlNs, "No Access", idsNoAcp,
169                                 Util.cborEncodeString("Cannot be retrieved"))
170                         .build();
171 
172         byte[] proofOfProvisioningSignature = wc.personalize(personalizationData);
173         byte[] proofOfProvisioning = Util.coseSign1GetData(proofOfProvisioningSignature);
174 
175         String pretty = "";
176         try {
177             pretty = Util.cborPrettyPrint(proofOfProvisioning);
178         } catch (CborException e) {
179             e.printStackTrace();
180             assertTrue(false);
181         }
182         // Checks that order of elements is the order it was added, using the API.
183         assertEquals("[\n"
184                 + "  'ProofOfProvisioning',\n"
185                 + "  'org.iso.18013-5.2019.mdl',\n"
186                 + "  [\n"
187                 + "    {\n"
188                 + "      'id' : " + id + "\n"
189                 + "    }\n"
190                 + "  ],\n"
191                 + "  {\n"
192                 + "    'org.iso.18013-5.2019' : [\n"
193                 + "      {\n"
194                 + "        'name' : 'First name',\n"
195                 + "        'value' : 'Alan',\n"
196                 + "        'accessControlProfiles' : [" + id + "]\n"
197                 + "      },\n"
198                 + "      {\n"
199                 + "        'name' : 'Last name',\n"
200                 + "        'value' : 'Turing',\n"
201                 + "        'accessControlProfiles' : [" + id + "]\n"
202                 + "      },\n"
203                 + "      {\n"
204                 + "        'name' : 'Home address',\n"
205                 + "        'value' : 'Maida Vale, London, England',\n"
206                 + "        'accessControlProfiles' : [" + id + "]\n"
207                 + "      },\n"
208                 + "      {\n"
209                 + "        'name' : 'Birth date',\n"
210                 + "        'value' : '19120623',\n"
211                 + "        'accessControlProfiles' : [" + id + "]\n"
212                 + "      },\n"
213                 + "      {\n"
214                 + "        'name' : 'Cryptanalyst',\n"
215                 + "        'value' : true,\n"
216                 + "        'accessControlProfiles' : [" + id + "]\n"
217                 + "      },\n"
218                 + "      {\n"
219                 + "        'name' : 'Portrait image',\n"
220                 + "        'value' : [0x01, 0x02],\n"
221                 + "        'accessControlProfiles' : [" + id + "]\n"
222                 + "      },\n"
223                 + "      {\n"
224                 + "        'name' : 'Height',\n"
225                 + "        'value' : 180,\n"
226                 + "        'accessControlProfiles' : [" + id + "]\n"
227                 + "      },\n"
228                 + "      {\n"
229                 + "        'name' : 'Neg Item',\n"
230                 + "        'value' : -42,\n"
231                 + "        'accessControlProfiles' : [" + id + "]\n"
232                 + "      },\n"
233                 + "      {\n"
234                 + "        'name' : 'Int Two Bytes',\n"
235                 + "        'value' : 257,\n"
236                 + "        'accessControlProfiles' : [" + id + "]\n"
237                 + "      },\n"
238                 + "      {\n"
239                 + "        'name' : 'Int Four Bytes',\n"
240                 + "        'value' : 65537,\n"
241                 + "        'accessControlProfiles' : [" + id + "]\n"
242                 + "      },\n"
243                 + "      {\n"
244                 + "        'name' : 'Int Eight Bytes',\n"
245                 + "        'value' : 4294967297,\n"
246                 + "        'accessControlProfiles' : [" + id + "]\n"
247                 + "      },\n"
248                 + "      {\n"
249                 + "        'name' : 'driving_privileges',\n"
250                 + "        'value' : [\n"
251                 + "          {\n"
252                 + "            'value' : 42,\n"
253                 + "            'vehicle_category_code' : 'TODO'\n"
254                 + "          }\n"
255                 + "        ],\n"
256                 + "        'accessControlProfiles' : [" + id + "]\n"
257                 + "      },\n"
258                 + "      {\n"
259                 + "        'name' : 'No Access',\n"
260                 + "        'value' : 'Cannot be retrieved',\n"
261                 + "        'accessControlProfiles' : []\n"
262                 + "      }\n"
263                 + "    ]\n"
264                 + "  },\n"
265                 + "  false\n"
266                 + "]", pretty);
267 
268         try {
269             assertTrue(Util.coseSign1CheckSignature(
270                 proofOfProvisioningSignature,
271                 new byte[0], // Additional data
272                 certificateChain.iterator().next().getPublicKey()));
273         } catch (NoSuchAlgorithmException | InvalidKeyException e) {
274             e.printStackTrace();
275             assertTrue(false);
276         }
277 
278         // TODO: Check challenge is in certificatechain
279 
280         // TODO: Check each cert signs the next one
281 
282         // TODO: Check bottom cert is the Google well-know cert
283 
284         // TODO: need to also get and check SecurityStatement
285 
286         return certificateChain;
287     }
288 
createCredentialMultipleNamespaces( IdentityCredentialStore store, String credentialName)289     static Collection<X509Certificate> createCredentialMultipleNamespaces(
290             IdentityCredentialStore store,
291             String credentialName) throws IdentityCredentialException {
292         WritableIdentityCredential wc = null;
293         wc = store.createCredential(credentialName, "org.iso.18013-5.2019.mdl");
294 
295         Collection<X509Certificate> certificateChain =
296                 wc.getCredentialKeyCertificateChain("SomeChallenge".getBytes());
297 
298         // Profile 0 (no authentication)
299         AccessControlProfile noAuthProfile =
300                 new AccessControlProfile.Builder(new AccessControlProfileId(0))
301                         .setUserAuthenticationRequired(false)
302                         .build();
303 
304         Collection<AccessControlProfileId> idsNoAuth = new ArrayList<AccessControlProfileId>();
305         idsNoAuth.add(new AccessControlProfileId(0));
306         PersonalizationData personalizationData =
307                 new PersonalizationData.Builder()
308                         .addAccessControlProfile(noAuthProfile)
309                         .putEntry("org.example.barfoo", "Bar", idsNoAuth,
310                                 Util.cborEncodeString("Foo"))
311                         .putEntry("org.example.barfoo", "Foo", idsNoAuth,
312                                 Util.cborEncodeString("Bar"))
313                         .putEntry("org.example.foobar", "Foo", idsNoAuth,
314                                 Util.cborEncodeString("Bar"))
315                         .putEntry("org.example.foobar", "Bar", idsNoAuth,
316                                 Util.cborEncodeString("Foo"))
317                         .build();
318 
319         try {
320             byte[] proofOfProvisioningSignature = wc.personalize(personalizationData);
321             byte[] proofOfProvisioning = Util.coseSign1GetData(proofOfProvisioningSignature);
322             String pretty = Util.cborPrettyPrint(proofOfProvisioning);
323             // Checks that order of elements is the order it was added, using the API.
324             assertEquals("[\n"
325                     + "  'ProofOfProvisioning',\n"
326                     + "  'org.iso.18013-5.2019.mdl',\n"
327                     + "  [\n"
328                     + "    {\n"
329                     + "      'id' : 0\n"
330                     + "    }\n"
331                     + "  ],\n"
332                     + "  {\n"
333                     + "    'org.example.barfoo' : [\n"
334                     + "      {\n"
335                     + "        'name' : 'Bar',\n"
336                     + "        'value' : 'Foo',\n"
337                     + "        'accessControlProfiles' : [0]\n"
338                     + "      },\n"
339                     + "      {\n"
340                     + "        'name' : 'Foo',\n"
341                     + "        'value' : 'Bar',\n"
342                     + "        'accessControlProfiles' : [0]\n"
343                     + "      }\n"
344                     + "    ],\n"
345                     + "    'org.example.foobar' : [\n"
346                     + "      {\n"
347                     + "        'name' : 'Foo',\n"
348                     + "        'value' : 'Bar',\n"
349                     + "        'accessControlProfiles' : [0]\n"
350                     + "      },\n"
351                     + "      {\n"
352                     + "        'name' : 'Bar',\n"
353                     + "        'value' : 'Foo',\n"
354                     + "        'accessControlProfiles' : [0]\n"
355                     + "      }\n"
356                     + "    ]\n"
357                     + "  },\n"
358                     + "  false\n"
359                     + "]", pretty);
360 
361         } catch (CborException e) {
362             e.printStackTrace();
363             assertTrue(false);
364         }
365 
366         return certificateChain;
367     }
368 
369     @Test
alreadyPersonalized()370     public void alreadyPersonalized() throws IdentityCredentialException {
371         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
372 
373         Context appContext = InstrumentationRegistry.getTargetContext();
374         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
375 
376         store.deleteCredentialByName("test");
377         createCredential(store, "test");
378         try {
379             createCredential(store, "test");
380             assertTrue(false);
381         } catch (AlreadyPersonalizedException e) {
382             // The expected path.
383         }
384         store.deleteCredentialByName("test");
385         // TODO: check retutrned |proofOfDeletion|
386     }
387 
388     @Test
nonExistent()389     public void nonExistent() throws IdentityCredentialException {
390         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
391 
392         Context appContext = InstrumentationRegistry.getTargetContext();
393         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
394 
395         store.deleteCredentialByName("test");
396         IdentityCredential credential = store.getCredentialByName("test",
397                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
398         assertNull(credential);
399     }
400 
401     @Test
defaultStoreSupportsAnyDocumentType()402     public void defaultStoreSupportsAnyDocumentType() throws IdentityCredentialException {
403         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
404 
405         Context appContext = InstrumentationRegistry.getTargetContext();
406         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
407 
408         String[] supportedDocTypes = store.getSupportedDocTypes();
409         assertEquals(0, supportedDocTypes.length);
410     }
411 
412     @Test
deleteCredentialByName()413     public void deleteCredentialByName()
414             throws IdentityCredentialException, CborException, CertificateEncodingException {
415         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
416 
417         Context appContext = InstrumentationRegistry.getTargetContext();
418         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
419 
420         store.deleteCredentialByName("test");
421         assertNull(store.deleteCredentialByName("test"));
422         Collection<X509Certificate> certificateChain = createCredential(store, "test");
423 
424         // Deleting the credential involves destroying the keys referenced in the returned
425         // certificateChain... so get an encoded blob we can turn into a X509 cert when
426         // checking the deletion receipt below, post-deletion.
427         byte[] encodedCredentialCert = certificateChain.iterator().next().getEncoded();
428 
429         byte[] proofOfDeletionSignature = store.deleteCredentialByName("test");
430         byte[] proofOfDeletion = Util.coseSign1GetData(proofOfDeletionSignature);
431 
432         // Check the returned CBOR is what is expected.
433         String pretty = Util.cborPrettyPrint(proofOfDeletion);
434         assertEquals("['ProofOfDeletion', 'org.iso.18013-5.2019.mdl', false]", pretty);
435 
436         try {
437             assertTrue(Util.coseSign1CheckSignature(
438                 proofOfDeletionSignature,
439                 new byte[0], // Additional data
440                 certificateChain.iterator().next().getPublicKey()));
441         } catch (NoSuchAlgorithmException | InvalidKeyException e) {
442             e.printStackTrace();
443             assertTrue(false);
444         }
445 
446         // Finally, check deleting an already deleted credential returns the expected.
447         assertNull(store.deleteCredentialByName("test"));
448     }
449 
450     @Test
deleteCredential()451     public void deleteCredential()
452             throws IdentityCredentialException, CborException, CertificateEncodingException {
453         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
454 
455         Context appContext = InstrumentationRegistry.getTargetContext();
456         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
457         assumeTrue("IdentityCredential.delete() not supported", Util.getFeatureVersion() >= 202101);
458 
459         store.deleteCredentialByName("test");
460         assertNull(store.deleteCredentialByName("test"));
461         Collection<X509Certificate> certificateChain = createCredential(store, "test");
462 
463         // Deleting the credential involves destroying the keys referenced in the returned
464         // certificateChain... so get an encoded blob we can turn into a X509 cert when
465         // checking the deletion receipt below, post-deletion.
466         byte[] encodedCredentialCert = certificateChain.iterator().next().getEncoded();
467 
468         IdentityCredential credential = store.getCredentialByName("test",
469                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
470         assertNotNull(credential);
471 
472         byte[] challenge = new byte[]{0x20, 0x21};
473         byte[] proofOfDeletionSignature = credential.delete(challenge);
474         byte[] proofOfDeletion = Util.coseSign1GetData(proofOfDeletionSignature);
475 
476         // Check the returned CBOR is what is expected.
477         String pretty = Util.cborPrettyPrint(proofOfDeletion);
478         assertEquals("['ProofOfDeletion', 'org.iso.18013-5.2019.mdl', [0x20, 0x21], false]", pretty);
479 
480         try {
481             assertTrue(Util.coseSign1CheckSignature(
482                 proofOfDeletionSignature,
483                 new byte[0], // Additional data
484                 certificateChain.iterator().next().getPublicKey()));
485         } catch (NoSuchAlgorithmException | InvalidKeyException e) {
486             e.printStackTrace();
487             assertTrue(false);
488         }
489 
490         // Finally, check deleting an already deleted credential returns the expected.
491         assertNull(store.deleteCredentialByName("test"));
492     }
493 
494     @Test
proofOfOwnership()495     public void proofOfOwnership()
496             throws IdentityCredentialException, CborException, CertificateEncodingException {
497         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
498 
499         Context appContext = InstrumentationRegistry.getTargetContext();
500         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
501         assumeTrue("IdentityCredential.proveOwnership() not supported", Util.getFeatureVersion() >= 202101);
502 
503         store.deleteCredentialByName("test");
504         assertNull(store.deleteCredentialByName("test"));
505         Collection<X509Certificate> certificateChain = createCredential(store, "test");
506 
507         byte[] encodedCredentialCert = certificateChain.iterator().next().getEncoded();
508 
509         IdentityCredential credential = store.getCredentialByName("test",
510                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
511         assertNotNull(credential);
512 
513         byte[] challenge = new byte[]{0x12, 0x22};
514         byte[] proofOfOwnershipSignature = credential.proveOwnership(challenge);
515         byte[] proofOfOwnership = Util.coseSign1GetData(proofOfOwnershipSignature);
516 
517         // Check the returned CBOR is what is expected.
518         String pretty = Util.cborPrettyPrint(proofOfOwnership);
519         assertEquals("['ProofOfOwnership', 'org.iso.18013-5.2019.mdl', [0x12, 0x22], false]",
520                 pretty);
521 
522         try {
523             assertTrue(Util.coseSign1CheckSignature(
524                 proofOfOwnershipSignature,
525                 new byte[0], // Additional data
526                 certificateChain.iterator().next().getPublicKey()));
527         } catch (NoSuchAlgorithmException | InvalidKeyException e) {
528             e.printStackTrace();
529             assertTrue(false);
530         }
531 
532         // Finally, check the credential is still there
533         assertNotNull(store.deleteCredentialByName("test"));
534     }
535 
536     @Test
testProvisionAndRetrieve()537     public void testProvisionAndRetrieve() throws IdentityCredentialException, CborException {
538         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
539 
540         Context appContext = InstrumentationRegistry.getTargetContext();
541         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
542 
543         store.deleteCredentialByName("test");
544         Collection<X509Certificate> certChain = createCredential(store, "test");
545 
546         IdentityCredential credential = store.getCredentialByName("test",
547                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
548 
549         // Check that the read-back certChain matches the created one.
550         Collection<X509Certificate> readBackCertChain =
551                 credential.getCredentialKeyCertificateChain();
552         assertEquals(certChain.size(), readBackCertChain.size());
553         Iterator<X509Certificate> it = readBackCertChain.iterator();
554         for (X509Certificate expectedCert : certChain) {
555             X509Certificate readBackCert = it.next();
556             assertEquals(expectedCert, readBackCert);
557         }
558 
559         Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
560         entriesToRequest.put("org.iso.18013-5.2019",
561                 Arrays.asList("First name",
562                         "Last name",
563                         "Home address",
564                         "Birth date",
565                         "Cryptanalyst",
566                         "Portrait image",
567                         "Height",
568                         "Neg Item",
569                         "Int Two Bytes",
570                         "Int Eight Bytes",
571                         "Int Four Bytes",
572                         "driving_privileges"));
573         ResultData rd = credential.getEntries(
574                 Util.createItemsRequest(entriesToRequest, null),
575                 entriesToRequest,
576                 null,
577                 null);
578 
579         Collection<String> resultNamespaces = rd.getNamespaces();
580         assertEquals(resultNamespaces.size(), 1);
581         assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
582         assertEquals(12, rd.getEntryNames("org.iso.18013-5.2019").size());
583 
584         String ns = "org.iso.18013-5.2019";
585         assertEquals("Alan", Util.getStringEntry(rd, ns, "First name"));
586         assertEquals("Turing", Util.getStringEntry(rd, ns, "Last name"));
587         assertEquals("Maida Vale, London, England", Util.getStringEntry(rd, ns, "Home address"));
588         assertEquals("19120623", Util.getStringEntry(rd, ns, "Birth date"));
589         assertEquals(true, Util.getBooleanEntry(rd, ns, "Cryptanalyst"));
590         assertArrayEquals(new byte[]{0x01, 0x02},
591                 Util.getBytestringEntry(rd, ns, "Portrait image"));
592         assertEquals(180, Util.getIntegerEntry(rd, ns, "Height"));
593         assertEquals(-42, Util.getIntegerEntry(rd, ns, "Neg Item"));
594         assertEquals(0x101, Util.getIntegerEntry(rd, ns, "Int Two Bytes"));
595         assertEquals(0x10001, Util.getIntegerEntry(rd, ns, "Int Four Bytes"));
596         assertEquals(0x100000001L, Util.getIntegerEntry(rd, ns, "Int Eight Bytes"));
597         byte[] drivingPrivileges = getExampleDrivingPrivilegesCbor();
598         assertArrayEquals(drivingPrivileges, rd.getEntry(ns, "driving_privileges"));
599 
600         assertEquals("{\n"
601                 + "  'org.iso.18013-5.2019' : {\n"
602                 + "    'Height' : 180,\n"
603                 + "    'Neg Item' : -42,\n"
604                 + "    'Last name' : 'Turing',\n"
605                 + "    'Birth date' : '19120623',\n"
606                 + "    'First name' : 'Alan',\n"
607                 + "    'Cryptanalyst' : true,\n"
608                 + "    'Home address' : 'Maida Vale, London, England',\n"
609                 + "    'Int Two Bytes' : 257,\n"
610                 + "    'Int Four Bytes' : 65537,\n"
611                 + "    'Portrait image' : [0x01, 0x02],\n"
612                 + "    'Int Eight Bytes' : 4294967297,\n"
613                 + "    'driving_privileges' : [\n"
614                 + "      {\n"
615                 + "        'value' : 42,\n"
616                 + "        'vehicle_category_code' : 'TODO'\n"
617                 + "      }\n"
618                 + "    ]\n"
619                 + "  }\n"
620                 + "}", Util.cborPrettyPrint(Util.canonicalizeCbor(rd.getAuthenticatedData())));
621 
622         store.deleteCredentialByName("test");
623     }
624 
625     @Test
testProvisionAndRetrieveMultipleTimes()626     public void testProvisionAndRetrieveMultipleTimes() throws IdentityCredentialException,
627             InvalidKeyException {
628         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
629 
630         Context appContext = InstrumentationRegistry.getTargetContext();
631         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
632 
633         // This checks we can do multiple getEntries() calls
634 
635         store.deleteCredentialByName("test");
636         Collection<X509Certificate> certChain = createCredential(store, "test");
637 
638         IdentityCredential credential = store.getCredentialByName("test",
639                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
640 
641         // We're going to need some authentication keys for this so create some dummy ones.
642         credential.setAvailableAuthenticationKeys(5, 1);
643         Collection<X509Certificate> authKeys = credential.getAuthKeysNeedingCertification();
644         for (X509Certificate authKey : authKeys) {
645             byte[] staticAuthData = new byte[5];
646             credential.storeStaticAuthenticationData(authKey, staticAuthData);
647         }
648 
649         KeyPair ephemeralKeyPair = credential.createEphemeralKeyPair();
650         KeyPair readerEphemeralKeyPair = Util.createEphemeralKeyPair();
651         credential.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
652         byte[] sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
653 
654         Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
655         entriesToRequest.put("org.iso.18013-5.2019", Arrays.asList("First name", "Last name"));
656 
657         for (int n = 0; n < 3; n++) {
658             ResultData rd = credential.getEntries(
659                 Util.createItemsRequest(entriesToRequest, null),
660                 entriesToRequest,
661                 sessionTranscript,
662                 null);
663             assertEquals("Alan", Util.getStringEntry(rd, "org.iso.18013-5.2019", "First name"));
664             assertEquals("Turing", Util.getStringEntry(rd, "org.iso.18013-5.2019", "Last name"));
665             assertNotNull(rd.getMessageAuthenticationCode());
666         }
667 
668         // Now try with a different (but still valid) sessionTranscript - this should fail with
669         // SessionTranscriptMismatchException
670         KeyPair otherEphemeralKeyPair = Util.createEphemeralKeyPair();
671         byte[] otherSessionTranscript = Util.buildSessionTranscript(otherEphemeralKeyPair);
672         try {
673             ResultData rd = credential.getEntries(
674                 Util.createItemsRequest(entriesToRequest, null),
675                 entriesToRequest,
676                 otherSessionTranscript,
677                 null);
678             assertTrue(false);
679         } catch (SessionTranscriptMismatchException e) {
680             // This is the expected path...
681         } catch (IdentityCredentialException e) {
682             e.printStackTrace();
683             assertTrue(false);
684         }
685 
686         store.deleteCredentialByName("test");
687     }
688 
689     @Test
testProvisionAndRetrieveWithFiltering()690     public void testProvisionAndRetrieveWithFiltering() throws IdentityCredentialException {
691         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
692 
693         Context appContext = InstrumentationRegistry.getTargetContext();
694         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
695 
696         store.deleteCredentialByName("test");
697         Collection<X509Certificate> certChain = createCredential(store, "test");
698 
699         IdentityCredential credential = store.getCredentialByName("test",
700                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
701 
702         Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
703         entriesToRequest.put("org.iso.18013-5.2019",
704                 Arrays.asList("First name",
705                         "Last name",
706                         "Home address",
707                         "Birth date",
708                         "Cryptanalyst",
709                         "Portrait image",
710                         "Height"));
711         Map<String, Collection<String>> entriesToRequestWithoutHomeAddress = new LinkedHashMap<>();
712         entriesToRequestWithoutHomeAddress.put("org.iso.18013-5.2019",
713                 Arrays.asList("First name",
714                         "Last name",
715                         "Birth date",
716                         "Cryptanalyst",
717                         "Portrait image",
718                         "Height"));
719         ResultData rd = credential.getEntries(
720                 Util.createItemsRequest(entriesToRequest, null),
721                 entriesToRequestWithoutHomeAddress,
722                 null,
723                 null);
724 
725         Collection<String> resultNamespaces = rd.getNamespaces();
726         assertEquals(resultNamespaces.size(), 1);
727         assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
728         assertEquals(6, rd.getEntryNames("org.iso.18013-5.2019").size());
729 
730         String ns = "org.iso.18013-5.2019";
731         assertEquals("Alan", Util.getStringEntry(rd, ns, "First name"));
732         assertEquals("Turing", Util.getStringEntry(rd, ns, "Last name"));
733         assertEquals("19120623", Util.getStringEntry(rd, ns, "Birth date"));
734         assertEquals(true, Util.getBooleanEntry(rd, ns, "Cryptanalyst"));
735         assertArrayEquals(new byte[]{0x01, 0x02},
736                 Util.getBytestringEntry(rd, ns, "Portrait image"));
737         assertEquals(180, Util.getIntegerEntry(rd, ns, "Height"));
738 
739         store.deleteCredentialByName("test");
740     }
741 
742     @Test
testProvisionAndRetrieveElementWithNoACP()743     public void testProvisionAndRetrieveElementWithNoACP() throws IdentityCredentialException {
744         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
745 
746         Context appContext = InstrumentationRegistry.getTargetContext();
747         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
748 
749         store.deleteCredentialByName("test");
750         Collection<X509Certificate> certChain = createCredential(store, "test");
751 
752         IdentityCredential credential = store.getCredentialByName("test",
753                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
754 
755         Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
756         entriesToRequest.put("org.iso.18013-5.2019", Arrays.asList("No Access"));
757         ResultData rd = credential.getEntries(
758                 Util.createItemsRequest(entriesToRequest, null),
759                 entriesToRequest,
760                 null,
761                 null);
762 
763         Collection<String> resultNamespaces = rd.getNamespaces();
764         assertEquals(resultNamespaces.size(), 1);
765         assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
766         assertEquals(1, rd.getEntryNames("org.iso.18013-5.2019").size());
767         assertEquals(0, rd.getRetrievedEntryNames("org.iso.18013-5.2019").size());
768 
769         String ns = "org.iso.18013-5.2019";
770         assertEquals(STATUS_NO_ACCESS_CONTROL_PROFILES, rd.getStatus(ns, "No Access"));
771 
772         store.deleteCredentialByName("test");
773     }
774 
775     // TODO: Make sure we test retrieving an entry with multiple ACPs and test all four cases:
776     //
777     // - ACP1 bad,  ACP2 bad   -> NOT OK
778     // - ACP1 good, ACP2 bad   -> OK
779     // - ACP1 bad,  ACP2 good  -> OK
780     // - ACP1 good, ACP2 good  -> OK
781     //
782 
783     @Test
testProvisionAndRetrieveWithEntryNotInRequest()784     public void testProvisionAndRetrieveWithEntryNotInRequest() throws IdentityCredentialException {
785         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
786 
787         Context appContext = InstrumentationRegistry.getTargetContext();
788         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
789 
790         store.deleteCredentialByName("test");
791         Collection<X509Certificate> certChain = createCredential(store, "test");
792 
793         IdentityCredential credential = store.getCredentialByName("test",
794                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
795 
796         Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
797         entriesToRequest.put("org.iso.18013-5.2019",
798                 Arrays.asList("First name",
799                         "Last name",
800                         "Home address",
801                         "Birth date",
802                         "Cryptanalyst",
803                         "Portrait image",
804                         "Height"));
805         Map<String, Collection<String>> entriesToRequestWithoutHomeAddress = new LinkedHashMap<>();
806         entriesToRequestWithoutHomeAddress.put("org.iso.18013-5.2019",
807                 Arrays.asList("First name",
808                         "Last name",
809                         "Birth date",
810                         "Cryptanalyst",
811                         "Portrait image",
812                         "Height"));
813         ResultData rd = credential.getEntries(
814                 Util.createItemsRequest(entriesToRequestWithoutHomeAddress, null),
815                 entriesToRequest,
816                 null,
817                 null);
818 
819         Collection<String> resultNamespaces = rd.getNamespaces();
820         assertEquals(resultNamespaces.size(), 1);
821         assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
822         assertEquals(7, rd.getEntryNames("org.iso.18013-5.2019").size());
823         assertEquals(6, rd.getRetrievedEntryNames("org.iso.18013-5.2019").size());
824 
825         String ns = "org.iso.18013-5.2019";
826         assertEquals(STATUS_NOT_IN_REQUEST_MESSAGE, rd.getStatus(ns, "Home address"));
827 
828         assertEquals("Alan", Util.getStringEntry(rd, ns, "First name"));
829         assertEquals("Turing", Util.getStringEntry(rd, ns, "Last name"));
830         assertEquals("19120623", Util.getStringEntry(rd, ns, "Birth date"));
831         assertEquals(true, Util.getBooleanEntry(rd, ns, "Cryptanalyst"));
832         assertArrayEquals(new byte[]{0x01, 0x02},
833                 Util.getBytestringEntry(rd, ns, "Portrait image"));
834         assertEquals(180, Util.getIntegerEntry(rd, ns, "Height"));
835 
836         store.deleteCredentialByName("test");
837     }
838 
839     @Test
nonExistentEntries()840     public void nonExistentEntries() throws IdentityCredentialException {
841         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
842 
843         Context appContext = InstrumentationRegistry.getTargetContext();
844         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
845 
846         store.deleteCredentialByName("test");
847         Collection<X509Certificate> certChain = createCredential(store, "test");
848 
849         IdentityCredential credential = store.getCredentialByName("test",
850                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
851 
852         Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
853         entriesToRequest.put("org.iso.18013-5.2019",
854                 Arrays.asList("First name",
855                         "Last name",
856                         "Non-existent Entry"));
857         ResultData rd = credential.getEntries(
858                 null,
859                 entriesToRequest,
860                 null,
861                 null);
862 
863         Collection<String> resultNamespaces = rd.getNamespaces();
864         assertEquals(resultNamespaces.size(), 1);
865         assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
866         assertEquals(3, rd.getEntryNames("org.iso.18013-5.2019").size());
867         assertEquals(2, rd.getRetrievedEntryNames("org.iso.18013-5.2019").size());
868 
869         String ns = "org.iso.18013-5.2019";
870 
871         assertEquals(STATUS_OK, rd.getStatus(ns, "First name"));
872         assertEquals(STATUS_OK, rd.getStatus(ns, "Last name"));
873         assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Non-existent Entry"));
874         assertEquals(STATUS_NOT_REQUESTED, rd.getStatus(ns, "Entry not even requested"));
875 
876         assertEquals("Alan", Util.getStringEntry(rd, ns, "First name"));
877         assertEquals("Turing", Util.getStringEntry(rd, ns, "Last name"));
878         assertNull(rd.getEntry(ns, "Non-existent Entry"));
879         assertNull(rd.getEntry(ns, "Entry not even requested"));
880 
881         store.deleteCredentialByName("test");
882     }
883 
884     @Test
multipleNamespaces()885     public void multipleNamespaces() throws IdentityCredentialException, CborException {
886         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
887 
888         Context appContext = InstrumentationRegistry.getTargetContext();
889         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
890 
891         store.deleteCredentialByName("test");
892         Collection<X509Certificate> certChain = createCredentialMultipleNamespaces(
893                 store, "test");
894 
895         IdentityCredential credential = store.getCredentialByName("test",
896                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
897 
898         // Request these in different order than they are stored
899         Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
900         entriesToRequest.put("org.example.foobar", Arrays.asList("Foo", "Bar", "Non-exist"));
901         entriesToRequest.put("org.example.barfoo", Arrays.asList("Bar", "Non-exist", "Foo"));
902         entriesToRequest.put("org.example.foofoo", Arrays.asList("Bar", "Foo", "Non-exist"));
903 
904         ResultData rd = credential.getEntries(
905                 null,
906                 entriesToRequest,
907                 null,
908                 null);
909 
910         // We should get the same number of namespaces back, as we requested - even for namespaces
911         // that do not exist in the credential.
912         //
913         // Additionally, each namespace should have exactly the items requested, in the same order.
914         Collection<String> resultNamespaces = rd.getNamespaces();
915         assertEquals(resultNamespaces.size(), 3);
916 
917 
918         // First requested namespace - org.example.foobar
919         String ns = "org.example.foobar";
920         assertArrayEquals(new String[]{"Foo", "Bar", "Non-exist"}, rd.getEntryNames(ns).toArray());
921         assertArrayEquals(new String[]{"Foo", "Bar"}, rd.getRetrievedEntryNames(ns).toArray());
922 
923         assertEquals(STATUS_OK, rd.getStatus(ns, "Foo"));
924         assertEquals(STATUS_OK, rd.getStatus(ns, "Bar"));
925         assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Non-exist"));
926         assertEquals(STATUS_NOT_REQUESTED, rd.getStatus(ns, "Entry not even requested"));
927 
928         assertEquals("Bar", Util.getStringEntry(rd, ns, "Foo"));
929         assertEquals("Foo", Util.getStringEntry(rd, ns, "Bar"));
930         assertNull(rd.getEntry(ns, "Non-exist"));
931         assertNull(rd.getEntry(ns, "Entry not even requested"));
932 
933         // Second requested namespace - org.example.barfoo
934         ns = "org.example.barfoo";
935         assertArrayEquals(new String[]{"Bar", "Non-exist", "Foo"}, rd.getEntryNames(ns).toArray());
936         assertArrayEquals(new String[]{"Bar", "Foo"}, rd.getRetrievedEntryNames(ns).toArray());
937 
938         assertEquals(STATUS_OK, rd.getStatus(ns, "Foo"));
939         assertEquals(STATUS_OK, rd.getStatus(ns, "Bar"));
940         assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Non-exist"));
941         assertEquals(STATUS_NOT_REQUESTED, rd.getStatus(ns, "Entry not even requested"));
942 
943         assertEquals("Bar", Util.getStringEntry(rd, ns, "Foo"));
944         assertEquals("Foo", Util.getStringEntry(rd, ns, "Bar"));
945         assertNull(rd.getEntry(ns, "Non-exist"));
946         assertNull(rd.getEntry(ns, "Entry not even requested"));
947 
948         // Third requested namespace - org.example.foofoo
949         ns = "org.example.foofoo";
950         assertArrayEquals(new String[]{"Bar", "Foo", "Non-exist"}, rd.getEntryNames(ns).toArray());
951         assertEquals(0, rd.getRetrievedEntryNames(ns).size());
952         assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Foo"));
953         assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Bar"));
954         assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Non-exist"));
955         assertEquals(STATUS_NOT_REQUESTED, rd.getStatus(ns, "Entry not even requested"));
956 
957         // Now check the returned CBOR ... note how it only has entries _and_ namespaces
958         // for data that was returned.
959         //
960         // Importantly, this is unlike the returned ResultData which mirrors one to one the passed
961         // in Map<String,Collection<String>> structure, _including_ ensuring the order is the same
962         // ... (which we - painfully - test for just above.)
963         byte[] resultCbor = rd.getAuthenticatedData();
964         String pretty = Util.cborPrettyPrint(Util.canonicalizeCbor(resultCbor));
965         assertEquals("{\n"
966                 + "  'org.example.barfoo' : {\n"
967                 + "    'Bar' : 'Foo',\n"
968                 + "    'Foo' : 'Bar'\n"
969                 + "  },\n"
970                 + "  'org.example.foobar' : {\n"
971                 + "    'Bar' : 'Foo',\n"
972                 + "    'Foo' : 'Bar'\n"
973                 + "  }\n"
974                 + "}", pretty);
975 
976         store.deleteCredentialByName("test");
977     }
978 
979     @Test
testProvisionAcpIdNotInValidRange()980     public void testProvisionAcpIdNotInValidRange() throws IdentityCredentialException {
981         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
982 
983         Context appContext = InstrumentationRegistry.getTargetContext();
984         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
985 
986         WritableIdentityCredential wc =
987                 store.createCredential("testAcpNotInValidRange", "org.iso.18013-5.2019.mdl");
988 
989         Collection<X509Certificate> certificateChain =
990                 wc.getCredentialKeyCertificateChain("SomeChallenge".getBytes());
991 
992         // Profile 32 (no authentication) - invalid profile id
993         AccessControlProfile noAuthProfile =
994                 new AccessControlProfile.Builder(new AccessControlProfileId(32))
995                         .setUserAuthenticationRequired(false)
996                         .build();
997 
998         Collection<AccessControlProfileId> idsNoAuth = new ArrayList<AccessControlProfileId>();
999         idsNoAuth.add(new AccessControlProfileId(32));
1000         String mdlNs = "org.iso.18013-5.2019";
1001         PersonalizationData personalizationData =
1002                 new PersonalizationData.Builder()
1003                         .addAccessControlProfile(noAuthProfile)
1004                         .putEntry("com.example.ns", "Name", idsNoAuth, Util.cborEncodeString("Alan"))
1005                         .build();
1006 
1007         // personalize() should fail because of the invalid profile id
1008         try {
1009             byte[] proofOfProvisioningSignature = wc.personalize(personalizationData);
1010             assertTrue(false);
1011         } catch (Exception e) {
1012             // This is the expected path...
1013         }
1014     }
1015 
1016     @Test
testProvisionAcpIdNotStartingAtZero()1017     public void testProvisionAcpIdNotStartingAtZero() throws
1018             IdentityCredentialException, CborException {
1019         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
1020 
1021         Context appContext = InstrumentationRegistry.getTargetContext();
1022         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
1023 
1024         store.deleteCredentialByName("test");
1025         Collection<X509Certificate> certChain = createCredentialWithAcpId(store, "test", 1);
1026 
1027         IdentityCredential credential = store.getCredentialByName("test",
1028                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
1029 
1030         // Check that the read-back certChain matches the created one.
1031         Collection<X509Certificate> readBackCertChain =
1032                 credential.getCredentialKeyCertificateChain();
1033         assertEquals(certChain.size(), readBackCertChain.size());
1034         Iterator<X509Certificate> it = readBackCertChain.iterator();
1035         for (X509Certificate expectedCert : certChain) {
1036             X509Certificate readBackCert = it.next();
1037             assertEquals(expectedCert, readBackCert);
1038         }
1039 
1040         Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
1041         entriesToRequest.put("org.iso.18013-5.2019",
1042                 Arrays.asList("First name",
1043                         "Last name",
1044                         "Home address",
1045                         "Birth date",
1046                         "Cryptanalyst",
1047                         "Portrait image",
1048                         "Height",
1049                         "Neg Item",
1050                         "Int Two Bytes",
1051                         "Int Eight Bytes",
1052                         "Int Four Bytes",
1053                         "driving_privileges"));
1054         ResultData rd = credential.getEntries(
1055                 Util.createItemsRequest(entriesToRequest, null),
1056                 entriesToRequest,
1057                 null,
1058                 null);
1059 
1060         Collection<String> resultNamespaces = rd.getNamespaces();
1061         assertEquals(resultNamespaces.size(), 1);
1062         assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
1063         assertEquals(12, rd.getEntryNames("org.iso.18013-5.2019").size());
1064 
1065         String ns = "org.iso.18013-5.2019";
1066         assertEquals("Alan", Util.getStringEntry(rd, ns, "First name"));
1067         assertEquals("Turing", Util.getStringEntry(rd, ns, "Last name"));
1068         assertEquals("Maida Vale, London, England", Util.getStringEntry(rd, ns, "Home address"));
1069         assertEquals("19120623", Util.getStringEntry(rd, ns, "Birth date"));
1070         assertEquals(true, Util.getBooleanEntry(rd, ns, "Cryptanalyst"));
1071         assertArrayEquals(new byte[]{0x01, 0x02},
1072                 Util.getBytestringEntry(rd, ns, "Portrait image"));
1073         assertEquals(180, Util.getIntegerEntry(rd, ns, "Height"));
1074         assertEquals(-42, Util.getIntegerEntry(rd, ns, "Neg Item"));
1075         assertEquals(0x101, Util.getIntegerEntry(rd, ns, "Int Two Bytes"));
1076         assertEquals(0x10001, Util.getIntegerEntry(rd, ns, "Int Four Bytes"));
1077         assertEquals(0x100000001L, Util.getIntegerEntry(rd, ns, "Int Eight Bytes"));
1078         byte[] drivingPrivileges = getExampleDrivingPrivilegesCbor();
1079         assertArrayEquals(drivingPrivileges, rd.getEntry(ns, "driving_privileges"));
1080 
1081         assertEquals("{\n"
1082                 + "  'org.iso.18013-5.2019' : {\n"
1083                 + "    'Height' : 180,\n"
1084                 + "    'Neg Item' : -42,\n"
1085                 + "    'Last name' : 'Turing',\n"
1086                 + "    'Birth date' : '19120623',\n"
1087                 + "    'First name' : 'Alan',\n"
1088                 + "    'Cryptanalyst' : true,\n"
1089                 + "    'Home address' : 'Maida Vale, London, England',\n"
1090                 + "    'Int Two Bytes' : 257,\n"
1091                 + "    'Int Four Bytes' : 65537,\n"
1092                 + "    'Portrait image' : [0x01, 0x02],\n"
1093                 + "    'Int Eight Bytes' : 4294967297,\n"
1094                 + "    'driving_privileges' : [\n"
1095                 + "      {\n"
1096                 + "        'value' : 42,\n"
1097                 + "        'vehicle_category_code' : 'TODO'\n"
1098                 + "      }\n"
1099                 + "    ]\n"
1100                 + "  }\n"
1101                 + "}", Util.cborPrettyPrint(Util.canonicalizeCbor(rd.getAuthenticatedData())));
1102 
1103         store.deleteCredentialByName("test");
1104     }
1105 
1106     @Test
testUpdateCredential()1107     public void testUpdateCredential() throws IdentityCredentialException, CborException, NoSuchAlgorithmException {
1108         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
1109 
1110         Context appContext = InstrumentationRegistry.getTargetContext();
1111         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
1112         assumeTrue("IdentityCredential.update() not supported", Util.getFeatureVersion() >= 202101);
1113 
1114         // Create the credential...
1115         //
1116         String credentialName = "test";
1117         String exampleDocType = "org.example.myDocType";
1118         String exampleNs = "org.example.ns";
1119         byte[] challenge = {0x01, 0x02};
1120         int acpId = 3;
1121         store.deleteCredentialByName(credentialName);
1122         WritableIdentityCredential wc = store.createCredential(credentialName, exampleDocType);
1123         Collection<X509Certificate> certChain = wc.getCredentialKeyCertificateChain(challenge);
1124         AccessControlProfile noAuthProfile =
1125                 new AccessControlProfile.Builder(new AccessControlProfileId(acpId))
1126                         .setUserAuthenticationRequired(false)
1127                         .build();
1128         Collection<AccessControlProfileId> idsNoAuth = new ArrayList<AccessControlProfileId>();
1129         idsNoAuth.add(new AccessControlProfileId(acpId));
1130         PersonalizationData personalizationData =
1131                 new PersonalizationData.Builder()
1132                         .addAccessControlProfile(noAuthProfile)
1133                         .putEntry(exampleNs, "first_name", idsNoAuth, Util.cborEncodeString("John"))
1134                         .putEntry(exampleNs, "last_name", idsNoAuth, Util.cborEncodeString("Smith"))
1135                         .build();
1136         byte[] proofOfProvisioningSignature = wc.personalize(personalizationData);
1137         byte[] proofOfProvisioning = Util.coseSign1GetData(proofOfProvisioningSignature);
1138         byte[] proofOfProvisioningSha256 = MessageDigest.getInstance("SHA-256").digest(proofOfProvisioning);
1139         String pretty = "";
1140         try {
1141             pretty = Util.cborPrettyPrint(proofOfProvisioning);
1142         } catch (CborException e) {
1143             e.printStackTrace();
1144             assertTrue(false);
1145         }
1146         assertEquals("[\n"
1147                 + "  'ProofOfProvisioning',\n"
1148                 + "  '" + exampleDocType + "',\n"
1149                 + "  [\n"
1150                 + "    {\n"
1151                 + "      'id' : " + acpId + "\n"
1152                 + "    }\n"
1153                 + "  ],\n"
1154                 + "  {\n"
1155                 + "    '" + exampleNs + "' : [\n"
1156                 + "      {\n"
1157                 + "        'name' : 'first_name',\n"
1158                 + "        'value' : 'John',\n"
1159                 + "        'accessControlProfiles' : [" + acpId + "]\n"
1160                 + "      },\n"
1161                 + "      {\n"
1162                 + "        'name' : 'last_name',\n"
1163                 + "        'value' : 'Smith',\n"
1164                 + "        'accessControlProfiles' : [" + acpId + "]\n"
1165                 + "      }\n"
1166                 + "    ]\n"
1167                 + "  },\n"
1168                 + "  false\n"
1169                 + "]", pretty);
1170         try {
1171             assertTrue(Util.coseSign1CheckSignature(
1172                 proofOfProvisioningSignature,
1173                 new byte[0], // Additional data
1174                 certChain.iterator().next().getPublicKey()));
1175         } catch (NoSuchAlgorithmException | InvalidKeyException e) {
1176             e.printStackTrace();
1177             assertTrue(false);
1178         }
1179 
1180         IdentityCredential credential = store.getCredentialByName("test",
1181                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
1182 
1183         // Configure to use 3 auth keys and endorse all of them
1184         credential.setAvailableAuthenticationKeys(3, 5);
1185         Collection<X509Certificate> certificates = credential.getAuthKeysNeedingCertification();
1186         assertEquals(3, certificates.size());
1187         for (X509Certificate cert : certificates) {
1188             credential.storeStaticAuthenticationData(cert, new byte[]{1, 2});
1189             // Check each cert has the correct ProofOfProvisioning SHA-256 in the
1190             // ProofOfBinding CBOR stored at OID 1.3.6.1.4.1.11129.2.1.26
1191             byte[] popSha256FromCert = Util.getPopSha256FromAuthKeyCert(cert);
1192             assertArrayEquals(popSha256FromCert, proofOfProvisioningSha256);
1193         }
1194         assertEquals(0, credential.getAuthKeysNeedingCertification().size());
1195 
1196         // Update the credential
1197         AccessControlProfile updNoAuthProfile =
1198                 new AccessControlProfile.Builder(new AccessControlProfileId(31))
1199                         .setUserAuthenticationRequired(false)
1200                         .build();
1201         Collection<AccessControlProfileId> updIds = new ArrayList<AccessControlProfileId>();
1202         updIds.add(new AccessControlProfileId(31));
1203         String updNs = "org.iso.other_ns";
1204         PersonalizationData updPd =
1205                 new PersonalizationData.Builder()
1206                         .addAccessControlProfile(updNoAuthProfile)
1207                         .putEntry(updNs, "first_name", updIds, Util.cborEncodeString("Lawrence"))
1208                         .putEntry(updNs, "last_name", updIds, Util.cborEncodeString("Waterhouse"))
1209                         .build();
1210         byte[] updProofOfProvisioningSignature = credential.update(updPd);
1211 
1212         // Check the ProofOfProvisioning for the updated data (contents _and_ signature)
1213         byte[] updProofOfProvisioning = Util.coseSign1GetData(updProofOfProvisioningSignature);
1214         byte[] updProofOfProvisioningSha256 = MessageDigest.getInstance("SHA-256").digest(updProofOfProvisioning);
1215         try {
1216             pretty = Util.cborPrettyPrint(updProofOfProvisioning);
1217         } catch (CborException e) {
1218             e.printStackTrace();
1219             assertTrue(false);
1220         }
1221         assertEquals("[\n"
1222                 + "  'ProofOfProvisioning',\n"
1223                 + "  '" + exampleDocType + "',\n"
1224                 + "  [\n"
1225                 + "    {\n"
1226                 + "      'id' : 31\n"
1227                 + "    }\n"
1228                 + "  ],\n"
1229                 + "  {\n"
1230                 + "    'org.iso.other_ns' : [\n"
1231                 + "      {\n"
1232                 + "        'name' : 'first_name',\n"
1233                 + "        'value' : 'Lawrence',\n"
1234                 + "        'accessControlProfiles' : [31]\n"
1235                 + "      },\n"
1236                 + "      {\n"
1237                 + "        'name' : 'last_name',\n"
1238                 + "        'value' : 'Waterhouse',\n"
1239                 + "        'accessControlProfiles' : [31]\n"
1240                 + "      }\n"
1241                 + "    ]\n"
1242                 + "  },\n"
1243                 + "  false\n"
1244                 + "]", pretty);
1245         try {
1246             assertTrue(Util.coseSign1CheckSignature(
1247                 updProofOfProvisioningSignature,
1248                 new byte[0], // Additional data
1249                 certChain.iterator().next().getPublicKey()));
1250         } catch (NoSuchAlgorithmException | InvalidKeyException e) {
1251             e.printStackTrace();
1252             assertTrue(false);
1253         }
1254         // Check the returned CredentialKey cert chain from the now updated
1255         // IdentityCredential matches the original certificate chain.
1256         //
1257         Collection<X509Certificate> readBackCertChain =
1258                 credential.getCredentialKeyCertificateChain();
1259         assertEquals(certChain.size(), readBackCertChain.size());
1260         Iterator<X509Certificate> it = readBackCertChain.iterator();
1261         for (X509Certificate expectedCert : certChain) {
1262             X509Certificate readBackCert = it.next();
1263             assertEquals(expectedCert, readBackCert);
1264         }
1265 
1266         // Check that the credential is still configured to use 3 auth keys and
1267         // that they all need replacement... then check and endorse the
1268         // replacements
1269         Collection<X509Certificate> updCertificates = credential.getAuthKeysNeedingCertification();
1270         assertEquals(3, updCertificates.size());
1271         for (X509Certificate cert : updCertificates) {
1272             credential.storeStaticAuthenticationData(cert, new byte[]{1, 2});
1273             // Check each cert has the correct - *updated* - ProofOfProvisioning SHA-256 in the
1274             // ProofOfBinding CBOR stored at OID 1.3.6.1.4.1.11129.2.1.26
1275             byte[] popSha256FromCert = Util.getPopSha256FromAuthKeyCert(cert);
1276             assertArrayEquals(popSha256FromCert, updProofOfProvisioningSha256);
1277         }
1278         assertEquals(0, credential.getAuthKeysNeedingCertification().size());
1279 
1280         // Check we can read back the updated data and it matches what we
1281         // updated it to.
1282         Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
1283         entriesToRequest.put(updNs,
1284                 Arrays.asList("first_name",
1285                         "last_name"));
1286         ResultData rd = credential.getEntries(
1287                 Util.createItemsRequest(entriesToRequest, null),
1288                 entriesToRequest,
1289                 null,
1290                 null);
1291 
1292         Collection<String> resultNamespaces = rd.getNamespaces();
1293         assertEquals(resultNamespaces.size(), 1);
1294         assertEquals(updNs, resultNamespaces.iterator().next());
1295         assertEquals(2, rd.getEntryNames(updNs).size());
1296 
1297         assertEquals("Lawrence", Util.getStringEntry(rd, updNs, "first_name"));
1298         assertEquals("Waterhouse", Util.getStringEntry(rd, updNs, "last_name"));
1299 
1300         store.deleteCredentialByName("test");
1301     }
1302 
1303 }
1304