Changeset 258293 in webkit
- Timestamp:
- Mar 11, 2020 3:42:08 PM (4 years ago)
- Location:
- trunk
- Files:
-
- 30 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r258282 r258293 1 2020-03-11 Jiewen Tan <jiewen_tan@apple.com> 2 3 [WebAuthn] Formalize the Keychain schema 4 https://bugs.webkit.org/show_bug.cgi?id=183533 5 <rdar://problem/43347926> 6 7 Reviewed by Brent Fulgham. 8 9 New tests are added and all tests are modified to use Credential ID to identify a credential instead 10 of { RP ID, user handle }. 11 12 * http/wpt/webauthn/public-key-credential-create-failure-local-silent.https-expected.txt: 13 * http/wpt/webauthn/public-key-credential-create-failure-local-silent.https.html: 14 * http/wpt/webauthn/public-key-credential-create-failure-local.https-expected.txt: 15 * http/wpt/webauthn/public-key-credential-create-failure-local.https.html: 16 * http/wpt/webauthn/public-key-credential-create-success-local.https-expected.txt: 17 * http/wpt/webauthn/public-key-credential-create-success-local.https.html: 18 * http/wpt/webauthn/public-key-credential-get-failure-local-silent.https-expected.txt: 19 * http/wpt/webauthn/public-key-credential-get-failure-local-silent.https.html: 20 * http/wpt/webauthn/public-key-credential-get-failure-local.https.html: 21 * http/wpt/webauthn/public-key-credential-get-success-local.https.html: 22 * http/wpt/webauthn/resources/util.js: 23 1 24 2020-03-11 Myles C. Maxfield <mmaxfield@apple.com> 2 25 -
trunk/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-local-silent.https-expected.txt
r236842 r258293 1 1 2 PASS PublicKeyCredential's [[create]] with silent failurein a mock local authenticator.3 PASS PublicKeyCredential's [[create]] with silent failure in a mock local authenticator. 24 PASS PublicKeyCredential's [[create]] with silent failure in a mock local authenticator. 35 PASS PublicKeyCredential's [[create]] with silent failure in a mock local authenticator. 46 PASS PublicKeyCredential's [[create]] with silent failure in a mock local authenticator. 57 PASS PublicKeyCredential's [[create]] with silent failure in a mock local authenticator. 62 PASS PublicKeyCredential's [[create]] with unsupported public key credential parameters in a mock local authenticator. 3 PASS PublicKeyCredential's [[create]] with matched exclude credentials in a mock local authenticator. 4 PASS PublicKeyCredential's [[create]] with matched exclude credentials in a mock local authenticator. 2nd 5 PASS PublicKeyCredential's [[create]] without user consent in a mock local authenticator. 6 PASS PublicKeyCredential's [[create]] without private keys in a mock local authenticator. 7 PASS PublicKeyCredential's [[create]] without attestation in a mock local authenticator. 8 8 -
trunk/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-local-silent.https.html
r250940 r258293 5 5 <script src="./resources/util.js"></script> 6 6 <script> 7 (async function() { 8 const userhandleBase64 = generateUserhandleBase64(); 7 // Default mock configuration. Tests need to override if they need different configuration. 8 if (window.internals) 9 internals.setMockWebAuthenticationConfiguration({ silentFailure: true, local: { acceptAuthentication: false, acceptAttestation: false } }); 10 11 promise_test(t => { 12 const options = { 13 publicKey: { 14 rp: { 15 name: "example.com" 16 }, 17 user: { 18 name: "John Appleseed", 19 id: Base64URL.parse(testUserhandleBase64), 20 displayName: "John", 21 }, 22 challenge: asciiToUint8Array("123456"), 23 pubKeyCredParams: [{ type: "public-key", alg: -35 }, { type: "public-key", alg: -257 }], // ES384, RS256 24 timeout: 10 25 } 26 }; 27 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out."); 28 }, "PublicKeyCredential's [[create]] with unsupported public key credential parameters in a mock local authenticator."); 29 30 promise_test(async t => { 9 31 const privateKeyBase64 = await generatePrivateKeyBase64(); 10 32 const credentialID = await calculateCredentialID(privateKeyBase64); 11 // Default mock configuration. Tests need to override if they need different configuration. 33 const credentialIDBase64 = base64encode(credentialID); 34 35 const options = { 36 publicKey: { 37 rp: { 38 name: "example.com" 39 }, 40 user: { 41 name: "John Appleseed", 42 id: Base64URL.parse(testUserhandleBase64), 43 displayName: "John", 44 }, 45 challenge: asciiToUint8Array("123456"), 46 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 47 excludeCredentials: [{ type: "public-key", id: credentialID }], 48 timeout: 10 49 } 50 }; 51 if (window.testRunner) 52 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, testUserEntityBundleBase64); 53 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.").then(() => { 54 if (window.testRunner) 55 testRunner.cleanUpKeychain(testRpId, credentialIDBase64); 56 }); 57 }, "PublicKeyCredential's [[create]] with matched exclude credentials in a mock local authenticator."); 58 59 promise_test(async t => { 60 const privateKeyBase64 = await generatePrivateKeyBase64(); 61 const credentialID = await calculateCredentialID(privateKeyBase64); 62 const credentialIDBase64 = base64encode(credentialID); 63 64 const options = { 65 publicKey: { 66 rp: { 67 name: "example.com" 68 }, 69 user: { 70 name: "John Appleseed", 71 id: Base64URL.parse(testUserhandleBase64), 72 displayName: "John", 73 }, 74 challenge: asciiToUint8Array("123456"), 75 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 76 excludeCredentials: [ 77 { type: "public-key", id: credentialID, transports: ["usb"] }, 78 { type: "public-key", id: credentialID, transports: ["nfc"] }, 79 { type: "public-key", id: credentialID, transports: ["ble"] }, 80 { type: "public-key", id: credentialID, transports: ["internal"] } 81 ], 82 timeout: 10 83 } 84 }; 85 if (window.testRunner) 86 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, testUserEntityBundleBase64); 87 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.").then(() => { 88 if (window.testRunner) 89 testRunner.cleanUpKeychain(testRpId, credentialIDBase64); 90 }); 91 }, "PublicKeyCredential's [[create]] with matched exclude credentials in a mock local authenticator. 2nd"); 92 93 promise_test(t => { 94 const options = { 95 publicKey: { 96 rp: { 97 name: "example.com" 98 }, 99 user: { 100 name: "John Appleseed", 101 id: Base64URL.parse(testUserhandleBase64), 102 displayName: "John", 103 }, 104 challenge: asciiToUint8Array("123456"), 105 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 106 timeout: 10 107 } 108 }; 109 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out."); 110 }, "PublicKeyCredential's [[create]] without user consent in a mock local authenticator."); 111 112 promise_test(t => { 113 const options = { 114 publicKey: { 115 rp: { 116 name: "example.com" 117 }, 118 user: { 119 name: "John Appleseed", 120 id: Base64URL.parse(testUserhandleBase64), 121 displayName: "John", 122 }, 123 challenge: asciiToUint8Array("123456"), 124 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 125 timeout: 10 126 } 127 }; 12 128 if (window.internals) 13 internals.setMockWebAuthenticationConfiguration({ silentFailure: true, local: { acceptAuthentication: false, acceptAttestation: false } }); 129 internals.setMockWebAuthenticationConfiguration({ silentFailure: true, local: { acceptAuthentication: true, acceptAttestation: false } }); 130 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out."); 131 }, "PublicKeyCredential's [[create]] without private keys in a mock local authenticator."); 14 132 15 promise_test(t => { 16 const options = { 17 publicKey: { 18 rp: { 19 name: "example.com" 20 }, 21 user: { 22 name: "John Appleseed", 23 id: Base64URL.parse(testUserhandleBase64), 24 displayName: "John", 25 }, 26 challenge: asciiToUint8Array("123456"), 27 pubKeyCredParams: [{ type: "public-key", alg: -35 }, { type: "public-key", alg: -257 }], // ES384, RS256 28 timeout: 10 29 } 30 }; 31 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out."); 32 }, "PublicKeyCredential's [[create]] with silent failure in a mock local authenticator."); 133 promise_test(async t => { 134 const privateKeyBase64 = await generatePrivateKeyBase64(); 135 const credentialID = await calculateCredentialID(privateKeyBase64); 136 const credentialIDBase64 = base64encode(credentialID); 33 137 34 promise_test(t => { 35 const options = { 36 publicKey: { 37 rp: { 38 name: "example.com" 39 }, 40 user: { 41 name: "John Appleseed", 42 id: Base64URL.parse(userhandleBase64), 43 displayName: "John", 44 }, 45 challenge: asciiToUint8Array("123456"), 46 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 47 excludeCredentials: [{ type: "public-key", id: Base64URL.parse(testCredentialIdBase64) }], 48 timeout: 10 49 } 50 }; 138 const options = { 139 publicKey: { 140 rp: { 141 name: "example.com" 142 }, 143 user: { 144 name: "John Appleseed", 145 id: Base64URL.parse(testUserhandleBase64), 146 displayName: "John", 147 }, 148 challenge: asciiToUint8Array("123456"), 149 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 150 attestation: "direct", 151 timeout: 10 152 } 153 }; 154 if (window.internals) 155 internals.setMockWebAuthenticationConfiguration({ silentFailure: true, local: { acceptAuthentication: true, acceptAttestation: false, privateKeyBase64: privateKeyBase64 } }); 156 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.").then(() => { 51 157 if (window.testRunner) 52 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, userhandleBase64); 53 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.").then(() => { 54 if (window.testRunner) 55 testRunner.cleanUpKeychain(testRpId, userhandleBase64); 56 }); 57 }, "PublicKeyCredential's [[create]] with silent failure in a mock local authenticator. 2"); 58 59 promise_test(t => { 60 const options = { 61 publicKey: { 62 rp: { 63 name: "example.com" 64 }, 65 user: { 66 name: "John Appleseed", 67 id: Base64URL.parse(userhandleBase64), 68 displayName: "John", 69 }, 70 challenge: asciiToUint8Array("123456"), 71 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 72 excludeCredentials: [ 73 { type: "public-key", id: credentialID, transports: ["usb"] }, 74 { type: "public-key", id: credentialID, transports: ["nfc"] }, 75 { type: "public-key", id: credentialID, transports: ["ble"] }, 76 { type: "public-key", id: credentialID, transports: ["internal"] } 77 ], 78 timeout: 10 79 } 80 }; 81 if (window.testRunner) 82 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, userhandleBase64); 83 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.").then(() => { 84 if (window.testRunner) 85 testRunner.cleanUpKeychain(testRpId, userhandleBase64); 86 }); 87 }, "PublicKeyCredential's [[create]] with silent failure in a mock local authenticator. 3"); 88 89 promise_test(t => { 90 const options = { 91 publicKey: { 92 rp: { 93 name: "example.com" 94 }, 95 user: { 96 name: "John Appleseed", 97 id: Base64URL.parse(testUserhandleBase64), 98 displayName: "John", 99 }, 100 challenge: asciiToUint8Array("123456"), 101 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 102 timeout: 10 103 } 104 }; 105 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out."); 106 }, "PublicKeyCredential's [[create]] with silent failure in a mock local authenticator. 4"); 107 108 promise_test(t => { 109 const options = { 110 publicKey: { 111 rp: { 112 name: "example.com" 113 }, 114 user: { 115 name: "John Appleseed", 116 id: Base64URL.parse(testUserhandleBase64), 117 displayName: "John", 118 }, 119 challenge: asciiToUint8Array("123456"), 120 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 121 timeout: 10 122 } 123 }; 124 if (window.internals) 125 internals.setMockWebAuthenticationConfiguration({ silentFailure: true, local: { acceptAuthentication: true, acceptAttestation: false } }); 126 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out."); 127 }, "PublicKeyCredential's [[create]] with silent failure in a mock local authenticator. 5"); 128 129 promise_test(t => { 130 const options = { 131 publicKey: { 132 rp: { 133 name: "example.com" 134 }, 135 user: { 136 name: "John Appleseed", 137 id: Base64URL.parse(userhandleBase64), 138 displayName: "John", 139 }, 140 challenge: asciiToUint8Array("123456"), 141 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 142 timeout: 10 143 } 144 }; 145 if (window.internals) { 146 internals.setMockWebAuthenticationConfiguration({ silentFailure: true, local: { acceptAuthentication: true, acceptAttestation: false } }); 147 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, userhandleBase64); 148 } 149 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.").then(() => { 150 if (window.testRunner) 151 assert_false(testRunner.keyExistsInKeychain(testRpId, userhandleBase64)); 152 }); 153 }, "PublicKeyCredential's [[create]] with silent failure in a mock local authenticator. 6"); 154 })(); 158 testRunner.cleanUpKeychain(testRpId, credentialIDBase64); 159 }); 160 }, "PublicKeyCredential's [[create]] without attestation in a mock local authenticator."); 155 161 </script> -
trunk/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-local.https-expected.txt
r257085 r258293 6 6 PASS PublicKeyCredential's [[create]] without private keys in a mock local authenticator. 7 7 PASS PublicKeyCredential's [[create]] without attestation in a mock local authenticator. 8 PASS PublicKeyCredential's [[create]] deleting old credential in a mock local authenticator.8 PASS PublicKeyCredential's [[create]] not deleting old credential in a mock local authenticator. 9 9 PASS PublicKeyCredential's [[create]] with timeout in a mock local authenticator. 10 10 -
trunk/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-local.https.html
r258020 r258293 5 5 <script src="./resources/util.js"></script> 6 6 <script> 7 (async function() { 8 const userhandleBase64 = generateUserhandleBase64(); 9 const privateKeyBase64 = await generatePrivateKeyBase64(); 10 const credentialID = await calculateCredentialID(privateKeyBase64); 11 // Default mock configuration. Tests need to override if they need different configuration. 7 // Default mock configuration. Tests need to override if they need different configuration. 8 if (window.internals) 9 internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: false, acceptAttestation: false } }); 10 11 promise_test(t => { 12 const options = { 13 publicKey: { 14 rp: { 15 name: "example.com" 16 }, 17 user: { 18 name: "John Appleseed", 19 id: Base64URL.parse(testUserhandleBase64), 20 displayName: "John", 21 }, 22 challenge: asciiToUint8Array("123456"), 23 pubKeyCredParams: [{ type: "public-key", alg: -35 }, { type: "public-key", alg: -257 }], // ES384, RS256 24 } 25 }; 26 return promiseRejects(t, "NotSupportedError", navigator.credentials.create(options), "The platform attached authenticator doesn't support any provided PublicKeyCredentialParameters."); 27 }, "PublicKeyCredential's [[create]] with unsupported public key credential parameters in a mock local authenticator."); 28 29 promise_test(async t => { 30 const privateKeyBase64 = await generatePrivateKeyBase64(); 31 const credentialID = await calculateCredentialID(privateKeyBase64); 32 const credentialIDBase64 = base64encode(credentialID); 33 34 const options = { 35 publicKey: { 36 rp: { 37 name: "example.com" 38 }, 39 user: { 40 name: "John Appleseed", 41 id: Base64URL.parse(testUserhandleBase64), 42 displayName: "John", 43 }, 44 challenge: asciiToUint8Array("123456"), 45 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 46 excludeCredentials: [{ type: "public-key", id: credentialID }] 47 } 48 }; 49 if (window.testRunner) 50 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, testUserEntityBundleBase64); 51 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator.").then(() => { 52 if (window.testRunner) 53 testRunner.cleanUpKeychain(testRpId, credentialIDBase64); 54 }); 55 }, "PublicKeyCredential's [[create]] with matched exclude credentials in a mock local authenticator."); 56 57 promise_test(async t => { 58 const privateKeyBase64 = await generatePrivateKeyBase64(); 59 const credentialID = await calculateCredentialID(privateKeyBase64); 60 const credentialIDBase64 = base64encode(credentialID); 61 62 const options = { 63 publicKey: { 64 rp: { 65 name: "example.com" 66 }, 67 user: { 68 name: "John Appleseed", 69 id: Base64URL.parse(testUserhandleBase64), 70 displayName: "John", 71 }, 72 challenge: asciiToUint8Array("123456"), 73 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 74 excludeCredentials: [ 75 { type: "public-key", id: credentialID, transports: ["usb"] }, 76 { type: "public-key", id: credentialID, transports: ["nfc"] }, 77 { type: "public-key", id: credentialID, transports: ["ble"] }, 78 { type: "public-key", id: credentialID, transports: ["internal"] } 79 ] 80 } 81 }; 82 if (window.testRunner) 83 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, testUserEntityBundleBase64); 84 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator.").then(() => { 85 if (window.testRunner) 86 testRunner.cleanUpKeychain(testRpId, credentialIDBase64); 87 }); 88 }, "PublicKeyCredential's [[create]] with matched exclude credentials in a mock local authenticator. 2nd"); 89 90 promise_test(t => { 91 const options = { 92 publicKey: { 93 rp: { 94 name: "example.com" 95 }, 96 user: { 97 name: "John Appleseed", 98 id: Base64URL.parse(testUserhandleBase64), 99 displayName: "John", 100 }, 101 challenge: asciiToUint8Array("123456"), 102 pubKeyCredParams: [{ type: "public-key", alg: -7 }] 103 } 104 }; 105 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Couldn't verify user."); 106 }, "PublicKeyCredential's [[create]] without user consent in a mock local authenticator."); 107 108 promise_test(t => { 109 const options = { 110 publicKey: { 111 rp: { 112 name: "example.com" 113 }, 114 user: { 115 name: "John Appleseed", 116 id: Base64URL.parse(testUserhandleBase64), 117 displayName: "John", 118 }, 119 challenge: asciiToUint8Array("123456"), 120 pubKeyCredParams: [{ type: "public-key", alg: -7 }] 121 } 122 }; 123 if (window.internals) 124 internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false } }); 125 return promiseRejects(t, "UnknownError", navigator.credentials.create(options), "Couldn't create private key."); 126 }, "PublicKeyCredential's [[create]] without private keys in a mock local authenticator."); 127 128 promise_test(async t => { 129 const privateKeyBase64 = await generatePrivateKeyBase64(); 130 const credentialID = await calculateCredentialID(privateKeyBase64); 131 const credentialIDBase64 = base64encode(credentialID); 132 133 const options = { 134 publicKey: { 135 rp: { 136 name: "example.com" 137 }, 138 user: { 139 name: "John Appleseed", 140 id: Base64URL.parse(testUserhandleBase64), 141 displayName: "John", 142 }, 143 challenge: asciiToUint8Array("123456"), 144 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 145 attestation: "direct" 146 } 147 }; 148 if (window.internals) 149 internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false, privateKeyBase64: privateKeyBase64 } }); 150 return promiseRejects(t, "UnknownError", navigator.credentials.create(options), "Couldn't attest: The operation couldn't complete.").then(() => { 151 if (window.testRunner) 152 testRunner.cleanUpKeychain(testRpId, credentialIDBase64); 153 }); 154 }, "PublicKeyCredential's [[create]] without attestation in a mock local authenticator."); 155 156 promise_test(async t => { 157 const privateKeyBase64 = await generatePrivateKeyBase64(); 158 const credentialID = await calculateCredentialID(privateKeyBase64); 159 const credentialIDBase64 = base64encode(credentialID); 160 161 const options = { 162 publicKey: { 163 rp: { 164 name: "example.com" 165 }, 166 user: { 167 name: testUserhandleBase64, 168 id: Base64URL.parse(testUserhandleBase64), 169 displayName: "John", 170 }, 171 challenge: asciiToUint8Array("123456"), 172 pubKeyCredParams: [{ type: "public-key", alg: -7 }] 173 } 174 }; 175 if (window.internals) { 176 internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false } }); 177 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, testUserEntityBundleBase64); 178 } 179 return promiseRejects(t, "UnknownError", navigator.credentials.create(options), "Couldn't create private key.").then(() => { 180 if (window.testRunner) 181 assert_true(testRunner.keyExistsInKeychain(testRpId, credentialIDBase64)); 182 testRunner.cleanUpKeychain(testRpId, credentialIDBase64); 183 }); 184 }, "PublicKeyCredential's [[create]] not deleting old credential in a mock local authenticator."); 185 186 promise_test(function(t) { 187 const options = { 188 publicKey: { 189 rp: { 190 name: "example.com" 191 }, 192 user: { 193 name: "John Appleseed", 194 id: asciiToUint8Array("123456"), 195 displayName: "John", 196 }, 197 challenge: asciiToUint8Array("123456"), 198 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 199 timeout: 10, 200 authenticatorSelection: { authenticatorAttachment: "cross-platform" } 201 } 202 }; 203 12 204 if (window.internals) 13 205 internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: false, acceptAttestation: false } }); 14 15 promise_test(t => { 16 const options = { 17 publicKey: { 18 rp: { 19 name: "example.com" 20 }, 21 user: { 22 name: "John Appleseed", 23 id: Base64URL.parse(testUserhandleBase64), 24 displayName: "John", 25 }, 26 challenge: asciiToUint8Array("123456"), 27 pubKeyCredParams: [{ type: "public-key", alg: -35 }, { type: "public-key", alg: -257 }], // ES384, RS256 28 } 29 }; 30 return promiseRejects(t, "NotSupportedError", navigator.credentials.create(options), "The platform attached authenticator doesn't support any provided PublicKeyCredentialParameters."); 31 }, "PublicKeyCredential's [[create]] with unsupported public key credential parameters in a mock local authenticator."); 32 33 promise_test(t => { 34 const options = { 35 publicKey: { 36 rp: { 37 name: "example.com" 38 }, 39 user: { 40 name: "John Appleseed", 41 id: Base64URL.parse(userhandleBase64), 42 displayName: "John", 43 }, 44 challenge: asciiToUint8Array("123456"), 45 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 46 excludeCredentials: [{ type: "public-key", id: credentialID }] 47 } 48 }; 49 if (window.testRunner) 50 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, userhandleBase64); 51 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator.").then(() => { 52 if (window.testRunner) 53 testRunner.cleanUpKeychain(testRpId, userhandleBase64); 54 }); 55 }, "PublicKeyCredential's [[create]] with matched exclude credentials in a mock local authenticator."); 56 57 promise_test(t => { 58 const options = { 59 publicKey: { 60 rp: { 61 name: "example.com" 62 }, 63 user: { 64 name: "John Appleseed", 65 id: Base64URL.parse(userhandleBase64), 66 displayName: "John", 67 }, 68 challenge: asciiToUint8Array("123456"), 69 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 70 excludeCredentials: [ 71 { type: "public-key", id: credentialID, transports: ["usb"] }, 72 { type: "public-key", id: credentialID, transports: ["nfc"] }, 73 { type: "public-key", id: credentialID, transports: ["ble"] }, 74 { type: "public-key", id: credentialID, transports: ["internal"] } 75 ] 76 } 77 }; 78 if (window.testRunner) 79 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, userhandleBase64); 80 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator.").then(() => { 81 if (window.testRunner) 82 testRunner.cleanUpKeychain(testRpId, userhandleBase64); 83 }); 84 }, "PublicKeyCredential's [[create]] with matched exclude credentials in a mock local authenticator. 2nd"); 85 86 promise_test(t => { 87 const options = { 88 publicKey: { 89 rp: { 90 name: "example.com" 91 }, 92 user: { 93 name: "John Appleseed", 94 id: Base64URL.parse(testUserhandleBase64), 95 displayName: "John", 96 }, 97 challenge: asciiToUint8Array("123456"), 98 pubKeyCredParams: [{ type: "public-key", alg: -7 }] 99 } 100 }; 101 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Couldn't verify user."); 102 }, "PublicKeyCredential's [[create]] without user consent in a mock local authenticator."); 103 104 promise_test(t => { 105 const options = { 106 publicKey: { 107 rp: { 108 name: "example.com" 109 }, 110 user: { 111 name: "John Appleseed", 112 id: Base64URL.parse(testUserhandleBase64), 113 displayName: "John", 114 }, 115 challenge: asciiToUint8Array("123456"), 116 pubKeyCredParams: [{ type: "public-key", alg: -7 }] 117 } 118 }; 119 if (window.internals) 120 internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false } }); 121 return promiseRejects(t, "UnknownError", navigator.credentials.create(options), "Couldn't create private key."); 122 }, "PublicKeyCredential's [[create]] without private keys in a mock local authenticator."); 123 124 promise_test(t => { 125 const options = { 126 publicKey: { 127 rp: { 128 name: "example.com" 129 }, 130 user: { 131 name: "John Appleseed", 132 id: Base64URL.parse(userhandleBase64), 133 displayName: "John", 134 }, 135 challenge: asciiToUint8Array("123456"), 136 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 137 attestation: "direct" 138 } 139 }; 140 if (window.internals) 141 internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false, privateKeyBase64: privateKeyBase64 } }); 142 return promiseRejects(t, "UnknownError", navigator.credentials.create(options), "Couldn't attest: The operation couldn't complete.").then(() => { 143 if (window.testRunner) 144 testRunner.cleanUpKeychain(testRpId, userhandleBase64); 145 }); 146 }, "PublicKeyCredential's [[create]] without attestation in a mock local authenticator."); 147 148 promise_test(t => { 149 const options = { 150 publicKey: { 151 rp: { 152 name: "example.com" 153 }, 154 user: { 155 name: userhandleBase64, 156 id: Base64URL.parse(userhandleBase64), 157 displayName: "John", 158 }, 159 challenge: asciiToUint8Array("123456"), 160 pubKeyCredParams: [{ type: "public-key", alg: -7 }] 161 } 162 }; 163 if (window.internals) { 164 internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false } }); 165 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, userhandleBase64); 166 } 167 return promiseRejects(t, "UnknownError", navigator.credentials.create(options), "Couldn't create private key.").then(() => { 168 if (window.testRunner) 169 assert_false(testRunner.keyExistsInKeychain(testRpId, userhandleBase64)); 170 }); 171 }, "PublicKeyCredential's [[create]] deleting old credential in a mock local authenticator."); 172 173 promise_test(function(t) { 174 const options = { 175 publicKey: { 176 rp: { 177 name: "example.com" 178 }, 179 user: { 180 name: "John Appleseed", 181 id: asciiToUint8Array("123456"), 182 displayName: "John", 183 }, 184 challenge: asciiToUint8Array("123456"), 185 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 186 timeout: 10, 187 authenticatorSelection: { authenticatorAttachment: "cross-platform" } 188 } 189 }; 190 191 if (window.internals) 192 internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: false, acceptAttestation: false } }); 193 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out."); 194 }, "PublicKeyCredential's [[create]] with timeout in a mock local authenticator."); 195 })(); 206 return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out."); 207 }, "PublicKeyCredential's [[create]] with timeout in a mock local authenticator."); 196 208 </script> -
trunk/LayoutTests/http/wpt/webauthn/public-key-credential-create-success-local.https-expected.txt
r245638 r258293 6 6 PASS PublicKeyCredential's [[create]] with indirect attestation in a mock local authenticator. 7 7 PASS PublicKeyCredential's [[create]] with direct attestation in a mock local authenticator. 8 PASS PublicKeyCredential's [[create]] with duplicate credential in a mock local authenticator. 9 PASS PublicKeyCredential's [[create]] with duplicate credential in a mock local authenticator. 2 8 10 -
trunk/LayoutTests/http/wpt/webauthn/public-key-credential-create-success-local.https.html
r258020 r258293 6 6 <script src="./resources/cbor.js"></script> 7 7 <script> 8 (async function() { 9 const userhandleBase64 = generateUserhandleBase64(); 10 const privateKeyBase64 = await generatePrivateKeyBase64(); 11 const credentialID = await calculateCredentialID(privateKeyBase64); 12 // Default mock configuration. Tests need to override if they need different configuration. 13 if (window.internals) 14 internals.setMockWebAuthenticationConfiguration({ 15 local: { 16 acceptAuthentication: true, 17 acceptAttestation: false, 8 function checkResult(credential, credentialID, isNoneAttestation = true) 9 { 10 // Check keychain 11 if (window.testRunner) { 12 assert_true(testRunner.keyExistsInKeychain(testRpId, base64encode(credentialID))); 13 testRunner.cleanUpKeychain(testRpId, base64encode(credentialID)); 14 } 15 16 // Check respond 17 assert_array_equals(Base64URL.parse(credential.id), credentialID); 18 assert_equals(credential.type, 'public-key'); 19 assert_array_equals(new Uint8Array(credential.rawId), credentialID); 20 assert_equals(bytesToASCIIString(credential.response.clientDataJSON), '{"type":"webauthn.create","challenge":"MTIzNDU2","origin":"https://localhost:9443"}'); 21 assert_not_own_property(credential.getClientExtensionResults(), "appid"); 22 23 // Check attestation 24 const attestationObject = CBOR.decode(credential.response.attestationObject); 25 if (isNoneAttestation) 26 assert_equals(attestationObject.fmt, "none"); 27 else 28 assert_equals(attestationObject.fmt, "apple"); 29 // Check authData 30 const authData = decodeAuthData(attestationObject.authData); 31 assert_equals(bytesToHexString(authData.rpIdHash), "49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763"); 32 assert_equals(authData.flags, 69); 33 assert_equals(authData.counter, 0); 34 assert_equals(bytesToHexString(authData.aaguid), "00000000000000000000000000000000"); 35 assert_array_equals(authData.credentialID, credentialID); 36 // Check self attestation 37 assert_true(checkPublicKey(authData.publicKey)); 38 if (isNoneAttestation) 39 assert_object_equals(attestationObject.attStmt, { }); 40 else { 41 assert_equals(attestationObject.attStmt.alg, -7); 42 assert_equals(attestationObject.attStmt.x5c.length, 2); 43 assert_array_equals(attestationObject.attStmt.x5c[0], Base64URL.parse(testAttestationCertificateBase64)); 44 assert_array_equals(attestationObject.attStmt.x5c[1], Base64URL.parse(testAttestationIssuingCACertificateBase64)); 45 } 46 } 47 48 promise_test(async t => { 49 const privateKeyBase64 = await generatePrivateKeyBase64(); 50 const credentialID = await calculateCredentialID(privateKeyBase64); 51 const userhandleBase64 = generateUserhandleBase64(); 52 if (window.internals) 53 internals.setMockWebAuthenticationConfiguration({ 54 local: { 55 acceptAuthentication: true, 56 acceptAttestation: false, 57 privateKeyBase64: privateKeyBase64, 58 } 59 }); 60 61 const options = { 62 publicKey: { 63 rp: { 64 name: "localhost", 65 }, 66 user: { 67 name: userhandleBase64, 68 id: Base64URL.parse(userhandleBase64), 69 displayName: "Appleseed", 70 }, 71 challenge: Base64URL.parse("MTIzNDU2"), 72 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 73 } 74 }; 75 76 return navigator.credentials.create(options).then(credential => { 77 checkResult(credential, credentialID); 78 }); 79 }, "PublicKeyCredential's [[create]] with minimum options in a mock local authenticator."); 80 81 promise_test(async t => { 82 const privateKeyBase64 = await generatePrivateKeyBase64(); 83 const credentialID = await calculateCredentialID(privateKeyBase64); 84 const userhandleBase64 = generateUserhandleBase64(); 85 if (window.internals) 86 internals.setMockWebAuthenticationConfiguration({ 87 local: { 88 acceptAuthentication: true, 89 acceptAttestation: false, 90 privateKeyBase64: privateKeyBase64, 91 } 92 }); 93 94 const options = { 95 publicKey: { 96 rp: { 97 name: "localhost", 98 }, 99 user: { 100 name: userhandleBase64, 101 id: Base64URL.parse(userhandleBase64), 102 displayName: "Appleseed", 103 }, 104 challenge: Base64URL.parse("MTIzNDU2"), 105 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 106 authenticatorSelection: { authenticatorAttachment: "platform" } 107 } 108 }; 109 110 return navigator.credentials.create(options).then(credential => { 111 checkResult(credential, credentialID); 112 }); 113 }, "PublicKeyCredential's [[create]] with authenticatorSelection { 'platform' } in a mock local authenticator."); 114 115 promise_test(async t => { 116 const privateKeyBase64 = await generatePrivateKeyBase64(); 117 const credentialID = await calculateCredentialID(privateKeyBase64); 118 const userhandleBase64 = generateUserhandleBase64(); 119 if (window.internals) 120 internals.setMockWebAuthenticationConfiguration({ 121 local: { 122 acceptAuthentication: true, 123 acceptAttestation: false, 124 privateKeyBase64: privateKeyBase64, 125 } 126 }); 127 128 const anotherPrivateKeyBase64 = await generatePrivateKeyBase64(); 129 const anotherCredentialID = await calculateCredentialID(anotherPrivateKeyBase64); 130 const options = { 131 publicKey: { 132 rp: { 133 name: "example.com" 134 }, 135 user: { 136 name: userhandleBase64, 137 id: Base64URL.parse(userhandleBase64), 138 displayName: "John", 139 }, 140 challenge: asciiToUint8Array("123456"), 141 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 142 excludeCredentials: [ 143 { type: "public-key", id: anotherCredentialID, transports: ["usb"] }, 144 { type: "public-key", id: anotherCredentialID, transports: ["nfc"] }, 145 { type: "public-key", id: anotherCredentialID, transports: ["ble"] } 146 ] 147 } 148 }; 149 if (window.testRunner) 150 testRunner.addTestKeyToKeychain(anotherPrivateKeyBase64, testRpId, testUserEntityBundleBase64); 151 152 return navigator.credentials.create(options).then(credential => { 153 checkResult(credential, credentialID); 154 if (window.testRunner) 155 testRunner.cleanUpKeychain(testRpId, base64encode(anotherCredentialID)); 156 }); 157 }, "PublicKeyCredential's [[create]] with matched exclude credential ids but not transports in a mock local authenticator."); 158 159 promise_test(async t => { 160 const privateKeyBase64 = await generatePrivateKeyBase64(); 161 const credentialID = await calculateCredentialID(privateKeyBase64); 162 const userhandleBase64 = generateUserhandleBase64(); 163 if (window.internals) 164 internals.setMockWebAuthenticationConfiguration({ 165 local: { 166 acceptAuthentication: true, 167 acceptAttestation: false, 168 privateKeyBase64: privateKeyBase64, 169 } 170 }); 171 172 const options = { 173 publicKey: { 174 rp: { 175 name: "localhost", 176 }, 177 user: { 178 name: userhandleBase64, 179 id: Base64URL.parse(userhandleBase64), 180 displayName: "Appleseed", 181 }, 182 challenge: Base64URL.parse("MTIzNDU2"), 183 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 184 attestation: "none" 185 } 186 }; 187 188 return navigator.credentials.create(options).then(credential => { 189 checkResult(credential, credentialID); 190 }); 191 }, "PublicKeyCredential's [[create]] with none attestation in a mock local authenticator."); 192 193 promise_test(async t => { 194 const privateKeyBase64 = await generatePrivateKeyBase64(); 195 const credentialID = await calculateCredentialID(privateKeyBase64); 196 const userhandleBase64 = generateUserhandleBase64(); 197 if (window.internals) 198 internals.setMockWebAuthenticationConfiguration({ 199 local: { 200 acceptAuthentication: true, 201 acceptAttestation: true, 18 202 privateKeyBase64: privateKeyBase64, 19 203 userCertificateBase64: testAttestationCertificateBase64, … … 22 206 }); 23 207 24 function checkResult(credential, isNoneAttestation = true) 25 { 26 // Check keychain 27 if (window.testRunner) { 28 assert_true(testRunner.keyExistsInKeychain(testRpId, userhandleBase64)); 29 testRunner.cleanUpKeychain(testRpId, userhandleBase64); 30 } 31 32 // Check respond 33 assert_array_equals(Base64URL.parse(credential.id), credentialID); 34 assert_equals(credential.type, 'public-key'); 35 assert_array_equals(new Uint8Array(credential.rawId), credentialID); 36 assert_equals(bytesToASCIIString(credential.response.clientDataJSON), '{"type":"webauthn.create","challenge":"MTIzNDU2","origin":"https://localhost:9443"}'); 37 assert_not_own_property(credential.getClientExtensionResults(), "appid"); 38 39 // Check attestation 40 const attestationObject = CBOR.decode(credential.response.attestationObject); 41 if (isNoneAttestation) 42 assert_equals(attestationObject.fmt, "none"); 43 else 44 assert_equals(attestationObject.fmt, "apple"); 45 // Check authData 46 const authData = decodeAuthData(attestationObject.authData); 47 assert_equals(bytesToHexString(authData.rpIdHash), "49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763"); 48 assert_equals(authData.flags, 69); 49 assert_equals(authData.counter, 0); 50 assert_equals(bytesToHexString(authData.aaguid), "00000000000000000000000000000000"); 51 assert_array_equals(authData.credentialID, credentialID); 52 // Check self attestation 53 assert_true(checkPublicKey(authData.publicKey)); 54 if (isNoneAttestation) 55 assert_object_equals(attestationObject.attStmt, { }); 56 else { 57 assert_equals(attestationObject.attStmt.alg, -7); 58 assert_equals(attestationObject.attStmt.x5c.length, 2); 59 assert_array_equals(attestationObject.attStmt.x5c[0], Base64URL.parse(testAttestationCertificateBase64)); 60 assert_array_equals(attestationObject.attStmt.x5c[1], Base64URL.parse(testAttestationIssuingCACertificateBase64)); 61 } 62 } 63 64 promise_test(t => { 65 const options = { 66 publicKey: { 67 rp: { 68 name: "localhost", 69 }, 70 user: { 71 name: userhandleBase64, 72 id: Base64URL.parse(userhandleBase64), 73 displayName: "Appleseed", 74 }, 75 challenge: Base64URL.parse("MTIzNDU2"), 76 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 77 } 78 }; 79 80 return navigator.credentials.create(options).then(credential => { 81 checkResult(credential); 82 }); 83 }, "PublicKeyCredential's [[create]] with minimum options in a mock local authenticator."); 84 85 promise_test(t => { 86 const options = { 87 publicKey: { 88 rp: { 89 name: "localhost", 90 }, 91 user: { 92 name: userhandleBase64, 93 id: Base64URL.parse(userhandleBase64), 94 displayName: "Appleseed", 95 }, 96 challenge: Base64URL.parse("MTIzNDU2"), 97 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 98 authenticatorSelection: { authenticatorAttachment: "platform" } 99 } 100 }; 101 102 return navigator.credentials.create(options).then(credential => { 103 checkResult(credential); 104 }); 105 }, "PublicKeyCredential's [[create]] with authenticatorSelection { 'platform' } in a mock local authenticator."); 106 107 promise_test(t => { 108 const options = { 109 publicKey: { 110 rp: { 111 name: "example.com" 112 }, 113 user: { 114 name: userhandleBase64, 115 id: Base64URL.parse(userhandleBase64), 116 displayName: "John", 117 }, 118 challenge: asciiToUint8Array("123456"), 119 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 120 excludeCredentials: [ 121 { type: "public-key", id: credentialID, transports: ["usb"] }, 122 { type: "public-key", id: credentialID, transports: ["nfc"] }, 123 { type: "public-key", id: credentialID, transports: ["ble"] } 124 ] 125 } 126 }; 127 if (window.testRunner) 128 testRunner.addTestKeyToKeychain(testES256PrivateKeyBase64, testRpId, userhandleBase64); 129 130 return navigator.credentials.create(options).then(credential => { 131 checkResult(credential); 132 }); 133 }, "PublicKeyCredential's [[create]] with matched exclude credential ids but not transports in a mock local authenticator."); 134 135 promise_test(t => { 136 const options = { 137 publicKey: { 138 rp: { 139 name: "localhost", 140 }, 141 user: { 142 name: userhandleBase64, 143 id: Base64URL.parse(userhandleBase64), 144 displayName: "Appleseed", 145 }, 146 challenge: Base64URL.parse("MTIzNDU2"), 147 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 148 attestation: "none" 149 } 150 }; 151 152 return navigator.credentials.create(options).then(credential => { 153 checkResult(credential); 154 }); 155 }, "PublicKeyCredential's [[create]] with none attestation in a mock local authenticator."); 156 157 promise_test(t => { 158 const options = { 159 publicKey: { 160 rp: { 161 name: "localhost", 162 }, 163 user: { 164 name: userhandleBase64, 165 id: Base64URL.parse(userhandleBase64), 166 displayName: "Appleseed", 167 }, 168 challenge: Base64URL.parse("MTIzNDU2"), 169 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 170 attestation: "indirect" 171 } 172 }; 173 174 if (window.internals) 175 internals.setMockWebAuthenticationConfiguration({ 176 local: { 177 acceptAuthentication: true, 178 acceptAttestation: true, 179 privateKeyBase64: privateKeyBase64, 180 userCertificateBase64: testAttestationCertificateBase64, 181 intermediateCACertificateBase64: testAttestationIssuingCACertificateBase64 182 } 183 }); 184 185 return navigator.credentials.create(options).then(credential => { 186 checkResult(credential, false); 187 }); 188 }, "PublicKeyCredential's [[create]] with indirect attestation in a mock local authenticator."); 189 190 promise_test(t => { 191 const options = { 192 publicKey: { 193 rp: { 194 name: "localhost", 195 }, 196 user: { 197 name: userhandleBase64, 198 id: Base64URL.parse(userhandleBase64), 199 displayName: "Appleseed", 200 }, 201 challenge: Base64URL.parse("MTIzNDU2"), 202 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 203 attestation: "direct" 204 } 205 }; 206 207 return navigator.credentials.create(options).then(credential => { 208 checkResult(credential, false); 209 }); 210 }, "PublicKeyCredential's [[create]] with direct attestation in a mock local authenticator."); 211 })(); 208 const options = { 209 publicKey: { 210 rp: { 211 name: "localhost", 212 }, 213 user: { 214 name: userhandleBase64, 215 id: Base64URL.parse(userhandleBase64), 216 displayName: "Appleseed", 217 }, 218 challenge: Base64URL.parse("MTIzNDU2"), 219 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 220 attestation: "indirect" 221 } 222 }; 223 224 return navigator.credentials.create(options).then(credential => { 225 checkResult(credential, credentialID, false); 226 }); 227 }, "PublicKeyCredential's [[create]] with indirect attestation in a mock local authenticator."); 228 229 promise_test(async t => { 230 const privateKeyBase64 = await generatePrivateKeyBase64(); 231 const credentialID = await calculateCredentialID(privateKeyBase64); 232 const userhandleBase64 = generateUserhandleBase64(); 233 if (window.internals) 234 internals.setMockWebAuthenticationConfiguration({ 235 local: { 236 acceptAuthentication: true, 237 acceptAttestation: true, 238 privateKeyBase64: privateKeyBase64, 239 userCertificateBase64: testAttestationCertificateBase64, 240 intermediateCACertificateBase64: testAttestationIssuingCACertificateBase64 241 } 242 }); 243 244 const options = { 245 publicKey: { 246 rp: { 247 name: "localhost", 248 }, 249 user: { 250 name: userhandleBase64, 251 id: Base64URL.parse(userhandleBase64), 252 displayName: "Appleseed", 253 }, 254 challenge: Base64URL.parse("MTIzNDU2"), 255 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 256 attestation: "direct" 257 } 258 }; 259 260 return navigator.credentials.create(options).then(credential => { 261 checkResult(credential, credentialID, false); 262 }); 263 }, "PublicKeyCredential's [[create]] with direct attestation in a mock local authenticator."); 264 265 promise_test(async t => { 266 const privateKeyBase64 = await generatePrivateKeyBase64(); 267 const credentialID = await calculateCredentialID(privateKeyBase64); 268 const userhandleBase64 = generateUserhandleBase64(); 269 if (window.internals) 270 internals.setMockWebAuthenticationConfiguration({ 271 local: { 272 acceptAuthentication: true, 273 acceptAttestation: false, 274 privateKeyBase64: privateKeyBase64, 275 } 276 }); 277 278 const options = { 279 publicKey: { 280 rp: { 281 name: "localhost", 282 }, 283 user: { 284 name: userhandleBase64, 285 id: Base64URL.parse(userhandleBase64), 286 displayName: "Appleseed", 287 }, 288 challenge: Base64URL.parse("MTIzNDU2"), 289 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 290 } 291 }; 292 293 const anotherPrivateKeyBase64 = await generatePrivateKeyBase64(); 294 const anotherCredentialID = await calculateCredentialID(anotherPrivateKeyBase64); 295 if (window.internals) 296 testRunner.addTestKeyToKeychain(anotherPrivateKeyBase64, testRpId, base64encode(CBOR.encode({ "id": Base64URL.parse(userhandleBase64), "name": userhandleBase64 }))); 297 298 return navigator.credentials.create(options).then(credential => { 299 checkResult(credential, credentialID); 300 assert_false(testRunner.keyExistsInKeychain(testRpId, base64encode(anotherCredentialID))); 301 }); 302 }, "PublicKeyCredential's [[create]] with duplicate credential in a mock local authenticator."); 303 304 promise_test(async t => { 305 const privateKeyBase64 = await generatePrivateKeyBase64(); 306 const credentialID = await calculateCredentialID(privateKeyBase64); 307 const userhandleBase64 = generateUserhandleBase64(); 308 if (window.internals) 309 internals.setMockWebAuthenticationConfiguration({ 310 local: { 311 acceptAuthentication: true, 312 acceptAttestation: true, 313 privateKeyBase64: privateKeyBase64, 314 userCertificateBase64: testAttestationCertificateBase64, 315 intermediateCACertificateBase64: testAttestationIssuingCACertificateBase64 316 } 317 }); 318 319 const options = { 320 publicKey: { 321 rp: { 322 name: "localhost", 323 }, 324 user: { 325 name: userhandleBase64, 326 id: Base64URL.parse(userhandleBase64), 327 displayName: "Appleseed", 328 }, 329 challenge: Base64URL.parse("MTIzNDU2"), 330 pubKeyCredParams: [{ type: "public-key", alg: -7 }], 331 attestation: "direct" 332 } 333 }; 334 335 const anotherPrivateKeyBase64 = await generatePrivateKeyBase64(); 336 const anotherCredentialID = await calculateCredentialID(anotherPrivateKeyBase64); 337 if (window.internals) 338 testRunner.addTestKeyToKeychain(anotherPrivateKeyBase64, testRpId, base64encode(CBOR.encode({ "id": Base64URL.parse(userhandleBase64), "name": userhandleBase64 }))); 339 340 return navigator.credentials.create(options).then(credential => { 341 checkResult(credential, credentialID, false); 342 assert_false(testRunner.keyExistsInKeychain(testRpId, base64encode(anotherCredentialID))); 343 }); 344 }, "PublicKeyCredential's [[create]] with duplicate credential in a mock local authenticator. 2"); 212 345 </script> -
trunk/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-local-silent.https-expected.txt
r236842 r258293 1 1 2 PASS PublicKeyCredential's [[get]] with silent failurein a mock local authenticator.3 PASS PublicKeyCredential's [[get]] with silent failure in a mock local authenticator. 24 PASS PublicKeyCredential's [[get]] with silent failure in a mock local authenticator. 32 PASS PublicKeyCredential's [[get]] with no matched credentials in a mock local authenticator. 3 PASS PublicKeyCredential's [[get]] with no matched credentials in a mock local authenticator. 2nd 4 PASS PublicKeyCredential's [[get]] without user consent in a mock local authenticator. 5 5 -
trunk/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-local-silent.https.html
r250940 r258293 5 5 <script src="./resources/util.js"></script> 6 6 <script> 7 (async function() { 8 const userhandleBase64 = generateUserhandleBase64(); 7 promise_test(t => { 8 if (window.internals) 9 internals.setMockWebAuthenticationConfiguration({ silentFailure: true, local: { acceptAuthentication: false, acceptAttestation: false } }); 10 11 const options = { 12 publicKey: { 13 challenge: asciiToUint8Array("123456"), 14 allowCredentials: [ 15 { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["usb"] }, 16 { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["nfc"] }, 17 { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["ble"] }, 18 { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["internal"] } 19 ], 20 timeout: 10 21 } 22 }; 23 24 return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Operation timed out."); 25 }, "PublicKeyCredential's [[get]] with no matched credentials in a mock local authenticator."); 26 27 promise_test(async t => { 9 28 const privateKeyBase64 = await generatePrivateKeyBase64(); 10 29 const credentialID = await calculateCredentialID(privateKeyBase64); 30 const credentialIDBase64 = btoa(String.fromCharCode.apply(0, credentialID)); 11 31 // Default mock configuration. Tests need to override if they need different configuration. 12 32 if (window.internals) 13 internals.setMockWebAuthenticationConfiguration({ silentFailure: true, local: { acceptAuthentication: false, acceptAttestation: false, preferred UserhandleBase64: userhandleBase64 } });33 internals.setMockWebAuthenticationConfiguration({ silentFailure: true, local: { acceptAuthentication: false, acceptAttestation: false, preferredCredentialIdBase64: credentialIDBase64 } }); 14 34 15 promise_test(t => { 16 const options = { 17 publicKey: { 18 challenge: asciiToUint8Array("123456"), 19 allowCredentials: [ 20 { type: "public-key", id: credentialID, transports: ["usb"] }, 21 { type: "public-key", id: credentialID, transports: ["nfc"] }, 22 { type: "public-key", id: credentialID, transports: ["ble"] }, 23 { type: "public-key", id: credentialID, transports: ["internal"] } 24 ], 25 timeout: 10 26 } 27 }; 35 const options = { 36 publicKey: { 37 challenge: asciiToUint8Array("123456"), 38 allowCredentials: [ 39 { type: "public-key", id: Base64URL.parse(testUserhandleBase64) } 40 ], 41 timeout: 10 42 } 43 }; 28 44 29 return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Operation timed out."); 30 }, "PublicKeyCredential's [[get]] with silent failure in a mock local authenticator."); 45 if (window.testRunner) 46 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, testUserEntityBundleBase64); 47 return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Operation timed out.").then(() => { 48 if (window.testRunner) 49 testRunner.cleanUpKeychain(testRpId, credentialIDBase64); 50 }); 51 }, "PublicKeyCredential's [[get]] with no matched credentials in a mock local authenticator. 2nd"); 31 52 32 promise_test(t => { 33 const options = { 34 publicKey: { 35 challenge: asciiToUint8Array("123456"), 36 allowCredentials: [ 37 { type: "public-key", id: Base64URL.parse(userhandleBase64) } 38 ], 39 timeout: 10 40 } 41 }; 53 promise_test(async t => { 54 const privateKeyBase64 = await generatePrivateKeyBase64(); 55 const credentialID = await calculateCredentialID(privateKeyBase64); 56 const credentialIDBase64 = btoa(String.fromCharCode.apply(0, credentialID)); 57 // Default mock configuration. Tests need to override if they need different configuration. 58 if (window.internals) 59 internals.setMockWebAuthenticationConfiguration({ silentFailure: true, local: { acceptAuthentication: false, acceptAttestation: false, preferredCredentialIdBase64: credentialIDBase64 } }); 42 60 61 const options = { 62 publicKey: { 63 challenge: asciiToUint8Array("123456"), 64 timeout: 10 65 } 66 }; 67 68 if (window.testRunner) 69 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, testUserEntityBundleBase64); 70 return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Operation timed out.").then(() => { 43 71 if (window.testRunner) 44 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, userhandleBase64); 45 return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Operation timed out.").then(() => { 46 if (window.testRunner) 47 testRunner.cleanUpKeychain(testRpId, userhandleBase64); 48 }); 49 }, "PublicKeyCredential's [[get]] with silent failure in a mock local authenticator. 2"); 50 51 promise_test(t => { 52 const options = { 53 publicKey: { 54 challenge: asciiToUint8Array("123456"), 55 timeout: 10 56 } 57 }; 58 59 if (window.testRunner) 60 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, userhandleBase64); 61 return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Operation timed out.").then(() => { 62 if (window.testRunner) 63 testRunner.cleanUpKeychain(testRpId, userhandleBase64); 64 }); 65 }, "PublicKeyCredential's [[get]] with silent failure in a mock local authenticator. 3"); 66 })(); 72 testRunner.cleanUpKeychain(testRpId, credentialIDBase64); 73 }); 74 }, "PublicKeyCredential's [[get]] without user consent in a mock local authenticator."); 67 75 </script> -
trunk/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-local.https.html
r257877 r258293 5 5 <script src="./resources/util.js"></script> 6 6 <script> 7 (async function() { 8 const userhandleBase64 = generateUserhandleBase64(); 7 promise_test(t => { 8 if (window.internals) 9 internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: false, acceptAttestation: false } }); 10 11 const options = { 12 publicKey: { 13 challenge: asciiToUint8Array("123456"), 14 allowCredentials: [ 15 { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["usb"] }, 16 { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["nfc"] }, 17 { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["ble"] }, 18 { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["internal"] } 19 ] 20 } 21 }; 22 23 return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "No matched credentials are found in the platform attached authenticator."); 24 }, "PublicKeyCredential's [[get]] with no matched credentials in a mock local authenticator."); 25 26 promise_test(async t => { 9 27 const privateKeyBase64 = await generatePrivateKeyBase64(); 10 28 const credentialID = await calculateCredentialID(privateKeyBase64); 29 const credentialIDBase64 = btoa(String.fromCharCode.apply(0, credentialID)); 11 30 // Default mock configuration. Tests need to override if they need different configuration. 12 31 if (window.internals) 13 internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: false, acceptAttestation: false, preferred UserhandleBase64: userhandleBase64 } });32 internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: false, acceptAttestation: false, preferredCredentialIdBase64: credentialIDBase64 } }); 14 33 15 promise_test(t => { 16 const options = { 17 publicKey: { 18 challenge: asciiToUint8Array("123456"), 19 allowCredentials: [ 20 { type: "public-key", id: credentialID, transports: ["usb"] }, 21 { type: "public-key", id: credentialID, transports: ["nfc"] }, 22 { type: "public-key", id: credentialID, transports: ["ble"] }, 23 { type: "public-key", id: credentialID, transports: ["internal"] } 24 ] 25 } 26 }; 34 const options = { 35 publicKey: { 36 challenge: asciiToUint8Array("123456"), 37 allowCredentials: [ 38 { type: "public-key", id: Base64URL.parse(testUserhandleBase64) } 39 ] 40 } 41 }; 27 42 28 return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "No matched credentials are found in the platform attached authenticator."); 29 }, "PublicKeyCredential's [[get]] with no matched credentials in a mock local authenticator."); 43 if (window.testRunner) 44 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, testUserEntityBundleBase64); 45 return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "No matched credentials are found in the platform attached authenticator.").then(() => { 46 if (window.testRunner) 47 testRunner.cleanUpKeychain(testRpId, credentialIDBase64); 48 }); 49 }, "PublicKeyCredential's [[get]] with no matched credentials in a mock local authenticator. 2nd"); 30 50 31 promise_test(t => { 32 const options = { 33 publicKey: { 34 challenge: asciiToUint8Array("123456"), 35 allowCredentials: [ 36 { type: "public-key", id: Base64URL.parse(userhandleBase64) } 37 ] 38 } 39 }; 51 promise_test(async t => { 52 const privateKeyBase64 = await generatePrivateKeyBase64(); 53 const credentialID = await calculateCredentialID(privateKeyBase64); 54 const credentialIDBase64 = btoa(String.fromCharCode.apply(0, credentialID)); 55 // Default mock configuration. Tests need to override if they need different configuration. 56 if (window.internals) 57 internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: false, acceptAttestation: false, preferredCredentialIdBase64: credentialIDBase64 } }); 40 58 59 const options = { 60 publicKey: { 61 challenge: asciiToUint8Array("123456") 62 } 63 }; 64 65 if (window.testRunner) 66 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, testUserEntityBundleBase64); 67 return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Couldn't verify user.").then(() => { 41 68 if (window.testRunner) 42 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, userhandleBase64); 43 return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "No matched credentials are found in the platform attached authenticator.").then(() => { 44 if (window.testRunner) 45 testRunner.cleanUpKeychain(testRpId, userhandleBase64); 46 }); 47 }, "PublicKeyCredential's [[get]] with no matched credentials in a mock local authenticator. 2nd"); 69 testRunner.cleanUpKeychain(testRpId, credentialIDBase64); 70 }); 71 }, "PublicKeyCredential's [[get]] without user consent in a mock local authenticator."); 48 72 49 promise_test(t => { 50 const options = { 51 publicKey: { 52 challenge: asciiToUint8Array("123456") 53 } 54 }; 73 promise_test(t => { 74 const options = { 75 publicKey: { 76 challenge: asciiToUint8Array("123456"), 77 allowCredentials: [ 78 { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["usb"] }, 79 { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["nfc"] }, 80 { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["ble"] } 81 ], 82 timeout: 10 83 } 84 }; 55 85 56 if (window.testRunner) 57 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, userhandleBase64); 58 return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Couldn't verify user.").then(() => { 59 if (window.testRunner) 60 testRunner.cleanUpKeychain(testRpId, userhandleBase64); 61 }); 62 }, "PublicKeyCredential's [[get]] without user consent in a mock local authenticator."); 63 64 promise_test(t => { 65 const options = { 66 publicKey: { 67 challenge: asciiToUint8Array("123456"), 68 allowCredentials: [ 69 { type: "public-key", id: credentialID, transports: ["usb"] }, 70 { type: "public-key", id: credentialID, transports: ["nfc"] }, 71 { type: "public-key", id: credentialID, transports: ["ble"] } 72 ], 73 timeout: 10 74 } 75 }; 76 77 return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Operation timed out."); 78 }, "PublicKeyCredential's [[get]] with timeout in a mock local authenticator."); 79 })(); 86 return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Operation timed out."); 87 }, "PublicKeyCredential's [[get]] with timeout in a mock local authenticator."); 80 88 </script> -
trunk/LayoutTests/http/wpt/webauthn/public-key-credential-get-success-local.https.html
r250940 r258293 5 5 <script src="./resources/util.js"></script> 6 6 <script> 7 (async function() { 8 const userhandleBase64 = generateUserhandleBase64(); 7 function checkResult(credential, credentialID, privateKeyBase64) 8 { 9 if (window.testRunner) 10 testRunner.cleanUpKeychain(testRpId, base64encode(credentialID)); 11 12 // Check respond 13 assert_array_equals(Base64URL.parse(credential.id), credentialID); 14 assert_equals(credential.type, 'public-key'); 15 assert_array_equals(new Uint8Array(credential.rawId), credentialID); 16 assert_equals(bytesToASCIIString(credential.response.clientDataJSON), '{"type":"webauthn.get","challenge":"MTIzNDU2","origin":"https://localhost:9443"}'); 17 assert_array_equals(new Uint8Array(credential.response.userHandle), Base64URL.parse(testUserhandleBase64)); 18 assert_not_own_property(credential.getClientExtensionResults(), "appid"); 19 20 // Check authData 21 const authData = decodeAuthData(new Uint8Array(credential.response.authenticatorData)); 22 assert_equals(bytesToHexString(authData.rpIdHash), "49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763"); 23 assert_equals(authData.flags, 5); 24 assert_equals(authData.counter, 0); 25 26 // Check signature 27 return crypto.subtle.importKey("raw", Base64URL.parse(privateKeyBase64).slice(0, 65), { name: "ECDSA", namedCurve: "P-256" }, false, ['verify']).then( publicKey => { 28 return crypto.subtle.digest("sha-256", credential.response.clientDataJSON).then ( hash => { 29 // credential.response.signature is in ASN.1 and WebCrypto expect signatures provides in r|s. 30 return crypto.subtle.verify({name: "ECDSA", hash: "SHA-256"}, publicKey, extractRawSignature(credential.response.signature), concatenateBuffers(credential.response.authenticatorData, hash)).then( verified => { 31 assert_true(verified); 32 assert_not_own_property(credential.getClientExtensionResults(), "appid"); 33 }); 34 }); 35 }); 36 } 37 38 promise_test(async t => { 9 39 const privateKeyBase64 = await generatePrivateKeyBase64(); 10 40 const credentialID = await calculateCredentialID(privateKeyBase64); 41 const credentialIDBase64 = base64encode(credentialID); 11 42 // Default mock configuration. Tests need to override if they need different configuration. 12 43 if (window.internals) 13 internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false, preferred UserhandleBase64: userhandleBase64 } });44 internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false, preferredCredentialIdBase64: credentialIDBase64 } }); 14 45 15 function checkResult(credential) 16 { 17 if (window.testRunner) 18 testRunner.cleanUpKeychain(testRpId, userhandleBase64); 46 const options = { 47 publicKey: { 48 challenge: Base64URL.parse("MTIzNDU2") 49 } 50 }; 19 51 20 // Check respond 21 assert_array_equals(Base64URL.parse(credential.id), credentialID); 22 assert_equals(credential.type, 'public-key'); 23 assert_array_equals(new Uint8Array(credential.rawId), credentialID); 24 assert_equals(bytesToASCIIString(credential.response.clientDataJSON), '{"type":"webauthn.get","challenge":"MTIzNDU2","origin":"https://localhost:9443"}'); 25 assert_array_equals(new Uint8Array(credential.response.userHandle), Base64URL.parse(userhandleBase64)); 26 assert_not_own_property(credential.getClientExtensionResults(), "appid"); 52 if (window.testRunner) 53 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, testUserEntityBundleBase64); 54 return navigator.credentials.get(options).then(credential => { 55 return checkResult(credential, credentialID, privateKeyBase64); 56 }); 57 }, "PublicKeyCredential's [[get]] with minimum options in a mock local authenticator."); 27 58 28 // Check authData 29 const authData = decodeAuthData(new Uint8Array(credential.response.authenticatorData)); 30 assert_equals(bytesToHexString(authData.rpIdHash), "49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763"); 31 assert_equals(authData.flags, 5); 32 assert_equals(authData.counter, 0); 59 promise_test(async t => { 60 const privateKeyBase64 = await generatePrivateKeyBase64(); 61 const credentialID = await calculateCredentialID(privateKeyBase64); 62 const credentialIDBase64 = base64encode(credentialID); 63 // Default mock configuration. Tests need to override if they need different configuration. 64 if (window.internals) 65 internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false, preferredCredentialIdBase64: credentialIDBase64 } }); 33 66 34 // Check signature 35 return crypto.subtle.importKey("raw", Base64URL.parse(privateKeyBase64).slice(0, 65), { name: "ECDSA", namedCurve: "P-256" }, false, ['verify']).then( publicKey => { 36 return crypto.subtle.digest("sha-256", credential.response.clientDataJSON).then ( hash => { 37 // credential.response.signature is in ASN.1 and WebCrypto expect signatures provides in r|s. 38 return crypto.subtle.verify({name: "ECDSA", hash: "SHA-256"}, publicKey, extractRawSignature(credential.response.signature), concatenateBuffers(credential.response.authenticatorData, hash)).then( verified => { 39 assert_true(verified); 40 assert_not_own_property(credential.getClientExtensionResults(), "appid"); 41 }); 42 }); 43 }); 44 } 67 const options = { 68 publicKey: { 69 challenge: Base64URL.parse("MTIzNDU2"), 70 allowCredentials: [ 71 { type: "public-key", id: Base64URL.parse(testUserhandleBase64), transports: ["internal"] }, 72 { type: "public-key", id: credentialID, transports: ["internal"] } 73 ] 74 } 75 }; 45 76 46 promise_test(t => { 47 const options = { 48 publicKey: { 49 challenge: Base64URL.parse("MTIzNDU2") 50 } 51 }; 52 53 if (window.testRunner) 54 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, userhandleBase64); 55 return navigator.credentials.get(options).then(credential => { 56 return checkResult(credential); 57 }); 58 }, "PublicKeyCredential's [[get]] with minimum options in a mock local authenticator."); 59 60 promise_test(t => { 61 const options = { 62 publicKey: { 63 challenge: Base64URL.parse("MTIzNDU2"), 64 allowCredentials: [ 65 { type: "public-key", id: Base64URL.parse(userhandleBase64), transports: ["internal"] }, 66 { type: "public-key", id: credentialID, transports: ["internal"] } 67 ] 68 } 69 }; 70 71 if (window.testRunner) 72 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, userhandleBase64); 73 return navigator.credentials.get(options).then(credential => { 74 return checkResult(credential); 75 }); 76 }, "PublicKeyCredential's [[get]] with matched allow credentials in a mock local authenticator."); 77 })(); 77 if (window.testRunner) 78 testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, testUserEntityBundleBase64); 79 return navigator.credentials.get(options).then(credential => { 80 return checkResult(credential, credentialID, privateKeyBase64); 81 }); 82 }, "PublicKeyCredential's [[get]] with matched allow credentials in a mock local authenticator."); 78 83 </script> -
trunk/LayoutTests/http/wpt/webauthn/resources/util.js
r254356 r258293 6 6 const testRpId = "localhost"; 7 7 const testUserhandleBase64 = "AAECAwQFBgcICQ=="; 8 const testUserEntityBundleBase64 = "omJpZEoAAQIDBAUGBwgJZG5hbWVwQUFFQ0F3UUZCZ2NJQ1E9PQ=="; 8 9 const testAttestationCertificateBase64 = 9 10 "MIIB6jCCAZCgAwIBAgIGAWHAxcjvMAoGCCqGSM49BAMCMFMxJzAlBgNVBAMMHkJh" + … … 450 451 function generateUserhandleBase64() 451 452 { 452 let buffer = new Uint8Array( 16);453 let buffer = new Uint8Array(8); 453 454 crypto.getRandomValues(buffer); 454 455 return btoa(String.fromCharCode.apply(0, buffer)); … … 487 488 return new Uint8Array(await crypto.subtle.digest("sha-1", publicKey)); 488 489 } 490 491 function base64encode(binary) 492 { 493 const unit8Array = new Uint8Array(binary); 494 return btoa(String.fromCharCode.apply(0, unit8Array)); 495 } -
trunk/Source/WebCore/ChangeLog
r258287 r258293 1 2020-03-11 Jiewen Tan <jiewen_tan@apple.com> 2 3 [WebAuthn] Formalize the Keychain schema 4 https://bugs.webkit.org/show_bug.cgi?id=183533 5 <rdar://problem/43347926> 6 7 Reviewed by Brent Fulgham. 8 9 Covered by new test contents within existing files. 10 11 * Modules/webauthn/AuthenticatorAssertionResponse.cpp: 12 (WebCore::AuthenticatorAssertionResponse::create): 13 (WebCore::AuthenticatorAssertionResponse::AuthenticatorAssertionResponse): 14 * Modules/webauthn/AuthenticatorAssertionResponse.h: 15 Modifies the constructors to accept userEntity.name. 16 17 * Modules/webauthn/cbor/CBORValue.h: 18 Adds a FIXME. 19 20 * testing/MockWebAuthenticationConfiguration.h: 21 (WebCore::MockWebAuthenticationConfiguration::LocalConfiguration::encode const): 22 (WebCore::MockWebAuthenticationConfiguration::LocalConfiguration::decode): 23 * testing/MockWebAuthenticationConfiguration.idl: 24 Modifies the test infra to use Credential ID as the unique identifier for a credential instead of 25 the original combination of RP ID and user handle. 26 1 27 2020-03-11 Daniel Bates <dabates@apple.com> 2 28 -
trunk/Source/WebCore/Modules/webauthn/AuthenticatorAssertionResponse.cpp
r257269 r258293 49 49 } 50 50 51 Ref<AuthenticatorAssertionResponse> AuthenticatorAssertionResponse::create(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& userHandle, S ecAccessControlRef accessControl)51 Ref<AuthenticatorAssertionResponse> AuthenticatorAssertionResponse::create(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& userHandle, String&& name, SecAccessControlRef accessControl) 52 52 { 53 return adoptRef(*new AuthenticatorAssertionResponse(WTFMove(rawId), WTFMove(userHandle), accessControl));53 return adoptRef(*new AuthenticatorAssertionResponse(WTFMove(rawId), WTFMove(userHandle), WTFMove(name), accessControl)); 54 54 } 55 55 … … 67 67 } 68 68 69 AuthenticatorAssertionResponse::AuthenticatorAssertionResponse(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& userHandle, S ecAccessControlRef accessControl)69 AuthenticatorAssertionResponse::AuthenticatorAssertionResponse(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& userHandle, String&& name, SecAccessControlRef accessControl) 70 70 : AuthenticatorResponse(WTFMove(rawId)) 71 71 , m_userHandle(WTFMove(userHandle)) 72 , m_name(WTFMove(name)) 72 73 , m_accessControl(accessControl) 73 74 { -
trunk/Source/WebCore/Modules/webauthn/AuthenticatorAssertionResponse.h
r257269 r258293 38 38 static Ref<AuthenticatorAssertionResponse> create(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& authenticatorData, Ref<ArrayBuffer>&& signature, RefPtr<ArrayBuffer>&& userHandle, Optional<AuthenticationExtensionsClientOutputs>&&); 39 39 WEBCORE_EXPORT static Ref<AuthenticatorAssertionResponse> create(const Vector<uint8_t>& rawId, const Vector<uint8_t>& authenticatorData, const Vector<uint8_t>& signature, const Vector<uint8_t>& userHandle); 40 WEBCORE_EXPORT static Ref<AuthenticatorAssertionResponse> create(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& userHandle, S ecAccessControlRef);40 WEBCORE_EXPORT static Ref<AuthenticatorAssertionResponse> create(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& userHandle, String&& name, SecAccessControlRef); 41 41 virtual ~AuthenticatorAssertionResponse() = default; 42 42 … … 57 57 private: 58 58 AuthenticatorAssertionResponse(Ref<ArrayBuffer>&&, Ref<ArrayBuffer>&&, Ref<ArrayBuffer>&&, RefPtr<ArrayBuffer>&&); 59 AuthenticatorAssertionResponse(Ref<ArrayBuffer>&&, Ref<ArrayBuffer>&&, S ecAccessControlRef);59 AuthenticatorAssertionResponse(Ref<ArrayBuffer>&&, Ref<ArrayBuffer>&&, String&&, SecAccessControlRef); 60 60 61 61 Type type() const final { return Type::Assertion; } -
trunk/Source/WebCore/Modules/webauthn/cbor/CBORValue.h
r237983 r258293 165 165 bool isBool() const { return isSimple() && (m_simpleValue == SimpleValue::TrueValue || m_simpleValue == SimpleValue::FalseValue); } 166 166 167 // FIXME(183535): Considering adding && getter for better performance. 167 168 // These will all fatally assert if the type doesn't match. 168 169 SimpleValue getSimpleValue() const; -
trunk/Source/WebCore/testing/MockWebAuthenticationConfiguration.h
r254439 r258293 68 68 String userCertificateBase64; 69 69 String intermediateCACertificateBase64; 70 String preferred UserhandleBase64;70 String preferredCredentialIdBase64; 71 71 72 72 template<class Encoder> void encode(Encoder&) const; … … 113 113 void MockWebAuthenticationConfiguration::LocalConfiguration::encode(Encoder& encoder) const 114 114 { 115 encoder << acceptAuthentication << acceptAttestation << privateKeyBase64 << userCertificateBase64 << intermediateCACertificateBase64 << preferred UserhandleBase64;115 encoder << acceptAuthentication << acceptAttestation << privateKeyBase64 << userCertificateBase64 << intermediateCACertificateBase64 << preferredCredentialIdBase64; 116 116 } 117 117 … … 151 151 result.intermediateCACertificateBase64 = WTFMove(*intermediateCACertificateBase64); 152 152 153 Optional<String> preferred UserhandleBase64;154 decoder >> preferred UserhandleBase64;155 if (!preferred UserhandleBase64)156 return WTF::nullopt; 157 result.preferred UserhandleBase64 = WTFMove(*preferredUserhandleBase64);153 Optional<String> preferredCredentialIdBase64; 154 decoder >> preferredCredentialIdBase64; 155 if (!preferredCredentialIdBase64) 156 return WTF::nullopt; 157 result.preferredCredentialIdBase64 = WTFMove(*preferredCredentialIdBase64); 158 158 159 159 return result; -
trunk/Source/WebCore/testing/MockWebAuthenticationConfiguration.idl
r254439 r258293 77 77 DOMString userCertificateBase64; 78 78 DOMString intermediateCACertificateBase64; 79 DOMString preferred UserhandleBase64;79 DOMString preferredCredentialIdBase64; 80 80 }; 81 81 -
trunk/Source/WebKit/ChangeLog
r258289 r258293 1 2020-03-11 Jiewen Tan <jiewen_tan@apple.com> 2 3 [WebAuthn] Formalize the Keychain schema 4 https://bugs.webkit.org/show_bug.cgi?id=183533 5 <rdar://problem/43347926> 6 7 Reviewed by Brent Fulgham. 8 9 This patch formalizes the schema for the Keychain as follows: 10 kSecAttrLabel: RP ID 11 kSecAttrApplicationLabel: Credential ID (auto-gen by Keychain) 12 kSecAttrApplicationTag: { "id": UserEntity.id, "name": UserEntity.name } (CBOR encoded) 13 Noted, the vale of kSecAttrApplicationLabel is automatically generated by the Keychain, which is a SHA-1 hash of 14 the public key. 15 16 According to the Step 7. from https://www.w3.org/TR/webauthn/#op-make-cred, the following fields are mandatory 17 1. rpId (rpEntity.id); 18 2. userHandle (userEntity.id), this is required for authenticators that support resident keys; 19 3. credentialId. 20 21 Some other optional fields are: 22 (from https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity) 23 1. rpEntity.name; 24 2. rpEnitty.icon; 25 (from https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialuserentity) 26 3. userEntity.displayName; 27 4. userEntity.name; 28 5. userEntity.icon; 29 (from https://www.w3.org/TR/webauthn/#sign-counter) 30 6. signature counter. 31 32 Among the six possible fields, only 4. is chosen to store. Here is why: 33 For rpEntity, rpEntity.id which is either the domain or the eTLD + 1 of the website is 34 sufficient enough to either classify the credential or serving the UI. Also, this is the only 35 trustworthy information that the UserAgent produce. Others could potentially be used by 36 malicious websites for attacking the Keychain or spoofing/phishing users when being displayed 37 in the UI. Also, rpEnitty.icon is a URL to the website's favicon, which if not implemented 38 correctly can be used for tracking. 39 40 For userEntity, userEntity.name is the human readable version of userEntity.id, and therefore 41 is chosen to store such that later on WebKit can pass it to UI client to help users disambiguate 42 different credentials. And it is necessary as userEntity.id is not guaranteed to be human 43 readable. Others are abandoned for the very same reason as above. 44 45 We hard code a zero value for 'signature counter'. While this is a theoretically interesting 46 technique for a RP to detect private key cloning, it is unlikely to be useful in practice. 47 We store the private keys in our SEP. This counter would only be a meaningful protection if 48 adversaries were able to extract private key data from the SEP without Apple noticing, but 49 were not able to manipulate this counter to fool the RP. 50 51 In terms of the schema, 52 1) RP ID is needed to query all credentials related, and therefore it needs a column and kSecAttrLabel 53 is supposed to be human readable; 54 2) kSecAttrApplicationLabel is the auto generated programmatical identifier for a SecItem, and 55 therefore is suitable as the credential ID. Given the input to the SHA-1 is generated by us, and 56 it is only needed to be powerful enough to be unique across the keychain within a device, and potentially 57 to be unique across different other credential ID for the same user. The SHA-1 collision attack 58 doesn't seem valid here. 59 3) kSecAttrApplicationTag is the only other column Keychain allows applications to modify. Therefore, 60 UserEntity.id and UserEntity.name is bundled to use this slot. The reason to use CBOR here is that 61 it is more friendly then JSON to encode binaries, and it is used widely in WebAuthn. 62 63 * UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.h: 64 * UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm: 65 (WebKit::LocalAuthenticatorInternal::toArrayBuffer): 66 (WebKit::LocalAuthenticatorInternal::getExistingCredentials): 67 (WebKit::LocalAuthenticator::makeCredential): 68 (WebKit::LocalAuthenticator::continueMakeCredentialAfterUserVerification): 69 (WebKit::LocalAuthenticator::continueMakeCredentialAfterAttested): 70 (WebKit::LocalAuthenticator::getAssertion): 71 (WebKit::LocalAuthenticator::deleteDuplicateCredential const): 72 * UIProcess/WebAuthentication/Mock/MockLocalConnection.mm: 73 (WebKit::MockLocalConnection::filterResponses const): 74 1 75 2020-03-11 Per Arne Vollan <pvollan@apple.com> 2 76 -
trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.h
r258020 r258293 68 68 69 69 void receiveException(WebCore::ExceptionData&&, WebAuthenticationStatus = WebAuthenticationStatus::LAError) const; 70 void deleteDuplicateCredential() const; 70 71 71 72 State m_state { State::Init }; 72 73 UniqueRef<LocalConnection> m_connection; 74 // FIXME(183534): Combine these two. 73 75 HashSet<Ref<WebCore::AuthenticatorAssertionResponse>> m_assertionResponses; 76 Vector<Ref<WebCore::AuthenticatorAssertionResponse>> m_existingCredentials; 74 77 }; 75 78 -
trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm
r258020 r258293 32 32 #import <WebCore/AuthenticatorAssertionResponse.h> 33 33 #import <WebCore/AuthenticatorAttestationResponse.h> 34 #import <WebCore/CBORReader.h> 34 35 #import <WebCore/CBORWriter.h> 35 36 #import <WebCore/ExceptionData.h> … … 48 49 namespace WebKit { 49 50 using namespace WebCore; 51 using CBOR = cbor::CBORValue; 50 52 51 53 namespace LocalAuthenticatorInternal { … … 56 58 // Credential ID is currently SHA-1 of the corresponding public key. 57 59 const uint16_t credentialIdLength = 20; 60 const char* const userEntityIdKey = "id"; 61 const char* const userEntityNameKey = "name"; 62 const uint64_t counter = 0; 58 63 59 64 static inline bool emptyTransportsOrContain(const Vector<AuthenticatorTransport>& transports, AuthenticatorTransport target) … … 62 67 } 63 68 69 // FIXME(183534): Find a better way of comparing credential id. Doing it with array seems fine given the list should be small. 64 70 static inline HashSet<String> produceHashSet(const Vector<PublicKeyCredentialDescriptor>& credentialDescriptors) 65 71 { … … 99 105 } 100 106 107 static inline Ref<ArrayBuffer> toArrayBuffer(const Vector<uint8_t>& data) 108 { 109 return ArrayBuffer::create(data.data(), data.size()); 110 } 111 101 112 // FIXME(<rdar://problem/60108131>): Remove this whitelist once testing is complete. 102 113 static const HashSet<String>& whitelistedRpId() … … 110 121 } 111 122 123 static Optional<Vector<Ref<AuthenticatorAssertionResponse>>> getExistingCredentials(const String& rpId) 124 { 125 // Search Keychain for existing credential matched the RP ID. 126 NSDictionary *query = @{ 127 (id)kSecClass: (id)kSecClassKey, 128 (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate, 129 (id)kSecAttrLabel: rpId, 130 (id)kSecReturnAttributes: @YES, 131 (id)kSecMatchLimit: (id)kSecMatchLimitAll, 132 #if HAVE(DATA_PROTECTION_KEYCHAIN) 133 (id)kSecUseDataProtectionKeychain: @YES 134 #else 135 (id)kSecAttrNoLegacy: @YES 136 #endif 137 }; 138 CFTypeRef attributesArrayRef = nullptr; 139 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesArrayRef); 140 if (status && status != errSecItemNotFound) 141 return WTF::nullopt; 142 auto retainAttributesArray = adoptCF(attributesArrayRef); 143 NSArray *nsAttributesArray = (NSArray *)attributesArrayRef; 144 145 Vector<Ref<AuthenticatorAssertionResponse>> result; 146 result.reserveInitialCapacity(nsAttributesArray.count); 147 for (NSDictionary *attributes in nsAttributesArray) { 148 auto decodedResponse = cbor::CBORReader::read(toVector(attributes[(id)kSecAttrApplicationTag])); 149 if (!decodedResponse || !decodedResponse->isMap()) { 150 ASSERT_NOT_REACHED(); 151 return WTF::nullopt; 152 } 153 auto& responseMap = decodedResponse->getMap(); 154 155 auto it = responseMap.find(CBOR(userEntityIdKey)); 156 if (it == responseMap.end() || !it->second.isByteString()) { 157 ASSERT_NOT_REACHED(); 158 return WTF::nullopt; 159 } 160 auto& userHandle = it->second.getByteString(); 161 162 it = responseMap.find(CBOR(userEntityNameKey)); 163 if (it == responseMap.end() || !it->second.isString()) { 164 ASSERT_NOT_REACHED(); 165 return WTF::nullopt; 166 } 167 auto& username = it->second.getString(); 168 169 result.uncheckedAppend(AuthenticatorAssertionResponse::create(toArrayBuffer(attributes[(id)kSecAttrApplicationLabel]), toArrayBuffer(userHandle), String(username), (__bridge SecAccessControlRef)attributes[(id)kSecAttrAccessControl])); 170 } 171 return result; 172 } 173 112 174 } // LocalAuthenticatorInternal 113 175 … … 128 190 // Skip Step 9 as extensions are not supported yet. 129 191 // Step 8 is implicitly captured by all UnknownError exception receiveResponds. 192 // Skip Step 10 as counter is constantly 0. 130 193 // Step 2. 131 194 if (notFound == creationOptions.pubKeyCredParams.findMatching([] (auto& pubKeyCredParam) { … … 137 200 138 201 // Step 3. 202 auto existingCredentials = getExistingCredentials(creationOptions.rp.id); 203 if (!existingCredentials) { 204 receiveException({ UnknownError, makeString("Couldn't get existing credentials") }); 205 return; 206 } 207 m_existingCredentials = WTFMove(*existingCredentials); 208 139 209 auto excludeCredentialIds = produceHashSet(creationOptions.excludeCredentials); 140 210 if (!excludeCredentialIds.isEmpty()) { 141 // Search Keychain for the RP ID. 142 NSDictionary *query = @{ 143 (id)kSecClass: (id)kSecClassKey, 144 (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate, 145 (id)kSecAttrLabel: creationOptions.rp.id, 146 (id)kSecReturnAttributes: @YES, 147 (id)kSecMatchLimit: (id)kSecMatchLimitAll, 148 #if HAVE(DATA_PROTECTION_KEYCHAIN) 149 (id)kSecUseDataProtectionKeychain: @YES 150 #else 151 (id)kSecAttrNoLegacy: @YES 152 #endif 153 }; 154 CFTypeRef attributesArrayRef = nullptr; 155 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesArrayRef); 156 if (status && status != errSecItemNotFound) { 157 receiveException({ UnknownError, makeString("Couldn't query Keychain: ", status) }); 158 return; 159 } 160 auto retainAttributesArray = adoptCF(attributesArrayRef); 161 162 // FIXME: Need to obtain user consent and then return different error according to the result. 163 for (NSDictionary *nsAttributes in (NSArray *)attributesArrayRef) { 164 NSData *nsCredentialId = nsAttributes[(id)kSecAttrApplicationLabel]; 165 if (excludeCredentialIds.contains(String(reinterpret_cast<const char*>(nsCredentialId.bytes), nsCredentialId.length))) { 166 receiveException({ NotAllowedError, "At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator."_s }, WebAuthenticationStatus::LAExcludeCredentialsMatched); 167 return; 168 } 211 if (notFound != m_existingCredentials.findMatching([&excludeCredentialIds] (auto& credential) { 212 auto* rawId = credential->rawId(); 213 ASSERT(rawId); 214 return excludeCredentialIds.contains(String(reinterpret_cast<const char*>(rawId->data()), rawId->byteLength())); 215 })) { 216 receiveException({ NotAllowedError, "At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator."_s }, WebAuthenticationStatus::LAExcludeCredentialsMatched); 217 return; 169 218 } 170 219 } … … 229 278 } 230 279 231 // FIXME(183533): A single kSecClassKey item couldn't store all meta data. The following schema is a tentative solution 232 // to accommodate the most important meta data, i.e. RP ID, Credential ID, and userhandle. 280 // Here is the keychain schema. 233 281 // kSecAttrLabel: RP ID 234 282 // kSecAttrApplicationLabel: Credential ID (auto-gen by Keychain) 235 // kSecAttrApplicationTag: userhandle283 // kSecAttrApplicationTag: { "id": UserEntity.id, "name": UserEntity.name } (CBOR encoded) 236 284 // Noted, the vale of kSecAttrApplicationLabel is automatically generated by the Keychain, which is a SHA-1 hash of 237 // the public key. We borrow it directly for now to workaround the stated limitations.285 // the public key. 238 286 const auto& secAttrLabel = creationOptions.rp.id; 239 auto secAttrApplicationTag = toNSData(creationOptions.user.idVector); 240 241 // Step 7.5. 242 // Failures after this point could block users' accounts forever. Should we follow the spec? 243 NSDictionary* deleteQuery = @{ 244 (id)kSecClass: (id)kSecClassKey, 245 (id)kSecAttrLabel: secAttrLabel, 246 (id)kSecAttrApplicationTag: secAttrApplicationTag.get(), 247 #if HAVE(DATA_PROTECTION_KEYCHAIN) 248 (id)kSecUseDataProtectionKeychain: @YES 249 #else 250 (id)kSecAttrNoLegacy: @YES 251 #endif 252 }; 253 OSStatus status = SecItemDelete((__bridge CFDictionaryRef)deleteQuery); 254 if (status && status != errSecItemNotFound) { 255 receiveException({ UnknownError, makeString("Couldn't delete older credential: ", status) }); 256 return; 257 } 258 259 // Step 7.1-7.4. 287 288 cbor::CBORValue::MapValue userEntityMap; 289 userEntityMap[cbor::CBORValue(userEntityIdKey)] = cbor::CBORValue(creationOptions.user.idVector); 290 userEntityMap[cbor::CBORValue(userEntityNameKey)] = cbor::CBORValue(creationOptions.user.name); 291 auto userEntity = cbor::CBORWriter::write(cbor::CBORValue(WTFMove(userEntityMap))); 292 ASSERT(userEntity); 293 auto secAttrApplicationTag = toNSData(*userEntity); 294 295 // Step 7. 260 296 // The above-to-create private key will be inserted into keychain while using SEP. 261 297 auto privateKey = m_connection->createCredentialPrivateKey(context, accessControlRef, secAttrLabel, secAttrApplicationTag.get()); … … 265 301 } 266 302 303 RetainPtr<CFDataRef> publicKeyDataRef; 304 { 305 auto publicKey = adoptCF(SecKeyCopyPublicKey(privateKey.get())); 306 CFErrorRef errorRef = nullptr; 307 publicKeyDataRef = adoptCF(SecKeyCopyExternalRepresentation(publicKey.get(), &errorRef)); 308 auto retainError = adoptCF(errorRef); 309 if (errorRef) { 310 receiveException({ UnknownError, makeString("Couldn't export the public key: ", String(((NSError*)errorRef).localizedDescription)) }); 311 return; 312 } 313 ASSERT(((NSData *)publicKeyDataRef.get()).length == (1 + 2 * ES256FieldElementLength)); // 04 | X | Y 314 } 315 NSData *nsPublicKeyData = (NSData *)publicKeyDataRef.get(); 316 317 // Query credentialId in the keychain could be racy as it is the only unique identifier 318 // of the key item. Instead we calculate that, and examine its equaity in DEBUG build. 267 319 Vector<uint8_t> credentialId; 268 320 { 321 auto digest = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_1); 322 digest->addBytes(nsPublicKeyData.bytes, nsPublicKeyData.length); 323 credentialId = digest->computeHash(); 324 325 #ifndef NDEBUG 269 326 NSDictionary *credentialIdQuery = @{ 270 327 (id)kSecClass: (id)kSecClassKey, 271 328 (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate, 272 329 (id)kSecAttrLabel: secAttrLabel, 273 (id)kSecAttrApplicationTag: secAttrApplicationTag.get(), 274 (id)kSecReturnAttributes: @YES, 330 (id)kSecAttrApplicationLabel: toNSData(credentialId).get(), 275 331 #if HAVE(DATA_PROTECTION_KEYCHAIN) 276 332 (id)kSecUseDataProtectionKeychain: @YES … … 279 335 #endif 280 336 }; 281 CFTypeRef attributesRef = nullptr; 282 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)credentialIdQuery, &attributesRef); 283 if (status) { 284 receiveException({ UnknownError, makeString("Couldn't get Credential ID: ", status) }); 285 return; 286 } 287 auto retainAttributes = adoptCF(attributesRef); 288 289 NSDictionary *nsAttributes = (NSDictionary *)attributesRef; 290 credentialId = toVector(nsAttributes[(id)kSecAttrApplicationLabel]); 291 } 292 293 // Step 10. 294 // FIXME(183533): store the counter. 295 uint32_t counter = 0; 337 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)credentialIdQuery, nullptr); 338 ASSERT(!status); 339 #endif // NDEBUG 340 } 296 341 297 342 // Step 11. https://www.w3.org/TR/webauthn/#attested-credential-data … … 299 344 Vector<uint8_t> cosePublicKey; 300 345 { 301 RetainPtr<CFDataRef> publicKeyDataRef;302 {303 auto publicKey = adoptCF(SecKeyCopyPublicKey(privateKey.get()));304 CFErrorRef errorRef = nullptr;305 publicKeyDataRef = adoptCF(SecKeyCopyExternalRepresentation(publicKey.get(), &errorRef));306 auto retainError = adoptCF(errorRef);307 if (errorRef) {308 receiveException({ UnknownError, makeString("Couldn't export the public key: ", String(((NSError*)errorRef).localizedDescription)) });309 return;310 }311 ASSERT(((NSData *)publicKeyDataRef.get()).length == (1 + 2 * ES256FieldElementLength)); // 04 | X | Y312 }313 314 346 // COSE Encoding 315 347 Vector<uint8_t> x(ES256FieldElementLength); 316 [ (NSData *)publicKeyDataRef.get()getBytes: x.data() range:NSMakeRange(1, ES256FieldElementLength)];348 [nsPublicKeyData getBytes: x.data() range:NSMakeRange(1, ES256FieldElementLength)]; 317 349 Vector<uint8_t> y(ES256FieldElementLength); 318 [ (NSData *)publicKeyDataRef.get()getBytes: y.data() range:NSMakeRange(1 + ES256FieldElementLength, ES256FieldElementLength)];350 [nsPublicKeyData getBytes: y.data() range:NSMakeRange(1 + ES256FieldElementLength, ES256FieldElementLength)]; 319 351 cosePublicKey = encodeES256PublicKeyAsCBOR(WTFMove(x), WTFMove(y)); 320 352 } … … 327 359 // Skip Apple Attestation for none attestation, and non whitelisted RP ID for now. 328 360 if (creationOptions.attestation == AttestationConveyancePreference::None || !whitelistedRpId().contains(creationOptions.rp.id)) { 361 deleteDuplicateCredential(); 362 329 363 auto attestationObject = buildAttestationObject(WTFMove(authData), "", { }, AttestationConveyancePreference::None); 330 364 receiveRespond(AuthenticatorAttestationResponse::create(credentialId, attestationObject)); … … 371 405 auto attestationObject = buildAttestationObject(WTFMove(authData), "apple", WTFMove(attestationStatementMap), creationOptions.attestation); 372 406 407 deleteDuplicateCredential(); 373 408 receiveRespond(AuthenticatorAttestationResponse::create(credentialId, attestationObject)); 374 409 } … … 384 419 // Skip Step 2 as requireUserVerification is enforced. 385 420 // Skip Step 8 as extensions are not supported yet. 421 // Skip Step 9 as counter is constantly 0. 386 422 // Step 12 is implicitly captured by all UnknownError exception callbacks. 387 423 // Step 3-5. Unlike the spec, if an allow list is provided and there is no intersection between existing ones and the allow list, we always return NotAllowedError. … … 393 429 394 430 // Search Keychain for the RP ID. 395 NSDictionary *query = @{ 396 (id)kSecClass: (id)kSecClassKey, 397 (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate, 398 (id)kSecAttrLabel: requestOptions.rpId, 399 (id)kSecReturnAttributes: @YES, 400 (id)kSecMatchLimit: (id)kSecMatchLimitAll, 401 #if HAVE(DATA_PROTECTION_KEYCHAIN) 402 (id)kSecUseDataProtectionKeychain: @YES 403 #else 404 (id)kSecAttrNoLegacy: @YES 405 #endif 406 }; 407 CFTypeRef attributesArrayRef = nullptr; 408 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesArrayRef); 409 if (status && status != errSecItemNotFound) { 410 receiveException({ UnknownError, makeString("Couldn't query Keychain: ", status) }); 411 return; 412 } 413 auto retainAttributesArray = adoptCF(attributesArrayRef); 414 415 NSArray *intersectedCredentialsAttributes = nil; 416 if (requestOptions.allowCredentials.isEmpty()) 417 intersectedCredentialsAttributes = (NSArray *)attributesArrayRef; 418 else { 419 NSMutableArray *result = [NSMutableArray arrayWithCapacity:allowCredentialIds.size()]; 420 for (NSDictionary *nsAttributes in (NSArray *)attributesArrayRef) { 421 NSData *nsCredentialId = nsAttributes[(id)kSecAttrApplicationLabel]; 422 if (allowCredentialIds.contains(String(reinterpret_cast<const char*>(nsCredentialId.bytes), nsCredentialId.length))) 423 [result addObject:nsAttributes]; 424 } 425 intersectedCredentialsAttributes = result; 426 } 427 if (!intersectedCredentialsAttributes.count) { 431 auto existingCredentials = getExistingCredentials(requestOptions.rpId); 432 if (!existingCredentials) { 433 receiveException({ UnknownError, makeString("Couldn't get existing credentials") }); 434 return; 435 } 436 m_existingCredentials = WTFMove(*existingCredentials); 437 438 for (auto& credential : m_existingCredentials) { 439 if (allowCredentialIds.isEmpty()) { 440 auto addResult = m_assertionResponses.add(credential.copyRef()); 441 ASSERT_UNUSED(addResult, addResult.isNewEntry); 442 continue; 443 } 444 445 auto* rawId = credential->rawId(); 446 if (allowCredentialIds.contains(String(reinterpret_cast<const char*>(rawId->data()), rawId->byteLength()))) { 447 auto addResult = m_assertionResponses.add(credential.copyRef()); 448 ASSERT_UNUSED(addResult, addResult.isNewEntry); 449 } 450 } 451 if (m_assertionResponses.isEmpty()) { 428 452 receiveException({ NotAllowedError, "No matched credentials are found in the platform attached authenticator."_s }, WebAuthenticationStatus::LANoCredential); 429 453 return; … … 431 455 432 456 // Step 6-7. User consent is implicitly acquired by selecting responses. 433 for (NSDictionary *attribute : intersectedCredentialsAttributes) {434 auto addResult = m_assertionResponses.add(AuthenticatorAssertionResponse::create(435 toArrayBuffer(attribute[(id)kSecAttrApplicationLabel]),436 toArrayBuffer(attribute[(id)kSecAttrApplicationTag]),437 (__bridge SecAccessControlRef)attribute[(id)kSecAttrAccessControl]));438 ASSERT_UNUSED(addResult, addResult.isNewEntry);439 }440 457 m_connection->filterResponses(m_assertionResponses); 441 458 … … 485 502 } 486 503 487 // Step 9-10. 488 // FIXME(183533): Due to the stated Keychain limitations, we can't save the counter value. 489 // Therefore, it is always zero. 490 uint32_t counter = 0; 504 // Step 10. 491 505 auto authData = buildAuthData(WTF::get<PublicKeyCredentialRequestOptions>(requestData().options).rpId, getAssertionFlags, counter, { }); 492 506 … … 542 556 } 543 557 558 void LocalAuthenticator::deleteDuplicateCredential() const 559 { 560 using namespace LocalAuthenticatorInternal; 561 562 auto& creationOptions = WTF::get<PublicKeyCredentialCreationOptions>(requestData().options); 563 m_existingCredentials.findMatching([creationOptions] (auto& credential) { 564 auto* userHandle = credential->userHandle(); 565 ASSERT(userHandle); 566 if (memcmp(userHandle->data(), creationOptions.user.idVector.data(), userHandle->byteLength())) 567 return false; 568 569 NSDictionary* deleteQuery = @{ 570 (id)kSecClass: (id)kSecClassKey, 571 (id)kSecAttrApplicationLabel: toNSData(credential->rawId()).get(), 572 #if HAVE(DATA_PROTECTION_KEYCHAIN) 573 (id)kSecUseDataProtectionKeychain: @YES 574 #else 575 (id)kSecAttrNoLegacy: @YES 576 #endif 577 }; 578 OSStatus status = SecItemDelete((__bridge CFDictionaryRef)deleteQuery); 579 if (status && status != errSecItemNotFound) 580 LOG_ERROR(makeString("Couldn't delete older credential: "_s, status).utf8().data()); 581 return true; 582 }); 583 } 584 544 585 } // namespace WebKit 545 586 -
trunk/Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalConnection.mm
r257877 r258293 116 116 void MockLocalConnection::filterResponses(HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>& responses) const 117 117 { 118 const auto& preferred UserhandleBase64 = m_configuration.local->preferredUserhandleBase64;119 if (preferred UserhandleBase64.isEmpty())118 const auto& preferredCredentialIdBase64 = m_configuration.local->preferredCredentialIdBase64; 119 if (preferredCredentialIdBase64.isEmpty()) 120 120 return; 121 121 122 122 auto itr = responses.begin(); 123 123 for (; itr != responses.end(); ++itr) { 124 auto* userHandle = itr->get().userHandle();125 ASSERT( userHandle);126 auto userhandleBase64 = base64Encode(userHandle->data(), userHandle->byteLength());127 if ( userhandleBase64 == preferredUserhandleBase64)124 auto* rawId = itr->get().rawId(); 125 ASSERT(rawId); 126 auto rawIdBase64 = base64Encode(rawId->data(), rawId->byteLength()); 127 if (rawIdBase64 == preferredCredentialIdBase64) 128 128 break; 129 129 } -
trunk/Tools/ChangeLog
r258286 r258293 1 2020-03-11 Jiewen Tan <jiewen_tan@apple.com> 2 3 [WebAuthn] Formalize the Keychain schema 4 https://bugs.webkit.org/show_bug.cgi?id=183533 5 <rdar://problem/43347926> 6 7 Reviewed by Brent Fulgham. 8 9 Modifies the test infra to use Credential ID as the unique identifier for a credential instead of 10 the original combination of RP ID and user handle. 11 12 * WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl: 13 * WebKitTestRunner/InjectedBundle/TestRunner.cpp: 14 (WTR::TestRunner::cleanUpKeychain): 15 (WTR::TestRunner::keyExistsInKeychain): 16 * WebKitTestRunner/InjectedBundle/TestRunner.h: 17 * WebKitTestRunner/TestController.h: 18 * WebKitTestRunner/TestInvocation.cpp: 19 (WTR::TestInvocation::didReceiveSynchronousMessageFromInjectedBundle): 20 * WebKitTestRunner/cocoa/TestControllerCocoa.mm: 21 (WTR::TestController::cleanUpKeychain): 22 (WTR::TestController::keyExistsInKeychain): 23 1 24 2020-03-11 Keith Miller <keith_miller@apple.com> 2 25 -
trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm
r257954 r258293 63 63 "PH76c0+WFOzZKslPyyFse4goGIW2R7k9VHLPEZl5nfnBgEVFh5zev+/xpHQIvuq6" 64 64 "RQ=="; 65 static String testUser handleBase64 = "AAECAwQFBgcICQ==";65 static String testUserEntityBundleBase64 = "omJpZEoAAQIDBAUGBwgJZG5hbWVwQUFFQ0F3UUZCZ2NJQ1E9PQ=="; 66 66 67 67 @interface TestWebAuthenticationPanelDelegate : NSObject <_WKWebAuthenticationPanelDelegate> … … 1215 1215 [webView setUIDelegate:delegate.get()]; 1216 1216 1217 ASSERT_TRUE(addKeyToKeychain(testES256PrivateKeyBase64, "", testUser handleBase64));1217 ASSERT_TRUE(addKeyToKeychain(testES256PrivateKeyBase64, "", testUserEntityBundleBase64)); 1218 1218 [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]]; 1219 1219 Util::run(&webAuthenticationPanelUpdateLAExcludeCredentialsMatched); … … 1306 1306 [webView setUIDelegate:delegate.get()]; 1307 1307 1308 ASSERT_TRUE(addKeyToKeychain(testES256PrivateKeyBase64, "", testUser handleBase64));1308 ASSERT_TRUE(addKeyToKeychain(testES256PrivateKeyBase64, "", testUserEntityBundleBase64)); 1309 1309 [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]]; 1310 1310 [webView waitForMessage:@"Succeeded!"]; -
trunk/Tools/WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl
r257726 r258293 405 405 // WebAuthn 406 406 void addTestKeyToKeychain(DOMString privateKeyBase64, DOMString attrLabel, DOMString applicationTagBase64); 407 void cleanUpKeychain(DOMString attrLabel, optional DOMString application TagBase64);408 boolean keyExistsInKeychain(DOMString attrLabel, DOMString application TagBase64);407 void cleanUpKeychain(DOMString attrLabel, optional DOMString applicationLabelBase64); 408 boolean keyExistsInKeychain(DOMString attrLabel, DOMString applicationLabelBase64); 409 409 410 410 // Ad Click Attribution -
trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp
r257726 r258293 2789 2789 } 2790 2790 2791 void TestRunner::cleanUpKeychain(JSStringRef attrLabel, JSStringRef application TagBase64)2791 void TestRunner::cleanUpKeychain(JSStringRef attrLabel, JSStringRef applicationLabelBase64) 2792 2792 { 2793 2793 Vector<WKRetainPtr<WKStringRef>> keys; … … 2797 2797 values.append(toWK(attrLabel)); 2798 2798 2799 if (application TagBase64) {2800 keys.append(adoptWK(WKStringCreateWithUTF8CString("Application Tag")));2801 values.append(toWK(application TagBase64));2799 if (applicationLabelBase64) { 2800 keys.append(adoptWK(WKStringCreateWithUTF8CString("ApplicationLabel"))); 2801 values.append(toWK(applicationLabelBase64)); 2802 2802 } 2803 2803 … … 2818 2818 } 2819 2819 2820 bool TestRunner::keyExistsInKeychain(JSStringRef attrLabel, JSStringRef application TagBase64)2820 bool TestRunner::keyExistsInKeychain(JSStringRef attrLabel, JSStringRef applicationLabelBase64) 2821 2821 { 2822 2822 Vector<WKRetainPtr<WKStringRef>> keys; … … 2826 2826 values.append(toWK(attrLabel)); 2827 2827 2828 keys.append(adoptWK(WKStringCreateWithUTF8CString("Application Tag")));2829 values.append(toWK(application TagBase64));2828 keys.append(adoptWK(WKStringCreateWithUTF8CString("ApplicationLabel"))); 2829 values.append(toWK(applicationLabelBase64)); 2830 2830 2831 2831 Vector<WKStringRef> rawKeys; -
trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.h
r257726 r258293 513 513 // FIXME(189876) 514 514 void addTestKeyToKeychain(JSStringRef privateKeyBase64, JSStringRef attrLabel, JSStringRef applicationTagBase64); 515 void cleanUpKeychain(JSStringRef attrLabel, JSStringRef application TagBase64);516 bool keyExistsInKeychain(JSStringRef attrLabel, JSStringRef application TagBase64);515 void cleanUpKeychain(JSStringRef attrLabel, JSStringRef applicationLabelBase64); 516 bool keyExistsInKeychain(JSStringRef attrLabel, JSStringRef applicationLabelBase64); 517 517 518 518 unsigned long serverTrustEvaluationCallbackCallsCount(); -
trunk/Tools/WebKitTestRunner/TestController.h
r258075 r258293 317 317 318 318 void addTestKeyToKeychain(const String& privateKeyBase64, const String& attrLabel, const String& applicationTagBase64); 319 void cleanUpKeychain(const String& attrLabel, const String& application TagBase64);320 bool keyExistsInKeychain(const String& attrLabel, const String& application TagBase64);319 void cleanUpKeychain(const String& attrLabel, const String& applicationLabelBase64); 320 bool keyExistsInKeychain(const String& attrLabel, const String& applicationLabelBase64); 321 321 322 322 #if PLATFORM(COCOA) -
trunk/Tools/WebKitTestRunner/TestInvocation.cpp
r257726 r258293 1724 1724 WKStringRef attrLabelWK = static_cast<WKStringRef>(WKDictionaryGetItemForKey(testDictionary, attrLabelKey.get())); 1725 1725 1726 WKRetainPtr<WKStringRef> application TagKey = adoptWK(WKStringCreateWithUTF8CString("ApplicationTag"));1727 WKStringRef application TagWK = static_cast<WKStringRef>(WKDictionaryGetItemForKey(testDictionary, applicationTagKey.get()));1728 1729 TestController::singleton().cleanUpKeychain(toWTFString(attrLabelWK), application TagWK ? toWTFString(applicationTagWK) : String());1726 WKRetainPtr<WKStringRef> applicationLabelKey = adoptWK(WKStringCreateWithUTF8CString("ApplicationLabel")); 1727 WKStringRef applicationLabelWK = static_cast<WKStringRef>(WKDictionaryGetItemForKey(testDictionary, applicationLabelKey.get())); 1728 1729 TestController::singleton().cleanUpKeychain(toWTFString(attrLabelWK), applicationLabelWK ? toWTFString(applicationLabelWK) : String()); 1730 1730 return nullptr; 1731 1731 } … … 1738 1738 WKStringRef attrLabelWK = static_cast<WKStringRef>(WKDictionaryGetItemForKey(testDictionary, attrLabelKey.get())); 1739 1739 1740 WKRetainPtr<WKStringRef> application TagKey = adoptWK(WKStringCreateWithUTF8CString("ApplicationTag"));1741 WKStringRef application TagWK = static_cast<WKStringRef>(WKDictionaryGetItemForKey(testDictionary, applicationTagKey.get()));1742 1743 bool keyExistsInKeychain = TestController::singleton().keyExistsInKeychain(toWTFString(attrLabelWK), toWTFString(application TagWK));1740 WKRetainPtr<WKStringRef> applicationLabelKey = adoptWK(WKStringCreateWithUTF8CString("ApplicationLabel")); 1741 WKStringRef applicationLabelWK = static_cast<WKStringRef>(WKDictionaryGetItemForKey(testDictionary, applicationLabelKey.get())); 1742 1743 bool keyExistsInKeychain = TestController::singleton().keyExistsInKeychain(toWTFString(attrLabelWK), toWTFString(applicationLabelWK)); 1744 1744 WKRetainPtr<WKTypeRef> result = adoptWK(WKBooleanCreate(keyExistsInKeychain)); 1745 1745 return result; -
trunk/Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm
r257726 r258293 431 431 } 432 432 433 void TestController::cleanUpKeychain(const String& attrLabel, const String& application TagBase64)433 void TestController::cleanUpKeychain(const String& attrLabel, const String& applicationLabelBase64) 434 434 { 435 435 auto deleteQuery = adoptNS([[NSMutableDictionary alloc] init]); … … 441 441 [deleteQuery setObject:@YES forKey:(id)kSecAttrNoLegacy]; 442 442 #endif 443 if (!!application TagBase64)444 [deleteQuery setObject:adoptNS([[NSData alloc] initWithBase64EncodedString:application TagBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get() forKey:(id)kSecAttrApplicationTag];443 if (!!applicationLabelBase64) 444 [deleteQuery setObject:adoptNS([[NSData alloc] initWithBase64EncodedString:applicationLabelBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get() forKey:(id)kSecAttrApplicationLabel]; 445 445 446 446 SecItemDelete((__bridge CFDictionaryRef)deleteQuery.get()); 447 447 } 448 448 449 bool TestController::keyExistsInKeychain(const String& attrLabel, const String& application TagBase64)449 bool TestController::keyExistsInKeychain(const String& attrLabel, const String& applicationLabelBase64) 450 450 { 451 451 NSDictionary *query = @{ … … 453 453 (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate, 454 454 (id)kSecAttrLabel: attrLabel, 455 (id)kSecAttrApplication Tag: adoptNS([[NSData alloc] initWithBase64EncodedString:applicationTagBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get(),455 (id)kSecAttrApplicationLabel: adoptNS([[NSData alloc] initWithBase64EncodedString:applicationLabelBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get(), 456 456 #if HAVE(DATA_PROTECTION_KEYCHAIN) 457 457 (id)kSecUseDataProtectionKeychain: @YES
Note: See TracChangeset
for help on using the changeset viewer.