Changeset 258293 in webkit


Ignore:
Timestamp:
Mar 11, 2020 3:42:08 PM (4 years ago)
Author:
jiewen_tan@apple.com
Message:

[WebAuthn] Formalize the Keychain schema
https://bugs.webkit.org/show_bug.cgi?id=183533
<rdar://problem/43347926>

Reviewed by Brent Fulgham.

Source/WebCore:

Covered by new test contents within existing files.

  • Modules/webauthn/AuthenticatorAssertionResponse.cpp:

(WebCore::AuthenticatorAssertionResponse::create):
(WebCore::AuthenticatorAssertionResponse::AuthenticatorAssertionResponse):

  • Modules/webauthn/AuthenticatorAssertionResponse.h:

Modifies the constructors to accept userEntity.name.

  • Modules/webauthn/cbor/CBORValue.h:

Adds a FIXME.

  • testing/MockWebAuthenticationConfiguration.h:

(WebCore::MockWebAuthenticationConfiguration::LocalConfiguration::encode const):
(WebCore::MockWebAuthenticationConfiguration::LocalConfiguration::decode):

  • testing/MockWebAuthenticationConfiguration.idl:

Modifies the test infra to use Credential ID as the unique identifier for a credential instead of
the original combination of RP ID and user handle.

Source/WebKit:

This patch formalizes the schema for the Keychain as follows:
kSecAttrLabel: RP ID
kSecAttrApplicationLabel: Credential ID (auto-gen by Keychain)
kSecAttrApplicationTag: { "id": UserEntity.id, "name": UserEntity.name } (CBOR encoded)
Noted, the vale of kSecAttrApplicationLabel is automatically generated by the Keychain, which is a SHA-1 hash of
the public key.

According to the Step 7. from https://www.w3.org/TR/webauthn/#op-make-cred, the following fields are mandatory

  1. rpId (rpEntity.id);
  2. userHandle (userEntity.id), this is required for authenticators that support resident keys;
  3. credentialId.

Some other optional fields are:
(from https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity)

  1. rpEntity.name;
  2. rpEnitty.icon;

(from https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialuserentity)

  1. userEntity.displayName;
  2. userEntity.name;
  3. userEntity.icon;

(from https://www.w3.org/TR/webauthn/#sign-counter)

  1. signature counter.

Among the six possible fields, only 4. is chosen to store. Here is why:
For rpEntity, rpEntity.id which is either the domain or the eTLD + 1 of the website is
sufficient enough to either classify the credential or serving the UI. Also, this is the only
trustworthy information that the UserAgent produce. Others could potentially be used by
malicious websites for attacking the Keychain or spoofing/phishing users when being displayed
in the UI. Also, rpEnitty.icon is a URL to the website's favicon, which if not implemented
correctly can be used for tracking.

For userEntity, userEntity.name is the human readable version of userEntity.id, and therefore
is chosen to store such that later on WebKit can pass it to UI client to help users disambiguate
different credentials. And it is necessary as userEntity.id is not guaranteed to be human
readable. Others are abandoned for the very same reason as above.

We hard code a zero value for 'signature counter'. While this is a theoretically interesting
technique for a RP to detect private key cloning, it is unlikely to be useful in practice.
We store the private keys in our SEP. This counter would only be a meaningful protection if
adversaries were able to extract private key data from the SEP without Apple noticing, but
were not able to manipulate this counter to fool the RP.

In terms of the schema,
1) RP ID is needed to query all credentials related, and therefore it needs a column and kSecAttrLabel
is supposed to be human readable;
2) kSecAttrApplicationLabel is the auto generated programmatical identifier for a SecItem, and
therefore is suitable as the credential ID. Given the input to the SHA-1 is generated by us, and
it is only needed to be powerful enough to be unique across the keychain within a device, and potentially
to be unique across different other credential ID for the same user. The SHA-1 collision attack
doesn't seem valid here.
3) kSecAttrApplicationTag is the only other column Keychain allows applications to modify. Therefore,
UserEntity.id and UserEntity.name is bundled to use this slot. The reason to use CBOR here is that
it is more friendly then JSON to encode binaries, and it is used widely in WebAuthn.

  • UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.h:
  • UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm:

