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