Skip to content

Commit

Permalink
Make makeCredential() more precise.
Browse files Browse the repository at this point in the history
I've linked a lot more terms, reordered explanations to be clearer, and
specified some missing behavior.
  • Loading branch information
jyasskin committed Feb 15, 2017
1 parent 233ff10 commit a1c674c
Showing 1 changed file with 141 additions and 50 deletions.
191 changes: 141 additions & 50 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ Markup Shorthands: css off, markdown on

<pre class="anchors">

spec: ECMAScript; urlPrefix: https://tc39.github.io/ecma262/#
type: method
for: JSON; text: stringify; url: sec-json.stringify

<!-- spec: HTML; urlPrefix: https://html.spec.whatwg.org/multipage/ -->
spec: HTML51; urlPrefix: http://www.w3.org/TR/html51/; for: web
type: dfn
Expand All @@ -61,6 +65,11 @@ spec: Encoding; urlPrefix: https://www.w3.org/TR/encoding/;
text: UTF-8 Encoded; url: utf-8-encode
</pre> <!-- class=anchors -->

<pre class="link-defaults">
spec:infra; type:dfn; text:list
spec:webidl; type:interface; text:Promise
</pre>


# Introduction # {#intro}

Expand Down Expand Up @@ -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.

<div class="note">
This method takes the following parameters:

- The <dfn>accountInformation</dfn> parameter specifies information about the user account for which the credential is being
Expand All @@ -397,20 +407,28 @@ This method takes the following parameters:
- The optional <dfn dfn-for="makeCredential()">options</dfn> parameter specifies additional options, as described in
[[#credential-options]].

</div>

When this method is invoked, the user agent MUST execute the following algorithm:

1. If the {{ScopedCredentialOptions/timeoutSeconds}} member of {{options}} is <a>present</a>, check if its value lies within
a reasonable range as defined by the platform and if not, correct it to the closest value lying within that range. Set
|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 <a data-lt="Promises">Promise</a>. 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 <a link-for='web'>current settings object</a>'s <a link-for='web'>origin</a>. If |callerOrigin| is
an <a link-for='web'>opaque origin</a>, reject |promise| with a <a>DOMException</a> 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 <a>present</a>, then set |rpId| to |callerOrigin|.
- If the {{ScopedCredentialOptions/rpId}} member of {{options}} is <a>present</a>, then invoke the procedure used for
Expand All @@ -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
<a>DOMException</a> 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 <a>normalizing an algorithm</a> [[!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 <code>|current|.{{ScopedCredentialParameters/type}}</code> does not
contain a {{ScopedCredentialType}} supported by this implementation,
then [=continue=].
- Let |normalizedAlgorithm| be the result of <a>normalizing an algorithm</a>
[[!WebCryptoAPI]], with |alg| set to
<code>|current|.{{algorithm}}</code> 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
<code>|current|.{{ScopedCredentialParameters/type}}</code> 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
<a>present</a>, then [=map/for each=] |extension| → |argument| of
<code>{{options}}.{{ScopedCredentialOptions/extensions}}</code>:
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}}
:: <code>{{options}}.{{ScopedCredentialOptions/extensions}}</code>

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
<code>|clientData|.{{hashAlg}}</code>.

12. Let |issuedRequests| and |currentlyAvailableAuthenticators| be new [=ordered
sets=].

13. For each |authenticator| currently available on this platform, if
<code>{{options}}.{{ScopedCredentialOptions/attachment}}</code> 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 <code>{{options}}.{{ScopedCredentialOptions/excludeList}}</code>:
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
<code>|C|.{{transports}}</code>, 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 <a>authenticatorMakeCredential</a> 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 <a>present</a>, 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:
<dl class="switch">

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 <a>clientDataHash</a>.
<dt>If the |adjustedTimeout| timer expires,</dt>

8. Initialize |issuedRequests| and |currentlyAvailableAuthenticators| to empty lists.
<dd>[=set/For each=]
|authenticator| in |issuedRequests| invoke the
<a>authenticatorCancel</a> operation on |authenticator| and
[=set/remove=] |authenticator| from |issuedRequests|.</dd>

9. For each authenticator currently available on this platform, add the authenticator to |currentlyAvailableAuthenticators|
unless the {{ScopedCredentialOptions/attachment}} member of {{options}} is <a>present</a>. In that case, let |attachment|
be {{ScopedCredentialOptions/attachment}}, and add the authenticator to |currentlyAvailableAuthenticators| if its attachment
modality matches |attachment|.
<dt>If any |authenticator| returns a status indicating that the user cancelled the operation,</dt>

10. For each authenticator in |currentlyAvailableAuthenticators|: asynchronously invoke the <a>authenticatorMakeCredential</a>
operation on that authenticator with |rpId|, <a>clientDataHash</a>, {{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|.
<dd>
1. [=set/Remove=] |authenticator| from |issuedRequests|.
2. [=set/For each=] remaining |authenticator| in |issuedRequests| invoke
the <a>authenticatorCancel</a> 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 <a>authenticatorCancel</a>
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 <a>authenticatorCancel</a>
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 <a>authenticatorCancel</a> operation on that authenticator and
remove its entry from the list.
- Resolve |promise| with |value| and terminate this algorithm.
</dd>

<dt>If any |authenticator| returns an error status,</dt>

<dd>[=set/Remove=] |authenticator| from |issuedRequests|.</dd>

<dt>If any |authenticator| indicates success,</dt>

<dd>
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
<a>authenticatorCancel</a> operation on |authenticator| and
[=set/remove=] it from |issuedRequests|.
4. [=Resolve=] |promise| with |value| and terminate this algorithm.

</dd>

16. [=Reject=] |promise| with a {{DOMException}} whose name is
"{{NotAllowedError}}".

12. Reject |promise| with a <a>DOMException</a> 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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 <dfn>client processing</dfn> ## {#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 <a>[RP]</a> to verify the processing took place, or if the processing has a result
Expand Down

0 comments on commit a1c674c

Please sign in to comment.