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