(WebKit::LocalAuthenticatorInternal::toArrayBuffer):
(WebKit::LocalAuthenticatorInternal::getExistingCredentials):
(WebKit::LocalAuthenticator::makeCredential):
(WebKit::LocalAuthenticator::continueMakeCredentialAfterUserVerification):
(WebKit::LocalAuthenticator::continueMakeCredentialAfterAttested):
(WebKit::LocalAuthenticator::getAssertion):
(WebKit::LocalAuthenticator::deleteDuplicateCredential const):

  • UIProcess/WebAuthentication/Mock/MockLocalConnection.mm:

(WebKit::MockLocalConnection::filterResponses const):

Tools:

Modifies the test infra to use Credential ID as the unique identifier for a credential instead of
the original combination of RP ID and user handle.

  • WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl:
  • WebKitTestRunner/InjectedBundle/TestRunner.cpp:

(WTR::TestRunner::cleanUpKeychain):
(WTR::TestRunner::keyExistsInKeychain):

  • WebKitTestRunner/InjectedBundle/TestRunner.h:
  • WebKitTestRunner/TestController.h:
  • WebKitTestRunner/TestInvocation.cpp:

(WTR::TestInvocation::didReceiveSynchronousMessageFromInjectedBundle):

  • WebKitTestRunner/cocoa/TestControllerCocoa.mm:

(WTR::TestController::cleanUpKeychain):
(WTR::TestController::keyExistsInKeychain):

LayoutTests:

New tests are added and all tests are modified to use Credential ID to identify a credential instead
of { RP ID, user handle }.

  • http/wpt/webauthn/public-key-credential-create-failure-local-silent.https-expected.txt:
  • http/wpt/webauthn/public-key-credential-create-failure-local-silent.https.html:
  • http/wpt/webauthn/public-key-credential-create-failure-local.https-expected.txt:
  • http/wpt/webauthn/public-key-credential-create-failure-local.https.html:
  • http/wpt/webauthn/public-key-credential-create-success-local.https-expected.txt:
  • http/wpt/webauthn/public-key-credential-create-success-local.https.html:
  • http/wpt/webauthn/public-key-credential-get-failure-local-silent.https-expected.txt:
  • http/wpt/webauthn/public-key-credential-get-failure-local-silent.https.html:
  • http/wpt/webauthn/public-key-credential-get-failure-local.https.html:
  • http/wpt/webauthn/public-key-credential-get-success-local.https.html:
  • http/wpt/webauthn/resources/util.js:
