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.NoSuchAlgorithmException;
52 import java.security.cert.CertificateEncodingException;
53 import java.security.cert.X509Certificate;
54 import java.util.Arrays;
55 import java.util.ArrayList;
56 import java.util.Collection;
57 import java.util.Iterator;
58 import java.util.LinkedList;
59 import java.util.LinkedHashMap;
60 import java.util.Map;
61 
62 import co.nstant.in.cbor.CborBuilder;
63 import co.nstant.in.cbor.CborEncoder;
64 import co.nstant.in.cbor.CborException;
65 import co.nstant.in.cbor.model.UnicodeString;
66 import co.nstant.in.cbor.model.UnsignedInteger;
67 
68 /**
69  * Instrumented test, which will execute on an Android device.
70  *
71  * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
72  */
73 public class ProvisioningTest {
74     private static final String TAG = "ProvisioningTest";
75 
getExampleDrivingPrivilegesCbor()76     private static byte[] getExampleDrivingPrivilegesCbor() {
77         // As per 7.4.4 of ISO 18013-5, driving privileges are defined with the following CDDL:
78         //
79         // driving_privileges = [
80         //     * driving_privilege
81         // ]
82         //
83         // driving_privilege = {
84         //     vehicle_category_code: tstr ; Vehicle category code as per ISO 18013-2 Annex A
85         //     ? issue_date: #6.0(tstr)    ; Date of issue encoded as full-date per RFC 3339
86         //     ? expiry_date: #6.0(tstr)   ; Date of expiry encoded as full-date per RFC 3339
87         //     ? code: tstr                ; Code as per ISO 18013-2 Annex A
88         //     ? sign: tstr                ; Sign as per ISO 18013-2 Annex A
89         //     ? value: int                ; Value as per ISO 18013-2 Annex A
90         // }
91         //
92         ByteArrayOutputStream baos = new ByteArrayOutputStream();
93         try {
94             new CborEncoder(baos).encode(new CborBuilder()
95                     .addArray()
96                     .addMap()
97                     .put(new UnicodeString("vehicle_category_code"), new UnicodeString("TODO"))
98                     .put(new UnicodeString("value"), new UnsignedInteger(42))
99                     .end()
100                     .end()
101                     .build());
102         } catch (CborException e) {
103             assertTrue(false);
104         }
105         return baos.toByteArray();
106     }
107 
createCredential(IdentityCredentialStore store, String credentialName)108     static Collection<X509Certificate> createCredential(IdentityCredentialStore store,
109             String credentialName) throws IdentityCredentialException {
110         return createCredentialWithChallengeAndAcpId(store, credentialName, "SomeChallenge".getBytes(), 0);
111     }
112 
createCredentialWithAcpId(IdentityCredentialStore store, String credentialName, int accessControlProfileId)113     static Collection<X509Certificate> createCredentialWithAcpId(IdentityCredentialStore store,
114             String credentialName,
115             int accessControlProfileId) throws IdentityCredentialException {
116         return createCredentialWithChallengeAndAcpId(store, credentialName, "SomeChallenge".getBytes(),
117                                                      accessControlProfileId);
118     }
119 
createCredentialWithChallenge(IdentityCredentialStore store, String credentialName, byte[] challenge)120     static Collection<X509Certificate> createCredentialWithChallenge(IdentityCredentialStore store,
121             String credentialName,
122             byte[] challenge) throws IdentityCredentialException {
123         return createCredentialWithChallengeAndAcpId(store, credentialName, challenge, 0);
124     }
125 
createCredentialWithChallengeAndAcpId(IdentityCredentialStore store, String credentialName, byte[] challenge, int id)126     static Collection<X509Certificate> createCredentialWithChallengeAndAcpId(IdentityCredentialStore store,
127             String credentialName,
128             byte[] challenge,
129             int id) throws IdentityCredentialException {
130         WritableIdentityCredential wc = null;
131         wc = store.createCredential(credentialName, "org.iso.18013-5.2019.mdl");
132 
133         Collection<X509Certificate> certificateChain =
134                 wc.getCredentialKeyCertificateChain(challenge);
135         // TODO: inspect cert-chain
136 
137         // Profile X (no authentication)
138         AccessControlProfile noAuthProfile =
139                 new AccessControlProfile.Builder(new AccessControlProfileId(id))
140                         .setUserAuthenticationRequired(false)
141                         .build();
142 
143         byte[] drivingPrivileges = getExampleDrivingPrivilegesCbor();
144 
145         Collection<AccessControlProfileId> idsNoAuth = new ArrayList<AccessControlProfileId>();
146         idsNoAuth.add(new AccessControlProfileId(id));
147         Collection<AccessControlProfileId> idsNoAcp = new ArrayList<AccessControlProfileId>();
148         String mdlNs = "org.iso.18013-5.2019";
149         PersonalizationData personalizationData =
150                 new PersonalizationData.Builder()
151                         .addAccessControlProfile(noAuthProfile)
152                         .putEntry(mdlNs, "First name", idsNoAuth, Util.cborEncodeString("Alan"))
153                         .putEntry(mdlNs, "Last name", idsNoAuth, Util.cborEncodeString("Turing"))
154                         .putEntry(mdlNs, "Home address", idsNoAuth,
155                                 Util.cborEncodeString("Maida Vale, London, England"))
156                         .putEntry(mdlNs, "Birth date", idsNoAuth, Util.cborEncodeString("19120623"))
157                         .putEntry(mdlNs, "Cryptanalyst", idsNoAuth, Util.cborEncodeBoolean(true))
158                         .putEntry(mdlNs, "Portrait image", idsNoAuth, Util.cborEncodeBytestring(
159                             new byte[]{0x01, 0x02}))
160                         .putEntry(mdlNs, "Height", idsNoAuth, Util.cborEncodeInt(180))
161                         .putEntry(mdlNs, "Neg Item", idsNoAuth, Util.cborEncodeInt(-42))
162                         .putEntry(mdlNs, "Int Two Bytes", idsNoAuth, Util.cborEncodeInt(0x101))
163                         .putEntry(mdlNs, "Int Four Bytes", idsNoAuth, Util.cborEncodeInt(0x10001))
164                         .putEntry(mdlNs, "Int Eight Bytes", idsNoAuth,
165                                 Util.cborEncodeInt(0x100000001L))
166                         .putEntry(mdlNs, "driving_privileges", idsNoAuth, drivingPrivileges)
167                         .putEntry(mdlNs, "No Access", idsNoAcp,
168                                 Util.cborEncodeString("Cannot be retrieved"))
169                         .build();
170 
171         byte[] proofOfProvisioningSignature = wc.personalize(personalizationData);
172         byte[] proofOfProvisioning = Util.coseSign1GetData(proofOfProvisioningSignature);
173 
174         String pretty = "";
175         try {
176             pretty = Util.cborPrettyPrint(proofOfProvisioning);
177         } catch (CborException e) {
178             e.printStackTrace();
179             assertTrue(false);
180         }
181         // Checks that order of elements is the order it was added, using the API.
182         assertEquals("[\n"
183                 + "  'ProofOfProvisioning',\n"
184                 + "  'org.iso.18013-5.2019.mdl',\n"
185                 + "  [\n"
186                 + "    {\n"
187                 + "      'id' : " + id + "\n"
188                 + "    }\n"
189                 + "  ],\n"
190                 + "  {\n"
191                 + "    'org.iso.18013-5.2019' : [\n"
192                 + "      {\n"
193                 + "        'name' : 'First name',\n"
194                 + "        'value' : 'Alan',\n"
195                 + "        'accessControlProfiles' : [" + id + "]\n"
196                 + "      },\n"
197                 + "      {\n"
198                 + "        'name' : 'Last name',\n"
199                 + "        'value' : 'Turing',\n"
200                 + "        'accessControlProfiles' : [" + id + "]\n"
201                 + "      },\n"
202                 + "      {\n"
203                 + "        'name' : 'Home address',\n"
204                 + "        'value' : 'Maida Vale, London, England',\n"
205                 + "        'accessControlProfiles' : [" + id + "]\n"
206                 + "      },\n"
207                 + "      {\n"
208                 + "        'name' : 'Birth date',\n"
209                 + "        'value' : '19120623',\n"
210                 + "        'accessControlProfiles' : [" + id + "]\n"
211                 + "      },\n"
212                 + "      {\n"
213                 + "        'name' : 'Cryptanalyst',\n"
214                 + "        'value' : true,\n"
215                 + "        'accessControlProfiles' : [" + id + "]\n"
216                 + "      },\n"
217                 + "      {\n"
218                 + "        'name' : 'Portrait image',\n"
219                 + "        'value' : [0x01, 0x02],\n"
220                 + "        'accessControlProfiles' : [" + id + "]\n"
221                 + "      },\n"
222                 + "      {\n"
223                 + "        'name' : 'Height',\n"
224                 + "        'value' : 180,\n"
225                 + "        'accessControlProfiles' : [" + id + "]\n"
226                 + "      },\n"
227                 + "      {\n"
228                 + "        'name' : 'Neg Item',\n"
229                 + "        'value' : -42,\n"
230                 + "        'accessControlProfiles' : [" + id + "]\n"
231                 + "      },\n"
232                 + "      {\n"
233                 + "        'name' : 'Int Two Bytes',\n"
234                 + "        'value' : 257,\n"
235                 + "        'accessControlProfiles' : [" + id + "]\n"
236                 + "      },\n"
237                 + "      {\n"
238                 + "        'name' : 'Int Four Bytes',\n"
239                 + "        'value' : 65537,\n"
240                 + "        'accessControlProfiles' : [" + id + "]\n"
241                 + "      },\n"
242                 + "      {\n"
243                 + "        'name' : 'Int Eight Bytes',\n"
244                 + "        'value' : 4294967297,\n"
245                 + "        'accessControlProfiles' : [" + id + "]\n"
246                 + "      },\n"
247                 + "      {\n"
248                 + "        'name' : 'driving_privileges',\n"
249                 + "        'value' : [\n"
250                 + "          {\n"
251                 + "            'value' : 42,\n"
252                 + "            'vehicle_category_code' : 'TODO'\n"
253                 + "          }\n"
254                 + "        ],\n"
255                 + "        'accessControlProfiles' : [" + id + "]\n"
256                 + "      },\n"
257                 + "      {\n"
258                 + "        'name' : 'No Access',\n"
259                 + "        'value' : 'Cannot be retrieved',\n"
260                 + "        'accessControlProfiles' : []\n"
261                 + "      }\n"
262                 + "    ]\n"
263                 + "  },\n"
264                 + "  false\n"
265                 + "]", pretty);
266 
267         try {
268             assertTrue(Util.coseSign1CheckSignature(
269                 proofOfProvisioningSignature,
270                 new byte[0], // Additional data
271                 certificateChain.iterator().next().getPublicKey()));
272         } catch (NoSuchAlgorithmException | InvalidKeyException e) {
273             e.printStackTrace();
274             assertTrue(false);
275         }
276 
277         // TODO: Check challenge is in certificatechain
278 
279         // TODO: Check each cert signs the next one
280 
281         // TODO: Check bottom cert is the Google well-know cert
282 
283         // TODO: need to also get and check SecurityStatement
284 
285         return certificateChain;
286     }
287 
createCredentialMultipleNamespaces( IdentityCredentialStore store, String credentialName)288     static Collection<X509Certificate> createCredentialMultipleNamespaces(
289             IdentityCredentialStore store,
290             String credentialName) throws IdentityCredentialException {
291         WritableIdentityCredential wc = null;
292         wc = store.createCredential(credentialName, "org.iso.18013-5.2019.mdl");
293 
294         Collection<X509Certificate> certificateChain =
295                 wc.getCredentialKeyCertificateChain("SomeChallenge".getBytes());
296 
297         // Profile 0 (no authentication)
298         AccessControlProfile noAuthProfile =
299                 new AccessControlProfile.Builder(new AccessControlProfileId(0))
300                         .setUserAuthenticationRequired(false)
301                         .build();
302 
303         Collection<AccessControlProfileId> idsNoAuth = new ArrayList<AccessControlProfileId>();
304         idsNoAuth.add(new AccessControlProfileId(0));
305         PersonalizationData personalizationData =
306                 new PersonalizationData.Builder()
307                         .addAccessControlProfile(noAuthProfile)
308                         .putEntry("org.example.barfoo", "Bar", idsNoAuth,
309                                 Util.cborEncodeString("Foo"))
310                         .putEntry("org.example.barfoo", "Foo", idsNoAuth,
311                                 Util.cborEncodeString("Bar"))
312                         .putEntry("org.example.foobar", "Foo", idsNoAuth,
313                                 Util.cborEncodeString("Bar"))
314                         .putEntry("org.example.foobar", "Bar", idsNoAuth,
315                                 Util.cborEncodeString("Foo"))
316                         .build();
317 
318         try {
319             byte[] proofOfProvisioningSignature = wc.personalize(personalizationData);
320             byte[] proofOfProvisioning = Util.coseSign1GetData(proofOfProvisioningSignature);
321             String pretty = Util.cborPrettyPrint(proofOfProvisioning);
322             // Checks that order of elements is the order it was added, using the API.
323             assertEquals("[\n"
324                     + "  'ProofOfProvisioning',\n"
325                     + "  'org.iso.18013-5.2019.mdl',\n"
326                     + "  [\n"
327                     + "    {\n"
328                     + "      'id' : 0\n"
329                     + "    }\n"
330                     + "  ],\n"
331                     + "  {\n"
332                     + "    'org.example.barfoo' : [\n"
333                     + "      {\n"
334                     + "        'name' : 'Bar',\n"
335                     + "        'value' : 'Foo',\n"
336                     + "        'accessControlProfiles' : [0]\n"
337                     + "      },\n"
338                     + "      {\n"
339                     + "        'name' : 'Foo',\n"
340                     + "        'value' : 'Bar',\n"
341                     + "        'accessControlProfiles' : [0]\n"
342                     + "      }\n"
343                     + "    ],\n"
344                     + "    'org.example.foobar' : [\n"
345                     + "      {\n"
346                     + "        'name' : 'Foo',\n"
347                     + "        'value' : 'Bar',\n"
348                     + "        'accessControlProfiles' : [0]\n"
349                     + "      },\n"
350                     + "      {\n"
351                     + "        'name' : 'Bar',\n"
352                     + "        'value' : 'Foo',\n"
353                     + "        'accessControlProfiles' : [0]\n"
354                     + "      }\n"
355                     + "    ]\n"
356                     + "  },\n"
357                     + "  false\n"
358                     + "]", pretty);
359 
360         } catch (CborException e) {
361             e.printStackTrace();
362             assertTrue(false);
363         }
364 
365         return certificateChain;
366     }
367 
368     @Test
alreadyPersonalized()369     public void alreadyPersonalized() throws IdentityCredentialException {
370         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
371 
372         Context appContext = InstrumentationRegistry.getTargetContext();
373         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
374 
375         store.deleteCredentialByName("test");
376         createCredential(store, "test");
377         try {
378             createCredential(store, "test");
379             assertTrue(false);
380         } catch (AlreadyPersonalizedException e) {
381             // The expected path.
382         }
383         store.deleteCredentialByName("test");
384         // TODO: check retutrned |proofOfDeletion|
385     }
386 
387     @Test
nonExistent()388     public void nonExistent() throws IdentityCredentialException {
389         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
390 
391         Context appContext = InstrumentationRegistry.getTargetContext();
392         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
393 
394         store.deleteCredentialByName("test");
395         IdentityCredential credential = store.getCredentialByName("test",
396                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
397         assertNull(credential);
398     }
399 
400     @Test
defaultStoreSupportsAnyDocumentType()401     public void defaultStoreSupportsAnyDocumentType() throws IdentityCredentialException {
402         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
403 
404         Context appContext = InstrumentationRegistry.getTargetContext();
405         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
406 
407         String[] supportedDocTypes = store.getSupportedDocTypes();
408         assertEquals(0, supportedDocTypes.length);
409     }
410 
411     @Test
deleteCredential()412     public void deleteCredential()
413             throws IdentityCredentialException, CborException, CertificateEncodingException {
414         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
415 
416         Context appContext = InstrumentationRegistry.getTargetContext();
417         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
418 
419         store.deleteCredentialByName("test");
420         assertNull(store.deleteCredentialByName("test"));
421         Collection<X509Certificate> certificateChain = createCredential(store, "test");
422 
423         // Deleting the credential involves destroying the keys referenced in the returned
424         // certificateChain... so get an encoded blob we can turn into a X509 cert when
425         // checking the deletion receipt below, post-deletion.
426         byte[] encodedCredentialCert = certificateChain.iterator().next().getEncoded();
427 
428         byte[] proofOfDeletionSignature = store.deleteCredentialByName("test");
429         byte[] proofOfDeletion = Util.coseSign1GetData(proofOfDeletionSignature);
430 
431         // Check the returned CBOR is what is expected.
432         String pretty = Util.cborPrettyPrint(proofOfDeletion);
433         assertEquals("['ProofOfDeletion', 'org.iso.18013-5.2019.mdl', false]", pretty);
434 
435         try {
436             assertTrue(Util.coseSign1CheckSignature(
437                 proofOfDeletionSignature,
438                 new byte[0], // Additional data
439                 certificateChain.iterator().next().getPublicKey()));
440         } catch (NoSuchAlgorithmException | InvalidKeyException e) {
441             e.printStackTrace();
442             assertTrue(false);
443         }
444 
445         // Finally, check deleting an already deleted credential returns the expected.
446         assertNull(store.deleteCredentialByName("test"));
447     }
448 
449     @Test
testProvisionAndRetrieve()450     public void testProvisionAndRetrieve() throws IdentityCredentialException, CborException {
451         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
452 
453         Context appContext = InstrumentationRegistry.getTargetContext();
454         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
455 
456         store.deleteCredentialByName("test");
457         Collection<X509Certificate> certChain = createCredential(store, "test");
458 
459         IdentityCredential credential = store.getCredentialByName("test",
460                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
461 
462         // Check that the read-back certChain matches the created one.
463         Collection<X509Certificate> readBackCertChain =
464                 credential.getCredentialKeyCertificateChain();
465         assertEquals(certChain.size(), readBackCertChain.size());
466         Iterator<X509Certificate> it = readBackCertChain.iterator();
467         for (X509Certificate expectedCert : certChain) {
468             X509Certificate readBackCert = it.next();
469             assertEquals(expectedCert, readBackCert);
470         }
471 
472         Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
473         entriesToRequest.put("org.iso.18013-5.2019",
474                 Arrays.asList("First name",
475                         "Last name",
476                         "Home address",
477                         "Birth date",
478                         "Cryptanalyst",
479                         "Portrait image",
480                         "Height",
481                         "Neg Item",
482                         "Int Two Bytes",
483                         "Int Eight Bytes",
484                         "Int Four Bytes",
485                         "driving_privileges"));
486         ResultData rd = credential.getEntries(
487                 Util.createItemsRequest(entriesToRequest, null),
488                 entriesToRequest,
489                 null,
490                 null);
491 
492         Collection<String> resultNamespaces = rd.getNamespaces();
493         assertEquals(resultNamespaces.size(), 1);
494         assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
495         assertEquals(12, rd.getEntryNames("org.iso.18013-5.2019").size());
496 
497         String ns = "org.iso.18013-5.2019";
498         assertEquals("Alan", Util.getStringEntry(rd, ns, "First name"));
499         assertEquals("Turing", Util.getStringEntry(rd, ns, "Last name"));
500         assertEquals("Maida Vale, London, England", Util.getStringEntry(rd, ns, "Home address"));
501         assertEquals("19120623", Util.getStringEntry(rd, ns, "Birth date"));
502         assertEquals(true, Util.getBooleanEntry(rd, ns, "Cryptanalyst"));
503         assertArrayEquals(new byte[]{0x01, 0x02},
504                 Util.getBytestringEntry(rd, ns, "Portrait image"));
505         assertEquals(180, Util.getIntegerEntry(rd, ns, "Height"));
506         assertEquals(-42, Util.getIntegerEntry(rd, ns, "Neg Item"));
507         assertEquals(0x101, Util.getIntegerEntry(rd, ns, "Int Two Bytes"));
508         assertEquals(0x10001, Util.getIntegerEntry(rd, ns, "Int Four Bytes"));
509         assertEquals(0x100000001L, Util.getIntegerEntry(rd, ns, "Int Eight Bytes"));
510         byte[] drivingPrivileges = getExampleDrivingPrivilegesCbor();
511         assertArrayEquals(drivingPrivileges, rd.getEntry(ns, "driving_privileges"));
512 
513         assertEquals("{\n"
514                 + "  'org.iso.18013-5.2019' : {\n"
515                 + "    'Height' : 180,\n"
516                 + "    'Neg Item' : -42,\n"
517                 + "    'Last name' : 'Turing',\n"
518                 + "    'Birth date' : '19120623',\n"
519                 + "    'First name' : 'Alan',\n"
520                 + "    'Cryptanalyst' : true,\n"
521                 + "    'Home address' : 'Maida Vale, London, England',\n"
522                 + "    'Int Two Bytes' : 257,\n"
523                 + "    'Int Four Bytes' : 65537,\n"
524                 + "    'Portrait image' : [0x01, 0x02],\n"
525                 + "    'Int Eight Bytes' : 4294967297,\n"
526                 + "    'driving_privileges' : [\n"
527                 + "      {\n"
528                 + "        'value' : 42,\n"
529                 + "        'vehicle_category_code' : 'TODO'\n"
530                 + "      }\n"
531                 + "    ]\n"
532                 + "  }\n"
533                 + "}", Util.cborPrettyPrint(Util.canonicalizeCbor(rd.getAuthenticatedData())));
534 
535         store.deleteCredentialByName("test");
536     }
537 
538     @Test
testProvisionAndRetrieveMultipleTimes()539     public void testProvisionAndRetrieveMultipleTimes() throws IdentityCredentialException,
540             InvalidKeyException {
541         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
542 
543         Context appContext = InstrumentationRegistry.getTargetContext();
544         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
545 
546         // This checks we can do multiple getEntries() calls
547 
548         store.deleteCredentialByName("test");
549         Collection<X509Certificate> certChain = createCredential(store, "test");
550 
551         IdentityCredential credential = store.getCredentialByName("test",
552                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
553 
554         // We're going to need some authentication keys for this so create some dummy ones.
555         credential.setAvailableAuthenticationKeys(5, 1);
556         Collection<X509Certificate> authKeys = credential.getAuthKeysNeedingCertification();
557         for (X509Certificate authKey : authKeys) {
558             byte[] staticAuthData = new byte[5];
559             credential.storeStaticAuthenticationData(authKey, staticAuthData);
560         }
561 
562         KeyPair ephemeralKeyPair = credential.createEphemeralKeyPair();
563         KeyPair readerEphemeralKeyPair = Util.createEphemeralKeyPair();
564         credential.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
565         byte[] sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
566 
567         Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
568         entriesToRequest.put("org.iso.18013-5.2019", Arrays.asList("First name", "Last name"));
569 
570         for (int n = 0; n < 3; n++) {
571             ResultData rd = credential.getEntries(
572                 Util.createItemsRequest(entriesToRequest, null),
573                 entriesToRequest,
574                 sessionTranscript,
575                 null);
576             assertEquals("Alan", Util.getStringEntry(rd, "org.iso.18013-5.2019", "First name"));
577             assertEquals("Turing", Util.getStringEntry(rd, "org.iso.18013-5.2019", "Last name"));
578             assertNotNull(rd.getMessageAuthenticationCode());
579         }
580 
581         // Now try with a different (but still valid) sessionTranscript - this should fail with
582         // SessionTranscriptMismatchException
583         KeyPair otherEphemeralKeyPair = Util.createEphemeralKeyPair();
584         byte[] otherSessionTranscript = Util.buildSessionTranscript(otherEphemeralKeyPair);
585         try {
586             ResultData rd = credential.getEntries(
587                 Util.createItemsRequest(entriesToRequest, null),
588                 entriesToRequest,
589                 otherSessionTranscript,
590                 null);
591             assertTrue(false);
592         } catch (SessionTranscriptMismatchException e) {
593             // This is the expected path...
594         } catch (IdentityCredentialException e) {
595             e.printStackTrace();
596             assertTrue(false);
597         }
598 
599         store.deleteCredentialByName("test");
600     }
601 
602     @Test
testProvisionAndRetrieveWithFiltering()603     public void testProvisionAndRetrieveWithFiltering() throws IdentityCredentialException {
604         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
605 
606         Context appContext = InstrumentationRegistry.getTargetContext();
607         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
608 
609         store.deleteCredentialByName("test");
610         Collection<X509Certificate> certChain = createCredential(store, "test");
611 
612         IdentityCredential credential = store.getCredentialByName("test",
613                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
614 
615         Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
616         entriesToRequest.put("org.iso.18013-5.2019",
617                 Arrays.asList("First name",
618                         "Last name",
619                         "Home address",
620                         "Birth date",
621                         "Cryptanalyst",
622                         "Portrait image",
623                         "Height"));
624         Map<String, Collection<String>> entriesToRequestWithoutHomeAddress = new LinkedHashMap<>();
625         entriesToRequestWithoutHomeAddress.put("org.iso.18013-5.2019",
626                 Arrays.asList("First name",
627                         "Last name",
628                         "Birth date",
629                         "Cryptanalyst",
630                         "Portrait image",
631                         "Height"));
632         ResultData rd = credential.getEntries(
633                 Util.createItemsRequest(entriesToRequest, null),
634                 entriesToRequestWithoutHomeAddress,
635                 null,
636                 null);
637 
638         Collection<String> resultNamespaces = rd.getNamespaces();
639         assertEquals(resultNamespaces.size(), 1);
640         assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
641         assertEquals(6, rd.getEntryNames("org.iso.18013-5.2019").size());
642 
643         String ns = "org.iso.18013-5.2019";
644         assertEquals("Alan", Util.getStringEntry(rd, ns, "First name"));
645         assertEquals("Turing", Util.getStringEntry(rd, ns, "Last name"));
646         assertEquals("19120623", Util.getStringEntry(rd, ns, "Birth date"));
647         assertEquals(true, Util.getBooleanEntry(rd, ns, "Cryptanalyst"));
648         assertArrayEquals(new byte[]{0x01, 0x02},
649                 Util.getBytestringEntry(rd, ns, "Portrait image"));
650         assertEquals(180, Util.getIntegerEntry(rd, ns, "Height"));
651 
652         store.deleteCredentialByName("test");
653     }
654 
655     @Test
testProvisionAndRetrieveElementWithNoACP()656     public void testProvisionAndRetrieveElementWithNoACP() throws IdentityCredentialException {
657         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
658 
659         Context appContext = InstrumentationRegistry.getTargetContext();
660         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
661 
662         store.deleteCredentialByName("test");
663         Collection<X509Certificate> certChain = createCredential(store, "test");
664 
665         IdentityCredential credential = store.getCredentialByName("test",
666                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
667 
668         Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
669         entriesToRequest.put("org.iso.18013-5.2019", Arrays.asList("No Access"));
670         ResultData rd = credential.getEntries(
671                 Util.createItemsRequest(entriesToRequest, null),
672                 entriesToRequest,
673                 null,
674                 null);
675 
676         Collection<String> resultNamespaces = rd.getNamespaces();
677         assertEquals(resultNamespaces.size(), 1);
678         assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
679         assertEquals(1, rd.getEntryNames("org.iso.18013-5.2019").size());
680         assertEquals(0, rd.getRetrievedEntryNames("org.iso.18013-5.2019").size());
681 
682         String ns = "org.iso.18013-5.2019";
683         assertEquals(STATUS_NO_ACCESS_CONTROL_PROFILES, rd.getStatus(ns, "No Access"));
684 
685         store.deleteCredentialByName("test");
686     }
687 
688     // TODO: Make sure we test retrieving an entry with multiple ACPs and test all four cases:
689     //
690     // - ACP1 bad,  ACP2 bad   -> NOT OK
691     // - ACP1 good, ACP2 bad   -> OK
692     // - ACP1 bad,  ACP2 good  -> OK
693     // - ACP1 good, ACP2 good  -> OK
694     //
695 
696     @Test
testProvisionAndRetrieveWithEntryNotInRequest()697     public void testProvisionAndRetrieveWithEntryNotInRequest() throws IdentityCredentialException {
698         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
699 
700         Context appContext = InstrumentationRegistry.getTargetContext();
701         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
702 
703         store.deleteCredentialByName("test");
704         Collection<X509Certificate> certChain = createCredential(store, "test");
705 
706         IdentityCredential credential = store.getCredentialByName("test",
707                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
708 
709         Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
710         entriesToRequest.put("org.iso.18013-5.2019",
711                 Arrays.asList("First name",
712                         "Last name",
713                         "Home address",
714                         "Birth date",
715                         "Cryptanalyst",
716                         "Portrait image",
717                         "Height"));
718         Map<String, Collection<String>> entriesToRequestWithoutHomeAddress = new LinkedHashMap<>();
719         entriesToRequestWithoutHomeAddress.put("org.iso.18013-5.2019",
720                 Arrays.asList("First name",
721                         "Last name",
722                         "Birth date",
723                         "Cryptanalyst",
724                         "Portrait image",
725                         "Height"));
726         ResultData rd = credential.getEntries(
727                 Util.createItemsRequest(entriesToRequestWithoutHomeAddress, null),
728                 entriesToRequest,
729                 null,
730                 null);
731 
732         Collection<String> resultNamespaces = rd.getNamespaces();
733         assertEquals(resultNamespaces.size(), 1);
734         assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
735         assertEquals(7, rd.getEntryNames("org.iso.18013-5.2019").size());
736         assertEquals(6, rd.getRetrievedEntryNames("org.iso.18013-5.2019").size());
737 
738         String ns = "org.iso.18013-5.2019";
739         assertEquals(STATUS_NOT_IN_REQUEST_MESSAGE, rd.getStatus(ns, "Home address"));
740 
741         assertEquals("Alan", Util.getStringEntry(rd, ns, "First name"));
742         assertEquals("Turing", Util.getStringEntry(rd, ns, "Last name"));
743         assertEquals("19120623", Util.getStringEntry(rd, ns, "Birth date"));
744         assertEquals(true, Util.getBooleanEntry(rd, ns, "Cryptanalyst"));
745         assertArrayEquals(new byte[]{0x01, 0x02},
746                 Util.getBytestringEntry(rd, ns, "Portrait image"));
747         assertEquals(180, Util.getIntegerEntry(rd, ns, "Height"));
748 
749         store.deleteCredentialByName("test");
750     }
751 
752     @Test
nonExistentEntries()753     public void nonExistentEntries() throws IdentityCredentialException {
754         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
755 
756         Context appContext = InstrumentationRegistry.getTargetContext();
757         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
758 
759         store.deleteCredentialByName("test");
760         Collection<X509Certificate> certChain = createCredential(store, "test");
761 
762         IdentityCredential credential = store.getCredentialByName("test",
763                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
764 
765         Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
766         entriesToRequest.put("org.iso.18013-5.2019",
767                 Arrays.asList("First name",
768                         "Last name",
769                         "Non-existent Entry"));
770         ResultData rd = credential.getEntries(
771                 null,
772                 entriesToRequest,
773                 null,
774                 null);
775 
776         Collection<String> resultNamespaces = rd.getNamespaces();
777         assertEquals(resultNamespaces.size(), 1);
778         assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
779         assertEquals(3, rd.getEntryNames("org.iso.18013-5.2019").size());
780         assertEquals(2, rd.getRetrievedEntryNames("org.iso.18013-5.2019").size());
781 
782         String ns = "org.iso.18013-5.2019";
783 
784         assertEquals(STATUS_OK, rd.getStatus(ns, "First name"));
785         assertEquals(STATUS_OK, rd.getStatus(ns, "Last name"));
786         assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Non-existent Entry"));
787         assertEquals(STATUS_NOT_REQUESTED, rd.getStatus(ns, "Entry not even requested"));
788 
789         assertEquals("Alan", Util.getStringEntry(rd, ns, "First name"));
790         assertEquals("Turing", Util.getStringEntry(rd, ns, "Last name"));
791         assertNull(rd.getEntry(ns, "Non-existent Entry"));
792         assertNull(rd.getEntry(ns, "Entry not even requested"));
793 
794         store.deleteCredentialByName("test");
795     }
796 
797     @Test
multipleNamespaces()798     public void multipleNamespaces() throws IdentityCredentialException, CborException {
799         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
800 
801         Context appContext = InstrumentationRegistry.getTargetContext();
802         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
803 
804         store.deleteCredentialByName("test");
805         Collection<X509Certificate> certChain = createCredentialMultipleNamespaces(
806                 store, "test");
807 
808         IdentityCredential credential = store.getCredentialByName("test",
809                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
810 
811         // Request these in different order than they are stored
812         Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
813         entriesToRequest.put("org.example.foobar", Arrays.asList("Foo", "Bar", "Non-exist"));
814         entriesToRequest.put("org.example.barfoo", Arrays.asList("Bar", "Non-exist", "Foo"));
815         entriesToRequest.put("org.example.foofoo", Arrays.asList("Bar", "Foo", "Non-exist"));
816 
817         ResultData rd = credential.getEntries(
818                 null,
819                 entriesToRequest,
820                 null,
821                 null);
822 
823         // We should get the same number of namespaces back, as we requested - even for namespaces
824         // that do not exist in the credential.
825         //
826         // Additionally, each namespace should have exactly the items requested, in the same order.
827         Collection<String> resultNamespaces = rd.getNamespaces();
828         assertEquals(resultNamespaces.size(), 3);
829 
830 
831         // First requested namespace - org.example.foobar
832         String ns = "org.example.foobar";
833         assertArrayEquals(new String[]{"Foo", "Bar", "Non-exist"}, rd.getEntryNames(ns).toArray());
834         assertArrayEquals(new String[]{"Foo", "Bar"}, rd.getRetrievedEntryNames(ns).toArray());
835 
836         assertEquals(STATUS_OK, rd.getStatus(ns, "Foo"));
837         assertEquals(STATUS_OK, rd.getStatus(ns, "Bar"));
838         assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Non-exist"));
839         assertEquals(STATUS_NOT_REQUESTED, rd.getStatus(ns, "Entry not even requested"));
840 
841         assertEquals("Bar", Util.getStringEntry(rd, ns, "Foo"));
842         assertEquals("Foo", Util.getStringEntry(rd, ns, "Bar"));
843         assertNull(rd.getEntry(ns, "Non-exist"));
844         assertNull(rd.getEntry(ns, "Entry not even requested"));
845 
846         // Second requested namespace - org.example.barfoo
847         ns = "org.example.barfoo";
848         assertArrayEquals(new String[]{"Bar", "Non-exist", "Foo"}, rd.getEntryNames(ns).toArray());
849         assertArrayEquals(new String[]{"Bar", "Foo"}, rd.getRetrievedEntryNames(ns).toArray());
850 
851         assertEquals(STATUS_OK, rd.getStatus(ns, "Foo"));
852         assertEquals(STATUS_OK, rd.getStatus(ns, "Bar"));
853         assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Non-exist"));
854         assertEquals(STATUS_NOT_REQUESTED, rd.getStatus(ns, "Entry not even requested"));
855 
856         assertEquals("Bar", Util.getStringEntry(rd, ns, "Foo"));
857         assertEquals("Foo", Util.getStringEntry(rd, ns, "Bar"));
858         assertNull(rd.getEntry(ns, "Non-exist"));
859         assertNull(rd.getEntry(ns, "Entry not even requested"));
860 
861         // Third requested namespace - org.example.foofoo
862         ns = "org.example.foofoo";
863         assertArrayEquals(new String[]{"Bar", "Foo", "Non-exist"}, rd.getEntryNames(ns).toArray());
864         assertEquals(0, rd.getRetrievedEntryNames(ns).size());
865         assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Foo"));
866         assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Bar"));
867         assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Non-exist"));
868         assertEquals(STATUS_NOT_REQUESTED, rd.getStatus(ns, "Entry not even requested"));
869 
870         // Now check the returned CBOR ... note how it only has entries _and_ namespaces
871         // for data that was returned.
872         //
873         // Importantly, this is unlike the returned ResultData which mirrors one to one the passed
874         // in Map<String,Collection<String>> structure, _including_ ensuring the order is the same
875         // ... (which we - painfully - test for just above.)
876         byte[] resultCbor = rd.getAuthenticatedData();
877         String pretty = Util.cborPrettyPrint(Util.canonicalizeCbor(resultCbor));
878         assertEquals("{\n"
879                 + "  'org.example.barfoo' : {\n"
880                 + "    'Bar' : 'Foo',\n"
881                 + "    'Foo' : 'Bar'\n"
882                 + "  },\n"
883                 + "  'org.example.foobar' : {\n"
884                 + "    'Bar' : 'Foo',\n"
885                 + "    'Foo' : 'Bar'\n"
886                 + "  }\n"
887                 + "}", pretty);
888 
889         store.deleteCredentialByName("test");
890     }
891 
892     @Test
testProvisionAcpIdNotInValidRange()893     public void testProvisionAcpIdNotInValidRange() throws IdentityCredentialException {
894         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
895 
896         Context appContext = InstrumentationRegistry.getTargetContext();
897         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
898 
899         WritableIdentityCredential wc =
900                 store.createCredential("testAcpNotInValidRange", "org.iso.18013-5.2019.mdl");
901 
902         Collection<X509Certificate> certificateChain =
903                 wc.getCredentialKeyCertificateChain("SomeChallenge".getBytes());
904 
905         // Profile 32 (no authentication) - invalid profile id
906         AccessControlProfile noAuthProfile =
907                 new AccessControlProfile.Builder(new AccessControlProfileId(32))
908                         .setUserAuthenticationRequired(false)
909                         .build();
910 
911         Collection<AccessControlProfileId> idsNoAuth = new ArrayList<AccessControlProfileId>();
912         idsNoAuth.add(new AccessControlProfileId(32));
913         String mdlNs = "org.iso.18013-5.2019";
914         PersonalizationData personalizationData =
915                 new PersonalizationData.Builder()
916                         .addAccessControlProfile(noAuthProfile)
917                         .putEntry("com.example.ns", "Name", idsNoAuth, Util.cborEncodeString("Alan"))
918                         .build();
919 
920         // personalize() should fail because of the invalid profile id
921         try {
922             byte[] proofOfProvisioningSignature = wc.personalize(personalizationData);
923             assertTrue(false);
924         } catch (Exception e) {
925             // This is the expected path...
926         }
927     }
928 
929     @Test
testProvisionAcpIdNotStartingAtZero()930     public void testProvisionAcpIdNotStartingAtZero() throws
931             IdentityCredentialException, CborException {
932         assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
933 
934         Context appContext = InstrumentationRegistry.getTargetContext();
935         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
936 
937         store.deleteCredentialByName("test");
938         Collection<X509Certificate> certChain = createCredentialWithAcpId(store, "test", 1);
939 
940         IdentityCredential credential = store.getCredentialByName("test",
941                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
942 
943         // Check that the read-back certChain matches the created one.
944         Collection<X509Certificate> readBackCertChain =
945                 credential.getCredentialKeyCertificateChain();
946         assertEquals(certChain.size(), readBackCertChain.size());
947         Iterator<X509Certificate> it = readBackCertChain.iterator();
948         for (X509Certificate expectedCert : certChain) {
949             X509Certificate readBackCert = it.next();
950             assertEquals(expectedCert, readBackCert);
951         }
952 
953         Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
954         entriesToRequest.put("org.iso.18013-5.2019",
955                 Arrays.asList("First name",
956                         "Last name",
957                         "Home address",
958                         "Birth date",
959                         "Cryptanalyst",
960                         "Portrait image",
961                         "Height",
962                         "Neg Item",
963                         "Int Two Bytes",
964                         "Int Eight Bytes",
965                         "Int Four Bytes",
966                         "driving_privileges"));
967         ResultData rd = credential.getEntries(
968                 Util.createItemsRequest(entriesToRequest, null),
969                 entriesToRequest,
970                 null,
971                 null);
972 
973         Collection<String> resultNamespaces = rd.getNamespaces();
974         assertEquals(resultNamespaces.size(), 1);
975         assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
976         assertEquals(12, rd.getEntryNames("org.iso.18013-5.2019").size());
977 
978         String ns = "org.iso.18013-5.2019";
979         assertEquals("Alan", Util.getStringEntry(rd, ns, "First name"));
980         assertEquals("Turing", Util.getStringEntry(rd, ns, "Last name"));
981         assertEquals("Maida Vale, London, England", Util.getStringEntry(rd, ns, "Home address"));
982         assertEquals("19120623", Util.getStringEntry(rd, ns, "Birth date"));
983         assertEquals(true, Util.getBooleanEntry(rd, ns, "Cryptanalyst"));
984         assertArrayEquals(new byte[]{0x01, 0x02},
985                 Util.getBytestringEntry(rd, ns, "Portrait image"));
986         assertEquals(180, Util.getIntegerEntry(rd, ns, "Height"));
987         assertEquals(-42, Util.getIntegerEntry(rd, ns, "Neg Item"));
988         assertEquals(0x101, Util.getIntegerEntry(rd, ns, "Int Two Bytes"));
989         assertEquals(0x10001, Util.getIntegerEntry(rd, ns, "Int Four Bytes"));
990         assertEquals(0x100000001L, Util.getIntegerEntry(rd, ns, "Int Eight Bytes"));
991         byte[] drivingPrivileges = getExampleDrivingPrivilegesCbor();
992         assertArrayEquals(drivingPrivileges, rd.getEntry(ns, "driving_privileges"));
993 
994         assertEquals("{\n"
995                 + "  'org.iso.18013-5.2019' : {\n"
996                 + "    'Height' : 180,\n"
997                 + "    'Neg Item' : -42,\n"
998                 + "    'Last name' : 'Turing',\n"
999                 + "    'Birth date' : '19120623',\n"
1000                 + "    'First name' : 'Alan',\n"
1001                 + "    'Cryptanalyst' : true,\n"
1002                 + "    'Home address' : 'Maida Vale, London, England',\n"
1003                 + "    'Int Two Bytes' : 257,\n"
1004                 + "    'Int Four Bytes' : 65537,\n"
1005                 + "    'Portrait image' : [0x01, 0x02],\n"
1006                 + "    'Int Eight Bytes' : 4294967297,\n"
1007                 + "    'driving_privileges' : [\n"
1008                 + "      {\n"
1009                 + "        'value' : 42,\n"
1010                 + "        'vehicle_category_code' : 'TODO'\n"
1011                 + "      }\n"
1012                 + "    ]\n"
1013                 + "  }\n"
1014                 + "}", Util.cborPrettyPrint(Util.canonicalizeCbor(rd.getAuthenticatedData())));
1015 
1016         store.deleteCredentialByName("test");
1017     }
1018 
1019 }
1020