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