From b0c1b60dd4165d11f6a213a52771db0832489339 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Tue, 25 Nov 2025 20:30:44 -0500 Subject: [PATCH 01/48] initial shape of the cmtgKey extension --- index.bs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 7c8b4f5bb..3bdfbaee9 100644 --- a/index.bs +++ b/index.bs @@ -7739,7 +7739,82 @@ However, [=authenticators=] that do not utilize [[!FIDO-CTAP]] do not necessaril This section defines extensions that are both [=client extensions=] and [=authenticator extensions=]. -This section is currently empty. +### Credential Manager Trust Group Key extension (cmtgKey) ### {#sctn-cmtg-key-extension} + +#### Relying Party Usage #### {#sctn-cmtg-key-extension-usage} + +#### Extension Definition #### {#sctn-cmtg-key-extension-definition} + +: Extension identifier +:: `cmtgKey` + +: Operation applicability +:: [=registration extension|Registration=] and [=authentication extension|authentication=] + +: Client extension input +:: The Boolean value [TRUE] to indicate that this extension is requested by the [=[RP]=]. + + partial dictionary AuthenticationExtensionsClientInputs { + boolean cmtgKey; + }; + + +: Client extension processing +:: None, except creating the authenticator extension input from the client extension input. + +: Client extension output +:: An ArrayBuffer containing the COSE_Key-encoded Credential Manager Trust Group Key's public key that was returned in the authenticator extension output and the signature returned as the [=unsigned extension output=]. + + dictionary AuthenticationExtensionsCmtgKeyOutputs { + ArrayBuffer cmtgKey; + ArrayBuffer signature; + }; + + partial dictionary AuthenticationExtensionsClientOutputs { + AuthenticationExtensionsCmtgKeyOutputs cmtgKey; + }; + + +: Authenticator extension input +:: The Boolean value [TRUE], encoded in CBOR (major type 7, value 21). + + ``` + $$extensionInput //= ( + cmtgKey: true, + ) + ``` + +: Authenticator extension output +:: A byte string containing the COSE_Key-encoded Credential Manager Trust Group Key's public key: + + ``` + $$extensionOutput //= ( + cmtgKey: bstr, + ) + ``` + +: Unsigned extension output +:: A CBOR byte string containing a signature generated with the Credential Manager Trust Group Key's 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 Credential Manager Trust Group Key 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 Credential Manager Trust Group Key. + + 1. Let |cmtgPublicKey| be the newly created or existing [=Credential Manager Trust Group Key=], in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=]. + + 1. Let |cmtgPrivateKey| be the newly created or existing [=Credential Manager Trust Group private key=]. + + 1. Let |ctmgKeySig| be the result of signing the [=authenticator data=] using |cmtgPrivateKey|, using the same signature algorithm as that used by the [=user credential=]'s [=credential key pair=]. + + Note: |ctmgKeySig|, covers the [=[RP]=]'s {{PublicKeyCredentialCreationOptions/challenge}} because it includes the [=hash of the serialized client data=]. Thus the [=[RP]=] knows that |ctmgKeySig| is a fresh signature. + + 1. Output |ctmgKeySig| as the extension's [=unsigned extension output=]. + + Note: |ctmgKeySig| 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. # User Agent Automation # {#sctn-automation} From a67d0816d69988749e0286d203ff8db1bb6ad7d8 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Wed, 26 Nov 2025 16:28:36 -0500 Subject: [PATCH 02/48] add dfns --- index.bs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/index.bs b/index.bs index 3bdfbaee9..2ebf5c747 100644 --- a/index.bs +++ b/index.bs @@ -1043,6 +1043,7 @@ BCP 14 [[!RFC2119]] [[!RFC8174]] when, and only when, they appear in all capital : Credential Private Key : Credential Public Key : User Public Key +: User Credential :: A [=credential key pair=] is a pair of asymmetric cryptographic keys generated by an [=authenticator=] and [=scoped=] to a specific [=[WRP]=]. It is the central part of a [=public key credential=]. @@ -1060,6 +1061,20 @@ BCP 14 [[!RFC2119]] [[!RFC8174]] when, and only when, they appear in all capital Note: The [=credential public key=] is referred to as the [=user public key=] in FIDO UAF [[UAFProtocol]], and in FIDO U2F [[FIDO-U2F-Message-Formats]] and some parts of this specification that relate to it. +: Credential Manager Trust Group Key +: Credential Manager Trust Group Private Key +: Credential Manager Trust Group Public Key +:: A [=Credential Manager Trust Group Key=], is a [=authenticator=]- / [=credential manager=]-, [=[RP]=]-, and [=user credential=]-specific + public key pair created upon a [=[RP]=]'s request via the [=cmtgKey=] [=WebAuthn extension=]. + + The [=Credential Manager Trust Group Public Key=] is conveyed to the [=[RP]=] during a + [=registration ceremony|registration=] or [=authentication ceremony|authentication=] ceremony + as part of the [=cmtgKey=] extension output. + + The [=Credential Manager Trust Group Private Key=] is stored by the [=authenticator=] / [=credential manager=]. + + See [[#sctn-cmtg-key-extension]]. + : Credential Properties :: A [=credential property=] is some characteristic property of a [=public key credential source=], such as whether it is a [=client-side discoverable credential=] or a [=server-side credential=]. From ae644a8fbf6f00663bb3a42e0ce7c801b873b31b Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Thu, 18 Dec 2025 14:22:22 -0500 Subject: [PATCH 03/48] simplify to assertion signature --- index.bs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index 2ebf5c747..47aa0ccd4 100644 --- a/index.bs +++ b/index.bs @@ -7823,15 +7823,14 @@ This section defines extensions that are both [=client extensions=] and [=authen 1. Let |cmtgPrivateKey| be the newly created or existing [=Credential Manager Trust Group private key=]. - 1. Let |ctmgKeySig| be the result of signing the [=authenticator data=] using |cmtgPrivateKey|, using the same signature algorithm as that used by the [=user credential=]'s [=credential key pair=]. + 1. Let |ctmgKeySig| be the result of signing the [=assertion signature=] [input](#fig-signature) with |cmtgPrivateKey|, using the same signature algorithm as that used by the [=user credential=]'s [=credential key pair=]. - Note: |ctmgKeySig|, covers the [=[RP]=]'s {{PublicKeyCredentialCreationOptions/challenge}} because it includes the [=hash of the serialized client data=]. Thus the [=[RP]=] knows that |ctmgKeySig| is a fresh signature. + Note: the [=assertion signature=] [input](#fig-signature) and |ctmgKeySig| covers the [=[RP]=]'s {{PublicKeyCredentialCreationOptions/challenge}} because it includes the [=hash of the serialized client data=]. Thus the [=[RP]=] knows that |ctmgKeySig| is a fresh signature. 1. Output |ctmgKeySig| as the extension's [=unsigned extension output=]. Note: |ctmgKeySig| 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. - # 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 01db582a3aafd6530f29af9a5343e448a439d705 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Thu, 18 Dec 2025 14:28:10 -0500 Subject: [PATCH 04/48] spelling --- .cspell/acronyms.txt | 3 ++- index.bs | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.cspell/acronyms.txt b/.cspell/acronyms.txt index 6de53f6da..314a12c0f 100644 --- a/.cspell/acronyms.txt +++ b/.cspell/acronyms.txt @@ -23,4 +23,5 @@ VCHAR attestn RSASSA PKCS1 -bytestrings \ No newline at end of file +bytestrings +cmtg \ No newline at end of file diff --git a/index.bs b/index.bs index 4a21192c4..d1a6fcbb4 100644 --- a/index.bs +++ b/index.bs @@ -7829,13 +7829,13 @@ This section defines extensions that are both [=client extensions=] and [=authen 1. Let |cmtgPrivateKey| be the newly created or existing [=Credential Manager Trust Group private key=]. - 1. Let |ctmgKeySig| be the result of signing the [=assertion signature=] [input](#fig-signature) with |cmtgPrivateKey|, using the same signature algorithm as that used by the [=user credential=]'s [=credential key pair=]. + 1. Let |cmtgKeySig| be the result of signing the [=assertion signature=] [input](#fig-signature) with |cmtgPrivateKey|, using the same signature algorithm as that used by the [=user credential=]'s [=credential key pair=]. - Note: the [=assertion signature=] [input](#fig-signature) and |ctmgKeySig| covers the [=[RP]=]'s {{PublicKeyCredentialCreationOptions/challenge}} because it includes the [=hash of the serialized client data=]. Thus the [=[RP]=] knows that |ctmgKeySig| is a fresh signature. + Note: the [=assertion signature=] [input](#fig-signature) and |cmtgKeySig| covers the [=[RP]=]'s {{PublicKeyCredentialCreationOptions/challenge}} because it includes the [=hash of the serialized client data=]. Thus the [=[RP]=] knows that |cmtgKeySig| is a fresh signature. - 1. Output |ctmgKeySig| as the extension's [=unsigned extension output=]. + 1. Output |cmtgKeySig| as the extension's [=unsigned extension output=]. - Note: |ctmgKeySig| 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. + Note: |cmtgKeySig| 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. # User Agent Automation # {#sctn-automation} From 0a4ead0a2ccc4eb90b7f8067d345adb676e5b380 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Thu, 18 Dec 2025 14:40:56 -0500 Subject: [PATCH 05/48] line breaks --- index.bs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/index.bs b/index.bs index d1a6fcbb4..a6b68ee50 100644 --- a/index.bs +++ b/index.bs @@ -7753,7 +7753,8 @@ However, [=authenticators=] that do not utilize [[!FIDO-CTAP]] do not necessaril : Authenticator extension processing -:: [=largeblob|This extension=] directs the user-agent to cause the large blob to be stored on, or retrieved from, the authenticator. It thus does not specify any direct authenticator interaction for [=[RPS]=]. +:: [=largeblob|This extension=] directs the user-agent to cause the large blob to be stored on, + or retrieved from, the authenticator. It thus does not specify any direct authenticator interaction for [=[RPS]=]. ## Authenticator Extensions ## {#sctn-defined-authenticator-extensions} @@ -7784,7 +7785,9 @@ This section defines extensions that are both [=client extensions=] and [=authen :: None, except creating the authenticator extension input from the client extension input. : Client extension output -:: An ArrayBuffer containing the COSE_Key-encoded Credential Manager Trust Group Key's public key that was returned in the authenticator extension output and the signature returned as the [=unsigned extension output=]. +:: An ArrayBuffer containing the COSE_Key-encoded Credential Manager Trust Group Key's public key + that was returned in the authenticator extension output + and the signature returned as the [=unsigned extension output=]. dictionary AuthenticationExtensionsCmtgKeyOutputs { ArrayBuffer cmtgKey; @@ -7819,23 +7822,32 @@ This section defines extensions that are both [=client extensions=] and [=authen : 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. 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 the [=public key credential source=] is not [=backup eligible=] + then terminate these processing steps as this extension only applies to [=multi-device credentials=]. - 1. If a Credential Manager Trust Group Key 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 Credential Manager Trust Group Key. + 1. If a Credential Manager Trust Group Key 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 Credential Manager Trust Group Key. - 1. Let |cmtgPublicKey| be the newly created or existing [=Credential Manager Trust Group Key=], in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=]. + 1. Let |cmtgPublicKey| be the newly created or existing [=Credential Manager Trust Group Key=], + in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=]. 1. Let |cmtgPrivateKey| be the newly created or existing [=Credential Manager Trust Group private key=]. - 1. Let |cmtgKeySig| be the result of signing the [=assertion signature=] [input](#fig-signature) with |cmtgPrivateKey|, using the same signature algorithm as that used by the [=user credential=]'s [=credential key pair=]. + 1. Let |cmtgKeySig| be the result of signing the [=assertion signature=] [input](#fig-signature) with |cmtgPrivateKey|, + using the same signature algorithm as that used by the [=user credential=]'s [=credential key pair=]. - Note: the [=assertion signature=] [input](#fig-signature) and |cmtgKeySig| covers the [=[RP]=]'s {{PublicKeyCredentialCreationOptions/challenge}} because it includes the [=hash of the serialized client data=]. Thus the [=[RP]=] knows that |cmtgKeySig| is a fresh signature. + Note: the [=assertion signature=] [input](#fig-signature) and |cmtgKeySig| covers the [=[RP]=]'s {{PublicKeyCredentialCreationOptions/challenge}} + because it includes the [=hash of the serialized client data=]. Thus the [=[RP]=] knows that |cmtgKeySig| is a fresh signature. 1. Output |cmtgKeySig| as the extension's [=unsigned extension output=]. - Note: |cmtgKeySig| 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. + Note: |cmtgKeySig| 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. # User Agent Automation # {#sctn-automation} From 5f7b9f126e3d87c60f4161ef725c36f807eb8ab8 Mon Sep 17 00:00:00 2001 From: Tim Cappalli <tim@cloudauth.dev> Date: Thu, 8 Jan 2026 16:39:00 -0500 Subject: [PATCH 06/48] intro text and RP usage --- index.bs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/index.bs b/index.bs index 0f81b1138..b2047a44a 100644 --- a/index.bs +++ b/index.bs @@ -7763,8 +7763,46 @@ This section defines extensions that are both [=client extensions=] and [=authen ### Credential Manager Trust Group Key extension (<dfn>cmtgKey</dfn>) ### {#sctn-cmtg-key-extension} +This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] +enables an [=authenticator=]/credential manager to provide a signal to a [=[RP]=] +that two devices possessing the same [=backup eligible=] credential +have established a trust relationship through a non-remote interaction, +such as a local setup or physical proximity. + +This is done by creating an additional [=public key credential source=]-specific key pair +on the [=authenticator=]/credential manager, if such a key pair does not already exist for +the [=public key credential source=] being created or exercised, +and returning the [=Credential Manager Trust Group public key=] +along with a signature by the [=Credential Manager Trust Group private key=] to the [=[RP]=]. + +This is done each time this [=cmtgKey=] extension is included with either a +{{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/get()|navigator.credentials.get()}} call. + #### Relying Party Usage #### {#sctn-cmtg-key-extension-usage} +This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. + +This extension provides a "trust group" signal <i>when used consistently</i> with both +{{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations. + +When a [=[RP]=] uses the `cmtgKey` extension during a {{CredentialsContainer/create()|create()}} option on the first device (`dev1`), +the selected [=authenticator=]/credential manager (`cm1`) generates a new [=Credential Manager Trust Group Key=] pair. +The extension output includes the public key and a signature generated using the +[=Credential Manager Trust Group private key=] (`cmtgKey1`). + +A subsequent {{CredentialsContainer/get()|get()}} operation (with the `cmtgKey` extension) on `dev1` +using the same credential and the same [=authenticator=]/credential manager (`cm1`), +will include a signature from the same [=Credential Manager Trust Group private key=], `cmtgKey1`. + +`cmtgKey1` is only synced to an [=authenticator=]/credential manager on another device (`dev2`) +if a non-remote relationship exists between the source (`dev1`) and destination (`dev2`) device. + +If a third device (`device 3`) is introduced (with `cm1` configured) +that does not share a non-remote relationship with `device 1` or `device 2` +(as evaluated by the [=authenticator=]/credential manager, `cm1`), +a new [=Credential Manager Trust Group private key=] (`cmtgKey2`) is generated and returned +to the [=[RP]=] along with it's public key, signaling the start of a separate trust group. + #### Extension Definition #### {#sctn-cmtg-key-extension-definition} : Extension identifier From adcef86bc2b79b0225ac7e2fc94d9ed0c03dc722 Mon Sep 17 00:00:00 2001 From: Tim Cappalli <tim@cloudauth.dev> Date: Thu, 8 Jan 2026 17:32:44 -0500 Subject: [PATCH 07/48] RP processing rules --- index.bs | 103 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 92 insertions(+), 11 deletions(-) diff --git a/index.bs b/index.bs index b2047a44a..bdb5e3cc6 100644 --- a/index.bs +++ b/index.bs @@ -7764,13 +7764,13 @@ This section defines extensions that are both [=client extensions=] and [=authen ### Credential Manager Trust Group Key extension (<dfn>cmtgKey</dfn>) ### {#sctn-cmtg-key-extension} This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] -enables an [=authenticator=]/credential manager to provide a signal to a [=[RP]=] +enables an [=authenticator=]/[=credential manager=] to provide a signal to a [=[RP]=] that two devices possessing the same [=backup eligible=] credential have established a trust relationship through a non-remote interaction, such as a local setup or physical proximity. This is done by creating an additional [=public key credential source=]-specific key pair -on the [=authenticator=]/credential manager, if such a key pair does not already exist for +in the authenticator/credential manager, if such a key pair does not already exist for the [=public key credential source=] being created or exercised, and returning the [=Credential Manager Trust Group public key=] along with a signature by the [=Credential Manager Trust Group private key=] to the [=[RP]=]. @@ -7786,20 +7786,20 @@ This extension provides a "trust group" signal <i>when used consistently</i> wit {{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations. When a [=[RP]=] uses the `cmtgKey` extension during a {{CredentialsContainer/create()|create()}} option on the first device (`dev1`), -the selected [=authenticator=]/credential manager (`cm1`) generates a new [=Credential Manager Trust Group Key=] pair. +the selected [=authenticator=]/[=credential manager=] (`cm1`) generates a new [=Credential Manager Trust Group Key=] pair. The extension output includes the public key and a signature generated using the [=Credential Manager Trust Group private key=] (`cmtgKey1`). A subsequent {{CredentialsContainer/get()|get()}} operation (with the `cmtgKey` extension) on `dev1` -using the same credential and the same [=authenticator=]/credential manager (`cm1`), +using the same credential and the same authenticator/credential manager (`cm1`), will include a signature from the same [=Credential Manager Trust Group private key=], `cmtgKey1`. -`cmtgKey1` is only synced to an [=authenticator=]/credential manager on another device (`dev2`) +`cmtgKey1` is only synced to an authenticator/credential manager on another device (`dev2`) if a non-remote relationship exists between the source (`dev1`) and destination (`dev2`) device. If a third device (`device 3`) is introduced (with `cm1` configured) that does not share a non-remote relationship with `device 1` or `device 2` -(as evaluated by the [=authenticator=]/credential manager, `cm1`), +(as evaluated by the authenticator/credential manager, `cm1`), a new [=Credential Manager Trust Group private key=] (`cmtgKey2`) is generated and returned to the [=[RP]=] along with it's public key, signaling the start of a separate trust group. @@ -7863,13 +7863,13 @@ to the [=[RP]=] along with it's public key, signaling the start of a separate tr 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 as this extension only applies to [=multi-device credentials=]. + 1. If the [=public key credential source=] is not [=backup eligible=], + terminate these processing steps as this extension only applies to [=multi-device credentials=]. - 1. If a Credential Manager Trust Group Key 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=], + 1. If a [=Credential Manager Trust Group Key=] 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 in the [=authenticator=]/[=credential manager=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], - otherwise locate the existing Credential Manager Trust Group Key. + otherwise locate the existing [=Credential Manager Trust Group Key=]. 1. Let |cmtgPublicKey| be the newly created or existing [=Credential Manager Trust Group Key=], in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=]. @@ -7887,6 +7887,87 @@ to the [=[RP]=] along with it's public key, signaling the start of a separate tr Note: |cmtgKeySig| 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. +#### `cmtgKey` Extension Output Verification Procedures #### {#sctn-cmtg-key-extension-verification} + +Verifying the <code>[=cmtgKey=]</code> extension output is performed by the [=[RP]=] whenever a [=Credential Manager Trust Group Key=] is returned within the extension output. + +##### Registration (`create()`) ##### {#sctn-cmtg-key-extension-verification-create} + +If the `cmtgKey` extension was included on a {{CredentialsContainer/create()|navigator.credentials.create()}} call, +then the below verification steps are performed in the context of <a href=#reg-ceremony-verify-extension-outputs>this step</a> 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 `cmtgKey` is acceptable. + +1. Let |attObjForCmtgKey| be the value of the {{AuthenticationExtensionsClientOutputs/cmtgKey}} member of |clientExtensionResults|. + +1. Verify that |attObjForCmtgKey| is valid CBOR conforming to the syntax defined above + and perform CBOR decoding on it to extract the contained fields: |signature|, |cmtgKey|. + + Note: The latter |attObjForCmtgKey| 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 {{AuthenticationExtensionsCmtgKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) + (i.e. `authData` and `hash`) by the [=Credential Manager Trust Group Key=] |cmtgKey|. + (The signature algorithm is the same as for the [=user credential=].) + +1. Complete the steps from [[#sctn-registering-a-new-credential]] and, if those steps are successful, + store the |cmtgKey| value indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. + +See also [[#sctn-cmtg-key-extension-usage]] for further details. + +##### Authentication (`get()`) ##### {#sctn-cmtg-key-extension-verification-get} + +If the `cmtgKey` extension was included on a {{CredentialsContainer/get()|navigator.credentials.get()}} call, +then the below verification steps are performed in the context of <a href=#authn-ceremony-verify-extension-outputs>this step</a> of [[#sctn-verifying-assertion]] +using these variables established therein: |credential|, |clientExtensionResults|, |authData|, and |hash|. +[=[RP]=] policy may specify whether a response without a `cmtgKey` is acceptable. + +1. Let |attObjForCmtgKey| be the value of the `cmtgKey` member of |clientExtensionResults|. + +1. Verify that |attObjForCmtgKey| is valid CBOR conforming to the syntax defined above + and perform CBOR decoding on it to extract the contained fields: |signature|, |cmtgKey|. + + Note: The latter |attObjForCmtgKey| 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 {{AuthenticationExtensionsCmtgKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) + (i.e. `authData` and `hash`) by the [=Credential Manager Trust Group public key=] |cmtgKey|. + (The signature algorithm is the same as for the [=user credential=].) + +1. If the [=[RP]=]'s [=user account=] mapped to the <code>|credential|.{{Credential/id}}</code> in play (i.e., for the user being authenticated) + hold a `cmtgKey` value corresponding to the extracted |attObjForCmtgKey| fields, + then perform binary equality checks between the corresponding stored value and the extracted field value. + The [=[RP]=] MAY have more than one `cmtgKey` value mapped to the [=user account=] and <code>|credential|.{{Credential/id}}</code> pair + and each value MUST be checked. + + If the above set of binary equality checks resulted in: + + <dl class="switch"> + : more than one match + :: Some form of error has occurred. + Terminate these verification steps. + + : exactly one match + :: The [=authenticator=]/[=credential manager=] is part of a previously seen trust group: + + Terminate these verification steps. + + : zero matches + :: This is possibly a new [=Credential Manager Trust Group Key=], signifying a new trust group: + + Store the extracted |cmtgKey| value indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. + Terminate these verification steps. + + </dl> + +1. Otherwise, the [=[RP]=] does not have |attObjForCmtgKey| fields presently mapped to this [=user account=] + and <code>|credential|.{{Credential/id}}</code> pair: + + 1. Store the extracted |cmtgKey| value indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. + Terminate these verification steps. + +See also [[#sctn-cmtg-key-extension-usage]]. + # 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 1f3fc61ed1adab0721144c5da34e4f059e60b28c Mon Sep 17 00:00:00 2001 From: Tim Cappalli <tim@cloudauth.dev> Date: Wed, 4 Feb 2026 10:38:41 +0100 Subject: [PATCH 08/48] first pass of editorial feedback from Nina --- index.bs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/index.bs b/index.bs index bdb5e3cc6..88cb2575a 100644 --- a/index.bs +++ b/index.bs @@ -7765,14 +7765,14 @@ This section defines extensions that are both [=client extensions=] and [=authen This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] enables an [=authenticator=]/[=credential manager=] to provide a signal to a [=[RP]=] -that two devices possessing the same [=backup eligible=] credential +that two devices possessing the same [=backed up=] credential have established a trust relationship through a non-remote interaction, such as a local setup or physical proximity. -This is done by creating an additional [=public key credential source=]-specific key pair +This is done by creating an additional [=public key credential source=]-specific [=Credential Manager Trust Group key pair=] in the authenticator/credential manager, if such a key pair does not already exist for the [=public key credential source=] being created or exercised, -and returning the [=Credential Manager Trust Group public key=] +and returning one of the [=Credential Manager Trust Group public keys=] along with a signature by the [=Credential Manager Trust Group private key=] to the [=[RP]=]. This is done each time this [=cmtgKey=] extension is included with either a @@ -7800,8 +7800,8 @@ if a non-remote relationship exists between the source (`dev1`) and destination If a third device (`device 3`) is introduced (with `cm1` configured) that does not share a non-remote relationship with `device 1` or `device 2` (as evaluated by the authenticator/credential manager, `cm1`), -a new [=Credential Manager Trust Group private key=] (`cmtgKey2`) is generated and returned -to the [=[RP]=] along with it's public key, signaling the start of a separate trust group. +a new [=Credential Manager Trust Group private key=] (`cmtgKey2`) is generated and its public key +is returned to the [=[RP]=] along with it's public key, signaling the start of a separate trust group. #### Extension Definition #### {#sctn-cmtg-key-extension-definition} @@ -7823,7 +7823,7 @@ to the [=[RP]=] along with it's public key, signaling the start of a separate tr :: None, except creating the authenticator extension input from the client extension input. : Client extension output -:: An ArrayBuffer containing the COSE_Key-encoded Credential Manager Trust Group Key's public key +:: An ArrayBuffer containing the COSE_Key-encoded [=Credential Manager Trust Group public key=] that was returned in the authenticator extension output and the signature returned as the [=unsigned extension output=]. <xmp class="idl"> @@ -7847,7 +7847,7 @@ to the [=[RP]=] along with it's public key, signaling the start of a separate tr ``` : Authenticator extension output -:: A byte string containing the COSE_Key-encoded Credential Manager Trust Group Key's public key: +:: A byte string containing the COSE_Key-encoded [=Credential Manager Trust Group public key=]: ``` $$extensionOutput //= ( From e2e857cfb993d2e2d2e78415b8cd297d901975b8 Mon Sep 17 00:00:00 2001 From: Tim Cappalli <tim@cloudauth.dev> Date: Wed, 4 Feb 2026 04:41:30 -0500 Subject: [PATCH 09/48] Apply suggestion from @nsatragno Co-authored-by: Nina Satragno <nsatragno@gmail.com> --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 88cb2575a..ced1690b5 100644 --- a/index.bs +++ b/index.bs @@ -7770,8 +7770,8 @@ have established a trust relationship through a non-remote interaction, such as a local setup or physical proximity. This is done by creating an additional [=public key credential source=]-specific [=Credential Manager Trust Group key pair=] -in the authenticator/credential manager, if such a key pair does not already exist for -the [=public key credential source=] being created or exercised, +in the [=authenticator=], if such a key pair needs to be created for the +[=public key credential source=] being created or exercised on the specific device, and returning one of the [=Credential Manager Trust Group public keys=] along with a signature by the [=Credential Manager Trust Group private key=] to the [=[RP]=]. From 4920b67881b23f89a89e00632df5e2c691aa5fbb Mon Sep 17 00:00:00 2001 From: Tim Cappalli <tim@cloudauth.dev> Date: Wed, 4 Feb 2026 12:04:18 +0100 Subject: [PATCH 10/48] add verification of algo step --- index.bs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/index.bs b/index.bs index ced1690b5..4a8b0618f 100644 --- a/index.bs +++ b/index.bs @@ -7893,10 +7893,10 @@ Verifying the <code>[=cmtgKey=]</code> extension output is performed by the [=[R ##### Registration (`create()`) ##### {#sctn-cmtg-key-extension-verification-create} -If the `cmtgKey` extension was included on a {{CredentialsContainer/create()|navigator.credentials.create()}} call, -then the below verification steps are performed in the context of <a href=#reg-ceremony-verify-extension-outputs>this step</a> of [[#sctn-registering-a-new-credential]] +If the `cmtgKey` extension was included on a {{CredentialsContainer/create()|navigator.credentials.create()}} call +and a [=Credential Manager Trust Group Key=] is returned by an [=authenticator=], +the below verification steps are performed in the context of <a href=#reg-ceremony-verify-extension-outputs>this step</a> 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 `cmtgKey` is acceptable. 1. Let |attObjForCmtgKey| be the value of the {{AuthenticationExtensionsClientOutputs/cmtgKey}} member of |clientExtensionResults|. @@ -7906,9 +7906,12 @@ using these variables established therein: |credential|, |clientExtensionResults Note: The latter |attObjForCmtgKey| 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 the algorithm used for the signature in {{AuthenticationExtensionsCmtgKeyOutputs}} + matches the COSE algorithm identifier of the [=credentialPublicKey=] within the [=attestedCredentialData=] + of |authData|. + 1. Verify that {{AuthenticationExtensionsCmtgKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) (i.e. `authData` and `hash`) by the [=Credential Manager Trust Group Key=] |cmtgKey|. - (The signature algorithm is the same as for the [=user credential=].) 1. Complete the steps from [[#sctn-registering-a-new-credential]] and, if those steps are successful, store the |cmtgKey| value indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. @@ -7930,6 +7933,10 @@ using these variables established therein: |credential|, |clientExtensionResults Note: The latter |attObjForCmtgKey| 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 the algorithm used for the signature in {{AuthenticationExtensionsCmtgKeyOutputs}} + matches the algorithm of the public key previously stored for the credential + identified by the [=credentialId=]. + 1. Verify that {{AuthenticationExtensionsCmtgKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) (i.e. `authData` and `hash`) by the [=Credential Manager Trust Group public key=] |cmtgKey|. (The signature algorithm is the same as for the [=user credential=].) From a579300669ba2b41372bd1aff4425a678db55fc0 Mon Sep 17 00:00:00 2001 From: Tim Cappalli <tim@cloudauth.dev> Date: Wed, 4 Feb 2026 06:06:36 -0500 Subject: [PATCH 11/48] Apply suggestion from @nsatragno Co-authored-by: Nina Satragno <nsatragno@gmail.com> --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 4a8b0618f..abaf33c5e 100644 --- a/index.bs +++ b/index.bs @@ -1068,7 +1068,7 @@ BCP 14 [[!RFC2119]] [[!RFC8174]] when, and only when, they appear in all capital public key pair created upon a [=[RP]=]'s request via the [=cmtgKey=] [=WebAuthn extension=]. The [=Credential Manager Trust Group Public Key=] is conveyed to the [=[RP]=] during a - [=registration ceremony|registration=] or [=authentication ceremony|authentication=] ceremony + [=registration ceremony|registration=] or [=authentication ceremony|authentication=] ceremony as part of the [=cmtgKey=] extension output. The [=Credential Manager Trust Group Private Key=] is stored by the [=authenticator=] / [=credential manager=]. From 85df71ae9367eb44842e84cff070845e879aa76d Mon Sep 17 00:00:00 2001 From: Tim Cappalli <tim@cloudauth.dev> Date: Wed, 4 Feb 2026 12:28:32 +0100 Subject: [PATCH 12/48] consistency with device references --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index abaf33c5e..ca8b6e32b 100644 --- a/index.bs +++ b/index.bs @@ -7797,8 +7797,8 @@ will include a signature from the same [=Credential Manager Trust Group private `cmtgKey1` is only synced to an authenticator/credential manager on another device (`dev2`) if a non-remote relationship exists between the source (`dev1`) and destination (`dev2`) device. -If a third device (`device 3`) is introduced (with `cm1` configured) -that does not share a non-remote relationship with `device 1` or `device 2` +If a third device (`dev3`) is introduced (with `cm1` configured), +which does not share a non-remote relationship with `dev1`or `dev2` (as evaluated by the authenticator/credential manager, `cm1`), a new [=Credential Manager Trust Group private key=] (`cmtgKey2`) is generated and its public key is returned to the [=[RP]=] along with it's public key, signaling the start of a separate trust group. From 2646a20b230341e8ae0b31cc877512a235735c30 Mon Sep 17 00:00:00 2001 From: Tim Cappalli <tim@cloudauth.dev> Date: Mon, 16 Feb 2026 16:30:21 -0500 Subject: [PATCH 13/48] review feedback --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index ca8b6e32b..7939bc762 100644 --- a/index.bs +++ b/index.bs @@ -7942,7 +7942,7 @@ using these variables established therein: |credential|, |clientExtensionResults (The signature algorithm is the same as for the [=user credential=].) 1. If the [=[RP]=]'s [=user account=] mapped to the <code>|credential|.{{Credential/id}}</code> in play (i.e., for the user being authenticated) - hold a `cmtgKey` value corresponding to the extracted |attObjForCmtgKey| fields, + holds a `cmtgKey` value corresponding to the extracted |attObjForCmtgKey| fields, then perform binary equality checks between the corresponding stored value and the extracted field value. The [=[RP]=] MAY have more than one `cmtgKey` value mapped to the [=user account=] and <code>|credential|.{{Credential/id}}</code> pair and each value MUST be checked. From 49850fca626609bc52d697f765ac1678127e637f Mon Sep 17 00:00:00 2001 From: Tim Cappalli <tim@cloudauth.dev> Date: Thu, 12 Mar 2026 16:30:30 -0400 Subject: [PATCH 14/48] add CMTG credential record and registration update --- index.bs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 7939bc762..cb0dece34 100644 --- a/index.bs +++ b/index.bs @@ -7803,6 +7803,27 @@ which does not share a non-remote relationship with `dev1`or `dev2` a new [=Credential Manager Trust Group private key=] (`cmtgKey2`) is generated and its public key is returned to the [=[RP]=] along with it's public key, signaling the start of a separate trust group. +#### Updating the RP's Credential Record #### {#sctn-cmtg-key-credential-record} + +When this extension is used by a [=Relying Party=], +the [=credential record=] structure SHOULD be augmented with the following field: + +<dl dfn-for="credential record" dfn-type="abstract-op"> + : <dfn>cmtgKeyRecords</dfn> + :: A [=list=] of [=CMTG Key Records=], initially empty. +</dl> + +A <dfn id="cmtgKeyRecord">CMTG Key Record</dfn> is a [=struct=] with the following [=struct/items=]: + +<dl dfn-for="CMTG Key Record"> + : <dfn id="cmtgKeyRecordPublicKey">publicKey</dfn> + :: The [=Credential Manager Trust Group public key|CMTG public key=] + + : <dfn id="cmtgKeyRecordLastUsed">lastUsed</dfn> + :: A timestamp representing the last time this specific key was verified by the [=Relying Party=]. +</dl> + + #### Extension Definition #### {#sctn-cmtg-key-extension-definition} : Extension identifier @@ -7898,6 +7919,8 @@ and a [=Credential Manager Trust Group Key=] is returned by an [=authenticator=] the below verification steps are performed in the context of <a href=#reg-ceremony-verify-extension-outputs>this step</a> of [[#sctn-registering-a-new-credential]] using these variables established therein: |credential|, |clientExtensionResults|, |authData|, and |hash|. +1. Let |credentialRecord| be the [=credential record=] for the [=credential=]. + 1. Let |attObjForCmtgKey| be the value of the {{AuthenticationExtensionsClientOutputs/cmtgKey}} member of |clientExtensionResults|. 1. Verify that |attObjForCmtgKey| is valid CBOR conforming to the syntax defined above @@ -7913,8 +7936,11 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Verify that {{AuthenticationExtensionsCmtgKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) (i.e. `authData` and `hash`) by the [=Credential Manager Trust Group Key=] |cmtgKey|. -1. Complete the steps from [[#sctn-registering-a-new-credential]] and, if those steps are successful, - store the |cmtgKey| value indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. +1. Complete the steps from [[#sctn-registering-a-new-credential]] and, if those steps are successful: + + 1. Let |newEntry| be a new [=CMTG Key Record=] where its [=CMTG Key Record/publicKey=] is |cmtgKey| + and its [=CMTG Key Record/lastUsed=] is the current time. + 1. Append |newEntry| to |credentialRecord|.[$credential record/cmtgKeyRecords$] See also [[#sctn-cmtg-key-extension-usage]] for further details. From b5592aea6327250e6682ad3bbcbb36dd5bb4911c Mon Sep 17 00:00:00 2001 From: Tim Cappalli <tim@cloudauth.dev> Date: Thu, 12 Mar 2026 16:48:02 -0400 Subject: [PATCH 15/48] fix registratio verification steps --- index.bs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/index.bs b/index.bs index cb0dece34..a2ebe02d0 100644 --- a/index.bs +++ b/index.bs @@ -7921,19 +7921,23 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Let |credentialRecord| be the [=credential record=] for the [=credential=]. -1. Let |attObjForCmtgKey| be the value of the {{AuthenticationExtensionsClientOutputs/cmtgKey}} member of |clientExtensionResults|. +1. Let |extensionOutput| be the value of the {{AuthenticationExtensionsClientOutputs/cmtgKey}} member of |clientExtensionResults|. -1. Verify that |attObjForCmtgKey| is valid CBOR conforming to the syntax defined above - and perform CBOR decoding on it to extract the contained fields: |signature|, |cmtgKey|. +1. Verify that |extensionOutput| is valid CBOR conforming to the syntax defined above. + Perform CBOR decoding on |extensionOutput| and let |cmtgOutput| be the resulting {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. + +1. Let |signature| be |cmtgOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}}. + +1. Let |cmtgKey| be |cmtgOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/cmtgKey}}. + Verify that |cmtgKey| is a valid [=COSE_Key=] object. - Note: The latter |attObjForCmtgKey| fields are referenced exclusively in the below steps + Note: The members of |cmtgOutput| 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 the algorithm used for the signature in {{AuthenticationExtensionsCmtgKeyOutputs}} - matches the COSE algorithm identifier of the [=credentialPublicKey=] within the [=attestedCredentialData=] - of |authData|. +1. Verify that the algorithm used for |signature| matches the COSE algorithm identifier of the [=credentialPublicKey=] + within the [=attestedCredentialData=] of |authData|. -1. Verify that {{AuthenticationExtensionsCmtgKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) +1. Verify that |signature| is a valid signature over the [=assertion signature=] [input](#fig-signature) (i.e. `authData` and `hash`) by the [=Credential Manager Trust Group Key=] |cmtgKey|. 1. Complete the steps from [[#sctn-registering-a-new-credential]] and, if those steps are successful: From ff31f978ace2f8bd7ff480cf8f49a325f00eb032 Mon Sep 17 00:00:00 2001 From: Tim Cappalli <tim@cloudauth.dev> Date: Thu, 12 Mar 2026 16:54:06 -0400 Subject: [PATCH 16/48] fix authentication verification steps --- index.bs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/index.bs b/index.bs index a2ebe02d0..876ecd1c6 100644 --- a/index.bs +++ b/index.bs @@ -7955,19 +7955,23 @@ then the below verification steps are performed in the context of <a href=#authn using these variables established therein: |credential|, |clientExtensionResults|, |authData|, and |hash|. [=[RP]=] policy may specify whether a response without a `cmtgKey` is acceptable. -1. Let |attObjForCmtgKey| be the value of the `cmtgKey` member of |clientExtensionResults|. +1. Let |extensionOutput| be the value of the cmtgKey member of |clientExtensionResults|. -1. Verify that |attObjForCmtgKey| is valid CBOR conforming to the syntax defined above - and perform CBOR decoding on it to extract the contained fields: |signature|, |cmtgKey|. +1. Verify that |extensionOutput| is valid CBOR conforming to the syntax defined above. + Perform CBOR decoding on |extensionOutput| and let |cmtgOutput| be the resulting {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. + +1. Let |signature| be |cmtgOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}}. + +1. Let |cmtgKey| be |cmtgOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/cmtgKey}}. + Verify that |cmtgKey| is a valid [=COSE_Key=] object. Note: The latter |attObjForCmtgKey| 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 the algorithm used for the signature in {{AuthenticationExtensionsCmtgKeyOutputs}} - matches the algorithm of the public key previously stored for the credential +1. Verify that the algorithm used for |signature| matches the algorithm of the public key previously stored for the credential identified by the [=credentialId=]. -1. Verify that {{AuthenticationExtensionsCmtgKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) +1. Verify that |signature| is a valid signature over the [=assertion signature=] [input](#fig-signature) (i.e. `authData` and `hash`) by the [=Credential Manager Trust Group public key=] |cmtgKey|. (The signature algorithm is the same as for the [=user credential=].) From 7b44471b51efa65f6870a28ae46b060547b5ad50 Mon Sep 17 00:00:00 2001 From: Tim Cappalli <tim@cloudauth.dev> Date: Thu, 12 Mar 2026 17:08:10 -0400 Subject: [PATCH 17/48] credential record for authentication --- index.bs | 53 ++++++++++++++++++++--------------------------------- 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/index.bs b/index.bs index 876ecd1c6..ead207470 100644 --- a/index.bs +++ b/index.bs @@ -1064,6 +1064,7 @@ BCP 14 [[!RFC2119]] [[!RFC8174]] when, and only when, they appear in all capital : <dfn>Credential Manager Trust Group Key</dfn> : <dfn>Credential Manager Trust Group Private Key</dfn> : <dfn>Credential Manager Trust Group Public Key</dfn> +: <dfn>Credential Manager Trust Group Key Pair</dfn> :: A [=Credential Manager Trust Group Key=], is a [=authenticator=]- / [=credential manager=]-, [=[RP]=]-, and [=user credential=]-specific public key pair created upon a [=[RP]=]'s request via the [=cmtgKey=] [=WebAuthn extension=]. @@ -7929,10 +7930,7 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Let |signature| be |cmtgOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}}. 1. Let |cmtgKey| be |cmtgOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/cmtgKey}}. - Verify that |cmtgKey| is a valid [=COSE_Key=] object. - - Note: The members of |cmtgOutput| 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=]. + Verify that |cmtgKey| is a valid COSE_Key object. 1. Verify that the algorithm used for |signature| matches the COSE algorithm identifier of the [=credentialPublicKey=] within the [=attestedCredentialData=] of |authData|. @@ -7955,6 +7953,8 @@ then the below verification steps are performed in the context of <a href=#authn using these variables established therein: |credential|, |clientExtensionResults|, |authData|, and |hash|. [=[RP]=] policy may specify whether a response without a `cmtgKey` is acceptable. +1. Let |credentialRecord| be the [=credential record=] for the [=credential=]. + 1. Let |extensionOutput| be the value of the cmtgKey member of |clientExtensionResults|. 1. Verify that |extensionOutput| is valid CBOR conforming to the syntax defined above. @@ -7963,49 +7963,36 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Let |signature| be |cmtgOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}}. 1. Let |cmtgKey| be |cmtgOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/cmtgKey}}. - Verify that |cmtgKey| is a valid [=COSE_Key=] object. + Verify that |cmtgKey| is a valid COSE_Key object. - Note: The latter |attObjForCmtgKey| 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 the algorithm used for |signature| matches the algorithm of the public key previously stored for the credential - identified by the [=credentialId=]. +1. Verify that the algorithm used for |signature| matches the algorithm of the public key + previously stored for the |credentialRecord|. 1. Verify that |signature| is a valid signature over the [=assertion signature=] [input](#fig-signature) (i.e. `authData` and `hash`) by the [=Credential Manager Trust Group public key=] |cmtgKey|. (The signature algorithm is the same as for the [=user credential=].) -1. If the [=[RP]=]'s [=user account=] mapped to the <code>|credential|.{{Credential/id}}</code> in play (i.e., for the user being authenticated) - holds a `cmtgKey` value corresponding to the extracted |attObjForCmtgKey| fields, - then perform binary equality checks between the corresponding stored value and the extracted field value. - The [=[RP]=] MAY have more than one `cmtgKey` value mapped to the [=user account=] and <code>|credential|.{{Credential/id}}</code> pair - and each value MUST be checked. - - If the above set of binary equality checks resulted in: +1. If |credentialRecord|.[$credential record/cmtgKeyRecords$] contains an entry |e| + where |e|.[=CMTG Key Record/publicKey=] matches |cmtgKey|: - <dl class="switch"> - : more than one match - :: Some form of error has occurred. - Terminate these verification steps. + The [=authenticator=]/[=credential manager=] is part of a previously seen trust group. - : exactly one match - :: The [=authenticator=]/[=credential manager=] is part of a previously seen trust group: + 1. Update |e|.[=CMTG Key Record/lastUsed=] to the current time. - Terminate these verification steps. + 1. Terminate these verification steps. - : zero matches - :: This is possibly a new [=Credential Manager Trust Group Key=], signifying a new trust group: +1. Otherwise: - Store the extracted |cmtgKey| value indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. - Terminate these verification steps. + This is possibly a new [=Credential Manager Trust Group Key=], signifying a new trust group. - </dl> + 1. Optionally, perform additional policy checks. + + 1. Let |newEntry| be a new [=CMTG Key Record=] where its [=CMTG Key Record/publicKey=] is |cmtgKey| + and its [=CMTG Key Record/lastUsed=] is the current time. -1. Otherwise, the [=[RP]=] does not have |attObjForCmtgKey| fields presently mapped to this [=user account=] - and <code>|credential|.{{Credential/id}}</code> pair: + 1. Append |newEntry| to |credentialRecord|.[$credential record/cmtgKeyRecords$]. - 1. Store the extracted |cmtgKey| value indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. - Terminate these verification steps. + 1. Terminate these verification steps. See also [[#sctn-cmtg-key-extension-usage]]. From f1051fc971c779eec6674f77c10bdf64d8519b70 Mon Sep 17 00:00:00 2001 From: Tim Cappalli <tim@cloudauth.dev> Date: Mon, 16 Mar 2026 15:55:11 -0400 Subject: [PATCH 18/48] accidential change --- index.bs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.bs b/index.bs index ead207470..5ea032233 100644 --- a/index.bs +++ b/index.bs @@ -7754,8 +7754,7 @@ However, [=authenticators=] that do not utilize [[!FIDO-CTAP]] do not necessaril : Authenticator extension processing -:: [=largeblob|This extension=] directs the user-agent to cause the large blob to be stored on, - or retrieved from, the authenticator. It thus does not specify any direct authenticator interaction for [=[RPS]=]. +:: [=largeblob|This extension=] directs the user-agent to cause the large blob to be stored on, or retrieved from, the authenticator. It thus does not specify any direct authenticator interaction for [=[RPS]=]. ## Authenticator Extensions ## {#sctn-defined-authenticator-extensions} From 37309643441010e8e6e5e16489a08ed7c48f6b38 Mon Sep 17 00:00:00 2001 From: Tim Cappalli <tim@cloudauth.dev> Date: Mon, 16 Mar 2026 16:17:47 -0400 Subject: [PATCH 19/48] add No CMTG Key processing rules --- index.bs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 5ea032233..08986b610 100644 --- a/index.bs +++ b/index.bs @@ -7841,7 +7841,10 @@ A <dfn id="cmtgKeyRecord">CMTG Key Record</dfn> is a [=struct=] with the followi : Client extension processing -:: None, except creating the authenticator extension input from the client extension input. +:: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: + 1. Create the authenticator extension input from the client extension input. + 1. If the authenticator extension output is empty or indicates the extension was not processed, + stop processing this extension and return no output. : Client extension output :: An ArrayBuffer containing the COSE_Key-encoded [=Credential Manager Trust Group public key=] @@ -7858,6 +7861,9 @@ A CMTG Key Record is a [=struct=] with the followi }; + If the authenticator did not return an extension output, + this key MUST be omitted from the AuthenticationExtensionsClientOutputs map. + : Authenticator extension input :: The Boolean value [TRUE], encoded in CBOR (major type 7, value 21). From 4c93271cdd324e9993a0a0d89dbc9998339ee4b5 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Mon, 16 Mar 2026 16:39:14 -0400 Subject: [PATCH 20/48] typos and RP step fixes --- index.bs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/index.bs b/index.bs index 08986b610..df3ad86cd 100644 --- a/index.bs +++ b/index.bs @@ -7801,7 +7801,7 @@ If a third device (`dev3`) is introduced (with `cm1` configured), which does not share a non-remote relationship with `dev1`or `dev2` (as evaluated by the authenticator/credential manager, `cm1`), a new [=Credential Manager Trust Group private key=] (`cmtgKey2`) is generated and its public key -is returned to the [=[RP]=] along with it's public key, signaling the start of a separate trust group. +is returned to the [=[RP]=], signaling the start of a separate trust group. #### Updating the RP's Credential Record #### {#sctn-cmtg-key-credential-record} @@ -7847,8 +7847,8 @@ A CMTG Key Record is a [=struct=] with the followi stop processing this extension and return no output. : Client extension output -:: An ArrayBuffer containing the COSE_Key-encoded [=Credential Manager Trust Group public key=] - that was returned in the authenticator extension output +:: An {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary containing the COSE_Key-encoded [=Credential Manager Trust Group public key=] + returned in the authenticator extension output, and the signature returned as the [=unsigned extension output=]. dictionary AuthenticationExtensionsCmtgKeyOutputs { @@ -7862,7 +7862,7 @@ A <dfn id="cmtgKeyRecord">CMTG Key Record</dfn> is a [=struct=] with the followi If the authenticator did not return an extension output, - this key MUST be omitted from the AuthenticationExtensionsClientOutputs map. + this key MUST be omitted from the {{AuthenticationExtensionsClientOutputs}} map. : Authenticator extension input :: The Boolean value [TRUE], encoded in CBOR (major type 7, value 21). @@ -7893,8 +7893,8 @@ A CMTG Key Record is a [=struct=] with the followi 1. If the [=public key credential source=] is not [=backup eligible=], terminate these processing steps as this extension only applies to [=multi-device credentials=]. - 1. If a [=Credential Manager Trust Group Key=] 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 in the [=authenticator=]/[=credential manager=], + 1. If a [=Credential Manager Trust Group Key=] 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 in the [=authenticator=]/[=credential manager=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing [=Credential Manager Trust Group Key=]. @@ -7929,13 +7929,13 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Let |extensionOutput| be the value of the {{AuthenticationExtensionsClientOutputs/cmtgKey}} member of |clientExtensionResults|. -1. Verify that |extensionOutput| is valid CBOR conforming to the syntax defined above. - Perform CBOR decoding on |extensionOutput| and let |cmtgOutput| be the resulting {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. +1. Verify that |extensionOutput| is a valid {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. 1. Let |signature| be |cmtgOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}}. + Verify that |signature| is a valid ArrayBuffer. 1. Let |cmtgKey| be |cmtgOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/cmtgKey}}. - Verify that |cmtgKey| is a valid COSE_Key object. + Verify that |cmtgKey| is a valid ArrayBuffer containing a COSE_Key object. 1. Verify that the algorithm used for |signature| matches the COSE algorithm identifier of the [=credentialPublicKey=] within the [=attestedCredentialData=] of |authData|. @@ -7960,15 +7960,15 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Let |credentialRecord| be the [=credential record=] for the [=credential=]. -1. Let |extensionOutput| be the value of the cmtgKey member of |clientExtensionResults|. +1. Let |extensionOutput| be the value of the {{AuthenticationExtensionsClientOutputs/cmtgKey}} member of |clientExtensionResults|. -1. Verify that |extensionOutput| is valid CBOR conforming to the syntax defined above. - Perform CBOR decoding on |extensionOutput| and let |cmtgOutput| be the resulting {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. +1. Verify that |extensionOutput| is a valid {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. 1. Let |signature| be |cmtgOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}}. + Verify that |signature| is a valid ArrayBuffer. 1. Let |cmtgKey| be |cmtgOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/cmtgKey}}. - Verify that |cmtgKey| is a valid COSE_Key object. + Verify that |cmtgKey| is a valid ArrayBuffer containing a COSE_Key object. 1. Verify that the algorithm used for |signature| matches the algorithm of the public key previously stored for the |credentialRecord|. From 5ba995573c39c5af5deee7ffddc47d22684b7d8a Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Mon, 16 Mar 2026 17:00:02 -0400 Subject: [PATCH 21/48] be more explicit about time --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index df3ad86cd..ceff17730 100644 --- a/index.bs +++ b/index.bs @@ -7946,7 +7946,7 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Complete the steps from [[#sctn-registering-a-new-credential]] and, if those steps are successful: 1. Let |newEntry| be a new [=CMTG Key Record=] where its [=CMTG Key Record/publicKey=] is |cmtgKey| - and its [=CMTG Key Record/lastUsed=] is the current time. + and its [=CMTG Key Record/lastUsed=] is the [=[RP]=]'s current time. 1. Append |newEntry| to |credentialRecord|.[$credential record/cmtgKeyRecords$] See also [[#sctn-cmtg-key-extension-usage]] for further details. From eaf26f887ba01f35921b3444acd85841b89eb55e Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Mon, 16 Mar 2026 17:08:08 -0400 Subject: [PATCH 22/48] Fix references and variables --- index.bs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/index.bs b/index.bs index ceff17730..0906c315f 100644 --- a/index.bs +++ b/index.bs @@ -7894,7 +7894,7 @@ A CMTG Key Record is a [=struct=] with the followi terminate these processing steps as this extension only applies to [=multi-device credentials=]. 1. If a [=Credential Manager Trust Group Key=] 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 in the [=authenticator=]/[=credential manager=], + [=public key credential source/rpId|RP ID=], [=public key credential source/userHandle|User Handle=]) tuple in the [=authenticator=]/[=credential manager=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing [=Credential Manager Trust Group Key=]. @@ -7931,10 +7931,10 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Verify that |extensionOutput| is a valid {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. -1. Let |signature| be |cmtgOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}}. +1. Let |signature| be |extensionOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}}. Verify that |signature| is a valid ArrayBuffer. -1. Let |cmtgKey| be |cmtgOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/cmtgKey}}. +1. Let |cmtgKey| be |extensionOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/cmtgKey}}. Verify that |cmtgKey| is a valid ArrayBuffer containing a COSE_Key object. 1. Verify that the algorithm used for |signature| matches the COSE algorithm identifier of the [=credentialPublicKey=] @@ -7964,10 +7964,10 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Verify that |extensionOutput| is a valid {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. -1. Let |signature| be |cmtgOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}}. +1. Let |signature| be |extensionOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}}. Verify that |signature| is a valid ArrayBuffer. -1. Let |cmtgKey| be |cmtgOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/cmtgKey}}. +1. Let |cmtgKey| be |extensionOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/cmtgKey}}. Verify that |cmtgKey| is a valid ArrayBuffer containing a COSE_Key object. 1. Verify that the algorithm used for |signature| matches the algorithm of the public key From 7577f5ea00398a7a324e09e136e9da19bfa33eee Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Mon, 16 Mar 2026 17:20:31 -0400 Subject: [PATCH 23/48] Small tweaks --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 0906c315f..c05380eca 100644 --- a/index.bs +++ b/index.bs @@ -7798,7 +7798,7 @@ will include a signature from the same [=Credential Manager Trust Group private if a non-remote relationship exists between the source (`dev1`) and destination (`dev2`) device. If a third device (`dev3`) is introduced (with `cm1` configured), -which does not share a non-remote relationship with `dev1`or `dev2` +which does not share a non-remote relationship with `dev1` or `dev2` (as evaluated by the authenticator/credential manager, `cm1`), a new [=Credential Manager Trust Group private key=] (`cmtgKey2`) is generated and its public key is returned to the [=[RP]=], signaling the start of a separate trust group. @@ -7945,7 +7945,7 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Complete the steps from [[#sctn-registering-a-new-credential]] and, if those steps are successful: - 1. Let |newEntry| be a new [=CMTG Key Record=] where its [=CMTG Key Record/publicKey=] is |cmtgKey| + 1. Let |newEntry| be a new [=CMTG Key Record=] where its [=CMTG Key Record/publicKey=] is |cmtgKey| and its [=CMTG Key Record/lastUsed=] is the [=[RP]=]'s current time. 1. Append |newEntry| to |credentialRecord|.[$credential record/cmtgKeyRecords$] From 38c0c5ee68161c059400ae6125fe3b3c3a83e834 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Mon, 16 Mar 2026 17:21:56 -0400 Subject: [PATCH 24/48] Add additional steps to client extension processing for clarity --- index.bs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/index.bs b/index.bs index c05380eca..31d3ddc6f 100644 --- a/index.bs +++ b/index.bs @@ -7845,6 +7845,10 @@ A CMTG Key Record is a [=struct=] with the followi 1. Create the authenticator extension input from the client extension input. 1. If the authenticator extension output is empty or indicates the extension was not processed, stop processing this extension and return no output. + 1. Let |cmtgKeyOutput| be a new {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. + 1. Set |cmtgKeyOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/cmtgKey}} to the CBOR-decoded byte string from the [=authenticator extension output=]. + 1. Set |cmtgKeyOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}} to the CBOR-decoded byte string from the [=unsigned extension output=]. + 1. Return |cmtgKeyOutput| as the client extension output. : Client extension output :: An {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary containing the COSE_Key-encoded [=Credential Manager Trust Group public key=] From e95e03a6608551a9fb09ec4cfb68f7b404e97d28 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Mon, 16 Mar 2026 17:30:27 -0400 Subject: [PATCH 25/48] clarity --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 31d3ddc6f..a1252de1a 100644 --- a/index.bs +++ b/index.bs @@ -7947,11 +7947,11 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Verify that |signature| is a valid signature over the [=assertion signature=] [input](#fig-signature) (i.e. `authData` and `hash`) by the [=Credential Manager Trust Group Key=] |cmtgKey|. -1. Complete the steps from [[#sctn-registering-a-new-credential]] and, if those steps are successful: +1. If the remaining steps of [[#sctn-registering-a-new-credential]] are successful: 1. Let |newEntry| be a new [=CMTG Key Record=] where its [=CMTG Key Record/publicKey=] is |cmtgKey| and its [=CMTG Key Record/lastUsed=] is the [=[RP]=]'s current time. - 1. Append |newEntry| to |credentialRecord|.[$credential record/cmtgKeyRecords$] + 1. Append |newEntry| to |credentialRecord|.[$credential record/cmtgKeyRecords$] See also [[#sctn-cmtg-key-extension-usage]] for further details. From e8b365cb97a6d3f64030df08299a0b989f7422b1 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Mon, 16 Mar 2026 17:31:32 -0400 Subject: [PATCH 26/48] Need to actually output the public key --- index.bs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.bs b/index.bs index a1252de1a..3a20b9f19 100644 --- a/index.bs +++ b/index.bs @@ -7913,6 +7913,8 @@ A CMTG Key Record is a [=struct=] with the followi Note: the [=assertion signature=] [input](#fig-signature) and |cmtgKeySig| covers the [=[RP]=]'s {{PublicKeyCredentialCreationOptions/challenge}} because it includes the [=hash of the serialized client data=]. Thus the [=[RP]=] knows that |cmtgKeySig| is a fresh signature. + 1. Output |cmtgPublicKey| as the [=authenticator extension output=]. + 1. Output |cmtgKeySig| as the extension's [=unsigned extension output=]. Note: |cmtgKeySig| cannot be included in the [=authenticator extension output=] because it is returned inside the [=authenticator data=] From 42b7204fc167c3aa92809ba5bea80f7695345766 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Tue, 21 Apr 2026 12:17:41 -0400 Subject: [PATCH 27/48] Review feedback --- index.bs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 3a20b9f19..293e91425 100644 --- a/index.bs +++ b/index.bs @@ -1065,7 +1065,7 @@ BCP 14 [[!RFC2119]] [[!RFC8174]] when, and only when, they appear in all capital : Credential Manager Trust Group Private Key : Credential Manager Trust Group Public Key : Credential Manager Trust Group Key Pair -:: A [=Credential Manager Trust Group Key=], is a [=authenticator=]- / [=credential manager=]-, [=[RP]=]-, and [=user credential=]-specific +:: A [=Credential Manager Trust Group Key=], is a [=credential manager=]-, [=[RP]=]-, and [=user credential=]-specific public key pair created upon a [=[RP]=]'s request via the [=cmtgKey=] [=WebAuthn extension=]. The [=Credential Manager Trust Group Public Key=] is conveyed to the [=[RP]=] during a @@ -7803,6 +7803,9 @@ which does not share a non-remote relationship with `dev1` or `dev2` a new [=Credential Manager Trust Group private key=] (`cmtgKey2`) is generated and its public key is returned to the [=[RP]=], signaling the start of a separate trust group. +If `dev1` and `dev3` or `dev2` and `dev3` are later determined to have non-remote relationships, +`cm1` may elect to use `cmtgKey1` for all three devices, signaling that they are now in the same trust group. + #### Updating the RP's Credential Record #### {#sctn-cmtg-key-credential-record} When this extension is used by a [=Relying Party=], From 2ed9a0f58d9dee22a5deb921764b42264194f0dc Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Tue, 21 Apr 2026 12:25:44 -0400 Subject: [PATCH 28/48] Review feedback --- index.bs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/index.bs b/index.bs index 293e91425..f7fd690f6 100644 --- a/index.bs +++ b/index.bs @@ -7971,6 +7971,12 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Let |extensionOutput| be the value of the {{AuthenticationExtensionsClientOutputs/cmtgKey}} member of |clientExtensionResults|. +1. If |extensionOutput| is not present, + + 1. Optionally, perform additional policy checks. + + 1. Terminate this `cmtgKey` validation algorithm. + 1. Verify that |extensionOutput| is a valid {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. 1. Let |signature| be |extensionOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}}. @@ -7993,8 +7999,6 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Update |e|.[=CMTG Key Record/lastUsed=] to the current time. - 1. Terminate these verification steps. - 1. Otherwise: This is possibly a new [=Credential Manager Trust Group Key=], signifying a new trust group. @@ -8006,8 +8010,6 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Append |newEntry| to |credentialRecord|.[$credential record/cmtgKeyRecords$]. - 1. Terminate these verification steps. - See also [[#sctn-cmtg-key-extension-usage]]. # User Agent Automation # {#sctn-automation} From f5d417f8f0620929c5740453be98724506194e3a Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Tue, 21 Apr 2026 12:53:23 -0400 Subject: [PATCH 29/48] WebIDL to required, update verification steps --- index.bs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/index.bs b/index.bs index f7fd690f6..ef8053d5b 100644 --- a/index.bs +++ b/index.bs @@ -7859,8 +7859,8 @@ A CMTG Key Record is a [=struct=] with the followi and the signature returned as the [=unsigned extension output=]. dictionary AuthenticationExtensionsCmtgKeyOutputs { - ArrayBuffer cmtgKey; - ArrayBuffer signature; + required ArrayBuffer cmtgKey; + required ArrayBuffer signature; }; partial dictionary AuthenticationExtensionsClientOutputs { @@ -7938,7 +7938,10 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Let |extensionOutput| be the value of the {{AuthenticationExtensionsClientOutputs/cmtgKey}} member of |clientExtensionResults|. -1. Verify that |extensionOutput| is a valid {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. +1. If |extensionOutput| does not contain the required members of {{AuthenticationExtensionsCmtgKeyOutputs}}, + Terminate this `cmtgKey` validation algorithm. + + NOTE: [=[RP]=]s MUST ignore any keys in |extensionOutput| that are not defined in the {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. 1. Let |signature| be |extensionOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}}. Verify that |signature| is a valid ArrayBuffer. @@ -7971,13 +7974,13 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Let |extensionOutput| be the value of the {{AuthenticationExtensionsClientOutputs/cmtgKey}} member of |clientExtensionResults|. -1. If |extensionOutput| is not present, +1. If |extensionOutput| is not present or doe snot contain the required members of {{AuthenticationExtensionsCmtgKeyOutputs}} 1. Optionally, perform additional policy checks. - + 1. Terminate this `cmtgKey` validation algorithm. -1. Verify that |extensionOutput| is a valid {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. + NOTE: [=[RP]=]s MUST ignore any keys in |extensionOutput| that are not defined in the {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. 1. Let |signature| be |extensionOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}}. Verify that |signature| is a valid ArrayBuffer. From fb9f2e0d7183147363a7f196274fc53a623e0ebd Mon Sep 17 00:00:00 2001 From: Tim Cappalli <tim@cloudauth.dev> Date: Tue, 21 Apr 2026 12:53:38 -0400 Subject: [PATCH 30/48] spelling --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index ef8053d5b..75f46b9d2 100644 --- a/index.bs +++ b/index.bs @@ -7974,7 +7974,7 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Let |extensionOutput| be the value of the {{AuthenticationExtensionsClientOutputs/cmtgKey}} member of |clientExtensionResults|. -1. If |extensionOutput| is not present or doe snot contain the required members of {{AuthenticationExtensionsCmtgKeyOutputs}} +1. If |extensionOutput| is not present or does not contain the required members of {{AuthenticationExtensionsCmtgKeyOutputs}} 1. Optionally, perform additional policy checks. From e1541dfb59eb9c5ebdac501a2484817797586771 Mon Sep 17 00:00:00 2001 From: Tim Cappalli <tim@cloudauth.dev> Date: Tue, 21 Apr 2026 18:51:26 -0400 Subject: [PATCH 31/48] Add examples --- index.bs | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/index.bs b/index.bs index 75f46b9d2..9d1c91291 100644 --- a/index.bs +++ b/index.bs @@ -7790,6 +7790,59 @@ the selected [=authenticator=]/[=credential manager=] (`cm1`) generates a new [= The extension output includes the public key and a signature generated using the [=Credential Manager Trust Group private key=] (`cmtgKey1`). +<div class="example"> + +Here's what a WebAuthn registration ceremony with the `cmtgKey` extension +might look like on the client side: + +```javascript +async function createPasskey() { + // 1. Fetch JSON options from your server. + // The server should return a JSON structure where buffers are Base64URL encoded strings. + const response = await fetch('/auth/register/options'); + const optionsJSON = await response.json(); + + // 2. Add the CMTG extension request to the JSON object before parsing + // Note: This is illustrative. This would likely come from the server in the options response in a real implementation. + optionsJSON.extensions = { + ...optionsJSON.extensions, + cmtgKey: true + }; + + // 3. Convert the JSON into an object + const publicKeyCredentialCreationOptions = + PublicKeyCredential.parseCreationOptionsFromJSON(optionsJSON); + + try { + // 4. Trigger the passkey creation + const credential = await navigator.credentials.create({ + publicKey: publicKeyCredentialCreationOptions + }); + + // 5. Serialize the entire credential (including extension results) + const credentialJSON = credential.toJSON(); + + // 6. Send the serialized object directly to the backend + const verificationResponse = await fetch('/auth/register/verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(credentialJSON) + }); + + // The backend should verify both the passkey and the CMTG Key and update the credential record. + // If verification is successful, it should return a success response. + + if (verificationResponse.ok) { + establishSession(); + console.log("Passkey and CMTG Key registered successfully!"); + } + } catch (err) { + console.error("Registration failed:", err); + } +} +``` +</div> + A subsequent {{CredentialsContainer/get()|get()}} operation (with the `cmtgKey` extension) on `dev1` using the same credential and the same authenticator/credential manager (`cm1`), will include a signature from the same [=Credential Manager Trust Group private key=], `cmtgKey1`. @@ -7806,6 +7859,64 @@ is returned to the [=[RP]=], signaling the start of a separate trust group. If `dev1` and `dev3` or `dev2` and `dev3` are later determined to have non-remote relationships, `cm1` may elect to use `cmtgKey1` for all three devices, signaling that they are now in the same trust group. +<div class="example"> + +Here's what a WebAuthn authentication ceremony with the `cmtgKey` extension +might look like on the client side: + +```javascript +async function getPasskey() { + // 1. Fetch authentication options from your server. + // The server returns a JSON object with a challenge and allowed credentials. + const response = await fetch('/auth/login/options'); + const optionsJSON = await response.json(); + + // 2. Add the CMTG extension request to the JSON object before parsing + // Note: This is illustrative. This would likely come from the server in the options response in a real implementation. + optionsJSON.extensions = { + ...optionsJSON.extensions, + cmtgKey: true + }; + + // 3. Convert the JSON into an object + const publicKeyCredentialRequestOptions = + PublicKeyCredential.parseRequestOptionsFromJSON(optionsJSON); + + try { + // 4. Trigger the passkey login + const assertion = await navigator.credentials.get({ + publicKey: publicKeyCredentialRequestOptions + }); + + // 5. Serialize the assertion (including CMTG extension results) + const assertionJSON = assertion.toJSON(); + + // 6. Send the serialized assertion to the backend + const verificationResponse = await fetch('/auth/login/verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(assertionJSON) + }); + + // The backend should verify both the passkey and the CMTG Key. + // If verification is successful, it should return a success response. + + if (verificationResponse.ok) { + // If the CMTG Key matches an expected entry from the credential record, + // the passkey was from a previously trusted credential manager. + establishSession(); + // If the CMTG Key was not returned, was not the expected entry, + // or has not been seen before, optionally + // run additional risk analysis and policy checks. + console.log("Authenticated! Trust Group verified via CMTG."); + } + } catch (err) { + console.error("Authentication failed:", err); + } +} +``` +</div> + #### Updating the RP's Credential Record #### {#sctn-cmtg-key-credential-record} When this extension is used by a [=Relying Party=], From 349b691302d514938777839ff53f8bba0beee5f2 Mon Sep 17 00:00:00 2001 From: Emil Lundberg <emil@yubico.com> Date: Wed, 13 May 2026 14:29:37 +0200 Subject: [PATCH 32/48] Delete unused dfn ids --- index.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index 9d1c91291..04058a74b 100644 --- a/index.bs +++ b/index.bs @@ -7927,13 +7927,13 @@ the [=credential record=] structure SHOULD be augmented with the following field :: A [=list=] of [=CMTG Key Records=], initially empty. </dl> -A <dfn id="cmtgKeyRecord">CMTG Key Record</dfn> is a [=struct=] with the following [=struct/items=]: +A <dfn>CMTG Key Record</dfn> is a [=struct=] with the following [=struct/items=]: <dl dfn-for="CMTG Key Record"> - : <dfn id="cmtgKeyRecordPublicKey">publicKey</dfn> + : <dfn>publicKey</dfn> :: The [=Credential Manager Trust Group public key|CMTG public key=] - : <dfn id="cmtgKeyRecordLastUsed">lastUsed</dfn> + : <dfn>lastUsed</dfn> :: A timestamp representing the last time this specific key was verified by the [=Relying Party=]. </dl> From c7e430cda7a4246d53efc3bc0690a7b351de0244 Mon Sep 17 00:00:00 2001 From: Emil Lundberg <emil@yubico.com> Date: Wed, 13 May 2026 14:31:41 +0200 Subject: [PATCH 33/48] Use dfn-type="abstract-op" for CMTK Key Record members To align with credential record, which also uses this dfn-type. --- index.bs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/index.bs b/index.bs index 04058a74b..8a5574942 100644 --- a/index.bs +++ b/index.bs @@ -7929,7 +7929,7 @@ the [=credential record=] structure SHOULD be augmented with the following field A <dfn>CMTG Key Record</dfn> is a [=struct=] with the following [=struct/items=]: -<dl dfn-for="CMTG Key Record"> +<dl dfn-for="CMTG Key Record" dfn-type="abstract-op"> : <dfn>publicKey</dfn> :: The [=Credential Manager Trust Group public key|CMTG public key=] @@ -8068,8 +8068,8 @@ using these variables established therein: |credential|, |clientExtensionResults 1. If the remaining steps of [[#sctn-registering-a-new-credential]] are successful: - 1. Let |newEntry| be a new [=CMTG Key Record=] where its [=CMTG Key Record/publicKey=] is |cmtgKey| - and its [=CMTG Key Record/lastUsed=] is the [=[RP]=]'s current time. + 1. Let |newEntry| be a new [=CMTG Key Record=] where its [$CMTG Key Record/publicKey$] is |cmtgKey| + and its [$CMTG Key Record/lastUsed$] is the [=[RP]=]'s current time. 1. Append |newEntry| to |credentialRecord|.[$credential record/cmtgKeyRecords$] See also [[#sctn-cmtg-key-extension-usage]] for further details. @@ -8107,11 +8107,11 @@ using these variables established therein: |credential|, |clientExtensionResults (The signature algorithm is the same as for the [=user credential=].) 1. If |credentialRecord|.[$credential record/cmtgKeyRecords$] contains an entry |e| - where |e|.[=CMTG Key Record/publicKey=] matches |cmtgKey|: + where |e|.[$CMTG Key Record/publicKey$] matches |cmtgKey|: The [=authenticator=]/[=credential manager=] is part of a previously seen trust group. - 1. Update |e|.[=CMTG Key Record/lastUsed=] to the current time. + 1. Update |e|.[$CMTG Key Record/lastUsed$] to the current time. 1. Otherwise: @@ -8119,8 +8119,8 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Optionally, perform additional policy checks. - 1. Let |newEntry| be a new [=CMTG Key Record=] where its [=CMTG Key Record/publicKey=] is |cmtgKey| - and its [=CMTG Key Record/lastUsed=] is the current time. + 1. Let |newEntry| be a new [=CMTG Key Record=] where its [$CMTG Key Record/publicKey$] is |cmtgKey| + and its [$CMTG Key Record/lastUsed$] is the current time. 1. Append |newEntry| to |credentialRecord|.[$credential record/cmtgKeyRecords$]. From 3566f133210103ed62c9e3f274ce8710f8473744 Mon Sep 17 00:00:00 2001 From: Emil Lundberg <emil@yubico.com> Date: Wed, 13 May 2026 14:31:52 +0200 Subject: [PATCH 34/48] Tweak punctuation and similar --- index.bs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/index.bs b/index.bs index 8a5574942..e1a127f26 100644 --- a/index.bs +++ b/index.bs @@ -7931,7 +7931,7 @@ A <dfn>CMTG Key Record</dfn> is a [=struct=] with the following [=struct/items=] <dl dfn-for="CMTG Key Record" dfn-type="abstract-op"> : <dfn>publicKey</dfn> - :: The [=Credential Manager Trust Group public key|CMTG public key=] + :: The [=Credential Manager Trust Group public key|CMTG public key=]. : <dfn>lastUsed</dfn> :: A timestamp representing the last time this specific key was verified by the [=Relying Party=]. @@ -8024,7 +8024,7 @@ A <dfn>CMTG Key Record</dfn> is a [=struct=] with the following [=struct/items=] 1. Let |cmtgKeySig| be the result of signing the [=assertion signature=] [input](#fig-signature) with |cmtgPrivateKey|, using the same signature algorithm as that used by the [=user credential=]'s [=credential key pair=]. - Note: the [=assertion signature=] [input](#fig-signature) and |cmtgKeySig| covers the [=[RP]=]'s {{PublicKeyCredentialCreationOptions/challenge}} + Note: The [=assertion signature=] [input](#fig-signature) and |cmtgKeySig| covers the [=[RP]=]'s {{PublicKeyCredentialCreationOptions/challenge}} because it includes the [=hash of the serialized client data=]. Thus the [=[RP]=] knows that |cmtgKeySig| is a fresh signature. 1. Output |cmtgPublicKey| as the [=authenticator extension output=]. @@ -8050,9 +8050,9 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Let |extensionOutput| be the value of the {{AuthenticationExtensionsClientOutputs/cmtgKey}} member of |clientExtensionResults|. 1. If |extensionOutput| does not contain the required members of {{AuthenticationExtensionsCmtgKeyOutputs}}, - Terminate this `cmtgKey` validation algorithm. + terminate this `cmtgKey` validation algorithm. - NOTE: [=[RP]=]s MUST ignore any keys in |extensionOutput| that are not defined in the {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. + NOTE: [=[RPS]=] MUST ignore any keys in |extensionOutput| that are not defined in the {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. 1. Let |signature| be |extensionOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}}. Verify that |signature| is a valid ArrayBuffer. @@ -8064,13 +8064,13 @@ using these variables established therein: |credential|, |clientExtensionResults within the [=attestedCredentialData=] of |authData|. 1. Verify that |signature| is a valid signature over the [=assertion signature=] [input](#fig-signature) - (i.e. `authData` and `hash`) by the [=Credential Manager Trust Group Key=] |cmtgKey|. + (i.e. |authData| and |hash|) by the [=Credential Manager Trust Group Key=] |cmtgKey|. 1. If the remaining steps of [[#sctn-registering-a-new-credential]] are successful: 1. Let |newEntry| be a new [=CMTG Key Record=] where its [$CMTG Key Record/publicKey$] is |cmtgKey| and its [$CMTG Key Record/lastUsed$] is the [=[RP]=]'s current time. - 1. Append |newEntry| to |credentialRecord|.[$credential record/cmtgKeyRecords$] + 1. Append |newEntry| to |credentialRecord|.[$credential record/cmtgKeyRecords$]. See also [[#sctn-cmtg-key-extension-usage]] for further details. @@ -8079,19 +8079,19 @@ See also [[#sctn-cmtg-key-extension-usage]] for further details. If the `cmtgKey` extension was included on a {{CredentialsContainer/get()|navigator.credentials.get()}} call, then the below verification steps are performed in the context of <a href=#authn-ceremony-verify-extension-outputs>this step</a> of [[#sctn-verifying-assertion]] using these variables established therein: |credential|, |clientExtensionResults|, |authData|, and |hash|. -[=[RP]=] policy may specify whether a response without a `cmtgKey` is acceptable. +[=[RP]=] policy may specify whether a response without a {{AuthenticationExtensionsClientOutputs/cmtgKey}} is acceptable. 1. Let |credentialRecord| be the [=credential record=] for the [=credential=]. 1. Let |extensionOutput| be the value of the {{AuthenticationExtensionsClientOutputs/cmtgKey}} member of |clientExtensionResults|. -1. If |extensionOutput| is not present or does not contain the required members of {{AuthenticationExtensionsCmtgKeyOutputs}} +1. If |extensionOutput| is not present or does not contain the required members of {{AuthenticationExtensionsCmtgKeyOutputs}}: 1. Optionally, perform additional policy checks. 1. Terminate this `cmtgKey` validation algorithm. - NOTE: [=[RP]=]s MUST ignore any keys in |extensionOutput| that are not defined in the {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. + NOTE: [=[RPS]=] MUST ignore any keys in |extensionOutput| that are not defined in the {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. 1. Let |signature| be |extensionOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}}. Verify that |signature| is a valid ArrayBuffer. From 3fe52c366175a5aee28d5a9297f66deb8a169084 Mon Sep 17 00:00:00 2001 From: Emil Lundberg <emil@yubico.com> Date: Wed, 13 May 2026 14:37:12 +0200 Subject: [PATCH 35/48] Indicate that cmtgKey output should contain COSE_Key CBOR --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index e1a127f26..72680af98 100644 --- a/index.bs +++ b/index.bs @@ -7996,7 +7996,7 @@ A <dfn>CMTG Key Record</dfn> is a [=struct=] with the following [=struct/items=] ``` $$extensionOutput //= ( - cmtgKey: bstr, + cmtgKey: bstr .cbor COSE_Key, ) ``` From 3238904ba7186bf82876f9163a941b48a391b1dc Mon Sep 17 00:00:00 2001 From: Emil Lundberg <emil@yubico.com> Date: Wed, 13 May 2026 16:15:04 +0200 Subject: [PATCH 36/48] Tweak cmtgKey language for precision and conciseness --- index.bs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/index.bs b/index.bs index 72680af98..cb47f3938 100644 --- a/index.bs +++ b/index.bs @@ -7775,7 +7775,7 @@ in the [=authenticator=], if such a key pair needs to be created for the and returning one of the [=Credential Manager Trust Group public keys=] along with a signature by the [=Credential Manager Trust Group private key=] to the [=[RP]=]. -This is done each time this [=cmtgKey=] extension is included with either a +This is done each time this [=cmtgKey=] extension is requested in either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/get()|navigator.credentials.get()}} call. #### Relying Party Usage #### {#sctn-cmtg-key-extension-usage} @@ -8040,14 +8040,13 @@ Verifying the <code>[=cmtgKey=]</code> extension output is performed by the [=[R ##### Registration (`create()`) ##### {#sctn-cmtg-key-extension-verification-create} -If the `cmtgKey` extension was included on a {{CredentialsContainer/create()|navigator.credentials.create()}} call -and a [=Credential Manager Trust Group Key=] is returned by an [=authenticator=], +If a {{AuthenticationExtensionsClientOutputs/cmtgKey}} extension output is present in a {{CredentialsContainer/create()|navigator.credentials.create()}} call, the below verification steps are performed in the context of <a href=#reg-ceremony-verify-extension-outputs>this step</a> of [[#sctn-registering-a-new-credential]] using these variables established therein: |credential|, |clientExtensionResults|, |authData|, and |hash|. -1. Let |credentialRecord| be the [=credential record=] for the [=credential=]. +1. Let |credentialRecord| be the [=credential record=] for |credential|. -1. Let |extensionOutput| be the value of the {{AuthenticationExtensionsClientOutputs/cmtgKey}} member of |clientExtensionResults|. +1. Let |extensionOutput| be the value of <code>|clientExtensionResults|.{{AuthenticationExtensionsClientOutputs/cmtgKey}}</code>. 1. If |extensionOutput| does not contain the required members of {{AuthenticationExtensionsCmtgKeyOutputs}}, terminate this `cmtgKey` validation algorithm. @@ -8076,14 +8075,14 @@ See also [[#sctn-cmtg-key-extension-usage]] for further details. ##### Authentication (`get()`) ##### {#sctn-cmtg-key-extension-verification-get} -If the `cmtgKey` extension was included on a {{CredentialsContainer/get()|navigator.credentials.get()}} call, +If a {{AuthenticationExtensionsClientOutputs/cmtgKey}} extension output is present in a {{CredentialsContainer/get()|navigator.credentials.get()}} call, then the below verification steps are performed in the context of <a href=#authn-ceremony-verify-extension-outputs>this step</a> of [[#sctn-verifying-assertion]] using these variables established therein: |credential|, |clientExtensionResults|, |authData|, and |hash|. [=[RP]=] policy may specify whether a response without a {{AuthenticationExtensionsClientOutputs/cmtgKey}} is acceptable. -1. Let |credentialRecord| be the [=credential record=] for the [=credential=]. +1. Let |credentialRecord| be the [=credential record=] for |credential|. -1. Let |extensionOutput| be the value of the {{AuthenticationExtensionsClientOutputs/cmtgKey}} member of |clientExtensionResults|. +1. Let |extensionOutput| be the value of <code>|clientExtensionResults|.{{AuthenticationExtensionsClientOutputs/cmtgKey}}</code>. 1. If |extensionOutput| is not present or does not contain the required members of {{AuthenticationExtensionsCmtgKeyOutputs}}: From 517baf4c8e364d5acadbe4d6cf63c7ca30199171 Mon Sep 17 00:00:00 2001 From: Tim Cappalli <tim@cloudauth.dev> Date: Thu, 11 Jun 2026 14:16:36 -0400 Subject: [PATCH 37/48] Adds missing JSON dictionaries --- index.bs | 66 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/index.bs b/index.bs index cb47f3938..9f8c1dbf9 100644 --- a/index.bs +++ b/index.bs @@ -353,14 +353,14 @@ Beyond that, the intended audiences for this document are the following main gro ## Use Cases ## {#sctn-use-cases} -The below use case scenarios illustrate use of two very different types of [=authenticators=] and credentials across two common deployment types, -as well as outline further scenarios. Additional scenarios, including sample code, are given later in [[#sctn-sample-scenarios]]. +The below use case scenarios illustrate use of two very different types of [=authenticators=] and credentials across two common deployment types, +as well as outline further scenarios. Additional scenarios, including sample code, are given later in [[#sctn-sample-scenarios]]. These examples are for illustrative purposes only, and feature availability may differ between client and authenticator implementations. ### Consumer with Multi-Device Credentials ### {#sctn-usecase-consumer-mdc} -This use case illustrates how a consumer-centric [=[RP]=] can leverage the authenticator built-in to a user's devices -to provide phishing-resistant sign in using [=multi-device credentials=] (commonly referred to as synced [=passkeys=]). +This use case illustrates how a consumer-centric [=[RP]=] can leverage the authenticator built-in to a user's devices +to provide phishing-resistant sign in using [=multi-device credentials=] (commonly referred to as synced [=passkeys=]). #### Registration #### {#sctn-usecase-consumer-mdc-reg} @@ -378,7 +378,7 @@ to provide phishing-resistant sign in using [=multi-device credentials=] (common - On a laptop or desktop: * User navigates to example.com in a browser and initiates signing in. * If the [=multi-device credential=] (commonly referred to as a synced [=passkey=]) is available on the device: - * The browser or operating system prompts the user for a previously configured [=authorization gesture=] (PIN, biometric, etc.); + * The browser or operating system prompts the user for a previously configured [=authorization gesture=] (PIN, biometric, etc.); the user provides this. * Web page shows that the selected user is signed in, and navigates to the signed-in page. * If the synced [=passkey=] is not available on the device: @@ -1069,11 +1069,11 @@ BCP 14 [[!RFC2119]] [[!RFC8174]] when, and only when, they appear in all capital public key pair created upon a [=[RP]=]'s request via the [=cmtgKey=] [=WebAuthn extension=]. The [=Credential Manager Trust Group Public Key=] is conveyed to the [=[RP]=] during a - [=registration ceremony|registration=] or [=authentication ceremony|authentication=] ceremony + [=registration ceremony|registration=] or [=authentication ceremony|authentication=] ceremony as part of the [=cmtgKey=] extension output. The [=Credential Manager Trust Group Private Key=] is stored by the [=authenticator=] / [=credential manager=]. - + See [[#sctn-cmtg-key-extension]]. : <dfn>Credential Properties</dfn> @@ -1148,7 +1148,7 @@ BCP 14 [[!RFC2119]] [[!RFC8174]] when, and only when, they appear in all capital when the [=public key credential source=] was [=registration|registered=]. Storing this in combination with the above [$credential record/attestationObject$] [=struct/item=] enables the [=[RP]=] to re-verify the [=attestation signature=] at a later time. - + : <dfn>rpId</dfn> :: The value of the <code>{{PublicKeyCredentialCreationOptions/rp}}.{{PublicKeyCredentialRpEntity/id}}</code> parameter specified in the {{CredentialsContainer/create()}} operation during credential registration. @@ -1815,7 +1815,7 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o : is present :: If <code>|pkOptions|.{{PublicKeyCredentialCreationOptions/rp}}.{{PublicKeyCredentialRpEntity/id}}</code> [=is not a registrable domain suffix of and is not equal to=] |effectiveDomain|, and if the client - + <dl class="switch"> : supports [[#sctn-related-origins|related origin requests]] :: 1. Let |rpIdRequested| be the value of <code>|pkOptions|.{{PublicKeyCredentialCreationOptions/rp}}.{{PublicKeyCredentialRpEntity/id}}</code>. @@ -1826,7 +1826,7 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o : does not support [[#sctn-related-origins|related origin requests]] :: throw a "{{SecurityError}}" {{DOMException}}. </dl> - + : is not present :: Set <code>|pkOptions|.{{PublicKeyCredentialCreationOptions/rp}}.{{PublicKeyCredentialRpEntity/id}}</code> to |effectiveDomain|. </dl> @@ -4522,7 +4522,7 @@ feature-identifier token, respectively, in the <{iframe/allow}> attribute's valu By default, Web Authentication requires that the [=RP ID=] be equal to the [=determines the set of origins on which the public key credential may be exercised|origin=]'s [=effective domain=], or a [=is a registrable domain suffix of or is equal to|registrable domain suffix=] of the [=determines the set of origins on which the public key credential may be exercised|origin=]'s [=effective domain=]. -This can make deployment challenging for large environments where multiple country-specific domains are in use (e.g. example.com vs example.co.uk vs example.sg), where alternative or brand domains are required (e.g. myexampletravel.com vs examplecruises.com), and/or where platform as a service providers are used to support mobile apps. +This can make deployment challenging for large environments where multiple country-specific domains are in use (e.g. example.com vs example.co.uk vs example.sg), where alternative or brand domains are required (e.g. myexampletravel.com vs examplecruises.com), and/or where platform as a service providers are used to support mobile apps. [=[WRPS]=] can opt in to allowing [=WebAuthn Clients=] to enable a credential to be created and used across a limited set of related [=origin|origins=]. Such [=[RPS]=] MUST choose a common [=RP ID=] to use across all ceremonies from related origins. @@ -4761,7 +4761,7 @@ the requested [=public key credential|credential=] is [=scoped=] to exactly matc possibly combined in a single [=authorization gesture=], then the authenticator will set both the [=UP=] [=flag=] and the [=authData/flags/UV=] [=flag=]. -- The [=BE=] [=flag=] SHALL be set if and only if the credential is a [=multi-device credential=]. +- The [=BE=] [=flag=] SHALL be set if and only if the credential is a [=multi-device credential=]. This value MUST NOT change after a [=registration ceremony=] as defined in [[#sctn-credential-backup]]. - The [=BS=] [=flag=] SHALL be set if and only if the credential is a [=multi-device credential=] and is currently [=backed up=]. @@ -6247,13 +6247,13 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o <dd>The signature counter is valid.</dd> <dt>less than or equal to <code>|credentialRecord|.[$credential record/signCount$]</code>:</dt> <dd>This is a signal, but not proof, that the authenticator may be cloned. For example it might mean that: - - Two or more copies of the [=credential private key=] may exist and are being used in parallel. + - Two or more copies of the [=credential private key=] may exist and are being used in parallel. - An authenticator is malfunctioning. - A race condition exists where the [=[RP]=] is processing assertion responses in an order other than the order they were generated at the authenticator. - + [=[RPS]=] should evaluate their own operational characteristics and incorporate this information into their risk scoring. Whether the [=[RP]=] updates <code>|credentialRecord|.[$credential record/signCount$]</code> - below in this case, or not, or fails the [=authentication ceremony=] or not, is [=[RP]=]-specific. + below in this case, or not, or fails the [=authentication ceremony=] or not, is [=[RP]=]-specific. For more information on signature counter considerations, see [[#sctn-sign-counter]]. </dd> @@ -6476,7 +6476,7 @@ The attributes above are structured within this certificate as such: 04 12 -- OCTET STRING 04 10 -- OCTET STRING CD 8C 39 5C 26 ED EE DE -- AAGUID cd8c395c-26ed-eede-653b-00797d03ca3c - 65 3B 00 79 7D 03 CA 3C + 65 3B 00 79 7D 03 CA 3C 30 12 -- SEQUENCE 06 0B 2B 06 01 04 01 82 E5 1C 01 01 05 -- OID 1.3.6.1.4.1.45724.1.1.5 @@ -7766,7 +7766,7 @@ This section defines extensions that are both [=client extensions=] and [=authen This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] enables an [=authenticator=]/[=credential manager=] to provide a signal to a [=[RP]=] that two devices possessing the same [=backed up=] credential -have established a trust relationship through a non-remote interaction, +have established a trust relationship through a non-remote interaction, such as a local setup or physical proximity. This is done by creating an additional [=public key credential source=]-specific [=Credential Manager Trust Group key pair=] @@ -7787,7 +7787,7 @@ This extension provides a "trust group" signal <i>when used consistently</i> wit When a [=[RP]=] uses the `cmtgKey` extension during a {{CredentialsContainer/create()|create()}} option on the first device (`dev1`), the selected [=authenticator=]/[=credential manager=] (`cm1`) generates a new [=Credential Manager Trust Group Key=] pair. -The extension output includes the public key and a signature generated using the +The extension output includes the public key and a signature generated using the [=Credential Manager Trust Group private key=] (`cmtgKey1`). <div class="example"> @@ -7797,7 +7797,7 @@ might look like on the client side: ```javascript async function createPasskey() { - // 1. Fetch JSON options from your server. + // 1. Fetch JSON options from your server. // The server should return a JSON structure where buffers are Base64URL encoded strings. const response = await fetch('/auth/register/options'); const optionsJSON = await response.json(); @@ -7844,7 +7844,7 @@ async function createPasskey() { </div> A subsequent {{CredentialsContainer/get()|get()}} operation (with the `cmtgKey` extension) on `dev1` -using the same credential and the same authenticator/credential manager (`cm1`), +using the same credential and the same authenticator/credential manager (`cm1`), will include a signature from the same [=Credential Manager Trust Group private key=], `cmtgKey1`. `cmtgKey1` is only synced to an authenticator/credential manager on another device (`dev2`) @@ -7852,7 +7852,7 @@ if a non-remote relationship exists between the source (`dev1`) and destination If a third device (`dev3`) is introduced (with `cm1` configured), which does not share a non-remote relationship with `dev1` or `dev2` -(as evaluated by the authenticator/credential manager, `cm1`), +(as evaluated by the authenticator/credential manager, `cm1`), a new [=Credential Manager Trust Group private key=] (`cmtgKey2`) is generated and its public key is returned to the [=[RP]=], signaling the start of a separate trust group. @@ -7952,6 +7952,9 @@ A <dfn>CMTG Key Record</dfn> is a [=struct=] with the following [=struct/items=] partial dictionary AuthenticationExtensionsClientInputs { boolean cmtgKey; }; + partial dictionary AuthenticationExtensionsClientInputsJSON { + boolean cmtgKey; +} ; : Client extension processing @@ -7974,9 +7977,18 @@ A CMTG Key Record is a [=struct=] with the following [=struct/items=] required ArrayBuffer signature; }; + dictionary AuthenticationExtensionsCmtgKeyOutputsJSON { + required Base64URLString cmtgKey; + required Base64URLString signature; + }; + partial dictionary AuthenticationExtensionsClientOutputs { AuthenticationExtensionsCmtgKeyOutputs cmtgKey; }; + + partial dictionary AuthenticationExtensionsClientOutputsJSON { + AuthenticationExtensionsCmtgKeyOutputsJSON cmtgKey; + }; If the authenticator did not return an extension output, @@ -8016,11 +8028,11 @@ A CMTG Key Record is a [=struct=] with the following [=struct/items=] create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing [=Credential Manager Trust Group Key=]. - 1. Let |cmtgPublicKey| be the newly created or existing [=Credential Manager Trust Group Key=], + 1. Let |cmtgPublicKey| be the newly created or existing [=Credential Manager Trust Group Key=], in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=]. 1. Let |cmtgPrivateKey| be the newly created or existing [=Credential Manager Trust Group private key=]. - + 1. Let |cmtgKeySig| be the result of signing the [=assertion signature=] [input](#fig-signature) with |cmtgPrivateKey|, using the same signature algorithm as that used by the [=user credential=]'s [=credential key pair=]. @@ -8042,7 +8054,7 @@ Verifying the [=cmtgKey=] extension output is performed by the [=[R If a {{AuthenticationExtensionsClientOutputs/cmtgKey}} extension output is present in a {{CredentialsContainer/create()|navigator.credentials.create()}} call, the below verification steps are performed in the context of this step of [[#sctn-registering-a-new-credential]] -using these variables established therein: |credential|, |clientExtensionResults|, |authData|, and |hash|. +using these variables established therein: |credential|, |clientExtensionResults|, |authData|, and |hash|. 1. Let |credentialRecord| be the [=credential record=] for |credential|. @@ -8066,7 +8078,7 @@ using these variables established therein: |credential|, |clientExtensionResults (i.e. |authData| and |hash|) by the [=Credential Manager Trust Group Key=] |cmtgKey|. 1. If the remaining steps of [[#sctn-registering-a-new-credential]] are successful: - + 1. Let |newEntry| be a new [=CMTG Key Record=] where its [$CMTG Key Record/publicKey$] is |cmtgKey| and its [$CMTG Key Record/lastUsed$] is the [=[RP]=]'s current time. 1. Append |newEntry| to |credentialRecord|.[$credential record/cmtgKeyRecords$]. @@ -8077,7 +8089,7 @@ See also [[#sctn-cmtg-key-extension-usage]] for further details. If a {{AuthenticationExtensionsClientOutputs/cmtgKey}} extension output is present in a {{CredentialsContainer/get()|navigator.credentials.get()}} call, then the below verification steps are performed in the context of this step of [[#sctn-verifying-assertion]] -using these variables established therein: |credential|, |clientExtensionResults|, |authData|, and |hash|. +using these variables established therein: |credential|, |clientExtensionResults|, |authData|, and |hash|. [=[RP]=] policy may specify whether a response without a {{AuthenticationExtensionsClientOutputs/cmtgKey}} is acceptable. 1. Let |credentialRecord| be the [=credential record=] for |credential|. @@ -8117,7 +8129,7 @@ using these variables established therein: |credential|, |clientExtensionResults This is possibly a new [=Credential Manager Trust Group Key=], signifying a new trust group. 1. Optionally, perform additional policy checks. - + 1. Let |newEntry| be a new [=CMTG Key Record=] where its [$CMTG Key Record/publicKey$] is |cmtgKey| and its [$CMTG Key Record/lastUsed$] is the current time. From ee76bef5f0cf689cd498efa660610db27233e0e1 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Thu, 11 Jun 2026 14:21:06 -0400 Subject: [PATCH 38/48] Emil feedback --- index.bs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/index.bs b/index.bs index 9f8c1dbf9..b66027a35 100644 --- a/index.bs +++ b/index.bs @@ -8065,11 +8065,7 @@ using these variables established therein: |credential|, |clientExtensionResults NOTE: [=[RPS]=] MUST ignore any keys in |extensionOutput| that are not defined in the {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. -1. Let |signature| be |extensionOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}}. - Verify that |signature| is a valid ArrayBuffer. - -1. Let |cmtgKey| be |extensionOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/cmtgKey}}. - Verify that |cmtgKey| is a valid ArrayBuffer containing a COSE_Key object. +1. Parse |cmtgKey| as a COSE_Key. 1. Verify that the algorithm used for |signature| matches the COSE algorithm identifier of the [=credentialPublicKey=] within the [=attestedCredentialData=] of |authData|. @@ -8100,7 +8096,7 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Optionally, perform additional policy checks. - 1. Terminate this `cmtgKey` validation algorithm. + 1. Terminate these validation steps as inconclusive. NOTE: [=[RPS]=] MUST ignore any keys in |extensionOutput| that are not defined in the {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. From 0b644326b1bcda1cf1efc434fbc2d45d92ded7d4 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Fri, 17 Jul 2026 17:16:31 +0200 Subject: [PATCH 39/48] Split out make vs get signature --- index.bs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/index.bs b/index.bs index 3e7aaf665..bbb11e683 100644 --- a/index.bs +++ b/index.bs @@ -8191,10 +8191,18 @@ A CMTG Key Record is a [=struct=] with the following [=struct/items=] 1. Let |cmtgPrivateKey| be the newly created or existing [=Credential Manager Trust Group private key=]. - 1. Let |cmtgKeySig| be the result of signing the [=assertion signature=] [input](#fig-signature) with |cmtgPrivateKey|, - using the same signature algorithm as that used by the [=user credential=]'s [=credential key pair=]. + 1. If this algorithm is being run as part of [=authenticatorMakeCredential=]: - Note: The [=assertion signature=] [input](#fig-signature) and |cmtgKeySig| covers the [=[RP]=]'s {{PublicKeyCredentialCreationOptions/challenge}} + 1. Let |cmtgKeySig| be the result of signing the concatenation of |authenticatorData| (as constructed in [[#sctn-generating-an-attestation-object]], + which includes |attestedCredentialData|) and |clientDataHash|, with |cmtgPrivateKey|, + using the same signature algorithm as that used by the [=user credential=]'s [=credential key pair=]. + + 1. If this algorithm is being run as part of [=authenticatorGetAssertion=]: + + 1. Let |cmtgKeySig| be the result of signing the [=assertion signature|assertion signature input=] with |cmtgPrivateKey|, + using the same signature algorithm as that used by the [=user credential=]'s [=credential key pair=]. + + Note: The [=assertion signature=] [input](#fig-signature) and |cmtgKeySig| covers the [=[RP]=]'s {{PublicKeyCredentialCreationOptions/challenge}} because it includes the [=hash of the serialized client data=]. Thus the [=[RP]=] knows that |cmtgKeySig| is a fresh signature. 1. Output |cmtgPublicKey| as the [=authenticator extension output=]. @@ -8902,7 +8910,7 @@ The [=remote end steps=] are: 1. If |parameters| is not a JSON [=Object=], return a [=WebDriver error=] with [=WebDriver error code=] [=invalid argument=]. - + Note: |parameters| is a [=Set User Verified Parameters=] object. 1. If |authenticatorId| does not match any [=Virtual Authenticator=] stored in the [=Virtual Authenticator Database=], return a [=WebDriver error=] with [=WebDriver error code=] [=invalid argument=]. From c0a4befe51265ff798220e4f25650f37fa48c7a9 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Mon, 20 Jul 2026 22:31:49 +0200 Subject: [PATCH 40/48] Nina feedback --- index.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index bbb11e683..d7618c725 100644 --- a/index.bs +++ b/index.bs @@ -8112,7 +8112,7 @@ A CMTG Key Record is a [=struct=] with the following [=struct/items=] }; partial dictionary AuthenticationExtensionsClientInputsJSON { boolean cmtgKey; -} ; + }; : Client extension processing @@ -8226,8 +8226,8 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Let |extensionOutput| be the value of |clientExtensionResults|.{{AuthenticationExtensionsClientOutputs/cmtgKey}}. -1. If |extensionOutput| does not contain the required members of {{AuthenticationExtensionsCmtgKeyOutputs}}, - terminate this `cmtgKey` validation algorithm. +1. If |extensionOutput| is not present or does not contain the required members of + {{AuthenticationExtensionsCmtgKeyOutputs}}, terminate this `cmtgKey` validation algorithm. NOTE: [=[RPS]=] MUST ignore any keys in |extensionOutput| that are not defined in the {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. From fee02799117987bdca1a18d546534f2113491df0 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Wed, 23 Sep 2026 13:54:42 -0400 Subject: [PATCH 41/48] remove dangling comma --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index d7618c725..de6d7852d 100644 --- a/index.bs +++ b/index.bs @@ -1067,7 +1067,7 @@ BCP 14 [[!RFC2119]] [[!RFC8174]] when, and only when, they appear in all capital : Credential Manager Trust Group Private Key : Credential Manager Trust Group Public Key : Credential Manager Trust Group Key Pair -:: A [=Credential Manager Trust Group Key=], is a [=credential manager=]-, [=[RP]=]-, and [=user credential=]-specific +:: A [=Credential Manager Trust Group Key=] is a [=credential manager=]-, [=[RP]=]-, and [=user credential=]-specific public key pair created upon a [=[RP]=]'s request via the [=cmtgKey=] [=WebAuthn extension=]. The [=Credential Manager Trust Group Public Key=] is conveyed to the [=[RP]=] during a From 3ace13de45816b970469a5eb32793fadc2cf5883 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Wed, 23 Sep 2026 13:56:15 -0400 Subject: [PATCH 42/48] Added clarity around client/CM support --- index.bs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index de6d7852d..b9829ed2d 100644 --- a/index.bs +++ b/index.bs @@ -7943,7 +7943,8 @@ This extension is intended for use by those [=[RPS]=] employing risk-analysis sy This extension provides a "trust group" signal when used consistently with both {{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations. -When a [=[RP]=] uses the `cmtgKey` extension during a {{CredentialsContainer/create()|create()}} option on the first device (`dev1`), +When a [=[RP]=] uses the `cmtgKey` extension during a {{CredentialsContainer/create()|create()}} option on the first device (`dev1`) +and the extension is supported by the [=WebAuthn client=] and at least one [=authenticator=]/[=credential manager=], the selected [=authenticator=]/[=credential manager=] (`cm1`) generates a new [=Credential Manager Trust Group Key=] pair. The extension output includes the public key and a signature generated using the [=Credential Manager Trust Group private key=] (`cmtgKey1`). From 1fcdbf87f4a6f5b18aa3592b37782cf0a9fef8b4 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Wed, 23 Sep 2026 14:04:20 -0400 Subject: [PATCH 43/48] Simplify example and include server side --- index.bs | 70 +++++++++++++++++++++++++------------------------------- 1 file changed, 31 insertions(+), 39 deletions(-) diff --git a/index.bs b/index.bs index b9829ed2d..6ee9bed3d 100644 --- a/index.bs +++ b/index.bs @@ -7951,54 +7951,46 @@ The extension output includes the public key and a signature generated using the
-Here's what a WebAuthn registration ceremony with the `cmtgKey` extension -might look like on the client side: +On the client side, the `cmtgKey` extension requires only adding `cmtgKey: true` +to the extensions in the request options. + +Registration example: ```javascript -async function createPasskey() { - // 1. Fetch JSON options from your server. - // The server should return a JSON structure where buffers are Base64URL encoded strings. - const response = await fetch('/auth/register/options'); - const optionsJSON = await response.json(); +// Add to the options JSON before calling parseCreationOptionsFromJSON(): +optionsJSON.extensions = { ...optionsJSON.extensions, cmtgKey: true }; +``` - // 2. Add the CMTG extension request to the JSON object before parsing - // Note: This is illustrative. This would likely come from the server in the options response in a real implementation. - optionsJSON.extensions = { - ...optionsJSON.extensions, - cmtgKey: true - }; +Authentication example: - // 3. Convert the JSON into an object - const publicKeyCredentialCreationOptions = - PublicKeyCredential.parseCreationOptionsFromJSON(optionsJSON); +```javascript +// Add to the options JSON before calling parseRequestOptionsFromJSON(): +optionsJSON.extensions = { ...optionsJSON.extensions, cmtgKey: true }; +``` - try { - // 4. Trigger the passkey creation - const credential = await navigator.credentials.create({ - publicKey: publicKeyCredentialCreationOptions - }); +On the server side, the [=[RP]=] verifies the extension output +and maintains a list of known [=Credential Manager Trust Group public keys=] +per [=credential record=]: - // 5. Serialize the entire credential (including extension results) - const credentialJSON = credential.toJSON(); +``` +// After standard WebAuthn response verification: - // 6. Send the serialized object directly to the backend - const verificationResponse = await fetch('/auth/register/verify', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(credentialJSON) - }); +cmtgOutput = response.clientExtensionResults.cmtgKey +if cmtgOutput is not present: + // RP policy determines what to do next + return - // The backend should verify both the passkey and the CMTG Key and update the credential record. - // If verification is successful, it should return a success response. +// perform CMTG Key signature verification steps - if (verificationResponse.ok) { - establishSession(); - console.log("Passkey and CMTG Key registered successfully!"); - } - } catch (err) { - console.error("Registration failed:", err); - } -} +// Look up the key in the credential record's cmtgKeyRecords list. +existing = cmtgKeyRecords.find(entry => entry.publicKey == cmtgKey) + +if existing: + // Known trust group. Update timestamp. + existing.lastUsed = now() +else: + // New trust group — apply risk-analysis policy checks as needed. + cmtgKeyRecords.append({ publicKey: cmtgKey, lastUsed: now() }) ```
From 630c98da090b2f7141d17bb9c3dcd62f7be38120 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Wed, 23 Sep 2026 17:25:20 -0400 Subject: [PATCH 44/48] Split back out reg vs auth --- index.bs | 109 ++++++++++++++++++------------------------------------- 1 file changed, 35 insertions(+), 74 deletions(-) diff --git a/index.bs b/index.bs index 6ee9bed3d..4a4fb4401 100644 --- a/index.bs +++ b/index.bs @@ -7950,47 +7950,32 @@ The extension output includes the public key and a signature generated using the [=Credential Manager Trust Group private key=] (`cmtgKey1`).
- On the client side, the `cmtgKey` extension requires only adding `cmtgKey: true` -to the extensions in the request options. - -Registration example: +to the extensions in the options. The extension output is then included automatically +when the credential is serialized with `toJSON()`. ```javascript // Add to the options JSON before calling parseCreationOptionsFromJSON(): optionsJSON.extensions = { ...optionsJSON.extensions, cmtgKey: true }; -``` -Authentication example: - -```javascript -// Add to the options JSON before calling parseRequestOptionsFromJSON(): -optionsJSON.extensions = { ...optionsJSON.extensions, cmtgKey: true }; +// The rest of the ceremony is unchanged. credential.toJSON() includes +// the cmtgKey extension output automatically. ``` -On the server side, the [=[RP]=] verifies the extension output -and maintains a list of known [=Credential Manager Trust Group public keys=] -per [=credential record=]: +On the server side, the [=[RP]=] verifies the extension output after completing +standard [=registration=] verification: ``` -// After standard WebAuthn response verification: - cmtgOutput = response.clientExtensionResults.cmtgKey if cmtgOutput is not present: - // RP policy determines what to do next + // Extension was not processed by the client and/or authenticator. + // RP policy decides what to do next. return // perform CMTG Key signature verification steps -// Look up the key in the credential record's cmtgKeyRecords list. -existing = cmtgKeyRecords.find(entry => entry.publicKey == cmtgKey) - -if existing: - // Known trust group. Update timestamp. - existing.lastUsed = now() -else: - // New trust group — apply risk-analysis policy checks as needed. - cmtgKeyRecords.append({ publicKey: cmtgKey, lastUsed: now() }) +// Store the CMTG public key in the new credential record. +credentialRecord.cmtgKeyRecords.append({ publicKey: cmtgKey, lastUsed: now() }) ```
@@ -8011,60 +7996,36 @@ If `dev1` and `dev3` or `dev2` and `dev3` are later determined to have non-remot `cm1` may elect to use `cmtgKey1` for all three devices, signaling that they are now in the same trust group.
- -Here's what a WebAuthn authentication ceremony with the `cmtgKey` extension -might look like on the client side: +As with registration, the client side only needs to add `cmtgKey: true` to the extensions: ```javascript -async function getPasskey() { - // 1. Fetch authentication options from your server. - // The server returns a JSON object with a challenge and allowed credentials. - const response = await fetch('/auth/login/options'); - const optionsJSON = await response.json(); - - // 2. Add the CMTG extension request to the JSON object before parsing - // Note: This is illustrative. This would likely come from the server in the options response in a real implementation. - optionsJSON.extensions = { - ...optionsJSON.extensions, - cmtgKey: true - }; - - // 3. Convert the JSON into an object - const publicKeyCredentialRequestOptions = - PublicKeyCredential.parseRequestOptionsFromJSON(optionsJSON); - - try { - // 4. Trigger the passkey login - const assertion = await navigator.credentials.get({ - publicKey: publicKeyCredentialRequestOptions - }); +// Add to the options JSON before calling parseRequestOptionsFromJSON(): +optionsJSON.extensions = { ...optionsJSON.extensions, cmtgKey: true }; - // 5. Serialize the assertion (including CMTG extension results) - const assertionJSON = assertion.toJSON(); +// The rest of the ceremony is unchanged. +``` - // 6. Send the serialized assertion to the backend - const verificationResponse = await fetch('/auth/login/verify', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(assertionJSON) - }); +On the server side, the [=[RP]=] verifies the extension output after completing +standard [=authentication=] verification: - // The backend should verify both the passkey and the CMTG Key. - // If verification is successful, it should return a success response. - - if (verificationResponse.ok) { - // If the CMTG Key matches an expected entry from the credential record, - // the passkey was from a previously trusted credential manager. - establishSession(); - // If the CMTG Key was not returned, was not the expected entry, - // or has not been seen before, optionally - // run additional risk analysis and policy checks. - console.log("Authenticated! Trust Group verified via CMTG."); - } - } catch (err) { - console.error("Authentication failed:", err); - } -} +``` +cmtgOutput = response.clientExtensionResults.cmtgKey +if cmtgOutput is not present: + // RP policy decides what to do next + return + + +// perform CMTG Key signature verification steps + +// Look up the key in the credential record's cmtgKeyRecords list. +existing = cmtgKeyRecords.find(entry => entry.publicKey == cmtgKey) + +if existing: + // Known trust group. Update timestamp. + existing.lastUsed = now() +else: + // New trust group. Apply risk-analysis policy checks as needed. + cmtgKeyRecords.append({ publicKey: cmtgKey, lastUsed: now() }) ```
From 798f4e3d8973eabb7e4d88cdf52fb1f809d03cc7 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Wed, 23 Sep 2026 18:51:42 -0400 Subject: [PATCH 45/48] Remove note for MUST --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 4a4fb4401..dd27a2104 100644 --- a/index.bs +++ b/index.bs @@ -8183,7 +8183,7 @@ using these variables established therein: |credential|, |clientExtensionResults 1. If |extensionOutput| is not present or does not contain the required members of {{AuthenticationExtensionsCmtgKeyOutputs}}, terminate this `cmtgKey` validation algorithm. - NOTE: [=[RPS]=] MUST ignore any keys in |extensionOutput| that are not defined in the {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. + [=[RPS]=] MUST ignore any keys in |extensionOutput| that are not defined in the {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. 1. Parse |cmtgKey| as a COSE_Key. From 4464d5987d47f93b734ec9ae34b828484e3c0f9e Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Wed, 23 Sep 2026 18:59:31 -0400 Subject: [PATCH 46/48] s/PublicKeyCredentialCreationOptions/PublicKeyCredentialRequestOptions --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index dd27a2104..03a545d92 100644 --- a/index.bs +++ b/index.bs @@ -8156,7 +8156,7 @@ A CMTG Key Record is a [=struct=] with the following [=struct/items=] 1. Let |cmtgKeySig| be the result of signing the [=assertion signature|assertion signature input=] with |cmtgPrivateKey|, using the same signature algorithm as that used by the [=user credential=]'s [=credential key pair=]. - Note: The [=assertion signature=] [input](#fig-signature) and |cmtgKeySig| covers the [=[RP]=]'s {{PublicKeyCredentialCreationOptions/challenge}} + Note: The [=assertion signature=] [input](#fig-signature) and |cmtgKeySig| covers the [=[RP]=]'s {{PublicKeyCredentialRequestOptions/challenge}} because it includes the [=hash of the serialized client data=]. Thus the [=[RP]=] knows that |cmtgKeySig| is a fresh signature. 1. Output |cmtgPublicKey| as the [=authenticator extension output=]. From d0cf4655f826df4b581429e5f02c44cbf6534ab5 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Wed, 23 Sep 2026 19:02:44 -0400 Subject: [PATCH 47/48] Clarify authenticatorData for create --- index.bs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 03a545d92..7308d6680 100644 --- a/index.bs +++ b/index.bs @@ -8147,8 +8147,9 @@ A CMTG Key Record is a [=struct=] with the following [=struct/items=] 1. If this algorithm is being run as part of [=authenticatorMakeCredential=]: - 1. Let |cmtgKeySig| be the result of signing the concatenation of |authenticatorData| (as constructed in [[#sctn-generating-an-attestation-object]], - which includes |attestedCredentialData|) and |clientDataHash|, with |cmtgPrivateKey|, + 1. Let |cmtgKeySig| be the result of signing the concatenation of |authenticatorData| + (which includes [=attestedCredentialData=] during [=registration=]) + and |clientDataHash|, with |cmtgPrivateKey|, using the same signature algorithm as that used by the [=user credential=]'s [=credential key pair=]. 1. If this algorithm is being run as part of [=authenticatorGetAssertion=]: From 8be55be68115fc502a2eb3dfee70e00412271736 Mon Sep 17 00:00:00 2001 From: Nina Satragno Date: Wed, 14 Oct 2026 15:35:08 -0400 Subject: [PATCH 48/48] [PQ] Make cmtgKeys extension output a list Rename the CMTG key extension from cmtgKey to cmtgKeys and make its client output a sequence. The authenticator extension output and unsigned extension output become index-matched CBOR arrays. Authenticators currently MUST return exactly one entry, and Relying Parties MUST treat an output with more than one entry as invalid. The list format leaves room for future extensions, such as algorithm transitions (#2417), to return more than one key. Clients fail the operation with an "UnknownError" if the outputs are malformed or have mismatched lengths, since the signed keys cannot be removed from the authenticator data. Also rename the CMTG section IDs and update the explainer. Fixes #2472 --- .../credential-manager-trust-group-key.md | 8 +- index.bs | 145 ++++++++++++------ 2 files changed, 99 insertions(+), 54 deletions(-) diff --git a/explainers/credential-manager-trust-group-key.md b/explainers/credential-manager-trust-group-key.md index 8d8fcc0cd..b30a15583 100644 --- a/explainers/credential-manager-trust-group-key.md +++ b/explainers/credential-manager-trust-group-key.md @@ -50,20 +50,20 @@ When a relying party observes an CMTG Key they have not seen before, it indicate If the relying party has seen the same CMTG Key on a prior create or get call, it indicates that either the passkey is coming from the same device, or another device for which the credential manager has seen phishing-resistant signals linking it to the original device. This is a strong signal to the RP that trustworthiness of the original device can be considered when evaluating trust for the current device. -Relying parties request an CMTG key by requesting the `cmtgKey` extension on either a create or a get call: +Relying parties request an CMTG key by requesting the `cmtgKeys` extension on either a create or a get call: ```javascript const cred = await navigator.credentials.get({ publicKey: { challenge: ..., extensions: { - cmtgKey: true /* also works for .create */ + cmtgKeys: true /* also works for .create */ } } }); ``` -The `credentialManagerTrustGroupKey` extension is an [authenticator extension](https://www.w3.org/TR/webauthn-3/#authenticator-extension), and will have two outputs: +The `cmtgKeys` extension is an [authenticator extension](https://www.w3.org/TR/webauthn-3/#authenticator-extension), and will have two outputs, each a list that currently always contains exactly one element: 1. The public key of the CMTG will be present in the [authenticator extension outputs](https://www.w3.org/TR/webauthn-3/#authenticator-extension-output). 2. A signature proving possession of the CMTG private key will be In the [unsigned extension outputs](https://www.w3.org/TR/webauthn-3/#unsigned-extension-outputs). @@ -89,3 +89,5 @@ CMTG Keys are optional, i.e. RPs that don't need the additional signal can simpl For relying parties that need more fine grained or stricter signals, e.g. for compliance purposes, we think other more targeted APIs are a better option. **Who sets the guidelines for admissible signals for determining non-remote relationships?** This is left undecided in this proposal, but we expect a discussion in the relevant W3C WebAuthn and FIDO Alliance forums. + +**Why is the output a list if only one CMTG Key is returned?** Returning a list leaves room for future changes where more than one CMTG Key may be returned in a single response, for example passkey algorithm upgrades ([#2417](https://github.com/w3c/webauthn/issues/2417)), where a CMTG Key the relying party already knows could vouch for a newly minted one. Until such a change is specified, authenticators return exactly one CMTG Key, and relying parties treat an output containing more than one as invalid. diff --git a/index.bs b/index.bs index 7308d6680..04b060b76 100644 --- a/index.bs +++ b/index.bs @@ -1068,15 +1068,15 @@ BCP 14 [[!RFC2119]] [[!RFC8174]] when, and only when, they appear in all capital : Credential Manager Trust Group Public Key : Credential Manager Trust Group Key Pair :: A [=Credential Manager Trust Group Key=] is a [=credential manager=]-, [=[RP]=]-, and [=user credential=]-specific - public key pair created upon a [=[RP]=]'s request via the [=cmtgKey=] [=WebAuthn extension=]. + public key pair created upon a [=[RP]=]'s request via the [=cmtgKeys=] [=WebAuthn extension=]. The [=Credential Manager Trust Group Public Key=] is conveyed to the [=[RP]=] during a [=registration ceremony|registration=] or [=authentication ceremony|authentication=] ceremony - as part of the [=cmtgKey=] extension output. + as part of the [=cmtgKeys=] extension output. The [=Credential Manager Trust Group Private Key=] is stored by the [=authenticator=] / [=credential manager=]. - See [[#sctn-cmtg-key-extension]]. + See [[#sctn-cmtg-keys-extension]]. : Credential Properties :: A [=credential property=] is some characteristic property of a [=public key credential source=], such as whether it is a @@ -7919,7 +7919,7 @@ This permission MUST be configured per-origin and MUST NOT offer the capability This section defines extensions that are both [=client extensions=] and [=authenticator extensions=]. -### Credential Manager Trust Group Key extension (cmtgKey) ### {#sctn-cmtg-key-extension} +### Credential Manager Trust Group Keys extension (cmtgKeys) ### {#sctn-cmtg-keys-extension} This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] enables an [=authenticator=]/[=credential manager=] to provide a signal to a [=[RP]=] @@ -7933,45 +7933,51 @@ in the [=authenticator=], if such a key pair needs to be created for the and returning one of the [=Credential Manager Trust Group public keys=] along with a signature by the [=Credential Manager Trust Group private key=] to the [=[RP]=]. -This is done each time this [=cmtgKey=] extension is requested in either a +This is done each time this [=cmtgKeys=] extension is requested in either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/get()|navigator.credentials.get()}} call. -#### Relying Party Usage #### {#sctn-cmtg-key-extension-usage} +#### Relying Party Usage #### {#sctn-cmtg-keys-extension-usage} This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension provides a "trust group" signal when used consistently with both {{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations. -When a [=[RP]=] uses the `cmtgKey` extension during a {{CredentialsContainer/create()|create()}} option on the first device (`dev1`) +When a [=[RP]=] uses the `cmtgKeys` extension during a {{CredentialsContainer/create()|create()}} option on the first device (`dev1`) and the extension is supported by the [=WebAuthn client=] and at least one [=authenticator=]/[=credential manager=], the selected [=authenticator=]/[=credential manager=] (`cm1`) generates a new [=Credential Manager Trust Group Key=] pair. The extension output includes the public key and a signature generated using the [=Credential Manager Trust Group private key=] (`cmtgKey1`).
-On the client side, the `cmtgKey` extension requires only adding `cmtgKey: true` +On the client side, the `cmtgKeys` extension requires only adding `cmtgKeys: true` to the extensions in the options. The extension output is then included automatically when the credential is serialized with `toJSON()`. ```javascript // Add to the options JSON before calling parseCreationOptionsFromJSON(): -optionsJSON.extensions = { ...optionsJSON.extensions, cmtgKey: true }; +optionsJSON.extensions = { ...optionsJSON.extensions, cmtgKeys: true }; // The rest of the ceremony is unchanged. credential.toJSON() includes -// the cmtgKey extension output automatically. +// the cmtgKeys extension output automatically. ``` On the server side, the [=[RP]=] verifies the extension output after completing standard [=registration=] verification: ``` -cmtgOutput = response.clientExtensionResults.cmtgKey -if cmtgOutput is not present: +cmtgOutputs = response.clientExtensionResults.cmtgKeys +if cmtgOutputs is not present or is empty: // Extension was not processed by the client and/or authenticator. // RP policy decides what to do next. return +if cmtgOutputs.length > 1: + // Only a single CMTG key is currently supported. + reject the extension output as invalid + +{ cmtgKey, signature } = cmtgOutputs[0] + // perform CMTG Key signature verification steps // Store the CMTG public key in the new credential record. @@ -7979,7 +7985,7 @@ credentialRecord.cmtgKeyRecords.append({ publicKey: cmtgKey, lastUsed: now() }) ```
-A subsequent {{CredentialsContainer/get()|get()}} operation (with the `cmtgKey` extension) on `dev1` +A subsequent {{CredentialsContainer/get()|get()}} operation (with the `cmtgKeys` extension) on `dev1` using the same credential and the same authenticator/credential manager (`cm1`), will include a signature from the same [=Credential Manager Trust Group private key=], `cmtgKey1`. @@ -7996,11 +8002,11 @@ If `dev1` and `dev3` or `dev2` and `dev3` are later determined to have non-remot `cm1` may elect to use `cmtgKey1` for all three devices, signaling that they are now in the same trust group.
-As with registration, the client side only needs to add `cmtgKey: true` to the extensions: +As with registration, the client side only needs to add `cmtgKeys: true` to the extensions: ```javascript // Add to the options JSON before calling parseRequestOptionsFromJSON(): -optionsJSON.extensions = { ...optionsJSON.extensions, cmtgKey: true }; +optionsJSON.extensions = { ...optionsJSON.extensions, cmtgKeys: true }; // The rest of the ceremony is unchanged. ``` @@ -8009,11 +8015,16 @@ On the server side, the [=[RP]=] verifies the extension output after completing standard [=authentication=] verification: ``` -cmtgOutput = response.clientExtensionResults.cmtgKey -if cmtgOutput is not present: +cmtgOutputs = response.clientExtensionResults.cmtgKeys +if cmtgOutputs is not present or is empty: // RP policy decides what to do next return +if cmtgOutputs.length > 1: + // Only a single CMTG key is currently supported. + reject the extension output as invalid + +{ cmtgKey, signature } = cmtgOutputs[0] // perform CMTG Key signature verification steps @@ -8029,7 +8040,7 @@ else: ```
-#### Updating the RP's Credential Record #### {#sctn-cmtg-key-credential-record} +#### Updating the RP's Credential Record #### {#sctn-cmtg-keys-credential-record} When this extension is used by a [=Relying Party=], the [=credential record=] structure SHOULD be augmented with the following field: @@ -8050,10 +8061,10 @@ A CMTG Key Record is a [=struct=] with the following [=struct/items=] -#### Extension Definition #### {#sctn-cmtg-key-extension-definition} +#### Extension Definition #### {#sctn-cmtg-keys-extension-definition} : Extension identifier -:: `cmtgKey` +:: `cmtgKeys` : Operation applicability :: [=registration extension|Registration=] and [=authentication extension|authentication=] @@ -8062,27 +8073,35 @@ A CMTG Key Record is a [=struct=] with the following [=struct/items=] :: The Boolean value [TRUE] to indicate that this extension is requested by the [=[RP]=]. partial dictionary AuthenticationExtensionsClientInputs { - boolean cmtgKey; + boolean cmtgKeys; }; partial dictionary AuthenticationExtensionsClientInputsJSON { - boolean cmtgKey; + boolean cmtgKeys; }; : Client extension processing :: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: 1. Create the authenticator extension input from the client extension input. - 1. If the authenticator extension output is empty or indicates the extension was not processed, + 1. If neither the authenticator extension output nor the [=unsigned extension output=] is present, + or the authenticator extension output indicates the extension was not processed, stop processing this extension and return no output. - 1. Let |cmtgKeyOutput| be a new {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. - 1. Set |cmtgKeyOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/cmtgKey}} to the CBOR-decoded byte string from the [=authenticator extension output=]. - 1. Set |cmtgKeyOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}} to the CBOR-decoded byte string from the [=unsigned extension output=]. - 1. Return |cmtgKeyOutput| as the client extension output. + 1. If either the authenticator extension output or the [=unsigned extension output=] is absent + or is not a non-empty CBOR array of byte strings, + or if they do not contain the same number of elements, + return a {{DOMException}} whose name is "{{UnknownError}}". + 1. Let |cmtgKeyOutputs| be a new empty [=list=]. + 1. [=list/For each=] index |i| of the array in the [=authenticator extension output=]: + 1. Let |cmtgKeyOutput| be a new {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. + 1. Set |cmtgKeyOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/cmtgKey}} to the CBOR-decoded byte string at index |i| of the [=authenticator extension output=]. + 1. Set |cmtgKeyOutput|.{{AuthenticationExtensionsCmtgKeyOutputs/signature}} to the CBOR-decoded byte string at index |i| of the [=unsigned extension output=]. + 1. [=list/Append=] |cmtgKeyOutput| to |cmtgKeyOutputs|. + 1. Return |cmtgKeyOutputs| as the client extension output. : Client extension output -:: An {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary containing the COSE_Key-encoded [=Credential Manager Trust Group public key=] +:: A sequence of {{AuthenticationExtensionsCmtgKeyOutputs}} dictionaries, each containing a COSE_Key-encoded [=Credential Manager Trust Group public key=] returned in the authenticator extension output, - and the signature returned as the [=unsigned extension output=]. + and the corresponding signature returned in the [=unsigned extension output=]. dictionary AuthenticationExtensionsCmtgKeyOutputs { required ArrayBuffer cmtgKey; @@ -8095,11 +8114,11 @@ A <dfn>CMTG Key Record</dfn> is a [=struct=] with the following [=struct/items=] }; partial dictionary AuthenticationExtensionsClientOutputs { - AuthenticationExtensionsCmtgKeyOutputs cmtgKey; + sequence<AuthenticationExtensionsCmtgKeyOutputs> cmtgKeys; }; partial dictionary AuthenticationExtensionsClientOutputsJSON { - AuthenticationExtensionsCmtgKeyOutputsJSON cmtgKey; + sequence<AuthenticationExtensionsCmtgKeyOutputsJSON> cmtgKeys; }; @@ -8111,21 +8130,23 @@ A CMTG Key Record is a [=struct=] with the following [=struct/items=] ``` $$extensionInput //= ( - cmtgKey: true, + cmtgKeys: true, ) ``` : Authenticator extension output -:: A byte string containing the COSE_Key-encoded [=Credential Manager Trust Group public key=]: +:: A non-empty array of byte strings, each containing a COSE_Key-encoded [=Credential Manager Trust Group public key=]: ``` $$extensionOutput //= ( - cmtgKey: bstr .cbor COSE_Key, + cmtgKeys: [+ bstr .cbor COSE_Key], ) ``` : Unsigned extension output -:: A CBOR byte string containing a signature generated with the Credential Manager Trust Group Key's private key. +:: A non-empty CBOR array of byte strings, with the same number of elements as the [=authenticator extension output=]. + Each byte string contains a signature generated with the private key of the [=Credential Manager Trust Group Key=] + at the same index in the [=authenticator extension output=]. : Authenticator extension processing :: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: @@ -8160,29 +8181,40 @@ A CMTG Key Record is a [=struct=] with the following [=struct/items=] Note: The [=assertion signature=] [input](#fig-signature) and |cmtgKeySig| covers the [=[RP]=]'s {{PublicKeyCredentialRequestOptions/challenge}} because it includes the [=hash of the serialized client data=]. Thus the [=[RP]=] knows that |cmtgKeySig| is a fresh signature. - 1. Output |cmtgPublicKey| as the [=authenticator extension output=]. + 1. Output a CBOR array containing exactly one element, |cmtgPublicKey|, as the [=authenticator extension output=]. - 1. Output |cmtgKeySig| as the extension's [=unsigned extension output=]. + 1. Output a CBOR array containing exactly one element, |cmtgKeySig|, as the extension's [=unsigned extension output=]. Note: |cmtgKeySig| 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. -#### `cmtgKey` Extension Output Verification Procedures #### {#sctn-cmtg-key-extension-verification} + Note: The extension outputs are arrays so that future extensions may return more than one + [=Credential Manager Trust Group Key=], for example to support algorithm transitions + (see w3c/webauthn#2417). + +#### `cmtgKeys` Extension Output Verification Procedures #### {#sctn-cmtg-keys-extension-verification} -Verifying the [=cmtgKey=] extension output is performed by the [=[RP]=] whenever a [=Credential Manager Trust Group Key=] is returned within the extension output. +Verifying the [=cmtgKeys=] extension output is performed by the [=[RP]=] whenever a [=Credential Manager Trust Group Key=] is returned within the extension output. -##### Registration (`create()`) ##### {#sctn-cmtg-key-extension-verification-create} +##### Registration (`create()`) ##### {#sctn-cmtg-keys-extension-verification-create} -If a {{AuthenticationExtensionsClientOutputs/cmtgKey}} extension output is present in a {{CredentialsContainer/create()|navigator.credentials.create()}} call, +If a {{AuthenticationExtensionsClientOutputs/cmtgKeys}} extension output is present in a {{CredentialsContainer/create()|navigator.credentials.create()}} call, the below verification steps are performed in the context of this step of [[#sctn-registering-a-new-credential]] using these variables established therein: |credential|, |clientExtensionResults|, |authData|, and |hash|. 1. Let |credentialRecord| be the [=credential record=] for |credential|. -1. Let |extensionOutput| be the value of |clientExtensionResults|.{{AuthenticationExtensionsClientOutputs/cmtgKey}}. +1. Let |extensionOutputs| be the value of |clientExtensionResults|.{{AuthenticationExtensionsClientOutputs/cmtgKeys}}. + +1. If |extensionOutputs| is not present or [=list/is empty=], terminate this `cmtgKeys` validation algorithm. + +1. If the [=list/size=] of |extensionOutputs| is greater than 1, the extension output is invalid. + Terminate this `cmtgKeys` validation algorithm. + +1. Let |extensionOutput| be |extensionOutputs|[0]. -1. If |extensionOutput| is not present or does not contain the required members of - {{AuthenticationExtensionsCmtgKeyOutputs}}, terminate this `cmtgKey` validation algorithm. +1. If |extensionOutput| does not contain the required members of + {{AuthenticationExtensionsCmtgKeyOutputs}}, terminate this `cmtgKeys` validation algorithm. [=[RPS]=] MUST ignore any keys in |extensionOutput| that are not defined in the {{AuthenticationExtensionsCmtgKeyOutputs}} dictionary. @@ -8200,20 +8232,31 @@ using these variables established therein: |credential|, |clientExtensionResults and its [$CMTG Key Record/lastUsed$] is the [=[RP]=]'s current time. 1. Append |newEntry| to |credentialRecord|.[$credential record/cmtgKeyRecords$]. -See also [[#sctn-cmtg-key-extension-usage]] for further details. +See also [[#sctn-cmtg-keys-extension-usage]] for further details. -##### Authentication (`get()`) ##### {#sctn-cmtg-key-extension-verification-get} +##### Authentication (`get()`) ##### {#sctn-cmtg-keys-extension-verification-get} -If a {{AuthenticationExtensionsClientOutputs/cmtgKey}} extension output is present in a {{CredentialsContainer/get()|navigator.credentials.get()}} call, +If a {{AuthenticationExtensionsClientOutputs/cmtgKeys}} extension output is present in a {{CredentialsContainer/get()|navigator.credentials.get()}} call, then the below verification steps are performed in the context of this step of [[#sctn-verifying-assertion]] using these variables established therein: |credential|, |clientExtensionResults|, |authData|, and |hash|. -[=[RP]=] policy may specify whether a response without a {{AuthenticationExtensionsClientOutputs/cmtgKey}} is acceptable. +[=[RP]=] policy may specify whether a response without a {{AuthenticationExtensionsClientOutputs/cmtgKeys}} is acceptable. 1. Let |credentialRecord| be the [=credential record=] for |credential|. -1. Let |extensionOutput| be the value of |clientExtensionResults|.{{AuthenticationExtensionsClientOutputs/cmtgKey}}. +1. Let |extensionOutputs| be the value of |clientExtensionResults|.{{AuthenticationExtensionsClientOutputs/cmtgKeys}}. + +1. If |extensionOutputs| is not present or [=list/is empty=]: + + 1. Optionally, perform additional policy checks. + + 1. Terminate these validation steps as inconclusive. + +1. If the [=list/size=] of |extensionOutputs| is greater than 1, the extension output is invalid. + Terminate these validation steps. + +1. Let |extensionOutput| be |extensionOutputs|[0]. -1. If |extensionOutput| is not present or does not contain the required members of {{AuthenticationExtensionsCmtgKeyOutputs}}: +1. If |extensionOutput| does not contain the required members of {{AuthenticationExtensionsCmtgKeyOutputs}}: 1. Optionally, perform additional policy checks. @@ -8252,7 +8295,7 @@ using these variables established therein: |credential|, |clientExtensionResults 1. Append |newEntry| to |credentialRecord|.[$credential record/cmtgKeyRecords$]. -See also [[#sctn-cmtg-key-extension-usage]]. +See also [[#sctn-cmtg-keys-extension-usage]]. # User Agent Automation # {#sctn-automation}