Location:
trunk
Files:
30 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r258282 r258293  
     12020-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
    1242020-03-11  Myles C. Maxfield  <mmaxfield@apple.com>
    225
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-local-silent.https-expected.txt

    r236842 r258293  
    11
    2 PASS PublicKeyCredential's [[create]] with silent failure in a mock local authenticator.
    3 PASS PublicKeyCredential's [[create]] with silent failure in a mock local authenticator. 2
    4 PASS PublicKeyCredential's [[create]] with silent failure in a mock local authenticator. 3
    5 PASS PublicKeyCredential's [[create]] with silent failure in a mock local authenticator. 4
    6 PASS PublicKeyCredential's [[create]] with silent failure in a mock local authenticator. 5
    7 PASS PublicKeyCredential's [[create]] with silent failure in a mock local authenticator. 6
     2PASS PublicKeyCredential's [[create]] with unsupported public key credential parameters in a mock local authenticator.
     3PASS PublicKeyCredential's [[create]] with matched exclude credentials in a mock local authenticator.
     4PASS PublicKeyCredential's [[create]] with matched exclude credentials in a mock local authenticator. 2nd
     5PASS PublicKeyCredential's [[create]] without user consent in a mock local authenticator.
     6PASS PublicKeyCredential's [[create]] without private keys in a mock local authenticator.
     7PASS PublicKeyCredential's [[create]] without attestation in a mock local authenticator.
    88
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-local-silent.https.html

    r250940 r258293  
    55<script src="./resources/util.js"></script>
    66<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 => {
    931        const privateKeyBase64 = await generatePrivateKeyBase64();
    1032        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        };
    12128        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.");
    14132
    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);
    33137
    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(() => {
    51157            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.");
    155161</script>
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-local.https-expected.txt

    r257085 r258293  
    66PASS PublicKeyCredential's [[create]] without private keys in a mock local authenticator.
    77PASS PublicKeyCredential's [[create]] without attestation in a mock local authenticator.
    8 PASS PublicKeyCredential's [[create]] deleting old credential in a mock local authenticator.
     8PASS PublicKeyCredential's [[create]] not deleting old credential in a mock local authenticator.
    99PASS PublicKeyCredential's [[create]] with timeout in a mock local authenticator.
    1010
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-local.https.html

    r258020 r258293  
    55<script src="./resources/util.js"></script>
    66<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
    12204        if (window.internals)
    13205            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.");
    196208</script>
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-create-success-local.https-expected.txt

    r245638 r258293  
    66PASS PublicKeyCredential's [[create]] with indirect attestation in a mock local authenticator.
    77PASS PublicKeyCredential's [[create]] with direct attestation in a mock local authenticator.
     8PASS PublicKeyCredential's [[create]] with duplicate credential in a mock local authenticator.
     9PASS PublicKeyCredential's [[create]] with duplicate credential in a mock local authenticator. 2
    810
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-create-success-local.https.html

    r258020 r258293  
    66<script src="./resources/cbor.js"></script>
    77<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,
    18202                    privateKeyBase64: privateKeyBase64,
    19203                    userCertificateBase64: testAttestationCertificateBase64,
     
    22206            });
    23207
    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");
    212345</script>
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-local-silent.https-expected.txt

    r236842 r258293  
    11
    2 PASS PublicKeyCredential's [[get]] with silent failure in a mock local authenticator.
    3 PASS PublicKeyCredential's [[get]] with silent failure in a mock local authenticator. 2
    4 PASS PublicKeyCredential's [[get]] with silent failure in a mock local authenticator. 3
     2PASS PublicKeyCredential's [[get]] with no matched credentials in a mock local authenticator.
     3PASS PublicKeyCredential's [[get]] with no matched credentials in a mock local authenticator. 2nd
     4PASS PublicKeyCredential's [[get]] without user consent in a mock local authenticator.
    55
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-local-silent.https.html

    r250940 r258293  
    55<script src="./resources/util.js"></script>
    66<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 => {
    928        const privateKeyBase64 = await generatePrivateKeyBase64();
    1029        const credentialID = await calculateCredentialID(privateKeyBase64);
     30        const credentialIDBase64 = btoa(String.fromCharCode.apply(0, credentialID));
    1131        // Default mock configuration. Tests need to override if they need different configuration.
    1232        if (window.internals)
    13             internals.setMockWebAuthenticationConfiguration({ silentFailure: true, local: { acceptAuthentication: false, acceptAttestation: false, preferredUserhandleBase64: userhandleBase64 } });
     33            internals.setMockWebAuthenticationConfiguration({ silentFailure: true, local: { acceptAuthentication: false, acceptAttestation: false, preferredCredentialIdBase64: credentialIDBase64 } });
    1434
    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        };
    2844
    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");
    3152
    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 } });
    4260
     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(() => {
    4371            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.");
    6775</script>
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-local.https.html

    r257877 r258293  
    55<script src="./resources/util.js"></script>
    66<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 => {
    927        const privateKeyBase64 = await generatePrivateKeyBase64();
    1028        const credentialID = await calculateCredentialID(privateKeyBase64);
     29        const credentialIDBase64 = btoa(String.fromCharCode.apply(0, credentialID));
    1130        // Default mock configuration. Tests need to override if they need different configuration.
    1231        if (window.internals)
    13             internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: false, acceptAttestation: false, preferredUserhandleBase64: userhandleBase64 } });
     32            internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: false, acceptAttestation: false, preferredCredentialIdBase64: credentialIDBase64 } });
    1433
    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        };
    2742
    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");
    3050
    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 } });
    4058
     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(() => {
    4168            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.");
    4872
    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        };
    5585
    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.");
    8088</script>
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-get-success-local.https.html

    r250940 r258293  
    55<script src="./resources/util.js"></script>
    66<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 => {
    939        const privateKeyBase64 = await generatePrivateKeyBase64();
    1040        const credentialID = await calculateCredentialID(privateKeyBase64);
     41        const credentialIDBase64 = base64encode(credentialID);
    1142        // Default mock configuration. Tests need to override if they need different configuration.
    1243        if (window.internals)
    13             internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false, preferredUserhandleBase64: userhandleBase64 } });
     44            internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false, preferredCredentialIdBase64: credentialIDBase64 } });
    1445
    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        };
    1951
    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.");
    2758
    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 } });
    3366
    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        };
    4576
    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.");
    7883</script>
  • trunk/LayoutTests/http/wpt/webauthn/resources/util.js

    r254356 r258293  
    66const testRpId = "localhost";
    77const testUserhandleBase64 = "AAECAwQFBgcICQ==";
     8const testUserEntityBundleBase64 = "omJpZEoAAQIDBAUGBwgJZG5hbWVwQUFFQ0F3UUZCZ2NJQ1E9PQ==";
    89const testAttestationCertificateBase64 =
    910    "MIIB6jCCAZCgAwIBAgIGAWHAxcjvMAoGCCqGSM49BAMCMFMxJzAlBgNVBAMMHkJh" +
     
    450451function generateUserhandleBase64()
    451452{
    452     let buffer = new Uint8Array(16);
     453    let buffer = new Uint8Array(8);
    453454    crypto.getRandomValues(buffer);
    454455    return btoa(String.fromCharCode.apply(0, buffer));
     
    487488    return new Uint8Array(await crypto.subtle.digest("sha-1", publicKey));
    488489}
     490
     491function base64encode(binary)
     492{
     493    const unit8Array = new Uint8Array(binary);
     494    return btoa(String.fromCharCode.apply(0, unit8Array));
     495}
  • trunk/Source/WebCore/ChangeLog

    r258287 r258293  
     12020-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
    1272020-03-11  Daniel Bates  <dabates@apple.com>
    228
  • trunk/Source/WebCore/Modules/webauthn/AuthenticatorAssertionResponse.cpp

    r257269 r258293  
    4949}
    5050
    51 Ref<AuthenticatorAssertionResponse> AuthenticatorAssertionResponse::create(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& userHandle, SecAccessControlRef accessControl)
     51Ref<AuthenticatorAssertionResponse> AuthenticatorAssertionResponse::create(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& userHandle, String&& name, SecAccessControlRef accessControl)
    5252{
    53     return adoptRef(*new AuthenticatorAssertionResponse(WTFMove(rawId), WTFMove(userHandle), accessControl));
     53    return adoptRef(*new AuthenticatorAssertionResponse(WTFMove(rawId), WTFMove(userHandle), WTFMove(name), accessControl));
    5454}
    5555
     
    6767}
    6868
    69 AuthenticatorAssertionResponse::AuthenticatorAssertionResponse(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& userHandle, SecAccessControlRef accessControl)
     69AuthenticatorAssertionResponse::AuthenticatorAssertionResponse(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& userHandle, String&& name, SecAccessControlRef accessControl)
    7070    : AuthenticatorResponse(WTFMove(rawId))
    7171    , m_userHandle(WTFMove(userHandle))
     72    , m_name(WTFMove(name))
    7273    , m_accessControl(accessControl)
    7374{
  • trunk/Source/WebCore/Modules/webauthn/AuthenticatorAssertionResponse.h

    r257269 r258293  
    3838    static Ref<AuthenticatorAssertionResponse> create(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& authenticatorData, Ref<ArrayBuffer>&& signature, RefPtr<ArrayBuffer>&& userHandle, Optional<AuthenticationExtensionsClientOutputs>&&);
    3939    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, SecAccessControlRef);
     40    WEBCORE_EXPORT static Ref<AuthenticatorAssertionResponse> create(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& userHandle, String&& name, SecAccessControlRef);
    4141    virtual ~AuthenticatorAssertionResponse() = default;
    4242
     
    5757private:
    5858    AuthenticatorAssertionResponse(Ref<ArrayBuffer>&&, Ref<ArrayBuffer>&&, Ref<ArrayBuffer>&&, RefPtr<ArrayBuffer>&&);
    59     AuthenticatorAssertionResponse(Ref<ArrayBuffer>&&, Ref<ArrayBuffer>&&, SecAccessControlRef);
     59    AuthenticatorAssertionResponse(Ref<ArrayBuffer>&&, Ref<ArrayBuffer>&&, String&&, SecAccessControlRef);
    6060
    6161    Type type() const final { return Type::Assertion; }
  • trunk/Source/WebCore/Modules/webauthn/cbor/CBORValue.h

    r237983 r258293  
    165165    bool isBool() const { return isSimple() && (m_simpleValue == SimpleValue::TrueValue || m_simpleValue == SimpleValue::FalseValue); }
    166166
     167    // FIXME(183535): Considering adding && getter for better performance.
    167168    // These will all fatally assert if the type doesn't match.
    168169    SimpleValue getSimpleValue() const;
  • trunk/Source/WebCore/testing/MockWebAuthenticationConfiguration.h

    r254439 r258293  
    6868        String userCertificateBase64;
    6969        String intermediateCACertificateBase64;
    70         String preferredUserhandleBase64;
     70        String preferredCredentialIdBase64;
    7171
    7272        template<class Encoder> void encode(Encoder&) const;
     
    113113void MockWebAuthenticationConfiguration::LocalConfiguration::encode(Encoder& encoder) const
    114114{
    115     encoder << acceptAuthentication << acceptAttestation << privateKeyBase64 << userCertificateBase64 << intermediateCACertificateBase64 << preferredUserhandleBase64;
     115    encoder << acceptAuthentication << acceptAttestation << privateKeyBase64 << userCertificateBase64 << intermediateCACertificateBase64 << preferredCredentialIdBase64;
    116116}
    117117
     
    151151    result.intermediateCACertificateBase64 = WTFMove(*intermediateCACertificateBase64);
    152152
    153     Optional<String> preferredUserhandleBase64;
    154     decoder >> preferredUserhandleBase64;
    155     if (!preferredUserhandleBase64)
    156         return WTF::nullopt;
    157     result.preferredUserhandleBase64 = WTFMove(*preferredUserhandleBase64);
     153    Optional<String> preferredCredentialIdBase64;
     154    decoder >> preferredCredentialIdBase64;
     155    if (!preferredCredentialIdBase64)
     156        return WTF::nullopt;
     157    result.preferredCredentialIdBase64 = WTFMove(*preferredCredentialIdBase64);
    158158
    159159    return result;
  • trunk/Source/WebCore/testing/MockWebAuthenticationConfiguration.idl

    r254439 r258293  
    7777    DOMString userCertificateBase64;
    7878    DOMString intermediateCACertificateBase64;
    79     DOMString preferredUserhandleBase64;
     79    DOMString preferredCredentialIdBase64;
    8080};
    8181
  • trunk/Source/WebKit/ChangeLog

    r258289 r258293  
     12020-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
    1752020-03-11  Per Arne Vollan  <pvollan@apple.com>
    276
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.h

    r258020 r258293  
    6868
    6969    void receiveException(WebCore::ExceptionData&&, WebAuthenticationStatus = WebAuthenticationStatus::LAError) const;
     70    void deleteDuplicateCredential() const;
    7071
    7172    State m_state { State::Init };
    7273    UniqueRef<LocalConnection> m_connection;
     74    // FIXME(183534): Combine these two.
    7375    HashSet<Ref<WebCore::AuthenticatorAssertionResponse>> m_assertionResponses;
     76    Vector<Ref<WebCore::AuthenticatorAssertionResponse>> m_existingCredentials;
    7477};
    7578
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm

    r258020 r258293  
    3232#import <WebCore/AuthenticatorAssertionResponse.h>
    3333#import <WebCore/AuthenticatorAttestationResponse.h>
     34#import <WebCore/CBORReader.h>
    3435#import <WebCore/CBORWriter.h>
    3536#import <WebCore/ExceptionData.h>
     
    4849namespace WebKit {
    4950using namespace WebCore;
     51using CBOR = cbor::CBORValue;
    5052
    5153namespace LocalAuthenticatorInternal {
     
    5658// Credential ID is currently SHA-1 of the corresponding public key.
    5759const uint16_t credentialIdLength = 20;
     60const char* const userEntityIdKey = "id";
     61const char* const userEntityNameKey = "name";
     62const uint64_t counter = 0;
    5863
    5964static inline bool emptyTransportsOrContain(const Vector<AuthenticatorTransport>& transports, AuthenticatorTransport target)
     
    6267}
    6368
     69// FIXME(183534): Find a better way of comparing credential id. Doing it with array seems fine given the list should be small.
    6470static inline HashSet<String> produceHashSet(const Vector<PublicKeyCredentialDescriptor>& credentialDescriptors)
    6571{
     
    99105}
    100106
     107static inline Ref<ArrayBuffer> toArrayBuffer(const Vector<uint8_t>& data)
     108{
     109    return ArrayBuffer::create(data.data(), data.size());
     110}
     111
    101112// FIXME(<rdar://problem/60108131>): Remove this whitelist once testing is complete.
    102113static const HashSet<String>& whitelistedRpId()
     
    110121}
    111122
     123static 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
    112174} // LocalAuthenticatorInternal
    113175
     
    128190    // Skip Step 9 as extensions are not supported yet.
    129191    // Step 8 is implicitly captured by all UnknownError exception receiveResponds.
     192    // Skip Step 10 as counter is constantly 0.
    130193    // Step 2.
    131194    if (notFound == creationOptions.pubKeyCredParams.findMatching([] (auto& pubKeyCredParam) {
     
    137200
    138201    // 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
    139209    auto excludeCredentialIds = produceHashSet(creationOptions.excludeCredentials);
    140210    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;
    169218        }
    170219    }
     
    229278    }
    230279
    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.
    233281    // kSecAttrLabel: RP ID
    234282    // kSecAttrApplicationLabel: Credential ID (auto-gen by Keychain)
    235     // kSecAttrApplicationTag: userhandle
     283    // kSecAttrApplicationTag: { "id": UserEntity.id, "name": UserEntity.name } (CBOR encoded)
    236284    // 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.
    238286    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.
    260296    // The above-to-create private key will be inserted into keychain while using SEP.
    261297    auto privateKey = m_connection->createCredentialPrivateKey(context, accessControlRef, secAttrLabel, secAttrApplicationTag.get());
     
    265301    }
    266302
     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.
    267319    Vector<uint8_t> credentialId;
    268320    {
     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
    269326        NSDictionary *credentialIdQuery = @{
    270327            (id)kSecClass: (id)kSecClassKey,
    271328            (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
    272329            (id)kSecAttrLabel: secAttrLabel,
    273             (id)kSecAttrApplicationTag: secAttrApplicationTag.get(),
    274             (id)kSecReturnAttributes: @YES,
     330            (id)kSecAttrApplicationLabel: toNSData(credentialId).get(),
    275331#if HAVE(DATA_PROTECTION_KEYCHAIN)
    276332            (id)kSecUseDataProtectionKeychain: @YES
     
    279335#endif
    280336        };
    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    }
    296341
    297342    // Step 11. https://www.w3.org/TR/webauthn/#attested-credential-data
     
    299344    Vector<uint8_t> cosePublicKey;
    300345    {
    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 | Y
    312         }
    313 
    314346        // COSE Encoding
    315347        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)];
    317349        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)];
    319351        cosePublicKey = encodeES256PublicKeyAsCBOR(WTFMove(x), WTFMove(y));
    320352    }
     
    327359    // Skip Apple Attestation for none attestation, and non whitelisted RP ID for now.
    328360    if (creationOptions.attestation == AttestationConveyancePreference::None || !whitelistedRpId().contains(creationOptions.rp.id)) {
     361        deleteDuplicateCredential();
     362
    329363        auto attestationObject = buildAttestationObject(WTFMove(authData), "", { }, AttestationConveyancePreference::None);
    330364        receiveRespond(AuthenticatorAttestationResponse::create(credentialId, attestationObject));
     
    371405    auto attestationObject = buildAttestationObject(WTFMove(authData), "apple", WTFMove(attestationStatementMap), creationOptions.attestation);
    372406
     407    deleteDuplicateCredential();
    373408    receiveRespond(AuthenticatorAttestationResponse::create(credentialId, attestationObject));
    374409}
     
    384419    // Skip Step 2 as requireUserVerification is enforced.
    385420    // Skip Step 8 as extensions are not supported yet.
     421    // Skip Step 9 as counter is constantly 0.
    386422    // Step 12 is implicitly captured by all UnknownError exception callbacks.
    387423    // 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.
     
    393429
    394430    // 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()) {
    428452        receiveException({ NotAllowedError, "No matched credentials are found in the platform attached authenticator."_s }, WebAuthenticationStatus::LANoCredential);
    429453        return;
     
    431455
    432456    // 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     }
    440457    m_connection->filterResponses(m_assertionResponses);
    441458
     
    485502    }
    486503
    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.
    491505    auto authData = buildAuthData(WTF::get<PublicKeyCredentialRequestOptions>(requestData().options).rpId, getAssertionFlags, counter, { });
    492506
     
    542556}
    543557
     558void 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
    544585} // namespace WebKit
    545586
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalConnection.mm

    r257877 r258293  
    116116void MockLocalConnection::filterResponses(HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>& responses) const
    117117{
    118     const auto& preferredUserhandleBase64 = m_configuration.local->preferredUserhandleBase64;
    119     if (preferredUserhandleBase64.isEmpty())
     118    const auto& preferredCredentialIdBase64 = m_configuration.local->preferredCredentialIdBase64;
     119    if (preferredCredentialIdBase64.isEmpty())
    120120        return;
    121121
    122122    auto itr = responses.begin();
    123123    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)
    128128            break;
    129129    }
  • trunk/Tools/ChangeLog

    r258286 r258293  
     12020-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
    1242020-03-11  Keith Miller  <keith_miller@apple.com>
    225
  • trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm

    r257954 r258293  
    6363    "PH76c0+WFOzZKslPyyFse4goGIW2R7k9VHLPEZl5nfnBgEVFh5zev+/xpHQIvuq6"
    6464    "RQ==";
    65 static String testUserhandleBase64 = "AAECAwQFBgcICQ==";
     65static String testUserEntityBundleBase64 = "omJpZEoAAQIDBAUGBwgJZG5hbWVwQUFFQ0F3UUZCZ2NJQ1E9PQ==";
    6666
    6767@interface TestWebAuthenticationPanelDelegate : NSObject <_WKWebAuthenticationPanelDelegate>
     
    12151215    [webView setUIDelegate:delegate.get()];
    12161216
    1217     ASSERT_TRUE(addKeyToKeychain(testES256PrivateKeyBase64, "", testUserhandleBase64));
     1217    ASSERT_TRUE(addKeyToKeychain(testES256PrivateKeyBase64, "", testUserEntityBundleBase64));
    12181218    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
    12191219    Util::run(&webAuthenticationPanelUpdateLAExcludeCredentialsMatched);
     
    13061306    [webView setUIDelegate:delegate.get()];
    13071307
    1308     ASSERT_TRUE(addKeyToKeychain(testES256PrivateKeyBase64, "", testUserhandleBase64));
     1308    ASSERT_TRUE(addKeyToKeychain(testES256PrivateKeyBase64, "", testUserEntityBundleBase64));
    13091309    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
    13101310    [webView waitForMessage:@"Succeeded!"];
  • trunk/Tools/WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl

    r257726 r258293  
    405405    // WebAuthn
    406406    void addTestKeyToKeychain(DOMString privateKeyBase64, DOMString attrLabel, DOMString applicationTagBase64);
    407     void cleanUpKeychain(DOMString attrLabel, optional DOMString applicationTagBase64);
    408     boolean keyExistsInKeychain(DOMString attrLabel, DOMString applicationTagBase64);
     407    void cleanUpKeychain(DOMString attrLabel, optional DOMString applicationLabelBase64);
     408    boolean keyExistsInKeychain(DOMString attrLabel, DOMString applicationLabelBase64);
    409409
    410410    // Ad Click Attribution
  • trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp

    r257726 r258293  
    27892789}
    27902790
    2791 void TestRunner::cleanUpKeychain(JSStringRef attrLabel, JSStringRef applicationTagBase64)
     2791void TestRunner::cleanUpKeychain(JSStringRef attrLabel, JSStringRef applicationLabelBase64)
    27922792{
    27932793    Vector<WKRetainPtr<WKStringRef>> keys;
     
    27972797    values.append(toWK(attrLabel));
    27982798
    2799     if (applicationTagBase64) {
    2800         keys.append(adoptWK(WKStringCreateWithUTF8CString("ApplicationTag")));
    2801         values.append(toWK(applicationTagBase64));
     2799    if (applicationLabelBase64) {
     2800        keys.append(adoptWK(WKStringCreateWithUTF8CString("ApplicationLabel")));
     2801        values.append(toWK(applicationLabelBase64));
    28022802    }
    28032803
     
    28182818}
    28192819
    2820 bool TestRunner::keyExistsInKeychain(JSStringRef attrLabel, JSStringRef applicationTagBase64)
     2820bool TestRunner::keyExistsInKeychain(JSStringRef attrLabel, JSStringRef applicationLabelBase64)
    28212821{
    28222822    Vector<WKRetainPtr<WKStringRef>> keys;
     
    28262826    values.append(toWK(attrLabel));
    28272827
    2828     keys.append(adoptWK(WKStringCreateWithUTF8CString("ApplicationTag")));
    2829     values.append(toWK(applicationTagBase64));
     2828    keys.append(adoptWK(WKStringCreateWithUTF8CString("ApplicationLabel")));
     2829    values.append(toWK(applicationLabelBase64));
    28302830
    28312831    Vector<WKStringRef> rawKeys;
  • trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.h

    r257726 r258293  
    513513    // FIXME(189876)
    514514    void addTestKeyToKeychain(JSStringRef privateKeyBase64, JSStringRef attrLabel, JSStringRef applicationTagBase64);
    515     void cleanUpKeychain(JSStringRef attrLabel, JSStringRef applicationTagBase64);
    516     bool keyExistsInKeychain(JSStringRef attrLabel, JSStringRef applicationTagBase64);
     515    void cleanUpKeychain(JSStringRef attrLabel, JSStringRef applicationLabelBase64);
     516    bool keyExistsInKeychain(JSStringRef attrLabel, JSStringRef applicationLabelBase64);
    517517
    518518    unsigned long serverTrustEvaluationCallbackCallsCount();
  • trunk/Tools/WebKitTestRunner/TestController.h

    r258075 r258293  
    317317
    318318    void addTestKeyToKeychain(const String& privateKeyBase64, const String& attrLabel, const String& applicationTagBase64);
    319     void cleanUpKeychain(const String& attrLabel, const String& applicationTagBase64);
    320     bool keyExistsInKeychain(const String& attrLabel, const String& applicationTagBase64);
     319    void cleanUpKeychain(const String& attrLabel, const String& applicationLabelBase64);
     320    bool keyExistsInKeychain(const String& attrLabel, const String& applicationLabelBase64);
    321321
    322322#if PLATFORM(COCOA)
  • trunk/Tools/WebKitTestRunner/TestInvocation.cpp

    r257726 r258293  
    17241724        WKStringRef attrLabelWK = static_cast<WKStringRef>(WKDictionaryGetItemForKey(testDictionary, attrLabelKey.get()));
    17251725
    1726         WKRetainPtr<WKStringRef> applicationTagKey = adoptWK(WKStringCreateWithUTF8CString("ApplicationTag"));
    1727         WKStringRef applicationTagWK = static_cast<WKStringRef>(WKDictionaryGetItemForKey(testDictionary, applicationTagKey.get()));
    1728 
    1729         TestController::singleton().cleanUpKeychain(toWTFString(attrLabelWK), applicationTagWK ? 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());
    17301730        return nullptr;
    17311731    }
     
    17381738        WKStringRef attrLabelWK = static_cast<WKStringRef>(WKDictionaryGetItemForKey(testDictionary, attrLabelKey.get()));
    17391739
    1740         WKRetainPtr<WKStringRef> applicationTagKey = adoptWK(WKStringCreateWithUTF8CString("ApplicationTag"));
    1741         WKStringRef applicationTagWK = static_cast<WKStringRef>(WKDictionaryGetItemForKey(testDictionary, applicationTagKey.get()));
    1742 
    1743         bool keyExistsInKeychain = TestController::singleton().keyExistsInKeychain(toWTFString(attrLabelWK), toWTFString(applicationTagWK));
     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));
    17441744        WKRetainPtr<WKTypeRef> result = adoptWK(WKBooleanCreate(keyExistsInKeychain));
    17451745        return result;
  • trunk/Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm

    r257726 r258293  
    431431}
    432432
    433 void TestController::cleanUpKeychain(const String& attrLabel, const String& applicationTagBase64)
     433void TestController::cleanUpKeychain(const String& attrLabel, const String& applicationLabelBase64)
    434434{
    435435    auto deleteQuery = adoptNS([[NSMutableDictionary alloc] init]);
     
    441441    [deleteQuery setObject:@YES forKey:(id)kSecAttrNoLegacy];
    442442#endif
    443     if (!!applicationTagBase64)
    444         [deleteQuery setObject:adoptNS([[NSData alloc] initWithBase64EncodedString:applicationTagBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get() forKey:(id)kSecAttrApplicationTag];
     443    if (!!applicationLabelBase64)
     444        [deleteQuery setObject:adoptNS([[NSData alloc] initWithBase64EncodedString:applicationLabelBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get() forKey:(id)kSecAttrApplicationLabel];
    445445
    446446    SecItemDelete((__bridge CFDictionaryRef)deleteQuery.get());
    447447}
    448448
    449 bool TestController::keyExistsInKeychain(const String& attrLabel, const String& applicationTagBase64)
     449bool TestController::keyExistsInKeychain(const String& attrLabel, const String& applicationLabelBase64)
    450450{
    451451    NSDictionary *query = @{
     
    453453        (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
    454454        (id)kSecAttrLabel: attrLabel,
    455         (id)kSecAttrApplicationTag: adoptNS([[NSData alloc] initWithBase64EncodedString:applicationTagBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get(),
     455        (id)kSecAttrApplicationLabel: adoptNS([[NSData alloc] initWithBase64EncodedString:applicationLabelBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get(),
    456456#if HAVE(DATA_PROTECTION_KEYCHAIN)
    457457        (id)kSecUseDataProtectionKeychain: @YES
Note: See TracChangeset for help on using the changeset viewer.