| <!DOCTYPE html> |
| <title>Web Authentication API: PublicKeyCredential's [[create]] success cases with a mock local authenticator.</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="./resources/util.js"></script> |
| <script src="./resources/cbor.js"></script> |
| <script> |
| (async function() { |
| const userhandleBase64 = generateUserhandleBase64(); |
| const privateKeyBase64 = await generatePrivateKeyBase64(); |
| const credentialID = await calculateCredentialID(privateKeyBase64); |
| // Default mock configuration. Tests need to override if they need different configuration. |
| if (window.internals) |
| internals.setMockWebAuthenticationConfiguration({ |
| local: { |
| acceptAuthentication: true, |
| acceptAttestation: true, |
| privateKeyBase64: privateKeyBase64, |
| userCertificateBase64: testAttestationCertificateBase64, |
| intermediateCACertificateBase64: testAttestationIssuingCACertificateBase64 |
| } |
| }); |
| |
| function checkResult(credential, isNoneAttestation = true) |
| { |
| // Check keychain |
| if (window.testRunner) { |
| assert_true(testRunner.keyExistsInKeychain(testRpId, userhandleBase64)); |
| testRunner.cleanUpKeychain(testRpId, userhandleBase64); |
| } |
| |
| // Check respond |
| assert_array_equals(Base64URL.parse(credential.id), credentialID); |
| assert_equals(credential.type, 'public-key'); |
| assert_array_equals(new Uint8Array(credential.rawId), credentialID); |
| assert_equals(bytesToASCIIString(credential.response.clientDataJSON), '{"type":"webauthn.create","challenge":"MTIzNDU2","origin":"https://localhost:9443"}'); |
| assert_not_own_property(credential.getClientExtensionResults(), "appid"); |
| |
| // Check attestation |
| const attestationObject = CBOR.decode(credential.response.attestationObject); |
| if (isNoneAttestation) |
| assert_equals(attestationObject.fmt, "none"); |
| else |
| assert_equals(attestationObject.fmt, "Apple"); |
| // Check authData |
| const authData = decodeAuthData(attestationObject.authData); |
| assert_equals(bytesToHexString(authData.rpIdHash), "49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763"); |
| assert_equals(authData.flags, 69); |
| assert_equals(authData.counter, 0); |
| assert_equals(bytesToHexString(authData.aaguid), "00000000000000000000000000000000"); |
| assert_array_equals(authData.credentialID, credentialID); |
| // Check self attestation |
| assert_true(checkPublicKey(authData.publicKey)); |
| if (isNoneAttestation) |
| assert_object_equals(attestationObject.attStmt, { }); |
| else { |
| assert_equals(attestationObject.attStmt.alg, -7); |
| assert_equals(attestationObject.attStmt.x5c.length, 2); |
| assert_array_equals(attestationObject.attStmt.x5c[0], Base64URL.parse(testAttestationCertificateBase64)); |
| assert_array_equals(attestationObject.attStmt.x5c[1], Base64URL.parse(testAttestationIssuingCACertificateBase64)); |
| |
| // Check signature |
| let publicKeyData = new Uint8Array(65); |
| publicKeyData[0] = 0x04; |
| publicKeyData.set(authData.publicKey['-2'], 1); |
| publicKeyData.set(authData.publicKey['-3'], 33); |
| return crypto.subtle.importKey("raw", publicKeyData, { |
| name: "ECDSA", |
| namedCurve: "P-256" |
| }, false, ['verify']).then(publicKey => { |
| return crypto.subtle.verify({ |
| name: "ECDSA", |
| hash: "SHA-256" |
| }, publicKey, extractRawSignature(attestationObject.attStmt.sig), attestationObject.authData).then(verified => { |
| assert_true(verified); |
| }); |
| }); |
| } |
| } |
| |
| promise_test(t => { |
| const options = { |
| publicKey: { |
| rp: { |
| name: "localhost", |
| }, |
| user: { |
| name: userhandleBase64, |
| id: Base64URL.parse(userhandleBase64), |
| displayName: "Appleseed", |
| }, |
| challenge: Base64URL.parse("MTIzNDU2"), |
| pubKeyCredParams: [{ type: "public-key", alg: -7 }], |
| } |
| }; |
| |
| return navigator.credentials.create(options).then(credential => { |
| checkResult(credential); |
| }); |
| }, "PublicKeyCredential's [[create]] with minimum options in a mock local authenticator."); |
| |
| promise_test(t => { |
| const options = { |
| publicKey: { |
| rp: { |
| name: "localhost", |
| }, |
| user: { |
| name: userhandleBase64, |
| id: Base64URL.parse(userhandleBase64), |
| displayName: "Appleseed", |
| }, |
| challenge: Base64URL.parse("MTIzNDU2"), |
| pubKeyCredParams: [{ type: "public-key", alg: -7 }], |
| authenticatorSelection: { authenticatorAttachment: "platform" } |
| } |
| }; |
| |
| return navigator.credentials.create(options).then(credential => { |
| checkResult(credential); |
| }); |
| }, "PublicKeyCredential's [[create]] with authenticatorSelection { 'platform' } in a mock local authenticator."); |
| |
| promise_test(t => { |
| const options = { |
| publicKey: { |
| rp: { |
| name: "example.com" |
| }, |
| user: { |
| name: userhandleBase64, |
| id: Base64URL.parse(userhandleBase64), |
| displayName: "John", |
| }, |
| challenge: asciiToUint8Array("123456"), |
| pubKeyCredParams: [{ type: "public-key", alg: -7 }], |
| excludeCredentials: [ |
| { type: "public-key", id: credentialID, transports: ["usb"] }, |
| { type: "public-key", id: credentialID, transports: ["nfc"] }, |
| { type: "public-key", id: credentialID, transports: ["ble"] } |
| ] |
| } |
| }; |
| if (window.testRunner) |
| testRunner.addTestKeyToKeychain(testES256PrivateKeyBase64, testRpId, userhandleBase64); |
| |
| return navigator.credentials.create(options).then(credential => { |
| checkResult(credential); |
| }); |
| }, "PublicKeyCredential's [[create]] with matched exclude credential ids but not transports in a mock local authenticator."); |
| |
| promise_test(t => { |
| const options = { |
| publicKey: { |
| rp: { |
| name: "localhost", |
| }, |
| user: { |
| name: userhandleBase64, |
| id: Base64URL.parse(userhandleBase64), |
| displayName: "Appleseed", |
| }, |
| challenge: Base64URL.parse("MTIzNDU2"), |
| pubKeyCredParams: [{ type: "public-key", alg: -7 }], |
| attestation: "none" |
| } |
| }; |
| |
| return navigator.credentials.create(options).then(credential => { |
| checkResult(credential); |
| }); |
| }, "PublicKeyCredential's [[create]] with none attestation in a mock local authenticator."); |
| |
| promise_test(t => { |
| const options = { |
| publicKey: { |
| rp: { |
| name: "localhost", |
| }, |
| user: { |
| name: userhandleBase64, |
| id: Base64URL.parse(userhandleBase64), |
| displayName: "Appleseed", |
| }, |
| challenge: Base64URL.parse("MTIzNDU2"), |
| pubKeyCredParams: [{ type: "public-key", alg: -7 }], |
| attestation: "indirect" |
| } |
| }; |
| |
| return navigator.credentials.create(options).then(credential => { |
| checkResult(credential, false); |
| }); |
| }, "PublicKeyCredential's [[create]] with indirect attestation in a mock local authenticator."); |
| |
| promise_test(t => { |
| const options = { |
| publicKey: { |
| rp: { |
| name: "localhost", |
| }, |
| user: { |
| name: userhandleBase64, |
| id: Base64URL.parse(userhandleBase64), |
| displayName: "Appleseed", |
| }, |
| challenge: Base64URL.parse("MTIzNDU2"), |
| pubKeyCredParams: [{ type: "public-key", alg: -7 }], |
| attestation: "direct" |
| } |
| }; |
| |
| return navigator.credentials.create(options).then(credential => { |
| checkResult(credential, false); |
| }); |
| }, "PublicKeyCredential's [[create]] with direct attestation in a mock local authenticator."); |
| })(); |
| </script> |