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