From a1c674cede604fd536949f223d76b256f07f9c66 Mon Sep 17 00:00:00 2001 From: Jeffrey Yasskin Date: Tue, 14 Feb 2017 12:37:34 -0800 Subject: [PATCH] Make makeCredential() more precise. I've linked a lot more terms, reordered explanations to be clearer, and specified some missing behavior. --- index.bs | 191 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 141 insertions(+), 50 deletions(-) diff --git a/index.bs b/index.bs index 4b2ab4cce..275077a4c 100644 --- a/index.bs +++ b/index.bs @@ -39,6 +39,10 @@ Markup Shorthands: css off, markdown on
 
+spec: ECMAScript; urlPrefix: https://tc39.github.io/ecma262/#
+    type: method
+        for: JSON; text: stringify; url: sec-json.stringify
+
 
 spec: HTML51; urlPrefix: http://www.w3.org/TR/html51/; for: web
     type: dfn
@@ -61,6 +65,11 @@ spec: Encoding; urlPrefix: https://www.w3.org/TR/encoding/;
         text: UTF-8 Encoded; url: utf-8-encode
 
+ + # Introduction # {#intro} @@ -374,6 +383,7 @@ underlying platform, which may involve data storage managed by the browser or th approve this operation. On success, the promise will be resolved with a {{ScopedCredentialInfo}} object describing the newly created credential. +
This method takes the following parameters: - The accountInformation parameter specifies information about the user account for which the credential is being @@ -397,6 +407,8 @@ This method takes the following parameters: - The optional options parameter specifies additional options, as described in [[#credential-options]]. +
+ When this method is invoked, the user agent MUST execute the following algorithm: 1. If the {{ScopedCredentialOptions/timeoutSeconds}} member of {{options}} is present, check if its value lies within @@ -404,13 +416,19 @@ When this method is invoked, the user agent MUST execute the following algorithm |adjustedTimeout| to this adjusted value. If {{ScopedCredentialOptions/timeoutSeconds}} was not specified, then set |adjustedTimeout| to a platform-specific default. -2. Let |promise| be a new Promise. Return |promise| and start a timer for |adjustedTimeout| seconds. - Then asynchronously continue executing the following steps. If any fatal error is encountered in this process other than the - ones enumerated below, cancel the timer, reject |promise| with a DOMException whose name is "UnknownError", and terminate + Issue: Put some constraints on the "reasonable range". + +2. Let |promise| be [=a new promise=]. Return |promise| and start a timer for |adjustedTimeout| seconds. + Then execute the following steps [=in parallel=]. If any fatal error is encountered in this process other than the + ones enumerated below, cancel the timer, [=reject=] |promise| with a DOMException whose name is "{{UnknownError}}", and terminate this algorithm. -3. Set |callerOrigin| to the current settings object's origin. If |callerOrigin| is - an opaque origin, reject |promise| with a DOMException whose name is "NotAllowedError", and + Issue: Should "process" be "algorithm", or does it actually mean an OS + process? What kinds of fatal errors are you worried about? I suggest we just + remove that sentence. + +3. Set |callerOrigin| to the [=web/current settings object=]'s [=web/origin=]. If |callerOrigin| is + an [=web/opaque origin=], reject |promise| with a {{DOMException}} whose name is "{{NotAllowedError}}", and terminate this algorithm. Otherwise, - If the {{ScopedCredentialOptions/rpId}} member of {{options}} is not present, then set |rpId| to |callerOrigin|. - If the {{ScopedCredentialOptions/rpId}} member of {{options}} is present, then invoke the procedure used for @@ -419,58 +437,131 @@ When this method is invoked, the user agent MUST execute the following algorithm are thrown, set |rpId| to the value of `host` as computed by this procedure. Otherwise, reject |promise| with a DOMException whose name is "SecurityError", and terminate this algorithm. -4. Process each element of {{cryptoParameters}} using the following steps, to produce a new sequence |normalizedParameters|. - - Let |current| be the currently selected element of {{cryptoParameters}}. - - If `current.type` does not contain a {{ScopedCredentialType}} supported by this implementation, then stop processing - |current| and move on to the next element in {{cryptoParameters}}. - - Let |normalizedAlgorithm| be the result of normalizing an algorithm [[!WebCryptoAPI]], - with |alg| set to `current.algorithm` and |op| set to 'generateKey'. If an error occurs during this - procedure, then stop processing |current| and move on to the next element in {{cryptoParameters}}. - - Add a new object of type {{ScopedCredentialParameters}} to |normalizedParameters|, with |type| set to `current.type` and +4. Let |normalizedParameters| be a new [=list=]. +5. [=list/For each=] |current| of {{cryptoParameters}}: + - If |current|.{{ScopedCredentialParameters/type}} does not + contain a {{ScopedCredentialType}} supported by this implementation, + then [=continue=]. + - Let |normalizedAlgorithm| be the result of normalizing an algorithm + [[!WebCryptoAPI]], with |alg| set to + |current|.{{algorithm}} and |op| set to `"generateKey"`. If + an error occurs during this procedure, then [=continue=]. + - [=list/Append=] a new object of type {{ScopedCredentialParameters}} to + |normalizedParameters|, with |type| set to + |current|.{{ScopedCredentialParameters/type}} and |algorithm| set to |normalizedAlgorithm|. -5. If |normalizedAlgorithm| is empty and {{cryptoParameters}} was not empty, cancel the timer started in step 2, reject - |promise| with a DOMException whose name is "NotSupportedError", and terminate this algorithm. +6. If |normalizedAlgorithm| is empty and {{cryptoParameters}} was not empty, + cancel the timer started in step 2, [=reject=] |promise| with a + {{DOMException}} whose name is "{{NotSupportedError}}", and terminate this + algorithm. + +7. Let |clientExtensions| be a new [=list=]. +8. If the {{ScopedCredentialOptions/extensions}} member of {{options}} is + present, then [=map/for each=] |extension| → |argument| of + {{options}}.{{ScopedCredentialOptions/extensions}}: + 1. If |extension| is not supported by this client platform, [=continue=]. + 2. Let |result| be the result of running |extension|'s [=client processing=] algorithm on |argument|. + 3. If the algorithm returned an error, [=continue=]. + 4. [=list/Append=] |result| to |clientExtensions|. + +9. Let |clientData| be a new {{ClientData}} instance whose fields are: + : {{challenge}} + :: The [=base64url encoding=] of {{attestationChallenge}} + : {{origin}} + :: |rpId| + : {{hashAlg}} + :: UA-chosen + + Issue: We need *some* constraints on the possible hash algorithms, or + else sites will fail on unusual UAs. + : {{tokenBinding}} + :: The token binding key associated with |callerOrigin| (if any) + + Issue: We need a definition of token binding. + : {{ClientData/extensions}} + :: {{options}}.{{ScopedCredentialOptions/extensions}} + +10. Let |clientDataJSON| be the UTF-8 encoding of the result of calling the + original value of {{JSON/stringify|JSON.stringify}} on |clientData|. + + Issue: Some extensions contain ArrayBuffers, which don't stringify well. + What's the intent here? +11. Let |clientDataHash| be the hash of |clientDataJSON| using + |clientData|.{{hashAlg}}. + +12. Let |issuedRequests| and |currentlyAvailableAuthenticators| be new [=ordered + sets=]. + +13. For each |authenticator| currently available on this platform, if + {{options}}.{{ScopedCredentialOptions/attachment}} is not + [=present=] or its value matches |authenticator|'s attachment modality, + [=set/append=] |authenticator| to |currentlyAvailableAuthenticators|. + +14. [=set/For each=] |authenticator| in |currentlyAvailableAuthenticators|, [=in + parallel=]: + 1. Let |excludeList| be a new [=list=]. + 2. [=list/For each=] credential |C| in {{options}}.{{ScopedCredentialOptions/excludeList}}: + 1. If |C| has an empty {{transports}} list, [=list/append=] it to + |excludeList| and [=continue=]. + 2. If |authenticator| is connected over a transport not mentioned in + |C|.{{transports}}, the client MAY [=continue=]. + + Issue: I'm not sure this captures the intent of the original wording. + 3. [=list/Append=] |C| to |excludeList|. + 3. Invoke the authenticatorMakeCredential operation on that + authenticator with |rpId|, |clientDataHash|, {{accountInformation}}, + |normalizedParameters|, |excludeList| and |clientExtensions| as + parameters. + 4. [=set/Append=] |authenticator| to |issuedRequests|. -6. If the {{ScopedCredentialOptions/extensions}} member of {{options}} is present, process any extensions supported by - this client platform, to produce the extension data that needs to be sent to the authenticator. If an error is encountered - while processing an extension, skip that extension and do not produce any extension data for it. Call the result of this - processing |clientExtensions|. +11. While |issuedRequests| is not empty, perform the following actions depending upon the |adjustedTimeout| timer and responses + from the authenticators: +
-7. Use {{attestationChallenge}}, |callerOrigin| and |rpId|, along with the token binding key associated with |callerOrigin| (if - any), to create a {{ClientData}} structure representing this request. Choose a hash algorithm for {{ClientData/hashAlg}} and - compute the {{ScopedCredentialInfo/clientDataJSON}} and its clientDataHash. +
If the |adjustedTimeout| timer expires,
-8. Initialize |issuedRequests| and |currentlyAvailableAuthenticators| to empty lists. +
[=set/For each=] + |authenticator| in |issuedRequests| invoke the + authenticatorCancel operation on |authenticator| and + [=set/remove=] |authenticator| from |issuedRequests|.
-9. For each authenticator currently available on this platform, add the authenticator to |currentlyAvailableAuthenticators| - unless the {{ScopedCredentialOptions/attachment}} member of {{options}} is present. In that case, let |attachment| - be {{ScopedCredentialOptions/attachment}}, and add the authenticator to |currentlyAvailableAuthenticators| if its attachment - modality matches |attachment|. +
If any |authenticator| returns a status indicating that the user cancelled the operation,
-10. For each authenticator in |currentlyAvailableAuthenticators|: asynchronously invoke the authenticatorMakeCredential - operation on that authenticator with |rpId|, clientDataHash, {{accountInformation}}, |normalizedParameters|, - {{ScopedCredentialOptions/excludeList}} and |clientExtensions| as parameters. Add a corresponding entry to |issuedRequests|. - - For each credential |C| in the {{ScopedCredentialOptions/excludeList}} member of {{options}} that has a non-empty - |transports| list, optionally use only the specified transports to test for the existence of |C|. +
+ 1. [=set/Remove=] |authenticator| from |issuedRequests|. + 2. [=set/For each=] remaining |authenticator| in |issuedRequests| invoke + the authenticatorCancel operation on |authenticator| and + [=set/remove=] it from |issuedRequests|. -11. While |issuedRequests| is not empty, perform the following actions depending upon the |adjustedTimeout| timer and responses - from the authenticators: - - If the |adjustedTimeout| timer expires, then for each entry in |issuedRequests| invoke the authenticatorCancel - operation on that authenticator and remove its entry from the list. - - If any authenticator returns a status indicating that the user cancelled the operation, delete that authenticator's - entry from |issuedRequests|. For each remaining entry in |issuedRequests| invoke the authenticatorCancel - operation on that authenticator and remove its entry from the list. - - If any authenticator returns an error status, delete the corresponding entry from |issuedRequests|. - - If any authenticator indicates success: - - Remove this authenticator's entry from |issuedRequests|. - - Create a new {{ScopedCredentialInfo}} object named |value| and populate its fields with the values returned from the - authenticator as well as the {{ScopedCredentialInfo/clientDataJSON}} computed earlier. - - For each remaining entry in |issuedRequests| invoke the authenticatorCancel operation on that authenticator and - remove its entry from the list. - - Resolve |promise| with |value| and terminate this algorithm. +
+ +
If any |authenticator| returns an error status,
+ +
[=set/Remove=] |authenticator| from |issuedRequests|.
+ +
If any |authenticator| indicates success,
+ +
+ 1. [=set/Remove=] |authenticator| from |issuedRequests|. + 2. Let |value| be a new {{ScopedCredentialInfo}} object whose fields are: + : {{ScopedCredentialInfo/clientDataJSON}} + :: A new {{ArrayBuffer}} containing the bytes of |clientDataJSON|. + : {{ScopedCredentialInfo/attestationObject}} + :: A new {{ArrayBuffer}} containing the bytes of the value returned + from the successful [=authenticatorMakeCredential=] operation + 3. [=set/For each=] remaining |authenticator| in |issuedRequests| invoke the + authenticatorCancel operation on |authenticator| and + [=set/remove=] it from |issuedRequests|. + 4. [=Resolve=] |promise| with |value| and terminate this algorithm. + +
+ +16. [=Reject=] |promise| with a {{DOMException}} whose name is + "{{NotAllowedError}}". -12. Reject |promise| with a DOMException whose name is "NotAllowedError", and terminate this algorithm. + Issue: {{NotAllowedError}} seems incorrect for at least the timeout and + cancelled exit conditions above. During the above process, the user agent SHOULD show some UI to the user to guide them in the process of selecting and authorizing an authenticator. @@ -531,7 +622,7 @@ When this method is invoked, the user agent MUST execute the following algorithm execute a platform-specific procedure to determine which, if any, credentials listed in {{AssertionOptions/allowList}} might be present on this authenticator, and set |credentialList| to this filtered list. If no such filtering is possible, set |credentialList| to an empty list. - - For each credential C within the |credentialList| that has a non-empty |transports| list, optionally use only the + - For each credential C within the |credentialList| that has a non-empty {{transports}} list, optionally use only the specified transports to get assertions using credential C. - If the above filtering process concludes that none of the credentials on the {{AssertionOptions/allowList}} can possibly be on this authenticator, do not perform any of the following steps for this authenticator, and proceed to the next @@ -2104,7 +2195,7 @@ Note: Extensions should aim to define authenticator arguments that are as small over low-bandwidth links such as Bluetooth Low-Energy or NFC. -## Extending client processing ## {#extension-client-processing} +## Extending client processing ## {#extension-client-processing} Extensions may define additional processing requirements on the client platform during the creation of credentials or the generation of an assertion. In order for the [RP] to verify the processing took place, or if the processing has a result