From 04dd9e842540167d86791775972389e5f3022389 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Wed, 6 Sep 2023 16:01:50 -0700 Subject: [PATCH 01/10] Remove `devicePubKey` extension. --- index.bs | 448 ------------------------------------------------------- 1 file changed, 448 deletions(-) diff --git a/index.bs b/index.bs index 28eaedd3d..29547c613 100644 --- a/index.bs +++ b/index.bs @@ -1203,15 +1203,6 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S : {{PublicKeyCredentialDescriptor/transports}} :: The [$credential record/transports$] of the [=credential record=]. -: Hardware-bound Device Key Pair -: Device-bound Key -: Device Private Key -: Device Public Key -:: A [=hardware-bound device key pair=], also known as a [=device-bound key=], is an [=authenticator=]-, [=[RP]=]-, and [=user credential=]-specific public key pair created upon a [=[RP]=]'s request via the [=devicePubKey=] [=WebAuthn extension=]. - The [=authenticator=] that a [=hardware-bound device key pair=] is created upon guarantees that the [=device private key=] is securely stored in hardware, i.e., it is unextractable. See also [[#sctn-device-publickey-extension]]. - - Note: All guarantees about the operation of an [=authenticator=] operation rely on [=attestation=]. In particular, [=[RPS]=] MUST NOT rely on the above guarantee of unextractability unless supported by a valid, trusted [=attestation statement=]. - : Generating Authenticator :: The Generating Authenticator is the authenticator involved in the [=authenticatorMakeCredential=] operation resulting in the creation of a given [=public key credential source=]. The [=generating authenticator=] is the same as the [=managing authenticator=] @@ -5437,8 +5428,6 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o Note: Since all extensions are OPTIONAL for both the [=client=] and the [=authenticator=], the [=[RP]=] MUST also be prepared to handle cases where none or not all of the requested extensions were acted upon. - - Note: The [=devicePubKey=] extension has explicit verification procedures, see [[#sctn-device-publickey-extension-verification-create]]. 1. Determine the attestation statement format by performing a USASCII case-sensitive match on |fmt| against the set of @@ -5670,8 +5659,6 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o Note: Since all extensions are OPTIONAL for both the [=client=] and the [=authenticator=], the [=[RP]=] MUST also be prepared to handle cases where none or not all of the requested extensions were acted upon. - - Note: The [=devicePubKey=] extension has explicit verification procedures, see [[#sctn-device-publickey-extension-verification-get]]. 1. Let |hash| be the result of computing a hash over the |cData| using SHA-256. @@ -7088,441 +7075,6 @@ This extension enables use of a user verification method. -### Device-bound public key extension (devicePubKey) ### {#sctn-device-publickey-extension} - -This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal for [=backup eligible=] credentials. This is done by creating a [=user credential=]-specific [=hardware-bound device key pair=] on the [=authenticator=], if such a key pair does not already exist for the [=user credential=] being created or exercised, and returning the [=device public key=] along with a signature by the [=device private key=] to the [=[RP]=]. This is done each time this [=devicePubKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/get()|navigator.credentials.get()}} call. - -If the [=authenticator=] is incapable of generating a [=hardware-bound device key pair=], or the registration or authentication operation fails for any reason, this extension is ignored and no [=hardware-bound device key pair=] is created. In this case, there is no [=devicePubKey=] extension output generated. - -The [=hardware-bound device key pair=] is not on its own a [=user credential=] and does not have its own [=credential ID=]. Instead, the returned [=device public key=] is a device-specific contextual attribute of its associated [=user credential=]. That is, when that [=user credential=] is used—along with the [=devicePubKey=] extension—on a particular [=authenticator=], a particular [=device public key=] is returned by the extension, along with a signature demonstrating proof-of-possession of the [=device private key=] by that device. - - -#### Relying Party Usage #### {#sctn-device-publickey-extension-usage} - -This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension provides a "device continuity" signal when used consistently with both {{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations: - -When a [=[RP]=] uses the `devicePubKey` extension with a {{CredentialsContainer/create()|create()}} call to create a new [=user credential=], a signature by a new [=device-bound key=] ("dpk 1") is returned along with the new [=device public key=]. Even if the [=user credential=] is [=backed up=], "dpk 1" never leaves the [=generating authenticator=] ("authenticator 1"). The [=[RP]=]'s subsequent {{CredentialsContainer/get()|get()}} operations using the same user credential with that same [=authenticator=] generate [=assertions=] including further signatures by the same [=device-bound key=] ("dpk 1"). This behavior on "authenticator 1" is independent of whether this user credential has been copied to any other [=authenticator=]. - -Then, if this same user credential is copied to a different [=authenticator=] ("authenticator 2"), the [=[RP]=]'s first {{CredentialsContainer/get()|get()}} call on "authenticator 2" (that includes the `devicePubKey` extension) will produce an [=assertion=] including a signature by a new [=device-bound key=] ("dpk 2"). Note that such a [=multi-device credential=] can be exercised on "authenticator 2" without a {{CredentialsContainer/create()|create()}} having been performed on "authenticator 2". The [=[RP]=]'s subsequent {{CredentialsContainer/get()|get()}} calls on "authenticator 2", using the `devicePubKey` extension and the same user credential, yield further signatures by "dpk 2". - -A usage example is thus: - -> Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with an assertion by a [=multi-device credential=] on its own. But if a signature by a [=device-bound key=] that is well established for this user can also be presented, then that may tip the balance. - -Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures before associating and storing extension output value fields in conjunction with the [=user account=]. See [[#sctn-device-publickey-extension-verification]]. - -The weight that [=[RPS]=] give to the presence of a signature from a [=device-bound key=] may be based on information learned from its optional attestation. An attestation can indicate the level of protection that the hardware offers the private key, certifications for the hardware, etc. - -#### Extension Definition #### {#sctn-device-publickey-extension-definition} - -: Extension identifier -:: `devicePubKey` - -: Operation applicability -:: [=registration extension|Registration=] and [=authentication extension|authentication=] - -: Client extension input -:: - dictionary AuthenticationExtensionsDevicePublicKeyInputs { - DOMString attestation = "none"; - sequence<DOMString> attestationFormats = []; - }; - - partial dictionary AuthenticationExtensionsClientInputs { - AuthenticationExtensionsDevicePublicKeyInputs devicePubKey; - }; - -
- : attestation - :: The [=[RP]=] MAY use this OPTIONAL member to specify a preference regarding [=attestation conveyance=]. - Its value SHOULD be a member of {{AttestationConveyancePreference}}. - [=Client platforms=] MUST ignore unknown values, treating an unknown value as if the [=map/exist|member does not exist=]. - - The default value is {{AttestationConveyancePreference/none}}. - - : attestationFormats - :: The [=[RP]=] MAY use this OPTIONAL member to specify a preference regarding the [=attestation=] statement format used by the [=authenticator=]. - Values SHOULD be taken from the IANA "WebAuthn Attestation Statement Format Identifiers" registry [[!IANA-WebAuthn-Registries]] established by [[!RFC8809]]. - Values are ordered from most preferable to least preferable. - This parameter is advisory and the [=authenticator=] MAY use an attestation statement not enumerated in this parameter. - - The default value is the empty list, which indicates no preference. -
- - Note: To request the `devicePubKey` extension processing with default options, pass an empty dictionary as the input. - -: Client extension processing -:: If {{AuthenticationExtensionsClientInputs/devicePubKey}} is present, the client creates the authenticator extension input from the client extension input. - -: Client extension output -:: An ArrayBuffer containing the signature returned as the [=unsigned extension output=]. - - dictionary AuthenticationExtensionsDevicePublicKeyOutputs { - ArrayBuffer signature; - }; - - partial dictionary AuthenticationExtensionsClientOutputs { - AuthenticationExtensionsDevicePublicKeyOutputs devicePubKey; - }; - - - -: Authenticator extension input -:: A CBOR expression of the client extension input - - ``` - devicePublicKeyInputs = { - attestation: "none" / "indirect" / "direct" / "enterprise", - attestationFormats: [tstr], - } - $$extensionInput //= ( - devicePubKey: devicePublicKeyInputs, - ) - ``` - -: Authenticator extension output -:: The device public key attestation object, defined by the `attObjForDevicePublicKey` type: - - ``` - $$extensionOutput //= ( - devicePubKey: attObjForDevicePublicKey, - ) - - attObjForDevicePublicKey = { ; Note: This object conveys an attested - ; device public key and is analogous to \`attObj\`. - - aaguid: bstr, ; Authenticator's AAGUID (16 bytes fixed-length) - ; https://www.w3.org/TR/webauthn/#aaguid - - dpk: bstr, ; The Device Public Key (self-describing variable length, - ; COSE_Key format, CBOR-encoded)). - - ; Whether this key is scoped to the entire device, or a loosely-defined, - ; narrower scope called "app". For example, a "device"-scoped key is expected - ; to be the same between an app and a browser on the same device, while - ; an "app"-scoped key would probably not be. - ; - ; Whatever the scope, a device key is still specific to a given credential - ; and does not provide any ability to link credentials. - ; - ; Whether device-scoped or not, keys are still device-bound. I.e. an - ; app-scoped key does not enjoy lesser protection from extraction. - - scope: uint .size 1, ; A value of 0x00 means "entire device" ("all apps") - ; scope. 0x01 means "per-app" scope. - ; Values other than 0x00 or 0x01 are reserved for future - ; use. - - ; An authenticator-generated random nonce for inclusion in the attestation - ; signature. If the authenticator chooses to not generate a nonce, it sets this - ; to a zero-length byte string. See the note below about "randomNonce" for a - ; discussion on the nonce's purpose. - - nonce: bstr .size (0..32), - - ; See https://www.w3.org/TR/webauthn/#sctn-generating-an-attestation-object - ; - ; Attestation statement formats define the \`fmt\` and \`attStmt\` members of - ; $$attStmtType. - ; Note that \`fmt\` and \`attStmt\` are top-level members of - ; \`attObjForDevicePublicKey\`. - ; - ; In summary, the \`attStmt\` will (typically) contain: - ; (1) a SIGNATURE value calculated (using the attestation private key) - ; over (prefix || aaguid || dpk || nonce) where \`prefix\` is - ; h'64657669636520626f756e64206b6579206174746573746174696f6e20736967 - ; 00ffffffff'. - ; (See the attestation calculations section, below, for a discussion - ; about the purpose of this \`prefix\` value.) - ; (2) the attestation certificate or public key, and supporting certificates, - ; if any. - ; - ; Note that there are details dependent upon the particular attestation - ; statement format. - ; See https://www.w3.org/TR/webauthn/#sctn-defined-attestation-formats. - - $$attStmtType, - - ; An optional boolean that indicates whether the attestation statement - ; contains uniquely identifying information. This can only be true - ; when the \`attestation\` field of the extension input is "enterprise" - ; and either the user-agent or the authenticator permits uniquely - ; identifying attestation for the requested RP ID. - - ? epAtt: bool .default false, - } - - ``` - -: Unsigned extension output -:: A CBOR byte string containing a signature generated with the device private key. - -: Authenticator extension processing -:: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: - 1. Create or select the [=public key credential source=] as usual (see [[#sctn-op-make-cred]], or [[#sctn-op-get-assertion]] as appropriate). - - 1. If the [=public key credential source=] is not [=backup eligible=] then terminate these processing steps: this extension only applies to [=multi-device credentials=]. - - 1. If a [=hardware-bound device key pair=] does not already exist for this {[=public key credential source/id|Credential ID=], [=public key credential source/rpId|RP ID=], [=public key credential source/rpId|userHandle=]} tuple on the [=authenticator=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing [=device-bound key=]. - - 1. Let |attFormat| be the chosen [=attestation statement format=], and |attAaguid| be a 16-byte value, based on the value of {{AuthenticationExtensionsDevicePublicKeyInputs/attestation}} in the extension input: - -
- : none - :: |attFormat| is "none" or "self", at the authenticator's discretion, and |attAaguid| is 16 zero bytes. (Note that, since the [=device-bound key=] is already exercised during {{CredentialsContainer/get()|navigator.credentials.get()}} calls, the proof-of-possession property provided by "self" attestation is superfluous in that context.) - - : indirect, direct - :: |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsDevicePublicKeyInputs/attestationFormats}}, and |attAaguid| is the [=authenticator's=] [=AAGUID=]. (Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust, although they do not have to be. This is in contrast to the associated [=user credential=]'s attestation, if it is a [=multi-device credential=].) - - : enterprise - :: The [=[RP]=] wants to receive an [=attestation statement=] that may include uniquely identifying information. This is intended for controlled deployments within an enterprise where the organization wishes to tie registrations to specific authenticators. [=Authenticators=] MUST NOT provide such an attestation unless the user agent or authenticator configuration expressly permits it for the requested [=RP ID=]. If not permitted, then |attFormat| is "none" and |attAaguid| is 16 zero bytes. Otherwise |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsDevicePublicKeyInputs/attestationFormats}}, and |attAaguid| is the [=authenticator's=] [=AAGUID=]. (Again, since the [=hardware-bound device key pair=] is specific to a particular authenticator, the attestation may be tied to hardware roots of trust.) - - Note: CTAP2 does not currently provide for an enterpriseAttestation signal during an authenticatorGetAssertion call. Until that is changed, platform-managed enterprise attestation will not work in that context with CTAP2 [=authenticators=]. -
- - 1. Let |dpk| be the newly created or existing [=device public key=], in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=] when the latter is conveyed in [=attested credential data=]. - - 1. Let |devicePrivateKey| be the newly created or existing [=device private key=]. - - 1. Let |randomNonce| be a fresh randomly-generated byte string of 32 bytes maximum length, or a zero length byte string if the authenticator chooses to not generate a nonce. - - Note: |randomNonce|'s purpose is to randomize the `devicePubKey` extension's [=attestation signature=] value. If this is not done, then the `devicePubKey` extension's [=attestation signature=] value remains constant for all such signatures issued on behalf of this [=user credential=], possibly exposing the [=authenticator=]'s [=attestation private key=] to side-channel attacks. The randomness-generation mechanism should be carefully chosen by the authenticator implementer. - - 1. Let the `devicePubKey` [=authenticator extension output=] value be a [=CBOR=] map as defined by `attObjForDevicePublicKey` above, with keys and values as follows: - - Note: as with all CBOR structures used in this specification, the [=CTAP2 canonical CBOR encoding form=] MUST be used. - - 1. Let the `aaguid` key's value be |attAaguid|. - - 1. Let the `dpk` key's value be |dpk|. - - 1. Let the `nonce` key's value be |randomNonce|. - - 1. Let the `scope` key have the value zero (0x00) if this is an "entire device" [=device public key=]. Otherwise, let `scope` have the value one (0x01), indicating a more narrow per-app scope. - - 1. Let the values of the `$$attStmtType` [=group socket=] be the result of generating an [=attestation statement=] with the [=attestation statement format=], |attFormat|. See [[#sctn-device-publickey-attestation-calculations]]. - - Note: The details of the `$$attStmtType` values are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust (although they do not have to be). This is in contrast to the associated [=user credential=]'s attestation, if it is a [=multi-device credential=]. - - 1. If the `$$attStmtType` [=group socket=] contains uniquely identifying information then let `epAtt` be [TRUE]. Otherwise omit the `epAtt` field. (This field can only be [TRUE] when the {{AuthenticationExtensionsDevicePublicKeyInputs/attestation}} field of the extension input is "enterprise" and either the user-agent or the authenticator permits uniquely identifying attestation for the requested RP ID.) - - 1. Let |dpkSig| be the result of signing the [=assertion signature=] [input](#fig-signature) with |devicePrivateKey|. - - Note: the [=assertion signature=] [input](#fig-signature), and thus |dpkSig|, covers the [=[RP]=]'s {{PublicKeyCredentialCreationOptions/challenge}} because it includes the [=hash of the serialized client data=]. Thus the [=[RP]=] knows that |dpkSig| is a fresh signature. - - 1. Output |dpkSig| as the extension's [=unsigned extension output=]. - - Note: |dpkSig| cannot be included in the [=authenticator extension output=] because it is returned inside the [=authenticator data=] and that would imply that the signature signs over itself. - -##### AAGUIDs ##### {#sctn-device-publickey-attestation-aaguid} - -The [=AAGUID=] included in the [=devicePubKey=] extension output, if non-zero, identifies the make or model of hardware that is storing the [=device-bound key=]. This is distinct from the [=AAGUID=] in the [=attested credential data=] of a [=multi-device credential=], which likely identifies something broader since such credentials are not bound to a single device. Thus the two AAGUIDs MAY be different in a single response and either, or both, may be zero depending on the options requested and authenticator behaviour. - -##### Attestation calculations ##### {#sctn-device-publickey-attestation-calculations} - -When computing attestations, the process in [[#sctn-generating-an-attestation-object]] takes two inputs: `authData` and `hash`. When calculating an attestation for a [=device-bound key=], the typical value for `hash` hashes over the attestation signature itself, which is impossible. Also the attestation of a [=device-bound key=] is potentially used repeatedly, thus may want to be cached. But signing over values that include [=[RP]=]-chosen nonces, like the [=hash of the serialized client data=], makes that impossible. - -Therefore when calculating an attestation for a [=device-bound key=], the inputs are: - - * For `authData`, substitute the concatenation of the byte string h'64657669636520626f756e64206b6579206174746573746174696f6e2073696700ffffffff' and the value of |aaguid| from the extension output. - * For `hash`, substitute the concatenation of the |dpk| and |nonce| fields from the extension output. (The nonce MAY be empty.) - -The attestation signature is thus typically calculated over the bytes of (h'64657669636520626f756e64206b6579206174746573746174696f6e2073696700ffffffff' || |aaguid| || |dpk| || |nonce|). The 37-byte prefix ensures domain separation: it takes the place of the RP ID hash, flags, and signature counter fields in those messages and ensures that no attestation signature for a [=device-bound key=] can be confused with a signature for a [=user credential=]. - -Note that when |nonce| is empty, then the (signed) authenticator extension output MAY be constant. However, the (unsigned) |dpkSig| output is always unique and prevents replay of the (signed) extension output without knowledge of the [=device private key=]. - -#### `devicePubKey` Extension Output Verification Procedures #### {#sctn-device-publickey-extension-verification} - -Verifying the [=devicePubKey=] extension output is performed by the [=[RP]=] whenever a [=device public key=] is returned within the extension output. - -The [=devicePubKey=] extension adds the following [=struct/item=] to [=credential records=]: - -
- : devicePubKeys - :: An initially [=set/empty=] [=set=] of [=device-bound key records=] associated with this [=public key credential source=]. - - A device-bound key record is an abstract representation of a registered [=device-bound key=]. - It is a [=struct=] with the following [=struct/items=]: - -
- : aaguid - :: The [=AAGUID=] of the [=device-bound key=]'s [=managing authenticator=]. - This MAY be different from the [=AAGUID=] in the [$credential record/attestationObject$], if any, of the containing [=credential record=]. - - : dpk - :: The public key portion of the [=device-bound key=]. - - : scope - :: The scope of the [=device-bound key=]. See [[#sctn-device-publickey-extension-definition]] for details. - - : fmt - :: The [=attestation statement format=] of the [=device-bound key=]'s [=attestation statement=]. - - : attStmt - :: The [=device-bound key=]'s [=attestation statement=]. -
-
- - -##### Registration (`create()`) ##### {#sctn-device-publickey-extension-verification-create} - -If the [=[RP]=] requested the `devicePubKey` extension in a {{CredentialsContainer/create()|navigator.credentials.create()}} call, -then the below verification steps are performed in the context of [step 19](#reg-ceremony-verify-extension-outputs) -of [[#sctn-registering-a-new-credential]] using these variables established therein: |credential|, |clientExtensionResults|, |authData|, and |hash|. -[=[RP]=] policy may specify whether a response without a `devicePubKey` extension output is acceptable. - -1. Verify that {{AuthenticationExtensionsClientOutputs/devicePubKey}} member of |clientExtensionResults| exists, and contains the {{AuthenticationExtensionsDevicePublicKeyOutputs/signature}} field. - -1. Let |attObjForDevicePublicKey| be the value of the `devicePubKey` member of the [=authenticator extension output=] from |authData|. - -1. Extract the contained fields from |attObjForDevicePublicKey|: |aaguid|, |dpk|, |scope|, |nonce|, |fmt|, |attStmt|. - - Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of the top-level [=attestation object=]. - -1. Verify that {{AuthenticationExtensionsDevicePublicKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) (i.e. `authData` and `hash`) by the [=device public key=] |dpk|. (The signature algorithm is the same as for the [=user credential=].) - -1. Optionally, if attestation was requested and the [=[RP]=] wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. [=[RP]=] policy may specifiy which attestations are acceptable. - - Note: If |fmt|'s value is "[=none=]" there is no attestation signature to verify. - -1. Create a new [=device-bound key record=] with the contents: - -
- : [$devicePubKey record/aaguid$] - :: The value of |aaguid|. - - : [$devicePubKey record/dpk$] - :: The value of |dpk|. - - : [$devicePubKey record/scope$] - :: The value of |scope|. - - : [$devicePubKey record/fmt$] - :: The value of |fmt|. - - : [$devicePubKey record/attStmt$] - :: The value of |attStmt|. -
- - In [step 26](#reg-ceremony-store-credential-record) of [[#sctn-registering-a-new-credential]], - add this [=device-bound key record=] to the [$credential record/devicePubKeys$] member of the new [=credential record=]. - -See also [[#sctn-device-publickey-extension-usage]] for further details. - -##### Authentication (`get()`) ##### {#sctn-device-publickey-extension-verification-get} - -If the [=[RP]=] requested the `devicePubKey` extension in a {{CredentialsContainer/get()|navigator.credentials.get()}} call, -then the below verification steps are performed in the context of [step 17](#authn-ceremony-verify-extension-outputs) -of [[#sctn-verifying-assertion]] using these variables established therein: |credential|, |clientExtensionResults|, |authData|, |hash|, and |credentialRecord|. -[=[RP]=] policy may specify whether a response without a `devicePubKey` extension output is acceptable. - -1. Verify that {{AuthenticationExtensionsClientOutputs/devicePubKey}} member of |clientExtensionResults| exists, and contains the {{AuthenticationExtensionsDevicePublicKeyOutputs/signature}} field. - -1. Let |attObjForDevicePublicKey| be the value of the `devicePubKey` member of the [=authenticator extension output=] from |authData|. - -1. Extract the contained fields from |attObjForDevicePublicKey|: |aaguid|, |dpk|, |scope|, |nonce|, |fmt|, |attStmt|. - - Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of [=authenticator data=]. - -1. Verify that {{AuthenticationExtensionsDevicePublicKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) (i.e. `authData` and `hash`) by the [=device public key=] |dpk|. (The signature algorithm is the same as for the [=user credential=].) - -1. Let |matchedDpkRecords| be a new [=set/empty=] [=set=]. - [=set/For each=] |dpkRecord| in |credentialRecord|.[$credential record/devicePubKeys$]: - 1. If |dpkRecord|.[$devicePubKey record/aaguid$] equals |aaguid|, - |dpkRecord|.[$devicePubKey record/dpk$] equals |dpk|, - and |dpkRecord|.[$devicePubKey record/scope$] equals |scope|: - 1. [=set/Append=] |dpkRecord| to |matchedDpkRecords|. - -1. If |matchedDpkRecords| - -
- : has [=set/size=] greater than one: - :: Some form of error has occurred. It is indeterminate whether this is a known device. Terminate these verification steps. - - : has [=set/size=] equal to one: - :: This is likely a known device. - - If |fmt|'s value is "none" then there is no attestation signature to verify and this is a known [=device public key=] with a valid signature and thus a known device. Terminate these verification steps. - - Otherwise, let |dpkRecord| be |matchedDpkRecords|[0]. If the |attStmt| in |attObjForDevicePublicKey|: -
- : equals |dpkRecord|.[$devicePubKey record/attStmt$] by binary equality: - :: This is a known [=device public key=] with a valid signature and valid attestation and thus a known device. Terminate these verification steps. - - Note: This authenticator is not generating a fresh per-response random nonce. - - : does not equal |dpkRecord|.[$devicePubKey record/attStmt$] by binary equality: - :: Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. [=[RP]=] policy may specifiy which attestations are acceptable. - - If the result is: - -
- : successful - :: This is a known [=device public key=] with a valid signature and valid attestation and thus a known device. Terminate these verification steps. - - : unsuccessful - :: Some form of error has occurred. It is indeterminate whether this is a known device. Terminate these verification steps. -
-
- - : is [=set/empty=]: - :: This is possibly a new [=device public key=] signifying a new device. - - 1. Let |matchedDpkKeys| be a new [=set/empty=] [=set=]. - [=set/For each=] |dpkRecord| in |credentialRecord|.[$credential record/devicePubKeys$]: - 1. If |dpkRecord|.[$devicePubKey record/dpk$] equals |dpk|: - 1. [=set/Append=] |dpkRecord| to |matchedDpkKeys|. - - 1. If |matchedDpkKeys| is [=set/empty=]: - -
- : If |fmt|'s value is "none": - :: There is no attestation signature to verify and this is a new device. Unless [=[RP]=] policy specifies that this attestation is unacceptable, [$Create a new device-bound key record$] and then terminate these verification steps. - - : Otherwise: - :: Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. [=[RP]=] policy may specifiy which attestations are acceptable. - - If the result is: - -
- : successful - :: This is a new [=device public key=] signifying a new device. [$Create a new device-bound key record$], then terminate these verification steps. - - : unsuccessful - :: Some form of error has occurred. It is indeterminate whether this is a valid new device. Terminate these verification steps. -
-
- - 1. Otherwise there is some form of error: we recieved a known |dpk| value, but one or more of the accompanying |aaguid| or |scope| values did not match what the [=[RP]=] has stored along with that |dpk| value. Terminate these verification steps. -
- -See also [[#sctn-device-publickey-extension-usage]]. - -To Create a new device-bound key record, perform the following steps: - - 1. Create a new [=device-bound key record=] with the contents: - -
- : [$devicePubKey record/aaguid$] - :: The value of |aaguid|. - - : [$devicePubKey record/dpk$] - :: The value of |dpk|. - - : [$devicePubKey record/scope$] - :: The value of |scope|. - - : [$devicePubKey record/fmt$] - :: The value of |fmt|. - - : [$devicePubKey record/attStmt$] - :: The value of |attStmt|. -
- - In [step 22](#authn-ceremony-update-credential-record) of [[#sctn-verifying-assertion]], - [=set/append=] this [=device-bound key record=] to |credentialRecord|.[$credential record/devicePubKeys$]. - - # User Agent Automation # {#sctn-automation} For the purposes of user agent automation and [=web application=] testing, this document defines a number of [[WebDriver]] [=extension commands=]. From b7c3b0ee62d92a2c776a55fe011b2320058fc5df Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Wed, 6 Sep 2023 16:02:11 -0700 Subject: [PATCH 02/10] Add `supplementalPubKeys` extension. This extension is very similar the previous `devicePubKeys` extension. The difference is that `supplementalPubKey` allows for one _or two_ supplemental keys, and while supplemental keys can be device bound, they can also have "provider" scope, which is defined by their attestation statement. --- index.bs | 488 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 488 insertions(+) diff --git a/index.bs b/index.bs index 29547c613..d1828e6ef 100644 --- a/index.bs +++ b/index.bs @@ -1203,6 +1203,11 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S : {{PublicKeyCredentialDescriptor/transports}} :: The [$credential record/transports$] of the [=credential record=]. +: Supplemental public key +:: A additional public key, associated with a [=user credential=], that can be obtained (and exercised) with the [=supplementalPubKeys=] extension. These public keys provide more specific continuity signals. For example, a supplemental public key with `device` scope never leaves the device, while a [=multi-device credential=] can. Other supplemental public keys might have a broader scope, as described by their [=attestation=]. + + Supplemental public keys MUST NOT have a scope that exceeds the scope of their [=user credential=]—i.e. they never link two [=user credentials=] together. While this specification defines norms around scopes, [=[RPS]=] have to evaluate supplemental public keys in light of their [=attestation=], if any, and assign credibility to claimed scopes accordingly. + : Generating Authenticator :: The Generating Authenticator is the authenticator involved in the [=authenticatorMakeCredential=] operation resulting in the creation of a given [=public key credential source=]. The [=generating authenticator=] is the same as the [=managing authenticator=] @@ -5428,6 +5433,8 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o Note: Since all extensions are OPTIONAL for both the [=client=] and the [=authenticator=], the [=[RP]=] MUST also be prepared to handle cases where none or not all of the requested extensions were acted upon. + + Note: The [=supplementalPubKeys=] extension has explicit verification procedures, see [[#sctn-supplemental-public-keys-extension-verification-create]]. 1. Determine the attestation statement format by performing a USASCII case-sensitive match on |fmt| against the set of @@ -5659,6 +5666,8 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o Note: Since all extensions are OPTIONAL for both the [=client=] and the [=authenticator=], the [=[RP]=] MUST also be prepared to handle cases where none or not all of the requested extensions were acted upon. + + Note: The [=supplementalPubKeys=] extension has explicit verification procedures, see [[#sctn-supplemental-public-keys-extension-verification-get]]. 1. Let |hash| be the result of computing a hash over the |cData| using SHA-256. @@ -7075,6 +7084,485 @@ This extension enables use of a user verification method. +### Supplemental public keys extension (supplementalPubKeys) ### {#sctn-supplemental-public-keys-extension} + +This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with additional public key(s) for [=backup eligible=] credentials. This is done by creating [=user credential=]-specific key pairs on the [=authenticator=], when required key pairs do not already exist for the [=user credential=] being created or exercised, and returning these supplemental keys (along with signatures by them) to the [=[RP]=]. This is done each time this [=supplementalPubKeys=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/get()|navigator.credentials.get()}} call. + +If the [=authenticator=] is incapable of generating a supplemental key pair, or the registration or authentication operation fails for any reason, that key pair is omitted from the output. If no supplemental key pairs remain then this extension is ignored. In this case, there is no [=supplementalPubKeys=] extension output generated. + +Supplemental keys are not [=user credentials=] and they do not have their own [=credential IDs=]. Instead, any returned keys are a contextual attribute of their associated [=user credential=]. That is, when that [=user credential=] is used—along with the [=supplementalPubKeys=] extension—on a particular [=authenticator=], a particular set of supplemental keys are returned by the extension, along with signatures demonstrating proof-of-possession of those keys. + +Supplemental keys never have a scope that is larger than the [=user credential=]. That is, they are never shared between any two [=user credentials=] and can never be used to link two [=user credentials=] together. They only communicate continuity of some property of their associated [=user credential=]. + + +#### Relying Party Usage #### {#sctn-supplemental-public-keys-extension-usage} + +This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension signals the continuity of some property when used consistently with both {{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations. The property being signaled depends on the scope of the supplemental key and its [=attestation=] statement, if any. The clearest example of a supplemental key is one that is device-bound. Repeated observation of a device-bound supplemental key indicates that a [=backup eligible=] credential is being used repeatedly from the same device. But supplemental keys with wider scopes are also defined and the exact properties that they communicate depend on the attestation included along with them. + +When a [=[RP]=] uses the `supplementalPubKeys` extension with a {{CredentialsContainer/create()|create()}} call to create a new [=user credential=], a signature by one or more new supplemental keys may be returned along with the new supplemental public keys themselves. For the sake of example, assume that a single supplemental public key is returned, called “SPK1”. For as long as the [=user credential=] is exercised within the scope of the supplemental key, the [=[RP]=]'s subsequent {{CredentialsContainer/get()|get()}} operations for that credential will generate [=assertions=] including further signatures by “SPK1”. + +Since the credential is [=backup eligible=], it may move beyound the scope of that supplemental key and, if used in this new domain, the {{CredentialsContainer/get()|get()}} operation will return a new supplemental key, SPK2, and its signature. Subsequent {{CredentialsContainer/get()|get()}} operations may observe either SPK1 or SPK2, depending on the context in which the [=user credential=] is being used. + +A usage example is thus: + +> A sign-in request is received by a website that, by regulation, must require certain authentication standards. The sign-in is done with a [=multi-device credential=], but also includes a supplemental key with an attestation that states that the supplemental key is only synced after a user has met or exceeded those standards. Since that supplemental key has been seen before, and was initially verified to meet the site's authentication standards, additional sign-in challenges are not required. + +Another example of supplemental keys: + +> Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with an assertion by a [=multi-device credential=] on its own. But if a signature from a supplimental key that is device-bound, and that is well established for this user can also be presented, then that may tip the balance. + +The weight that [=[RPS]=] give to the presence of a signature from a supplemental key may be based on information learned from its optional attestation. An attestation can indicate the level of protection enjoyed by a hardware-bound key, or the policies for other types of supplemental key. + +#### Extension Definition #### {#sctn-supplemental-public-keys-extension-definition} + +: Extension identifier +:: `supplementalPubKeys` + +: Operation applicability +:: [=registration extension|Registration=] and [=authentication extension|authentication=] + +: Client extension input +:: + dictionary AuthenticationExtensionsSupplementalPublicKeysInputs { + required sequence<DOMString> scopes; + DOMString attestation = "indirect"; + sequence<DOMString> attestationFormats = []; + }; + + partial dictionary AuthenticationExtensionsClientInputs { + AuthenticationExtensionsSupplementalPublicKeysInputs supplementalPubKeys; + }; + +
+ : scopes + :: This required member specifies the scopes of supplemental public keys that the [=[RP]=] requests. Values are taken from the `scope` group in the CDDL below. (I.e. currently valid values are `provider` and `device`.) Specifying the scopes that a [=[RP]=] can use allows an [=authenticator=] to avoid the work of generating superfluous supplemental keys. + + : attestation + :: The [=[RP]=] MAY use this OPTIONAL member to specify a preference regarding [=attestation conveyance=]. + Its value SHOULD be a member of {{AttestationConveyancePreference}}. + [=Client platforms=] MUST ignore unknown values, treating an unknown value as if the [=map/exist|member does not exist=]. + + The default value is {{AttestationConveyancePreference/indirect}}. + + : attestationFormats + :: The [=[RP]=] MAY use this OPTIONAL member to specify a preference regarding the [=attestation=] statement format used by the [=authenticator=]. + Values SHOULD be taken from the IANA "WebAuthn Attestation Statement Format Identifiers" registry [[!IANA-WebAuthn-Registries]] established by [[!RFC8809]]. + Values are ordered from most preferable to least preferable. + This parameter is advisory and the [=authenticator=] MAY use an attestation statement not enumerated in this parameter. + + The default value is the empty list, which indicates no preference. +
+ +: Client extension processing +:: If {{AuthenticationExtensionsClientInputs/supplementalPubKeys}} is present, the client creates the authenticator extension input from the client extension input. + +: Client extension output +:: An ArrayBuffer containing the signature returned as the [=unsigned extension output=]. + + dictionary AuthenticationExtensionsSupplementalPublicKeysOutputs { + sequence<ArrayBuffer> signatures; + }; + + partial dictionary AuthenticationExtensionsClientOutputs { + AuthenticationExtensionsSupplementalPublicKeysOutputs supplementalPubKeys; + }; + + + +: Authenticator extension input +:: A CBOR expression of the client extension input + + ``` + supplementalPublicKeyInputs = { + scopes: [tstr], + attestation: tstr, + attestationFormats: [tstr], + } + $$extensionInput //= ( + supplementalPubKeys: supplementalPublicKeyInputs, + ) + ``` + +: Authenticator extension output +:: The supplemental public key attestation objects, defined by the `attObjForSupplementalPublicKey` type: + + ``` + $$extensionOutput //= ( + ; This array of supplemental public keys MUST be ordered + ; lexicographically by scope and MUST NOT include more than one element + ; with a given scope. + + supplementalPubKeys: [attObjForSupplementalPublicKey], + ) + + scope = ( + ; A key that has a broader scope than a single device, but still more + ; limited than a multi-device credential. The precise scope is specified + ; by the attestation of this supplemental public key. + + provider: true // + + ; A key with "device" scope MUST NOT leave the device on which it is + ; created. The value of this key communicates whether the key is scoped to + ; the entire device, or a loosely-defined, narrower scope called "per-app". + ; For example, a "device-wide" key is expected to be the same between an + ; app and a browser on the same device, while a "per-app" key would + ; probably not be. + ; + ; Whether device-wide or not, keys are still device-bound. I.e. a + ; per-app key does not enjoy lesser protection from extraction. + + device: "device-wide" / "per-app" + ) + + ; This object conveys an attested supplemental public key and is analogous + ; to \`attObj\`. + attObjForSupplementalPublicKey = { + aaguid: bstr, ; AAGUID (16 bytes fixed-length) + ; https://www.w3.org/TR/webauthn/#aaguid + + spk: bstr, ; The public key (self-describing variable length, + ; COSE_Key format, CBOR-encoded)). + + scope, ; see scope group, above. + + ; An authenticator-generated random nonce for inclusion in the attestation + ; signature. If the authenticator chooses to not generate a nonce, it sets this + ; to a zero-length byte string. See the note below about "randomNonce" for a + ; discussion on the nonce's purpose. + + nonce: bstr .size (0..32), + + ; See https://www.w3.org/TR/webauthn/#sctn-generating-an-attestation-object + ; + ; Attestation statement formats define the \`fmt\` and \`attStmt\` members of + ; $$attStmtType. + ; Note that \`fmt\` and \`attStmt\` are top-level members of + ; \`attObjForSupplementalPublicKey\`. + ; + ; In summary, the \`attStmt\` will (typically) contain: + ; (1) a SIGNATURE value calculated (using the attestation private key) + ; over (prefix || aaguid || spk || nonce) where \`prefix\` is + ; h'64657669636520626f756e64206b6579206174746573746174696f6e20736967 + ; 00ffffffff'. + ; (See the attestation calculations section, below, for a discussion + ; about the purpose of this \`prefix\` value.) + ; (2) the attestation certificate or public key, and supporting certificates, + ; if any. + ; + ; Note that there are details dependent upon the particular attestation + ; statement format. + ; See https://www.w3.org/TR/webauthn/#sctn-defined-attestation-formats. + + $$attStmtType, + + ; An optional boolean that indicates whether the attestation statement + ; contains uniquely identifying information. This can only be true + ; when the \`attestation\` field of the extension input is "enterprise" + ; and either the user-agent or the authenticator permits uniquely + ; identifying attestation for the requested RP ID. + + ? epAtt: bool .default false, + } + + ``` + +: Unsigned extension output +:: A CBOR array of byte strings containing the signatures generated with the supplemental private keys, in the same order as in the authenticator extension output. + +: Authenticator extension processing +:: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: + 1. Create or select the [=public key credential source=] as usual (see [[#sctn-op-make-cred]], or [[#sctn-op-get-assertion]] as appropriate). + + 1. If the [=public key credential source=] is not [=backup eligible=] then terminate these processing steps: this extension only applies to [=multi-device credentials=]. + + 1. Let |scopes| be the [=set=] of all supplemental public key scopes that the [=authenticator=] supports. Updates |scopes| to be the [=set/intersection=] of itself and {{AuthenticationExtensionsSupplementalPublicKeysInputs/scopes}}. If |scopes| is empty, terminate these processing steps with no extension output. + + 1. Let |spks| and |spkSigs| be empty arrays. + + 1. Sort |scopes| lexicographically. + + 1. [=map/For each=] |scope| in |scopes|: + + 1. If a supplemental key with scope |scope| does not already exist for this {[=public key credential source/id|Credential ID=], [=public key credential source/rpId|RP ID=], [=public key credential source/rpId|userHandle=]} tuple on the [=authenticator=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing supplemental key. + + 1. Let |attFormat| be the chosen [=attestation statement format=], and |attAaguid| be a 16-byte value, based on the value of {{AuthenticationExtensionsSupplementalPublicKeysInputs/attestation}} in the extension input: + +
+ : none + :: |attFormat| is "none" or "self", at the authenticator's discretion, and |attAaguid| is 16 zero bytes. (Note that, since the [=supplemental public key=] is already exercised during {{CredentialsContainer/create()|navigator.credentials.create()}} calls, the proof-of-possession property provided by "self" attestation is superfluous in that context.) + + : indirect, direct + :: |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsSupplementalPublicKeysInputs/attestationFormats}}, and |attAaguid| is the corresponding [=AAGUID=], which MAY be the [=authenticator's=] AAGUID. (Since the [=supplemental public key=]'s scope is different from the [=user credential=], it will often have a different attestation. For example, the attestation for a [=supplemental public key=] with “device” scope can be tied to hardware roots of trust, although it does not have to be.) + + : enterprise + :: The [=[RP]=] wants to receive an [=attestation statement=] that may include uniquely identifying information. This is intended for controlled deployments within an enterprise where the organization wishes to tie registrations to specific authenticators. [=Authenticators=] MUST NOT provide such an attestation unless the user agent or authenticator configuration expressly permits it for the requested [=RP ID=]. If not permitted, then follow the steps for `direct` attestation. Otherwise |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsSupplementalPublicKeysInputs/attestationFormats}}, and |attAaguid| is the corresponding [=AAGUID=], which MAY be the [=authenticator's=] AAGUID. + + Note: CTAP2 does not currently provide for an enterpriseAttestation signal during an authenticatorGetAssertion call. Until that is changed, platform-managed enterprise attestation will not work in that context with CTAP2 [=authenticators=]. +
+ + 1. Let |spk| be the newly created or existing supplemental public key, in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=] when the latter is conveyed in [=attested credential data=]. + + 1. Let |supplementalPrivateKey| be the newly created or existing private key corresponding to |spk|. + + 1. Let |randomNonce| be a fresh randomly-generated byte string of 32 bytes maximum length, or a zero length byte string if the authenticator chooses to not generate a nonce. + + Note: |randomNonce|'s purpose is to randomize the [=attestation signature=] value for [=supplemental public keys=]. If this is not done, then an [=attestation signature=] value remains constant for all such signatures issued on behalf of this [=user credential=], possibly exposing the [=authenticator=]'s [=attestation private key=] to side-channel attacks. The randomness-generation mechanism should be carefully chosen by the authenticator implementer. + + 1. Let |supplementalPubKey| be a [=CBOR=] map as defined by `attObjForSupplementalPublicKey` above, with keys and values as follows: + + Note: as with all CBOR structures used in this specification, the [=CTAP2 canonical CBOR encoding form=] MUST be used. + + 1. Set a key and value for the `scope` group based on the value of |scope|. + + 1. Let the `aaguid` key's value be |attAaguid|. + + 1. Let the `spk` key's value be |spk|. + + 1. Let the `nonce` key's value be |randomNonce|. + + 1. Let the values of the `$$attStmtType` [=group socket=] be the result of generating an [=attestation statement=] with the [=attestation statement format=], |attFormat|. See [[#sctn-supplemental-public-keys-attestation-calculations]]. + + Note: The details of the `$$attStmtType` values are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. + + 1. If the `$$attStmtType` [=group socket=] contains uniquely identifying information then let `epAtt` be [TRUE]. Otherwise omit the `epAtt` field. (This field can only be [TRUE] when the {{AuthenticationExtensionsSupplementalPublicKeysInputs/attestation}} field of the extension input is "enterprise" and either the user-agent or the authenticator permits uniquely identifying attestation for the requested RP ID.) + + 1. Append |supplementalPubKey| to |spks|. + + 1. Let |spkSig| be the result of signing the [=assertion signature=] [input](#fig-signature) with the supplemental private key. + + Note: the [=assertion signature=] [input](#fig-signature), and thus |spkSig|, covers the [=[RP]=]'s {{PublicKeyCredentialCreationOptions/challenge}} because it includes the [=hash of the serialized client data=]. Thus the [=[RP]=] knows that |spkSig| is a fresh signature. + + 1. Append |spkSig| to |spkSigs|. + + 1. Let the `supplementalPubKeys` [=authenticator extension output=] value be the CBOR array |spks|. + + 1. Let the CBOR encoding of |spkSigs| be the extension's [=unsigned extension output=]. + + Note: |spkSigs| cannot be included in the [=authenticator extension output=] because it is returned inside the [=authenticator data=] and that would imply that the signature signs over itself. + +##### AAGUIDs ##### {#sctn-supplemental-public-keys-attestation-aaguid} + +The [=AAGUID=] included in the [=supplementalPubKeys=] extension output, if non-zero, aids a [=[RP]=] in validating the [=attestation statement=] of the supplemental public key. Its interpretation depends on the scope of the key. It may differ from the [=AAGUID=] in the [=attested credential data=] of a [=multi-device credential=]. Thus the AAGUID of [=supplemental public key=] MAY be different in a single response and either, or both, may be zero depending on the options requested and authenticator behaviour. + +##### Attestation calculations ##### {#sctn-supplemental-public-keys-attestation-calculations} + +When computing attestations, the process in [[#sctn-generating-an-attestation-object]] takes two inputs: `authData` and `hash`. When calculating an attestation for a supplemental public key, the typical value for `hash` hashes over the attestation signature itself, which is impossible. Also the attestation of a supplemental public key is potentially used repeatedly, thus may want to be cached. But signing over values that include [=[RP]=]-chosen nonces, like the [=hash of the serialized client data=], makes that impossible. + +Therefore when calculating an attestation for a supplemental public key, the inputs are: + + * For `authData`, substitute the concatenation of the byte string h'64657669636520626f756e64206b6579206174746573746174696f6e2073696700ffffffff' and the value of |aaguid| from the extension output. + * For `hash`, substitute the concatenation of the |spk| and |nonce| fields from the extension output. (The nonce MAY be empty.) + +The attestation signature is thus typically calculated over the bytes of (h'64657669636520626f756e64206b6579206174746573746174696f6e2073696700ffffffff' || |aaguid| || |spk| || |nonce|). The 37-byte prefix ensures domain separation: it takes the place of the RP ID hash, flags, and signature counter fields in those messages and ensures that no attestation signature for a supplemental public key can be confused with a signature for a [=user credential=]. + +Note that when |nonce| is empty, then the (signed) authenticator extension output MAY be constant. However, the (unsigned) |spkSig| output is always unique and prevents replay of the (signed) extension output without knowledge of the supplemental private key. + +#### `supplementalPubKeys` Extension Output Verification Procedures #### {#sctn-supplemental-public-keys-extension-verification} + +Verifying the [=supplementalPubKeys=] extension output is performed by the [=[RP]=] whenever a supplemental public key is returned within the extension output. + +The [=supplementalPubKeys=] extension adds the following [=struct/item=] to [=credential records=]: + +
+ : supplementalPubKeys + :: An initially [=set/empty=] [=set=] of [=supplemental public key records=] associated with this [=public key credential source=]. + + A supplemental public key record is an abstract representation of a registered supplemental public key. + It is a [=struct=] with the following [=struct/items=]: + +
+ : aaguid + :: The [=AAGUID=] included with the supplemental public key. + This MAY be different from the [=AAGUID=] in the [$credential record/attestationObject$], if any, of the containing [=credential record=]. + + : spk + :: The public key portion of the supplemental public key. + + : scope + :: The scope of the supplemental public key. See [[#sctn-supplemental-public-keys-extension-definition]] for details. + + : fmt + :: The [=attestation statement format=] of the supplemental public key's [=attestation statement=]. + + : attStmt + :: The supplemental public key's [=attestation statement=]. +
+
+ + +##### Registration (`create()`) ##### {#sctn-supplemental-public-keys-extension-verification-create} + +If the [=[RP]=] requested the `supplementalPubKeys` extension in a {{CredentialsContainer/create()|navigator.credentials.create()}} call, +then the below verification steps are performed in the context of [step 19](#reg-ceremony-verify-extension-outputs) +of [[#sctn-registering-a-new-credential]] using these variables established therein: |credential|, |clientExtensionResults|, |authData|, and |hash|. +[=[RP]=] policy may specify whether a response without a `supplementalPubKeys` extension output is acceptable. + +1. Verify that the {{AuthenticationExtensionsClientOutputs/supplementalPubKeys}} member of |clientExtensionResults| exists, and contains the {{AuthenticationExtensionsSupplementalPublicKeysOutputs/signatures}} field. Let |signatures| be the value of that field. + +1. Let |attObjsForSupplementalPublicKey| be the value of the `supplementalPubKeys` member of the [=authenticator extension output=] from |authData|. + +1. If the [=list/size=] of |attObjsForSupplementalPublicKey| is not equal to the [=list/size=] of |signatures| then abort the registration process with an error. + +1. [=list/Iterate=] over the indices of |attObjsForSupplementalPublicKey| (which, after the check in the previous step, must also be the indices of |signatures|). Then, for each (|attObjForSupplementalPublicKey|, |signature|) from the two lists: + + 1. Extract the contained fields from |attObjForSupplementalPublicKey|: |aaguid|, |spk|, |nonce|, |fmt| and |attStmt|. Also let |scope| reflect the scope, specified by the member of the `scope` group that is present. + + Note: The latter |attObjForSupplementalPublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of the top-level [=attestation object=]. + + 1. Verify that |signature| is a valid signature over the [=assertion signature=] [input](#fig-signature) (i.e. `authData` and `hash`) by the [=supplemental public key=] |spk|. (The signature algorithm is the same as for the [=user credential=].) + + 1. Optionally, if attestation was requested and the [=[RP]=] wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-supplemental-public-keys-attestation-calculations]]. [=[RP]=] policy may specifiy which attestations are acceptable. + + Note: If |fmt|'s value is "[=none=]" there is no attestation signature to verify. + + 1. Create a new [=supplemental public key record=] with the contents: + +
+ : [$supplementalPubKeys record/aaguid$] + :: The value of |aaguid|. + + : [$supplementalPubKeys record/spk$] + :: The value of |spk|. + + : [$supplementalPubKeys record/scope$] + :: The value of |scope|. + + : [$supplementalPubKeys record/fmt$] + :: The value of |fmt|. + + : [$supplementalPubKeys record/attStmt$] + :: The value of |attStmt|. +
+ + In [step 26](#reg-ceremony-store-credential-record) of [[#sctn-registering-a-new-credential]], + add this [=supplemental public key record=] to the [$credential record/supplementalPubKeys$] member of the new [=credential record=]. + +See also [[#sctn-supplemental-public-keys-extension-usage]] for further details. + +##### Authentication (`get()`) ##### {#sctn-supplemental-public-keys-extension-verification-get} + +If the [=[RP]=] requested the `supplementalPubKeys` extension in a {{CredentialsContainer/get()|navigator.credentials.get()}} call, +then the below verification steps are performed in the context of [step 17](#authn-ceremony-verify-extension-outputs) +of [[#sctn-verifying-assertion]] using these variables established therein: |credential|, |clientExtensionResults|, |authData|, |hash|, and |credentialRecord|. +[=[RP]=] policy may specify whether a response without a `supplementalPubKeys` extension output is acceptable. + +1. Let |recognizedSpks| be a new [=set/empty=] [=set=]. + +1. Verify that the {{AuthenticationExtensionsClientOutputs/supplementalPubKeys}} member of |clientExtensionResults| exists, and contains the {{AuthenticationExtensionsSupplementalPublicKeysOutputs/signatures}} field. Let |signatures| be the value of that field. + +1. Let |attObjsForSupplementalPublicKeys| be the value of the `supplementalPubKeys` member of the [=authenticator extension output=] from |authData|. + +1. If the [=list/size=] of |attObjsForSupplementalPublicKeys| is not equal to the [=list/size=] of |signatures| then abort the authentication process with an error. + +1. [=list/Iterate=] over the indices of |attObjsForSupplementalPublicKeys| (which, after the check in the previous step, must also be the indices of |signatures|). Then, for each (|attObjForSupplementalPublicKey|, |signature|) from the two lists: + + 1. Extract the contained fields from |attObjForSupplementalPublicKey|: |aaguid|, |spk|, |nonce|, |fmt|, |attStmt|. Also let |scope| reflect the scope, specified by the member of the `scope` group that is present. + + Note: The latter |attObjForSupplementalPublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of [=authenticator data=]. + + 1. Verify that |signature| is a valid signature over the [=assertion signature=] [input](#fig-signature) (i.e. `authData` and `hash`) by the [=supplemental public key=] |spk|. (The signature algorithm is the same as for the [=user credential=].) + + 1. Let |matchedSpkRecords| be a new [=set/empty=] [=set=]. + [=set/For each=] |spkRecord| in |credentialRecord|.[$credential record/supplementalPubKeys$]: + 1. If |spkRecord|.[$supplementalPubKeys record/aaguid$] equals |aaguid|, + |spkRecord|.[$supplementalPubKeys record/spk$] equals |spk|, + and |spkRecord|.[$supplementalPubKeys record/scope$] equals |scope|: + 1. [=set/Append=] |spkRecord| to |matchedSpkRecords|. + + 1. If |matchedSpkRecords| + +
+ : has [=set/size=] greater than one: + :: Some form of error has occurred: |credentialRecord| invariants have been broken. Terminate these verification steps. + + : has [=set/size=] equal to one: + :: This is a known [=supplemental public key=] + + If |fmt|'s value is "none" then there is no attestation signature to verify and this is a known [=supplemental public key=] with a valid signature. Append |spkRecord| to |recognizedSpks| and continue to the next iteration, if any. + + Otherwise, let |spkRecord| be |matchedSpkRecords|[0]. If the |attStmt| in |attObjForSupplementalPublicKey|: +
+ : equals |spkRecord|.[$supplementalPubKeys record/attStmt$] by binary equality: + :: This is a known [=supplemental public key=] with a valid signature and valid attestation. Append |spkRecord| to |recognizedSpks| and continue to the next iteration, if any. + + Note: This authenticator is not generating a fresh per-response random nonce. + + : does not equal |spkRecord|.[$supplementalPubKeys record/attStmt$] by binary equality: + :: Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-supplemental-public-keys-attestation-calculations]]. [=[RP]=] policy may specifiy which attestations are acceptable. + + If the result is: + +
+ : successful + :: This is a known [=supplemental public key=] with a valid signature and valid attestation. Append |spkRecord| to |recognizedSpks| and continue to the next iteration, if any. + + : unsuccessful + :: Some form of error has occurred. It is indeterminate whether this is a valid [=supplemental public key=]. Either terminate these verification steps with an error or continue to the next iteration, if any, to ignore this key. + +
+
+ + : is [=set/empty=]: + :: This is possibly a new [=supplemental public key=]. + + 1. Let |matchedSpkKeys| be a new [=set/empty=] [=set=]. + [=set/For each=] |spkRecord| in |credentialRecord|.[$credential record/supplementalPubKeys$]: + 1. If |spkRecord|.[$supplementalPubKeys record/spk$] equals |spk|: + 1. [=set/Append=] |spkRecord| to |matchedSpkKeys|. + + 1. If |matchedSpkKeys| is [=set/empty=]: + +
+ : If |fmt|'s value is "none": + :: There is no attestation signature to verify and this is a new supplemental key. Unless [=[RP]=] policy specifies that this attestation is unacceptable, [$Create a new supplemental public key record$] and then continue to the next iteration, if any. + + : Otherwise: + :: Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-supplemental-public-keys-attestation-calculations]]. [=[RP]=] policy may specifiy which attestations are acceptable. + + If the result is: + +
+ : successful + :: This is a new [=supplemental public key=]. [$Create a new supplemental public key record$] then continue to the next iteration, if any. + + : unsuccessful + :: Some form of error has occurred and a supplemental public key record cannot be created. Either terminate these verification steps with an error or continue to the next iteration, if any, to ignore this supplemental public key. +
+
+ + 1. Otherwise there is some form of error: we recieved a known |spk| value, but one or more of the accompanying |aaguid| or |scope| values did not match what the [=[RP]=] has stored along with that |spk| value. Terminate these verification steps. +
+ +At the end of these steps, |recognizedSpks| contains zero or more [=supplemental public keys=] that were previously known for this [=user credential=]. These keys make claims about the continuity of certain factors between this authentication attempt and previous ones. The [=[RP]=] should judge those claims in light of the attestations provided, if any, and evaluate the risk of this authentication in light of this information. + +See also [[#sctn-supplemental-public-keys-extension-usage]]. + +To Create a new supplemental public key record, perform the following steps: + + 1. Create a new [=supplemental public key record=] with the contents: + +
+ : [$supplementalPubKeys record/aaguid$] + :: The value of |aaguid|. + + : [$supplementalPubKeys record/spk$] + :: The value of |spk|. + + : [$supplementalPubKeys record/scope$] + :: The value of |scope|. + + : [$supplementalPubKeys record/fmt$] + :: The value of |fmt|. + + : [$supplementalPubKeys record/attStmt$] + :: The value of |attStmt|. +
+ + In [step 22](#authn-ceremony-update-credential-record) of [[#sctn-verifying-assertion]], + [=set/append=] this [=supplemental public key record=] to |credentialRecord|.[$credential record/supplementalPubKeys$]. + + # User Agent Automation # {#sctn-automation} For the purposes of user agent automation and [=web application=] testing, this document defines a number of [[WebDriver]] [=extension commands=]. From f5ea861fdc41f9bf2ee2238d46acd9247b12efc3 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Wed, 20 Sep 2023 13:55:20 -0700 Subject: [PATCH 03/10] Remove the requirement that SPK be used only with backup eligible credentials. (Based on the WG discussion of 2023-09-20.) --- index.bs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/index.bs b/index.bs index d1828e6ef..24036a3d5 100644 --- a/index.bs +++ b/index.bs @@ -7086,7 +7086,7 @@ This extension enables use of a user verification method. ### Supplemental public keys extension (supplementalPubKeys) ### {#sctn-supplemental-public-keys-extension} -This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with additional public key(s) for [=backup eligible=] credentials. This is done by creating [=user credential=]-specific key pairs on the [=authenticator=], when required key pairs do not already exist for the [=user credential=] being created or exercised, and returning these supplemental keys (along with signatures by them) to the [=[RP]=]. This is done each time this [=supplementalPubKeys=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/get()|navigator.credentials.get()}} call. +This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with additional public key(s) for credentials. This is done by creating [=user credential=]-specific key pairs on the [=authenticator=], when required key pairs do not already exist for the [=user credential=] being created or exercised, and returning these supplemental keys (along with signatures by them) to the [=[RP]=]. This is done each time this [=supplementalPubKeys=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/get()|navigator.credentials.get()}} call. If the [=authenticator=] is incapable of generating a supplemental key pair, or the registration or authentication operation fails for any reason, that key pair is omitted from the output. If no supplemental key pairs remain then this extension is ignored. In this case, there is no [=supplementalPubKeys=] extension output generated. @@ -7097,11 +7097,11 @@ Supplemental keys never have a scope that is larger than the [=user credential=] #### Relying Party Usage #### {#sctn-supplemental-public-keys-extension-usage} -This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension signals the continuity of some property when used consistently with both {{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations. The property being signaled depends on the scope of the supplemental key and its [=attestation=] statement, if any. The clearest example of a supplemental key is one that is device-bound. Repeated observation of a device-bound supplemental key indicates that a [=backup eligible=] credential is being used repeatedly from the same device. But supplemental keys with wider scopes are also defined and the exact properties that they communicate depend on the attestation included along with them. +This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension signals the continuity of some property when used consistently with both {{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations. The property being signaled depends on the scope of the supplemental key and its [=attestation=] statement, if any. The clearest example of a supplemental key is one that is device-bound. Repeated observation of a device-bound supplemental key indicates that a credential is being used repeatedly from the same device. (Which cannot be otherwise assumed in the case of [=backup eligible=] credentials.) But supplemental keys with wider scopes are also defined and the exact properties that they communicate depend on the attestation included along with them. When a [=[RP]=] uses the `supplementalPubKeys` extension with a {{CredentialsContainer/create()|create()}} call to create a new [=user credential=], a signature by one or more new supplemental keys may be returned along with the new supplemental public keys themselves. For the sake of example, assume that a single supplemental public key is returned, called “SPK1”. For as long as the [=user credential=] is exercised within the scope of the supplemental key, the [=[RP]=]'s subsequent {{CredentialsContainer/get()|get()}} operations for that credential will generate [=assertions=] including further signatures by “SPK1”. -Since the credential is [=backup eligible=], it may move beyound the scope of that supplemental key and, if used in this new domain, the {{CredentialsContainer/get()|get()}} operation will return a new supplemental key, SPK2, and its signature. Subsequent {{CredentialsContainer/get()|get()}} operations may observe either SPK1 or SPK2, depending on the context in which the [=user credential=] is being used. +The credential may move beyond the scope of that supplemental key (for example, by being exported and imported elsewhere, if [=backup eligible=]) and, if used in this new domain, the {{CredentialsContainer/get()|get()}} operation will return a new supplemental key, SPK2, and its signature. Subsequent {{CredentialsContainer/get()|get()}} operations may observe either SPK1 or SPK2, depending on the context in which the [=user credential=] is being used. A usage example is thus: @@ -7274,8 +7274,6 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa :: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: 1. Create or select the [=public key credential source=] as usual (see [[#sctn-op-make-cred]], or [[#sctn-op-get-assertion]] as appropriate). - 1. If the [=public key credential source=] is not [=backup eligible=] then terminate these processing steps: this extension only applies to [=multi-device credentials=]. - 1. Let |scopes| be the [=set=] of all supplemental public key scopes that the [=authenticator=] supports. Updates |scopes| to be the [=set/intersection=] of itself and {{AuthenticationExtensionsSupplementalPublicKeysInputs/scopes}}. If |scopes| is empty, terminate these processing steps with no extension output. 1. Let |spks| and |spkSigs| be empty arrays. From 24dd8e97fd19ce256f5e03658608d4a6491081c9 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Wed, 25 Oct 2023 12:25:02 -0700 Subject: [PATCH 04/10] Change "will" to "will likely". (Addressing Tim's comment.) --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 24036a3d5..c0fc0312b 100644 --- a/index.bs +++ b/index.bs @@ -7099,7 +7099,7 @@ Supplemental keys never have a scope that is larger than the [=user credential=] This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension signals the continuity of some property when used consistently with both {{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations. The property being signaled depends on the scope of the supplemental key and its [=attestation=] statement, if any. The clearest example of a supplemental key is one that is device-bound. Repeated observation of a device-bound supplemental key indicates that a credential is being used repeatedly from the same device. (Which cannot be otherwise assumed in the case of [=backup eligible=] credentials.) But supplemental keys with wider scopes are also defined and the exact properties that they communicate depend on the attestation included along with them. -When a [=[RP]=] uses the `supplementalPubKeys` extension with a {{CredentialsContainer/create()|create()}} call to create a new [=user credential=], a signature by one or more new supplemental keys may be returned along with the new supplemental public keys themselves. For the sake of example, assume that a single supplemental public key is returned, called “SPK1”. For as long as the [=user credential=] is exercised within the scope of the supplemental key, the [=[RP]=]'s subsequent {{CredentialsContainer/get()|get()}} operations for that credential will generate [=assertions=] including further signatures by “SPK1”. +When a [=[RP]=] uses the `supplementalPubKeys` extension with a {{CredentialsContainer/create()|create()}} call to create a new [=user credential=], a signature by one or more new supplemental keys may be returned along with the new supplemental public keys themselves. For the sake of example, assume that a single supplemental public key is returned, called “SPK1”. For as long as the [=user credential=] is exercised within the scope of the supplemental key, the [=[RP]=]'s subsequent {{CredentialsContainer/get()|get()}} operations for that credential will likely generate [=assertions=] including further signatures by “SPK1”. The credential may move beyond the scope of that supplemental key (for example, by being exported and imported elsewhere, if [=backup eligible=]) and, if used in this new domain, the {{CredentialsContainer/get()|get()}} operation will return a new supplemental key, SPK2, and its signature. Subsequent {{CredentialsContainer/get()|get()}} operations may observe either SPK1 or SPK2, depending on the context in which the [=user credential=] is being used. From 462113c4892dc9f80fbb67c453be11f6940061bc Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Wed, 15 Nov 2023 10:20:26 -0800 Subject: [PATCH 05/10] Apply emlun's changes from code review Co-authored-by: Emil Lundberg --- index.bs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/index.bs b/index.bs index c0fc0312b..0f1d70c43 100644 --- a/index.bs +++ b/index.bs @@ -7109,7 +7109,7 @@ A usage example is thus: Another example of supplemental keys: -> Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with an assertion by a [=multi-device credential=] on its own. But if a signature from a supplimental key that is device-bound, and that is well established for this user can also be presented, then that may tip the balance. +> Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with an assertion by a [=multi-device credential=] on its own. But if a signature from a supplemental key that is device-bound, and that is well established for this user can also be presented, then that may tip the balance. The weight that [=[RPS]=] give to the presence of a signature from a supplemental key may be based on information learned from its optional attestation. An attestation can indicate the level of protection enjoyed by a hardware-bound key, or the policies for other types of supplemental key. @@ -7135,7 +7135,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa
: scopes - :: This required member specifies the scopes of supplemental public keys that the [=[RP]=] requests. Values are taken from the `scope` group in the CDDL below. (I.e. currently valid values are `provider` and `device`.) Specifying the scopes that a [=[RP]=] can use allows an [=authenticator=] to avoid the work of generating superfluous supplemental keys. + :: This required member specifies the scopes of supplemental public keys that the [=[RP]=] requests. Values are taken from the `scope` group in the CDDL below (i.e., currently defined values are `provider` and `device`); authenticators silently ignore unrecognized values. Specifying the scopes that a [=[RP]=] can use allows an [=authenticator=] to avoid the work of generating superfluous supplemental keys. : attestation :: The [=[RP]=] MAY use this OPTIONAL member to specify a preference regarding [=attestation conveyance=]. @@ -7157,7 +7157,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa :: If {{AuthenticationExtensionsClientInputs/supplementalPubKeys}} is present, the client creates the authenticator extension input from the client extension input. : Client extension output -:: An ArrayBuffer containing the signature returned as the [=unsigned extension output=]. +:: A sequence of {{ArrayBuffer}}s containing the signatures returned as the [=unsigned extension output=]. dictionary AuthenticationExtensionsSupplementalPublicKeysOutputs { sequence<ArrayBuffer> signatures; @@ -7174,9 +7174,9 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa ``` supplementalPublicKeyInputs = { - scopes: [tstr], + scopes: [+ tstr], attestation: tstr, - attestationFormats: [tstr], + attestationFormats: [* tstr], } $$extensionInput //= ( supplementalPubKeys: supplementalPublicKeyInputs, @@ -7192,7 +7192,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa ; lexicographically by scope and MUST NOT include more than one element ; with a given scope. - supplementalPubKeys: [attObjForSupplementalPublicKey], + supplementalPubKeys: [+ attObjForSupplementalPublicKey], ) scope = ( @@ -7268,13 +7268,13 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa ``` : Unsigned extension output -:: A CBOR array of byte strings containing the signatures generated with the supplemental private keys, in the same order as in the authenticator extension output. +:: A non-empty CBOR array of byte strings containing the signatures generated with the supplemental private keys, in the same order as in the authenticator extension output. : Authenticator extension processing :: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: 1. Create or select the [=public key credential source=] as usual (see [[#sctn-op-make-cred]], or [[#sctn-op-get-assertion]] as appropriate). - 1. Let |scopes| be the [=set=] of all supplemental public key scopes that the [=authenticator=] supports. Updates |scopes| to be the [=set/intersection=] of itself and {{AuthenticationExtensionsSupplementalPublicKeysInputs/scopes}}. If |scopes| is empty, terminate these processing steps with no extension output. + 1. Let |scopes| be the [=set=] of all supplemental public key scopes that the [=authenticator=] supports. Update |scopes| to be the [=set/intersection=] of itself and {{AuthenticationExtensionsSupplementalPublicKeysInputs/scopes}}. If |scopes| is empty, terminate these processing steps with no extension output. 1. Let |spks| and |spkSigs| be empty arrays. @@ -7341,7 +7341,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa ##### AAGUIDs ##### {#sctn-supplemental-public-keys-attestation-aaguid} -The [=AAGUID=] included in the <code>[=supplementalPubKeys=]</code> extension output, if non-zero, aids a [=[RP]=] in validating the [=attestation statement=] of the supplemental public key. Its interpretation depends on the scope of the key. It may differ from the [=AAGUID=] in the [=attested credential data=] of a [=multi-device credential=]. Thus the AAGUID of [=supplemental public key=] MAY be different in a single response and either, or both, may be zero depending on the options requested and authenticator behaviour. +Any non-zero [=/AAGUID=]s included in the <code>[=supplementalPubKeys=]</code> extension output aid a [=[RP]=] in validating the [=attestation statement=] of the supplemental public key. The interpretation of each AAGUID depends on the scope of the corresponding key. Some or all may differ from the [=authData/attestedCredentialData/aaguid=] in the [=attested credential data=] of a [=multi-device credential=]. Thus the AAGUID of a [=supplemental public key=] MAY be different in a single response and either, or both, may be zero depending on the options requested and authenticator behaviour. ##### Attestation calculations ##### {#sctn-supplemental-public-keys-attestation-calculations} From d39e8b5e276eed21e710e98db3c035ef8fde7c3e Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@chromium.org> Date: Wed, 15 Nov 2023 10:27:44 -0800 Subject: [PATCH 06/10] Say "PubKeys" not "PublicKeys" The extension name uses "PubKeys" but some of the structures said "PublicKeys". Be consistent. --- index.bs | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/index.bs b/index.bs index 0f1d70c43..07306ef8f 100644 --- a/index.bs +++ b/index.bs @@ -7123,17 +7123,17 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa : Client extension input :: <xmp class="idl"> - dictionary AuthenticationExtensionsSupplementalPublicKeysInputs { + dictionary AuthenticationExtensionsSupplementalPubKeysInputs { required sequence<DOMString> scopes; DOMString attestation = "indirect"; sequence<DOMString> attestationFormats = []; }; partial dictionary AuthenticationExtensionsClientInputs { - AuthenticationExtensionsSupplementalPublicKeysInputs supplementalPubKeys; + AuthenticationExtensionsSupplementalPubKeysInputs supplementalPubKeys; }; -
+
: scopes :: This required member specifies the scopes of supplemental public keys that the [=[RP]=] requests. Values are taken from the `scope` group in the CDDL below (i.e., currently defined values are `provider` and `device`); authenticators silently ignore unrecognized values. Specifying the scopes that a [=[RP]=] can use allows an [=authenticator=] to avoid the work of generating superfluous supplemental keys. @@ -7159,12 +7159,12 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa : Client extension output :: A sequence of {{ArrayBuffer}}s containing the signatures returned as the [=unsigned extension output=]. - dictionary AuthenticationExtensionsSupplementalPublicKeysOutputs { + dictionary AuthenticationExtensionsSupplementalPubKeysOutputs { sequence<ArrayBuffer> signatures; }; partial dictionary AuthenticationExtensionsClientOutputs { - AuthenticationExtensionsSupplementalPublicKeysOutputs supplementalPubKeys; + AuthenticationExtensionsSupplementalPubKeysOutputs supplementalPubKeys; }; @@ -7184,7 +7184,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa ``` : Authenticator extension output -:: The supplemental public key attestation objects, defined by the `attObjForSupplementalPublicKey` type: +:: The supplemental public key attestation objects, defined by the `attObjForSupplementalPubKey` type: ``` $$extensionOutput //= ( @@ -7192,7 +7192,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa ; lexicographically by scope and MUST NOT include more than one element ; with a given scope. - supplementalPubKeys: [+ attObjForSupplementalPublicKey], + supplementalPubKeys: [+ attObjForSupplementalPubKey], ) scope = ( @@ -7217,7 +7217,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa ; This object conveys an attested supplemental public key and is analogous ; to \`attObj\`. - attObjForSupplementalPublicKey = { + attObjForSupplementalPubKey = { aaguid: bstr, ; AAGUID (16 bytes fixed-length) ; https://www.w3.org/TR/webauthn/#aaguid @@ -7238,7 +7238,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa ; Attestation statement formats define the \`fmt\` and \`attStmt\` members of ; $$attStmtType. ; Note that \`fmt\` and \`attStmt\` are top-level members of - ; \`attObjForSupplementalPublicKey\`. + ; \`attObjForSupplementalPubKey\`. ; ; In summary, the \`attStmt\` will (typically) contain: ; (1) a SIGNATURE value calculated (using the attestation private key) @@ -7274,7 +7274,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa :: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: 1. Create or select the [=public key credential source=] as usual (see [[#sctn-op-make-cred]], or [[#sctn-op-get-assertion]] as appropriate). - 1. Let |scopes| be the [=set=] of all supplemental public key scopes that the [=authenticator=] supports. Update |scopes| to be the [=set/intersection=] of itself and {{AuthenticationExtensionsSupplementalPublicKeysInputs/scopes}}. If |scopes| is empty, terminate these processing steps with no extension output. + 1. Let |scopes| be the [=set=] of all supplemental public key scopes that the [=authenticator=] supports. Update |scopes| to be the [=set/intersection=] of itself and {{AuthenticationExtensionsSupplementalPubKeysInputs/scopes}}. If |scopes| is empty, terminate these processing steps with no extension output. 1. Let |spks| and |spkSigs| be empty arrays. @@ -7284,17 +7284,17 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa 1. If a supplemental key with scope |scope| does not already exist for this {[=public key credential source/id|Credential ID=], [=public key credential source/rpId|RP ID=], [=public key credential source/rpId|userHandle=]} tuple on the [=authenticator=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing supplemental key. - 1. Let |attFormat| be the chosen [=attestation statement format=], and |attAaguid| be a 16-byte value, based on the value of {{AuthenticationExtensionsSupplementalPublicKeysInputs/attestation}} in the extension input: + 1. Let |attFormat| be the chosen [=attestation statement format=], and |attAaguid| be a 16-byte value, based on the value of {{AuthenticationExtensionsSupplementalPubKeysInputs/attestation}} in the extension input:
: none :: |attFormat| is "none" or "self", at the authenticator's discretion, and |attAaguid| is 16 zero bytes. (Note that, since the [=supplemental public key=] is already exercised during {{CredentialsContainer/create()|navigator.credentials.create()}} calls, the proof-of-possession property provided by "self" attestation is superfluous in that context.) : indirect, direct - :: |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsSupplementalPublicKeysInputs/attestationFormats}}, and |attAaguid| is the corresponding [=AAGUID=], which MAY be the [=authenticator's=] AAGUID. (Since the [=supplemental public key=]'s scope is different from the [=user credential=], it will often have a different attestation. For example, the attestation for a [=supplemental public key=] with “device” scope can be tied to hardware roots of trust, although it does not have to be.) + :: |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsSupplementalPubKeysInputs/attestationFormats}}, and |attAaguid| is the corresponding [=AAGUID=], which MAY be the [=authenticator's=] AAGUID. (Since the [=supplemental public key=]'s scope is different from the [=user credential=], it will often have a different attestation. For example, the attestation for a [=supplemental public key=] with “device” scope can be tied to hardware roots of trust, although it does not have to be.) : enterprise - :: The [=[RP]=] wants to receive an [=attestation statement=] that may include uniquely identifying information. This is intended for controlled deployments within an enterprise where the organization wishes to tie registrations to specific authenticators. [=Authenticators=] MUST NOT provide such an attestation unless the user agent or authenticator configuration expressly permits it for the requested [=RP ID=]. If not permitted, then follow the steps for `direct` attestation. Otherwise |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsSupplementalPublicKeysInputs/attestationFormats}}, and |attAaguid| is the corresponding [=AAGUID=], which MAY be the [=authenticator's=] AAGUID. + :: The [=[RP]=] wants to receive an [=attestation statement=] that may include uniquely identifying information. This is intended for controlled deployments within an enterprise where the organization wishes to tie registrations to specific authenticators. [=Authenticators=] MUST NOT provide such an attestation unless the user agent or authenticator configuration expressly permits it for the requested [=RP ID=]. If not permitted, then follow the steps for `direct` attestation. Otherwise |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsSupplementalPubKeysInputs/attestationFormats}}, and |attAaguid| is the corresponding [=AAGUID=], which MAY be the [=authenticator's=] AAGUID. Note: CTAP2 does not currently provide for an enterpriseAttestation signal during an authenticatorGetAssertion call. Until that is changed, platform-managed enterprise attestation will not work in that context with CTAP2 [=authenticators=].
@@ -7307,7 +7307,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa Note: |randomNonce|'s purpose is to randomize the [=attestation signature=] value for [=supplemental public keys=]. If this is not done, then an [=attestation signature=] value remains constant for all such signatures issued on behalf of this [=user credential=], possibly exposing the [=authenticator=]'s [=attestation private key=] to side-channel attacks. The randomness-generation mechanism should be carefully chosen by the authenticator implementer. - 1. Let |supplementalPubKey| be a [=CBOR=] map as defined by `attObjForSupplementalPublicKey` above, with keys and values as follows: + 1. Let |supplementalPubKey| be a [=CBOR=] map as defined by `attObjForSupplementalPubKey` above, with keys and values as follows: Note: as with all CBOR structures used in this specification, the [=CTAP2 canonical CBOR encoding form=] MUST be used. @@ -7323,7 +7323,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa Note: The details of the `$$attStmtType` values are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. - 1. If the `$$attStmtType` [=group socket=] contains uniquely identifying information then let `epAtt` be [TRUE]. Otherwise omit the `epAtt` field. (This field can only be [TRUE] when the {{AuthenticationExtensionsSupplementalPublicKeysInputs/attestation}} field of the extension input is "enterprise" and either the user-agent or the authenticator permits uniquely identifying attestation for the requested RP ID.) + 1. If the `$$attStmtType` [=group socket=] contains uniquely identifying information then let `epAtt` be [TRUE]. Otherwise omit the `epAtt` field. (This field can only be [TRUE] when the {{AuthenticationExtensionsSupplementalPubKeysInputs/attestation}} field of the extension input is "enterprise" and either the user-agent or the authenticator permits uniquely identifying attestation for the requested RP ID.) 1. Append |supplementalPubKey| to |spks|. @@ -7396,17 +7396,17 @@ then the below verification steps are performed in the context of [step 19](#reg of [[#sctn-registering-a-new-credential]] using these variables established therein: |credential|, |clientExtensionResults|, |authData|, and |hash|. [=[RP]=] policy may specify whether a response without a `supplementalPubKeys` extension output is acceptable. -1. Verify that the {{AuthenticationExtensionsClientOutputs/supplementalPubKeys}} member of |clientExtensionResults| exists, and contains the {{AuthenticationExtensionsSupplementalPublicKeysOutputs/signatures}} field. Let |signatures| be the value of that field. +1. Verify that the {{AuthenticationExtensionsClientOutputs/supplementalPubKeys}} member of |clientExtensionResults| exists, and contains the {{AuthenticationExtensionsSupplementalPubKeysOutputs/signatures}} field. Let |signatures| be the value of that field. 1. Let |attObjsForSupplementalPublicKey| be the value of the `supplementalPubKeys` member of the [=authenticator extension output=] from |authData|. 1. If the [=list/size=] of |attObjsForSupplementalPublicKey| is not equal to the [=list/size=] of |signatures| then abort the registration process with an error. -1. [=list/Iterate=] over the indices of |attObjsForSupplementalPublicKey| (which, after the check in the previous step, must also be the indices of |signatures|). Then, for each (|attObjForSupplementalPublicKey|, |signature|) from the two lists: +1. [=list/Iterate=] over the indices of |attObjsForSupplementalPublicKey| (which, after the check in the previous step, must also be the indices of |signatures|). Then, for each (|attObjForSupplementalPubKey|, |signature|) from the two lists: - 1. Extract the contained fields from |attObjForSupplementalPublicKey|: |aaguid|, |spk|, |nonce|, |fmt| and |attStmt|. Also let |scope| reflect the scope, specified by the member of the `scope` group that is present. + 1. Extract the contained fields from |attObjForSupplementalPubKey|: |aaguid|, |spk|, |nonce|, |fmt| and |attStmt|. Also let |scope| reflect the scope, specified by the member of the `scope` group that is present. - Note: The latter |attObjForSupplementalPublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of the top-level [=attestation object=]. + Note: The latter |attObjForSupplementalPubKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of the top-level [=attestation object=]. 1. Verify that |signature| is a valid signature over the [=assertion signature=] [input](#fig-signature) (i.e. `authData` and `hash`) by the [=supplemental public key=] |spk|. (The signature algorithm is the same as for the [=user credential=].) @@ -7447,17 +7447,17 @@ of [[#sctn-verifying-assertion]] using these variables established therein: |cre 1. Let |recognizedSpks| be a new [=set/empty=] [=set=]. -1. Verify that the {{AuthenticationExtensionsClientOutputs/supplementalPubKeys}} member of |clientExtensionResults| exists, and contains the {{AuthenticationExtensionsSupplementalPublicKeysOutputs/signatures}} field. Let |signatures| be the value of that field. +1. Verify that the {{AuthenticationExtensionsClientOutputs/supplementalPubKeys}} member of |clientExtensionResults| exists, and contains the {{AuthenticationExtensionsSupplementalPubKeysOutputs/signatures}} field. Let |signatures| be the value of that field. -1. Let |attObjsForSupplementalPublicKeys| be the value of the `supplementalPubKeys` member of the [=authenticator extension output=] from |authData|. +1. Let |attObjsForSupplementalPubKeys| be the value of the `supplementalPubKeys` member of the [=authenticator extension output=] from |authData|. -1. If the [=list/size=] of |attObjsForSupplementalPublicKeys| is not equal to the [=list/size=] of |signatures| then abort the authentication process with an error. +1. If the [=list/size=] of |attObjsForSupplementalPubKeys| is not equal to the [=list/size=] of |signatures| then abort the authentication process with an error. -1. [=list/Iterate=] over the indices of |attObjsForSupplementalPublicKeys| (which, after the check in the previous step, must also be the indices of |signatures|). Then, for each (|attObjForSupplementalPublicKey|, |signature|) from the two lists: +1. [=list/Iterate=] over the indices of |attObjsForSupplementalPubKeys| (which, after the check in the previous step, must also be the indices of |signatures|). Then, for each (|attObjForSupplementalPubKey|, |signature|) from the two lists: - 1. Extract the contained fields from |attObjForSupplementalPublicKey|: |aaguid|, |spk|, |nonce|, |fmt|, |attStmt|. Also let |scope| reflect the scope, specified by the member of the `scope` group that is present. + 1. Extract the contained fields from |attObjForSupplementalPubKey|: |aaguid|, |spk|, |nonce|, |fmt|, |attStmt|. Also let |scope| reflect the scope, specified by the member of the `scope` group that is present. - Note: The latter |attObjForSupplementalPublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of [=authenticator data=]. + Note: The latter |attObjForSupplementalPubKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of [=authenticator data=]. 1. Verify that |signature| is a valid signature over the [=assertion signature=] [input](#fig-signature) (i.e. `authData` and `hash`) by the [=supplemental public key=] |spk|. (The signature algorithm is the same as for the [=user credential=].) @@ -7479,7 +7479,7 @@ of [[#sctn-verifying-assertion]] using these variables established therein: |cre If |fmt|'s value is "none" then there is no attestation signature to verify and this is a known [=supplemental public key=] with a valid signature. Append |spkRecord| to |recognizedSpks| and continue to the next iteration, if any. - Otherwise, let |spkRecord| be |matchedSpkRecords|[0]. If the |attStmt| in |attObjForSupplementalPublicKey|: + Otherwise, let |spkRecord| be |matchedSpkRecords|[0]. If the |attStmt| in |attObjForSupplementalPubKey|:
: equals |spkRecord|.[$supplementalPubKeys record/attStmt$] by binary equality: :: This is a known [=supplemental public key=] with a valid signature and valid attestation. Append |spkRecord| to |recognizedSpks| and continue to the next iteration, if any. From 72b78b5a40922a69b51ed38d5ed0e2cffe9c5ba5 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Wed, 15 Nov 2023 11:36:25 -0800 Subject: [PATCH 07/10] Address more of emlun's comments. --- index.bs | 48 +++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/index.bs b/index.bs index 07306ef8f..09168e25e 100644 --- a/index.bs +++ b/index.bs @@ -7135,7 +7135,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa
: scopes - :: This required member specifies the scopes of supplemental public keys that the [=[RP]=] requests. Values are taken from the `scope` group in the CDDL below (i.e., currently defined values are `provider` and `device`); authenticators silently ignore unrecognized values. Specifying the scopes that a [=[RP]=] can use allows an [=authenticator=] to avoid the work of generating superfluous supplemental keys. + :: This required member MUST be non-empty. It specifies the scopes of supplemental public keys that the [=[RP]=] requests. Values are taken from the CDDL below (i.e., currently defined values are `provider` and `device`); authenticators silently ignore unrecognized values. Specifying the scopes that a [=[RP]=] can use allows an [=authenticator=] to avoid the work of generating superfluous supplemental keys. : attestation :: The [=[RP]=] MAY use this OPTIONAL member to specify a preference regarding [=attestation conveyance=]. @@ -7174,7 +7174,15 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa ``` supplementalPublicKeyInputs = { - scopes: [+ tstr], + scopes: [+ + ; A key that has a broader scope than a single device, but still more + ; limited than a multi-device credential. The precise scope is specified + ; by the attestation of this supplemental public key. + "provider" / + + ; A key with "device" scope MUST NOT leave the device on which it is + ; created. + "device"], attestation: tstr, attestationFormats: [* tstr], } @@ -7195,26 +7203,6 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa supplementalPubKeys: [+ attObjForSupplementalPubKey], ) - scope = ( - ; A key that has a broader scope than a single device, but still more - ; limited than a multi-device credential. The precise scope is specified - ; by the attestation of this supplemental public key. - - provider: true // - - ; A key with "device" scope MUST NOT leave the device on which it is - ; created. The value of this key communicates whether the key is scoped to - ; the entire device, or a loosely-defined, narrower scope called "per-app". - ; For example, a "device-wide" key is expected to be the same between an - ; app and a browser on the same device, while a "per-app" key would - ; probably not be. - ; - ; Whether device-wide or not, keys are still device-bound. I.e. a - ; per-app key does not enjoy lesser protection from extraction. - - device: "device-wide" / "per-app" - ) - ; This object conveys an attested supplemental public key and is analogous ; to \`attObj\`. attObjForSupplementalPubKey = { @@ -7224,7 +7212,9 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa spk: bstr, ; The public key (self-describing variable length, ; COSE_Key format, CBOR-encoded)). - scope, ; see scope group, above. + ; See the definition of `scopes` in `supplementalPublicKeyInputs`. + + scope: "provider" / "device", ; An authenticator-generated random nonce for inclusion in the attestation ; signature. If the authenticator chooses to not generate a nonce, it sets this @@ -7270,6 +7260,14 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa : Unsigned extension output :: A non-empty CBOR array of byte strings containing the signatures generated with the supplemental private keys, in the same order as in the authenticator extension output. + ``` + supplementalPublicKeyOutputs = [+ bstr] + + $$unsignedExtensionOutputs //= ( + supplementalPubKeys: supplementalPublicKeyOutputs, + ) + ``` + : Authenticator extension processing :: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: 1. Create or select the [=public key credential source=] as usual (see [[#sctn-op-make-cred]], or [[#sctn-op-get-assertion]] as appropriate). @@ -7345,11 +7343,11 @@ Any non-zero [=/AAGUID=]s included in the [=supplementalPubKeys=] e ##### Attestation calculations ##### {#sctn-supplemental-public-keys-attestation-calculations} -When computing attestations, the process in [[#sctn-generating-an-attestation-object]] takes two inputs: `authData` and `hash`. When calculating an attestation for a supplemental public key, the typical value for `hash` hashes over the attestation signature itself, which is impossible. Also the attestation of a supplemental public key is potentially used repeatedly, thus may want to be cached. But signing over values that include [=[RP]=]-chosen nonces, like the [=hash of the serialized client data=], makes that impossible. +When computing attestations, the process in [[#sctn-generating-an-attestation-object]] takes two inputs: `authData` and `hash`. When calculating an attestation for a supplemental public key, the typical value for `authHash` contains the attestation signature itself, which is impossible. Also the attestation of a supplemental public key is potentially used repeatedly, thus may want to be cached. But signing over values that include [=[RP]=]-chosen nonces, like the [=hash of the serialized client data=], makes that impossible. Therefore when calculating an attestation for a supplemental public key, the inputs are: - * For `authData`, substitute the concatenation of the byte string h'64657669636520626f756e64206b6579206174746573746174696f6e2073696700ffffffff' and the value of |aaguid| from the extension output. + * For `authData`, substitute the concatenation of the byte string h'737570706c656d656e74616c5075624b657973206174746573746174696f6e2e00ffffffff' and the value of |aaguid| from the extension output. * For `hash`, substitute the concatenation of the |spk| and |nonce| fields from the extension output. (The nonce MAY be empty.) The attestation signature is thus typically calculated over the bytes of (h'64657669636520626f756e64206b6579206174746573746174696f6e2073696700ffffffff' || |aaguid| || |spk| || |nonce|). The 37-byte prefix ensures domain separation: it takes the place of the RP ID hash, flags, and signature counter fields in those messages and ensures that no attestation signature for a supplemental public key can be confused with a signature for a [=user credential=]. From cf3ca3a479e939011dc2e5f18b69d34a5a61852f Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Wed, 15 Nov 2023 12:06:53 -0800 Subject: [PATCH 08/10] authHash -> authData typo fix. --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 09168e25e..b4613cbb2 100644 --- a/index.bs +++ b/index.bs @@ -7343,7 +7343,7 @@ Any non-zero [=/AAGUID=]s included in the [=supplementalPubKeys=] e ##### Attestation calculations ##### {#sctn-supplemental-public-keys-attestation-calculations} -When computing attestations, the process in [[#sctn-generating-an-attestation-object]] takes two inputs: `authData` and `hash`. When calculating an attestation for a supplemental public key, the typical value for `authHash` contains the attestation signature itself, which is impossible. Also the attestation of a supplemental public key is potentially used repeatedly, thus may want to be cached. But signing over values that include [=[RP]=]-chosen nonces, like the [=hash of the serialized client data=], makes that impossible. +When computing attestations, the process in [[#sctn-generating-an-attestation-object]] takes two inputs: `authData` and `hash`. When calculating an attestation for a supplemental public key, the typical value for `authData` contains the attestation signature itself, which is impossible. Also the attestation of a supplemental public key is potentially used repeatedly, thus may want to be cached. But signing over values that include [=[RP]=]-chosen nonces, like the [=hash of the serialized client data=], makes that impossible. Therefore when calculating an attestation for a supplemental public key, the inputs are: From 9cf1634c5ee5bad49c892c044cd008a5ee2096d6 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Wed, 15 Nov 2023 12:09:17 -0800 Subject: [PATCH 09/10] Update other instances of the SPK attestation prefix. --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index b4613cbb2..e8a22104d 100644 --- a/index.bs +++ b/index.bs @@ -7233,7 +7233,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa ; In summary, the \`attStmt\` will (typically) contain: ; (1) a SIGNATURE value calculated (using the attestation private key) ; over (prefix || aaguid || spk || nonce) where \`prefix\` is - ; h'64657669636520626f756e64206b6579206174746573746174696f6e20736967 + ; h'737570706c656d656e74616c5075624b657973206174746573746174696f6e2e ; 00ffffffff'. ; (See the attestation calculations section, below, for a discussion ; about the purpose of this \`prefix\` value.) @@ -7350,7 +7350,7 @@ Therefore when calculating an attestation for a supplemental public key, the inp * For `authData`, substitute the concatenation of the byte string h'737570706c656d656e74616c5075624b657973206174746573746174696f6e2e00ffffffff' and the value of |aaguid| from the extension output. * For `hash`, substitute the concatenation of the |spk| and |nonce| fields from the extension output. (The nonce MAY be empty.) -The attestation signature is thus typically calculated over the bytes of (h'64657669636520626f756e64206b6579206174746573746174696f6e2073696700ffffffff' || |aaguid| || |spk| || |nonce|). The 37-byte prefix ensures domain separation: it takes the place of the RP ID hash, flags, and signature counter fields in those messages and ensures that no attestation signature for a supplemental public key can be confused with a signature for a [=user credential=]. +The attestation signature is thus typically calculated over the bytes of (h'737570706c656d656e74616c5075624b657973206174746573746174696f6e2e00ffffffff' || |aaguid| || |spk| || |nonce|). The 37-byte prefix ensures domain separation: it takes the place of the RP ID hash, flags, and signature counter fields in those messages and ensures that no attestation signature for a supplemental public key can be confused with a signature for a [=user credential=]. Note that when |nonce| is empty, then the (signed) authenticator extension output MAY be constant. However, the (unsigned) |spkSig| output is always unique and prevents replay of the (signed) extension output without knowledge of the supplemental private key. From e168d78ff4e15937c79cc9af7d749dadd329cce3 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Wed, 15 Nov 2023 14:01:34 -0800 Subject: [PATCH 10/10] Fix bikeshed warnings post-merge --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index d65445f6b..e5f847486 100644 --- a/index.bs +++ b/index.bs @@ -7364,7 +7364,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa :: |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsSupplementalPubKeysInputs/attestationFormats}}, and |attAaguid| is the corresponding [=/AAGUID=], which MAY be the [=authenticator's=] AAGUID. (Since the [=supplemental public key=]'s scope is different from the [=user credential=], it will often have a different attestation. For example, the attestation for a [=supplemental public key=] with “device” scope can be tied to hardware roots of trust, although it does not have to be.) : enterprise - :: The [=[RP]=] wants to receive an [=attestation statement=] that may include uniquely identifying information. This is intended for controlled deployments within an enterprise where the organization wishes to tie registrations to specific authenticators. [=Authenticators=] MUST NOT provide such an attestation unless the user agent or authenticator configuration expressly permits it for the requested [=RP ID=]. If not permitted, then follow the steps for `direct` attestation. Otherwise |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsSupplementalPubKeysInputs/attestationFormats}}, and |attAaguid| is the corresponding [=AAGUID=], which MAY be the [=authenticator's=] AAGUID. + :: The [=[RP]=] wants to receive an [=attestation statement=] that may include uniquely identifying information. This is intended for controlled deployments within an enterprise where the organization wishes to tie registrations to specific authenticators. [=Authenticators=] MUST NOT provide such an attestation unless the user agent or authenticator configuration expressly permits it for the requested [=RP ID=]. If not permitted, then follow the steps for `direct` attestation. Otherwise |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsSupplementalPubKeysInputs/attestationFormats}}, and |attAaguid| is the corresponding [=/AAGUID=], which MAY be the [=authenticator's=] AAGUID. Note: CTAP2 does not currently provide for an enterpriseAttestation signal during an authenticatorGetAssertion call. Until that is changed, platform-managed enterprise attestation will not work in that context with CTAP2 [=authenticators=].
@@ -7397,7 +7397,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a supplementa 1. Append |supplementalPubKey| to |spks|. - 1. Let |spkSig| be the result of signing the [=assertion signature=] [input](#fig-signature) with the supplemental private key. + 1. Let |spkSig| be the result of signing the [=assertion signature=] [input](#fig-signature) with |supplementalPrivateKey|. Note: the [=assertion signature=] [input](#fig-signature), and thus |spkSig|, covers the [=[RP]=]'s {{PublicKeyCredentialCreationOptions/challenge}} because it includes the [=hash of the serialized client data=]. Thus the [=[RP]=] knows that |spkSig| is a fresh signature.