From 6719e054e8d27e04286d3e975d69371a3b135225 Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 16 Apr 2021 07:56:45 -0700 Subject: [PATCH 001/131] device-bound Public Key pair extension This PR instantiates the `getDevicePublicKey` extension. RPs desiring to have a guaranteed device-bound public key returned on `create()` and `get()` need to simply include this extension on their `create()` and `get()` calls. On `create()`, a device-bound public key pair is created in addition to the [credential key pair](https://www.w3.org/TR/webauthn-2/#credential-key-pair), and the extension result conveys the devicePublicKey to the RP. On `get()`, a device-bound public key pair is created if one does not yet exist, and the resulting devicePublicKey is conveyed in the extension result to the RP. --- index.bs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/index.bs b/index.bs index bf0c16392..cefbc38c8 100644 --- a/index.bs +++ b/index.bs @@ -5923,6 +5923,21 @@ Note: In order to interoperate, user agents storing large blobs on authenticator :: [=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]=]. +## Device public key extension (devicePubicKey) ## {#sctn-device-publickey-extension} + +This [=client extension|client=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal. This is done by creating a Relying Party-specific [=hardware-bound device key pair=] on the [=client device=], if such a key pair does not already exist, and returning a signature along with the [=device public key=] to the [=[RP]=], each time this [=devicePubicKey=] extension is included with either a `.create()` or `.get()` call. + +If the [=client device=] is incapable of generating a [=hardware-bound device key pair=], this extension is ignored and no + + +request the authenticator to generate hardware-bound key pair + + + + + + + # 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 4a6b8fe837eb3b74b7cd481c2b360fcfd06c2497 Mon Sep 17 00:00:00 2001 From: JeffH Date: Wed, 21 Apr 2021 12:09:37 -0700 Subject: [PATCH 002/131] further hacking... --- index.bs | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/index.bs b/index.bs index cefbc38c8..1120d536b 100644 --- a/index.bs +++ b/index.bs @@ -1092,6 +1092,11 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S :: 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=]. +: Hardware-bound Device Key Pair +: Device Private Key +: Device Public Key +:: [this is a not-yet-well-thought-out placeholder definition] A [=hardware-bound device key pair=] is a [=[RP]=]-specific public key pair created upon an [=[RP]=]'s request via the [=devicePublicKey=] extension. The [=authenticator=] a [=hardware-bound device key pair=] is created upon MUST guarantee that the [=device private key=] is securely stored in hardware, i.e., it is unextractable. The [=device public key=] is only returned to the [=[RP]=] that created the [=hardware-bound device key pair=]. + : Human Palatability :: An identifier that is [=human palatability|human-palatable=] is intended to be rememberable and reproducible by typical human users, in contrast to identifiers that are, for example, randomly generated sequences of bits [[EduPersonObjectClassSpec]]. @@ -5923,14 +5928,42 @@ Note: In order to interoperate, user agents storing large blobs on authenticator :: [=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]=]. -## Device public key extension (devicePubicKey) ## {#sctn-device-publickey-extension} +## Device-bound public key extension (devicePubicKey) ## {#sctn-device-publickey-extension} -This [=client extension|client=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal. This is done by creating a Relying Party-specific [=hardware-bound device key pair=] on the [=client device=], if such a key pair does not already exist, and returning a signature along with the [=device public key=] to the [=[RP]=], each time this [=devicePubicKey=] extension is included with either a `.create()` or `.get()` call. +This [=client extension|client=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal. This is done by creating a [=[RP]=]-specific [=hardware-bound device key pair=] on the [=client device=], if such a key pair does not already exist, and returning a signature along with the [=device public key=] to the [=[RP]=], each time this [=devicePubicKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/create()|navigator.credentials.get()}} call. -If the [=client device=] is incapable of generating a [=hardware-bound device key pair=], this extension is ignored and no +If the [=client device=] is incapable of generating a [=hardware-bound device key pair=], this extension is ignored and no [=hardware-bound device key pair=] is created. -request the authenticator to generate hardware-bound key pair +: Extension identifier +:: `devicePubicKey` + +: 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 getDevicePubicKey; + }; + + +: Client extension processing +:: None, except creating the authenticator extension input from the client extension input. + +: Client extension output +:: + + +: Authenticator extension input +:: The Boolean value [TRUE], encoded in CBOR (major type 7, value 21). + + ``` + $$extensionInput //= ( + getDevicePubicKey: true, + ) + ``` From 5d1662d6ca14f7bc01ed056ee7d245cc3fe7d72c Mon Sep 17 00:00:00 2001 From: JeffH Date: Wed, 28 Apr 2021 15:47:41 -0700 Subject: [PATCH 003/131] nearly complete tho likely needs to be re-worked to include attestation of dbPK --- index.bs | 53 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/index.bs b/index.bs index 1120d536b..226bf283f 100644 --- a/index.bs +++ b/index.bs @@ -1071,8 +1071,9 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S : 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=]. + and [=scoped=] to a specific [=[WRP]=]. It thus forms the central part of a [=public key credential source=]. A [=credential public key=] is the public key portion of a [=credential key pair=]. The [=credential public key=] is returned to the [=[RP]=] during a [=registration ceremony=]. @@ -1081,12 +1082,10 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S The [=credential private key=] is bound to a particular [=authenticator=] - its [=managing authenticator=] - and is expected to never be exposed to any other party, not even to the owner of the [=authenticator=]. - Note that in the case of [=self - attestation=], the [=credential key pair=] is also used as the [=attestation key pair=], see [=self attestation=] - for details. + Note: In the case of [=self attestation=], the [=credential key pair=] is also used as the [=attestation key pair=], see [=self attestation=] for details. 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. + [[FIDO-U2F-Message-Formats]] and some parts of this specification that relate to it. Also, the term [=user credential=] is occasionally used to synonymously refer to [=credential public key=] and [=user public key=]. : Credential Properties :: A [=credential property=] is some characteristic property of a [=public key credential source=], such as whether it is a @@ -1095,7 +1094,10 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S : Hardware-bound Device Key Pair : Device Private Key : Device Public Key -:: [this is a not-yet-well-thought-out placeholder definition] A [=hardware-bound device key pair=] is a [=[RP]=]-specific public key pair created upon an [=[RP]=]'s request via the [=devicePublicKey=] extension. The [=authenticator=] a [=hardware-bound device key pair=] is created upon MUST guarantee that the [=device private key=] is securely stored in hardware, i.e., it is unextractable. The [=device public key=] is only returned to the [=[RP]=] that created the [=hardware-bound device key pair=]. +:: A [=hardware-bound device key pair=] is a [=[RP]=]-specific and [=user credential=]-specific public key pair created upon a [=[RP]=]'s request via the [=devicePublicKey=] [=WebAuthn extension=]. + The [=[RP]=] may supply this extension during both [=registration=] and [=authentication=]. + The [=authenticator=] that a [=hardware-bound device key pair=] is created upon guarantees that the [=device private key=] is securely stored in hardware, i.e., it is unextractable. The [=device public key=] is returned only to the [=[RP]=] that created the [=hardware-bound device key pair=]. + Thus a [=user credential=], on an [=authenticator=] supporting the [=devicePublicKey=] extension, may have many associated [=[RP]=]-specific [=hardware-bound device key pairs=]. : Human Palatability :: An identifier that is [=human palatability|human-palatable=] is intended to be rememberable and reproducible by typical human @@ -5928,15 +5930,15 @@ Note: In order to interoperate, user agents storing large blobs on authenticator :: [=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]=]. -## Device-bound public key extension (devicePubicKey) ## {#sctn-device-publickey-extension} +## Device-bound public key extension (devicePublicKey) ## {#sctn-device-publickey-extension} -This [=client extension|client=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal. This is done by creating a [=[RP]=]-specific [=hardware-bound device key pair=] on the [=client device=], if such a key pair does not already exist, and returning a signature along with the [=device public key=] to the [=[RP]=], each time this [=devicePubicKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/create()|navigator.credentials.get()}} call. +This [=client extension|client=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal. This is done by creating a [=[RP]=]-specific [=hardware-bound device key pair=] on the [=client device=], if such a key pair does not already exist for the [=user credential=] being created or exercised, and returning the [=device public key=] along with a signature to the [=[RP]=]. This is done each time this [=devicePublicKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/create()|navigator.credentials.get()}} call. -If the [=client device=] is incapable of generating a [=hardware-bound device key pair=], this extension is ignored and no [=hardware-bound device key pair=] is created. +If the [=client device=] is incapable of generating a [=hardware-bound device key pair=], or the registration or authentication operation fails for any reason, this extension is ignored and no [=hardware-bound device key pair=] is created. In this case, there is no [=devicePublicKey=] extension output generated. : Extension identifier -:: `devicePubicKey` +:: `devicePublicKey` : Operation applicability :: [=registration extension|Registration=] and [=authentication extension|authentication=] @@ -5945,15 +5947,20 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke :: The Boolean value [TRUE] to indicate that this extension is requested by the [=[RP]=]. partial dictionary AuthenticationExtensionsClientInputs { - boolean getDevicePubicKey; + boolean devicePublicKey; // Explicitly set this to `true`! }; : Client extension processing -:: None, except creating the authenticator extension input from the client extension input. +:: If {{AuthenticationExtensionsClientInputs/devicePublicKey}} is [TRUE], then there is no processing, except for creating the authenticator extension input from the client extension input. Otherwise, the platform ignores this extension. : Client extension output -:: +:: An ArrayBuffer containing a CBOR record whose two components are: a byte string containing the [=device public key=] in COSE_Key format, followed by a byte string containing a signature value which is the result of signing the concatenation of the [=hash of the serialized client data=] and the COSE_Key representation of the [=device public key=]. + + partial dictionary AuthenticationExtensionsClientOutputs { + ArrayBuffer devicePublicKeyAndSignature; + }; + : Authenticator extension input @@ -5961,14 +5968,30 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke ``` $$extensionInput //= ( - getDevicePubicKey: true, + devicePublicKey: true ) ``` +: Authenticator extension processing +:: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: + * Create or select the [=user credential=] as usual. + * If a [=hardware-bound device key pair=] does not already exist for this [=user credential=] on the [=authenticator=], create it, otherwise recall the existing one. When creating a [=hardware-bound device key pair=], use the same public key algorithm as that of the [=user credential=] + * Then, using the newly created or existing [=hardware-bound device key pair=], de +: Authenticator extension output +:: The [fill-in here as appropriate. NOTE: devicePublicKeyAndSignature NEEDS TO BE UPDATED TO ACCOMODATE CONVEYING ATTESTATION OF THE hardware-bound device key pair. ] + ``` + $$extensionOutput //= ( + devicePublicKey: devicePublicKeyAndSignature, + ) - + devicePublicKeyAndSignature = [ ; Note: The element labels are annotations and do not appear + ; in an encoded record. + devicePublicKey: bstr, ; COSE_Key format + signatureValue: bstr, ; sign(devicePrivateKey, {clientDataHash || devicePublicKey}) + ] + ``` # User Agent Automation # {#sctn-automation} From 1622df27e3c595b6e69a40771fb1659b2ae9b665 Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 30 Apr 2021 15:12:27 -0700 Subject: [PATCH 004/131] add 'device-bound key' --- index.bs | 1 + 1 file changed, 1 insertion(+) diff --git a/index.bs b/index.bs index 226bf283f..0980baa0a 100644 --- a/index.bs +++ b/index.bs @@ -1092,6 +1092,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S [=client-side discoverable credential=] or a [=server-side credential=]. : Hardware-bound Device Key Pair +: Device-bound Key : Device Private Key : Device Public Key :: A [=hardware-bound device key pair=] is a [=[RP]=]-specific and [=user credential=]-specific public key pair created upon a [=[RP]=]'s request via the [=devicePublicKey=] [=WebAuthn extension=]. From 5e684aab30af5cdb106209b77b8e60e8fbaf5efe Mon Sep 17 00:00:00 2001 From: JeffH Date: Thu, 6 May 2021 09:03:20 -0700 Subject: [PATCH 005/131] in-progress updates... --- index.bs | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/index.bs b/index.bs index 0980baa0a..9fc2787d7 100644 --- a/index.bs +++ b/index.bs @@ -1095,7 +1095,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S : Device-bound Key : Device Private Key : Device Public Key -:: A [=hardware-bound device key pair=] is a [=[RP]=]-specific and [=user credential=]-specific public key pair created upon a [=[RP]=]'s request via the [=devicePublicKey=] [=WebAuthn extension=]. +:: A [=hardware-bound device key pair=], also known as a [=device-bound key=], is a [=[RP]=]-specific and [=user credential=]-specific public key pair created upon a [=[RP]=]'s request via the [=devicePublicKey=] [=WebAuthn extension=]. The [=[RP]=] may supply this extension during both [=registration=] and [=authentication=]. The [=authenticator=] that a [=hardware-bound device key pair=] is created upon guarantees that the [=device private key=] is securely stored in hardware, i.e., it is unextractable. The [=device public key=] is returned only to the [=[RP]=] that created the [=hardware-bound device key pair=]. Thus a [=user credential=], on an [=authenticator=] supporting the [=devicePublicKey=] extension, may have many associated [=[RP]=]-specific [=hardware-bound device key pairs=]. @@ -5933,7 +5933,7 @@ Note: In order to interoperate, user agents storing large blobs on authenticator ## Device-bound public key extension (devicePublicKey) ## {#sctn-device-publickey-extension} -This [=client extension|client=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal. This is done by creating a [=[RP]=]-specific [=hardware-bound device key pair=] on the [=client device=], if such a key pair does not already exist for the [=user credential=] being created or exercised, and returning the [=device public key=] along with a signature to the [=[RP]=]. This is done each time this [=devicePublicKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/create()|navigator.credentials.get()}} call. +This [=client extension|client=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal. This is done by creating a [=[RP]=]-specific [=hardware-bound device key pair=] on the [=client device=], if such a key pair does not already exist for the [=user credential=] being created or exercised, and returning the attested [=device public key=] along with a signature by the [=device private key=] to the [=[RP]=]. This is done each time this [=devicePublicKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/create()|navigator.credentials.get()}} call. If the [=client device=] is incapable of generating a [=hardware-bound device key pair=], or the registration or authentication operation fails for any reason, this extension is ignored and no [=hardware-bound device key pair=] is created. In this case, there is no [=devicePublicKey=] extension output generated. @@ -5984,14 +5984,33 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke :: The [fill-in here as appropriate. NOTE: devicePublicKeyAndSignature NEEDS TO BE UPDATED TO ACCOMODATE CONVEYING ATTESTATION OF THE hardware-bound device key pair. ] ``` $$extensionOutput //= ( - devicePublicKey: devicePublicKeyAndSignature, + devicePublicKey: attObjForDevicePublicKey, ) - - devicePublicKeyAndSignature = [ ; Note: The element labels are annotations and do not appear - ; in an encoded record. - devicePublicKey: bstr, ; COSE_Key format - signatureValue: bstr, ; sign(devicePrivateKey, {clientDataHash || devicePublicKey}) + + + AuthDataForDevicePublicKeyAttestation = [ ; Note: The element labels are annotations and + ; do not appear in an encoded record. + + rpIdHash: bstr, ; SHA-256 hash of the user credential's RP ID + credentialId: bstr, ; CredentialId of the user credential + devicePublicKey: bstr, ; COSE_Key format + dpkSignatureValue: bstr, ; sign(devicePrivateKey, (clientDataHash || devicePublicKey)) ] + + + attObjForDevicePublicKey = { ; Note: This object conveys an attested device public key + ; and is analogous to `attObj`. + + authDataForDevicePublicKeyAttestation: AuthDataForDevicePublicKeyAttestation, + + $$attStmtType, ; see section 6.5.4 "Generating an Attestation Object". + ; Attestation statement formats define the `fmt` and `attStmt` members of + ; $$attStmtType. In summary, it contains a signature value calculated over + ; the bytes of authDataForDevicePublicKeyAttestation, the attestation + ; certificate or public key, and supporting certificates, if any. Note + ; that the details are dependent upon the particular attestation statement + ; format. See section 8 "Defined Attestation Statement Formats" + } ``` From 71afdbe64b067cc535cf791b3a17ee2ba499da15 Mon Sep 17 00:00:00 2001 From: JeffH Date: Thu, 6 May 2021 09:06:01 -0700 Subject: [PATCH 006/131] further in-progress updates... --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 9fc2787d7..86e8ce987 100644 --- a/index.bs +++ b/index.bs @@ -5956,7 +5956,7 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke :: If {{AuthenticationExtensionsClientInputs/devicePublicKey}} is [TRUE], then there is no processing, except for creating the authenticator extension input from the client extension input. Otherwise, the platform ignores this extension. : Client extension output -:: An ArrayBuffer containing a CBOR record whose two components are: a byte string containing the [=device public key=] in COSE_Key format, followed by a byte string containing a signature value which is the result of signing the concatenation of the [=hash of the serialized client data=] and the COSE_Key representation of the [=device public key=]. +:: An ArrayBuffer containing a CBOR object whose two components are: a byte string containing the [=device public key=] in COSE_Key format, followed by a byte string containing a signature value which is the result of signing the concatenation of the [=hash of the serialized client data=] and the COSE_Key representation of the [=device public key=]. partial dictionary AuthenticationExtensionsClientOutputs { ArrayBuffer devicePublicKeyAndSignature; From 8040d13725a0769517c71780b38de9a08ceeb113 Mon Sep 17 00:00:00 2001 From: JeffH <jdhodges@google.com> Date: Fri, 7 May 2021 08:44:31 -0700 Subject: [PATCH 007/131] further in-progress updates... --- index.bs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index 86e8ce987..5664a135b 100644 --- a/index.bs +++ b/index.bs @@ -5976,9 +5976,16 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke : Authenticator extension processing :: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: * Create or select the [=user credential=] as usual. - * If a [=hardware-bound device key pair=] does not already exist for this [=user credential=] on the [=authenticator=], create it, otherwise recall the existing one. When creating a [=hardware-bound device key pair=], use the same public key algorithm as that of the [=user credential=] - * Then, using the newly created or existing [=hardware-bound device key pair=], de - + * If a [=hardware-bound device key pair=] does not already exist for this [=user credential=] on the [=authenticator=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential public key=], otherwise recall the existing [=device-bound key=]. + * Let |devicePublicKey| be the newly created or existing [=device public key=], in COSE_Key format. + * Let |devicePrivateKey| be the newly created or existing [=device private key=]. + * Let |clientDataHash| be the [=hash of the serialized client data=]. + * Let |dpkSignatureValue| be the result of `sign(devicePrivateKey, (clientDataHash || devicePublicKey))` using the signature algorithm appropriate for the |devicePrivateKey|'s public key algorithm. + * Let |rpIdHash| be the [=user credential=]'s [=rpIdHash]. + * Let |credentialId| be [=user credential=]'s [=credentialId=]. + * Let `authDataForDevicePublicKeyAttestation` be a CBOR record containing `rpIdHash`, `credentialId`, `devicePublicKey`, and `dpkSignatureValue`. + * Let `$$attStmtType` be the result of generating the appropriate attestation statement, using `authDataForDevicePublicKeyAttestation`, see [[#sctn-generating-an-attestation-object]]. Attestation statement formats define the `fmt` and `attStmt` members of `$$attStmtType`. + In summary, it contains a signature value calculated over the bytes of `authDataForDevicePublicKeyAttestation`, the attestation certificate or public key, and supporting certificates, if any. Note that the details are dependent upon the particular attestation statement format. See : Authenticator extension output :: The [fill-in here as appropriate. NOTE: devicePublicKeyAndSignature NEEDS TO BE UPDATED TO ACCOMODATE CONVEYING ATTESTATION OF THE hardware-bound device key pair. ] From ad71ff141aec2cd1db0b72eb00d1a3546fa21c4f Mon Sep 17 00:00:00 2001 From: JeffH <jdhodges@google.com> Date: Fri, 7 May 2021 10:14:54 -0700 Subject: [PATCH 008/131] devicePublicKey extension section functionally complete --- index.bs | 68 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/index.bs b/index.bs index 86d180a6f..ab3d4ab42 100644 --- a/index.bs +++ b/index.bs @@ -5943,7 +5943,7 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke :: The Boolean value [TRUE] to indicate that this extension is requested by the [=[RP]=]. <xmp class="idl"> partial dictionary AuthenticationExtensionsClientInputs { - boolean devicePublicKey; // Explicitly set this to `true`! + boolean devicePublicKey; // Explicitly set this to \`true\`! }; @@ -5951,10 +5951,10 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke :: If {{AuthenticationExtensionsClientInputs/devicePublicKey}} is [TRUE], then there is no processing, except for creating the authenticator extension input from the client extension input. Otherwise, the platform ignores this extension. : Client extension output -:: An ArrayBuffer containing a CBOR object whose two components are: a byte string containing the [=device public key=] in COSE_Key format, followed by a byte string containing a signature value which is the result of signing the concatenation of the [=hash of the serialized client data=] and the COSE_Key representation of the [=device public key=]. +:: An ArrayBuffer containing the [=device public key attestation object=] returned in the authenticator extension output. partial dictionary AuthenticationExtensionsClientOutputs { - ArrayBuffer devicePublicKeyAndSignature; + ArrayBuffer devicePublicKeyAttestationObject; }; @@ -5970,49 +5970,49 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke : Authenticator extension processing :: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: - * Create or select the [=user credential=] as usual. - * If a [=hardware-bound device key pair=] does not already exist for this [=user credential=] on the [=authenticator=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential public key=], otherwise recall the existing [=device-bound key=]. - * Let |devicePublicKey| be the newly created or existing [=device public key=], in COSE_Key format. - * Let |devicePrivateKey| be the newly created or existing [=device private key=]. - * Let |clientDataHash| be the [=hash of the serialized client data=]. - * Let |dpkSignatureValue| be the result of `sign(devicePrivateKey, (clientDataHash || devicePublicKey))` using the signature algorithm appropriate for the |devicePrivateKey|'s public key algorithm. - * Let |rpIdHash| be the [=user credential=]'s [=rpIdHash]. - * Let |credentialId| be [=user credential=]'s [=credentialId=]. - * Let `authDataForDevicePublicKeyAttestation` be a CBOR record containing `rpIdHash`, `credentialId`, `devicePublicKey`, and `dpkSignatureValue`. - * Let `$$attStmtType` be the result of generating the appropriate attestation statement, using `authDataForDevicePublicKeyAttestation`, see [[#sctn-generating-an-attestation-object]]. Attestation statement formats define the `fmt` and `attStmt` members of `$$attStmtType`. - In summary, it contains a signature value calculated over the bytes of `authDataForDevicePublicKeyAttestation`, the attestation certificate or public key, and supporting certificates, if any. Note that the details are dependent upon the particular attestation statement format. See + 1. Create or select the [=user credential=] as usual. + 1. If a [=hardware-bound device key pair=] does not already exist for this [=user credential=] on the [=authenticator=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing [=device-bound key=]. + 1. Let `devicePublicKey` be the newly created or existing [=device public key=], in COSE_Key format. + 1. Let `devicePrivateKey` be the newly created or existing [=device private key=]. + 1. Let `clientDataHash` be the [=hash of the serialized client data=]. + 1. Let `dpkSignatureValue` be the result of `sign(devicePrivateKey, (clientDataHash || devicePublicKey))` using the signature algorithm appropriate for the `devicePrivateKey`'s public key algorithm. + 1. Let `rpIdHash` be the [=user credential=]'s [=rpIdHash=]. + 1. Let `credentialId` be [=user credential=]'s [=credentialId=]. + 1. Let `AuthDataForDevicePublicKeyAttestation` be a CBOR record containing `rpIdHash`, `credentialId`, `devicePublicKey`, and `dpkSignatureValue`: -: Authenticator extension output -:: The [fill-in here as appropriate. NOTE: devicePublicKeyAndSignature NEEDS TO BE UPDATED TO ACCOMODATE CONVEYING ATTESTATION OF THE hardware-bound device key pair. ] ``` - $$extensionOutput //= ( - devicePublicKey: attObjForDevicePublicKey, - ) - - AuthDataForDevicePublicKeyAttestation = [ ; Note: The element labels are annotations and ; do not appear in an encoded record. rpIdHash: bstr, ; SHA-256 hash of the user credential's RP ID credentialId: bstr, ; CredentialId of the user credential devicePublicKey: bstr, ; COSE_Key format - dpkSignatureValue: bstr, ; sign(devicePrivateKey, (clientDataHash || devicePublicKey)) + dpkSignatureValue: bstr, ; sign(devicePrivateKey, + ; (clientDataHash || devicePublicKey)) ] + ``` + 10. Let `$$attStmtType` be the result of generating the appropriate attestation statement, using `AuthDataForDevicePublicKeyAttestation`, see [[#sctn-generating-an-attestation-object]]. Attestation statement formats define the `fmt` and `attStmt` members of `$$attStmtType`'s values. In summary, it typically contains a signature value calculated over the bytes of `AuthDataForDevicePublicKeyAttestation`, the attestation certificate or public key, and supporting certificates, if any. Note that the details are dependent upon the particular attestation statement format. See [[#sctn-attestation-formats]]. + 1. Let `attObjForDevicePublicKey` be a CBOR object containing a `dpkAuthData` member whose value is the `AuthDataForDevicePublicKeyAttestation` record constructed above, and the attestation statement constructed in the prior step as the value of `$$attStmtType`: + + ``` + attObjForDevicePublicKey = { ; Note: This object conveys an attested + ; device public key and is + ; analogous to \`attObj\`. - attObjForDevicePublicKey = { ; Note: This object conveys an attested device public key - ; and is analogous to `attObj`. - - authDataForDevicePublicKeyAttestation: AuthDataForDevicePublicKeyAttestation, + dpkAuthData: AuthDataForDevicePublicKeyAttestation, - $$attStmtType, ; see section 6.5.4 "Generating an Attestation Object". - ; Attestation statement formats define the `fmt` and `attStmt` members of - ; $$attStmtType. In summary, it contains a signature value calculated over - ; the bytes of authDataForDevicePublicKeyAttestation, the attestation - ; certificate or public key, and supporting certificates, if any. Note - ; that the details are dependent upon the particular attestation statement - ; format. See section 8 "Defined Attestation Statement Formats" - } + $$attStmtType, + } + ``` + +: Authenticator extension output +:: The device public key attestation object constructed above (i.e., `attObjForDevicePublicKey`): + + ``` + $$extensionOutput //= ( + devicePublicKey: attObjForDevicePublicKey, + ) ``` From 094d38589d84a20939d3e15105435ec1a883d863 Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 7 May 2021 11:45:03 -0700 Subject: [PATCH 009/131] further edits... --- index.bs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/index.bs b/index.bs index ab3d4ab42..6ecf6db7b 100644 --- a/index.bs +++ b/index.bs @@ -1092,8 +1092,8 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S : Device Public Key :: A [=hardware-bound device key pair=], also known as a [=device-bound key=], is a [=[RP]=]-specific and [=user credential=]-specific public key pair created upon a [=[RP]=]'s request via the [=devicePublicKey=] [=WebAuthn extension=]. The [=[RP]=] may supply this extension during both [=registration=] and [=authentication=]. - The [=authenticator=] that a [=hardware-bound device key pair=] is created upon guarantees that the [=device private key=] is securely stored in hardware, i.e., it is unextractable. The [=device public key=] is returned only to the [=[RP]=] that created the [=hardware-bound device key pair=]. - Thus a [=user credential=], on an [=authenticator=] supporting the [=devicePublicKey=] extension, may have many associated [=[RP]=]-specific [=hardware-bound device key pairs=]. + The [=authenticator=] that a [=hardware-bound device key pair=] is created upon guarantees that the [=device private key=] is securely stored in hardware, i.e., it is unextractable. The [=device public key=] is returned only to the [=[RP]=] that created the [=hardware-bound device key pair=]. See [[#sctn-device-publickey-extension]]. + : Human Palatability :: An identifier that is [=human palatability|human-palatable=] is intended to be rememberable and reproducible by typical human @@ -5943,7 +5943,7 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke :: The Boolean value [TRUE] to indicate that this extension is requested by the [=[RP]=]. partial dictionary AuthenticationExtensionsClientInputs { - boolean devicePublicKey; // Explicitly set this to \`true\`! + boolean devicePublicKey; // Explicitly set this to \`true\`! }; @@ -5951,10 +5951,10 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke :: If {{AuthenticationExtensionsClientInputs/devicePublicKey}} is [TRUE], then there is no processing, except for creating the authenticator extension input from the client extension input. Otherwise, the platform ignores this extension. : Client extension output -:: An ArrayBuffer containing the [=device public key attestation object=] returned in the authenticator extension output. +:: An ArrayBuffer containing the [=device public key attestation object=] that was returned in the authenticator extension output. partial dictionary AuthenticationExtensionsClientOutputs { - ArrayBuffer devicePublicKeyAttestationObject; + ArrayBuffer devicePublicKeyAttestationObject; }; @@ -5972,36 +5972,37 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke :: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: 1. Create or select the [=user credential=] as usual. 1. If a [=hardware-bound device key pair=] does not already exist for this [=user credential=] on the [=authenticator=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing [=device-bound key=]. - 1. Let `devicePublicKey` be the newly created or existing [=device public key=], in COSE_Key format. + 1. Let `devicePublicKey` be the newly created or existing [=device public key=], in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=] when conveyed in [=attested credential data=]. 1. Let `devicePrivateKey` be the newly created or existing [=device private key=]. 1. Let `clientDataHash` be the [=hash of the serialized client data=]. 1. Let `dpkSignatureValue` be the result of `sign(devicePrivateKey, (clientDataHash || devicePublicKey))` using the signature algorithm appropriate for the `devicePrivateKey`'s public key algorithm. 1. Let `rpIdHash` be the [=user credential=]'s [=rpIdHash=]. 1. Let `credentialId` be [=user credential=]'s [=credentialId=]. - 1. Let `AuthDataForDevicePublicKeyAttestation` be a CBOR record containing `rpIdHash`, `credentialId`, `devicePublicKey`, and `dpkSignatureValue`: + 1. Let `AuthDataForDevicePublicKeyAttestation` be a [=CBOR=] record containing `rpIdHash`, `credentialId`, `devicePublicKey`, and `dpkSignatureValue`. This is expressed in [=CDDL=] as: ``` - AuthDataForDevicePublicKeyAttestation = [ ; Note: The element labels are annotations and - ; do not appear in an encoded record. - + AuthDataForDevicePublicKeyAttestation = [ ; Note: The element labels are annotations + ; and do not appear in an encoded + ; record. + rpIdHash: bstr, ; SHA-256 hash of the user credential's RP ID credentialId: bstr, ; CredentialId of the user credential devicePublicKey: bstr, ; COSE_Key format - dpkSignatureValue: bstr, ; sign(devicePrivateKey, + dpkSignatureValue: bstr, ; sign(devicePrivateKey, ; (clientDataHash || devicePublicKey)) ] ``` - 10. Let `$$attStmtType` be the result of generating the appropriate attestation statement, using `AuthDataForDevicePublicKeyAttestation`, see [[#sctn-generating-an-attestation-object]]. Attestation statement formats define the `fmt` and `attStmt` members of `$$attStmtType`'s values. In summary, it typically contains a signature value calculated over the bytes of `AuthDataForDevicePublicKeyAttestation`, the attestation certificate or public key, and supporting certificates, if any. Note that the details are dependent upon the particular attestation statement format. See [[#sctn-attestation-formats]]. + 10. Let `$$attStmtType` be the result of generating the appropriate attestation statement, using `AuthDataForDevicePublicKeyAttestation`, see [[#sctn-generating-an-attestation-object]]. Attestation statement formats define the `fmt` and `attStmt` members of `$$attStmtType`'s value. In summary, it typically contains a signature value calculated over the bytes of `AuthDataForDevicePublicKeyAttestation`, the attestation certificate or public key, and supporting certificates, if any. Note that the details are dependent upon the particular attestation statement format. See [[#sctn-attestation-formats]]. 1. Let `attObjForDevicePublicKey` be a CBOR object containing a `dpkAuthData` member whose value is the `AuthDataForDevicePublicKeyAttestation` record constructed above, and the attestation statement constructed in the prior step as the value of `$$attStmtType`: ``` attObjForDevicePublicKey = { ; Note: This object conveys an attested ; device public key and is ; analogous to \`attObj\`. - + dpkAuthData: AuthDataForDevicePublicKeyAttestation, - + $$attStmtType, } ``` From e66eb2d8ea502970ec6350e8df0415b134207be9 Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 7 May 2021 11:50:27 -0700 Subject: [PATCH 010/131] cleanup trailing whitespace... --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 6ecf6db7b..0bed8ea62 100644 --- a/index.bs +++ b/index.bs @@ -1094,7 +1094,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S The [=[RP]=] may supply this extension during both [=registration=] and [=authentication=]. The [=authenticator=] that a [=hardware-bound device key pair=] is created upon guarantees that the [=device private key=] is securely stored in hardware, i.e., it is unextractable. The [=device public key=] is returned only to the [=[RP]=] that created the [=hardware-bound device key pair=]. See [[#sctn-device-publickey-extension]]. - + : Human Palatability :: An identifier that is [=human palatability|human-palatable=] is intended to be rememberable and reproducible by typical human users, in contrast to identifiers that are, for example, randomly generated sequences of bits [[EduPersonObjectClassSpec]]. @@ -5992,7 +5992,7 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke ; (clientDataHash || devicePublicKey)) ] ``` - + 10. Let `$$attStmtType` be the result of generating the appropriate attestation statement, using `AuthDataForDevicePublicKeyAttestation`, see [[#sctn-generating-an-attestation-object]]. Attestation statement formats define the `fmt` and `attStmt` members of `$$attStmtType`'s value. In summary, it typically contains a signature value calculated over the bytes of `AuthDataForDevicePublicKeyAttestation`, the attestation certificate or public key, and supporting certificates, if any. Note that the details are dependent upon the particular attestation statement format. See [[#sctn-attestation-formats]]. 1. Let `attObjForDevicePublicKey` be a CBOR object containing a `dpkAuthData` member whose value is the `AuthDataForDevicePublicKeyAttestation` record constructed above, and the attestation statement constructed in the prior step as the value of `$$attStmtType`: From 618b2de767d72e15d623cd15939a0b463f5cfae2 Mon Sep 17 00:00:00 2001 From: JeffH Date: Tue, 8 Jun 2021 17:37:26 -0700 Subject: [PATCH 011/131] Device-bound public key ProVerif model This adds a ProVerif model for the device-bound public key (device-bound key pair) extension. --- device-bound-key-pair.pv | 208 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 device-bound-key-pair.pv diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv new file mode 100644 index 000000000..dc16ac5d6 --- /dev/null +++ b/device-bound-key-pair.pv @@ -0,0 +1,208 @@ +(* ProVerif Web Authentication Formal Model + originally by Iness Ben Guirat and Harry Halpin + https://dl.acm.org/doi/10.1145/3190619.3190640 + github: https://github.com/hhalpin/weauthn-model +*) + +(**** Collected syntax for devicePublicKey aka "dpk" **** + +$$extensionOutput //= ( + devicePublicKey: AttObjForDevicePublicKey, +) + +AuthDataForDevicePublicKeyAttestation = rpIdHash AAGUID userCredentialIdLength userCredentialId devicePublicKey + ; (expressed in RFC5234 ABNF) + + rpIdHash = 32OCTET ; SHA-256 hash of the RP ID the credential is scoped to + AAGUID = 16OCTET ; 16 octets, authenticator's AAGUID + userCredentialIdLength = 2OCTET ; 2 octets, Credential ID length, 16-bit unsigned big-endian integer + userCredentialId = 16*OCTET ; credentialIdLength octets, at least 16 bytes long + devicePublicKey = 1*OCTET ; self-describing variable length, COSE_Key format (CBOR-endoded) +] + +AttObjForDevicePublicKey = { ; Note: This object conveys an attested device public key + ; and is analogous to `attObj`. + + dpkSignatureValue: bstr, ; result of sign(devicePrivateKey, (clientDataHash || userCredentialId)) + + authDataForDevicePublicKeyAttestation: AuthDataForDevicePublicKeyAttestation, + + $$attStmtType, ; see section 6.5.4 "Generating an Attestation Object". + ; Attestation statement formats define the `fmt` and `attStmt` members of + ; $$attStmtType. In summary, it contains a signature value calculated over + ; the bytes of authDataForDevicePublicKeyAttestation, the attestation + ; certificate or public key, and supporting certificates, if any. Note + ; that the details are dependent upon the particular attestation statement + ; format. See section 8 "Defined Attestation Statement Formats" +} + + +Where: + +`clientDataHash` is the hash of the serialized CollectedClientData object for the current registration or authentication operation (expressed in WebIDL): + + dictionary CollectedClientData { + required DOMString type; + required DOMString challenge; + required DOMString origin; // https://html.spec.whatwg.org/#ascii-serialisation-of-an-origin + boolean crossOrigin; + // tokenBinding optionally goes here + } + + +**** WebAuthn Signed Objects Hierarchy **** + +Encompassing Attestation Object (https://www.w3.org/TR/webauthn/#sctn-generating-an-attestation-object) returned by Registration Operation when returning a device-bound public key. This `attObj` attests the user credential, and signs over `AuthDataForRegistrationReturningDevicePublicKey`. The latter object's `extensions` field contains the `devicePublicKey` extension output (see above): + + attObj = { + AuthDataForRegistrationReturningDevicePublicKey: bytes, + $$attStmtType + } + + attStmtTemplate = ( + fmt: text, + attStmt: { * tstr => any } ; Map is filled in by each concrete attStmtType + ) + + ; Every attestation statement format must have the above fields + attStmtTemplate .within $$attStmtType + + +AuthData for Registration and Authentication Operations returning a devicePublicKey extension response (expressed in RFC5234 ABNF; https://www.w3.org/TR/webauthn/#sctn-authenticator-data ): + + AuthDataForRegistrationReturningDevicePublicKey = rpIdHash flags signCount attestedCredentialData extensions + + AuthDataForAuthenticationReturningDevicePublicKey = rpIdHash flags signCount extensions + + ; Where: + + rpIdHash = 32OCTET ; SHA-256 hash of the RP ID the credential is scoped to + flags = OCTET + signCount = 4OCTET ; 32-bit unsigned big-endian integer + + attestedCredentialData = AAGUID CredIDLength CredentialID CredentialPublicKey + + AAGUID = 16OCTET ; 16 octets + CredIDLength = 2OCTET ; 2 octets + CredentialID = 16*OCTET ; CredIDLength octets, at least 16 bytes long + CredentialPublicKey = 1*OCTET ; self-describing variable length, COSE_Key format (CBOR-endoded) + + extensions = 1*OCTET ; variable-length CBOR map of extension outputs + ; `devicePublicKey` extension ouput is in here + + + + + + + + + + + + + + + + + + + + + +****) + +free c:channel. + +type AttestationPublicKey. +type AttestationPrivatekey. +type nonce. +type pkey. +type skey. +type host. +type key. +type RP_id. + + +(*For Digital Signatures *) + +fun spk(skey):pkey. +fun sign(bitstring, skey):bitstring. +reduc forall x:bitstring, y:skey; getmess(sign(x, y)) = x. +reduc forall x:bitstring, y:skey; checksign(sign(x, y), spk(y)) = x. + +(*For nonces*) + +fun nonce_to_bitstring ( nonce ) : bitstring [ data , typeConverter ] . +table nonceTable(nonce) . + +(*For Digital signature over Attestation Credentials*) + +fun spkAtt(AttestationPrivatekey):AttestationPublicKey. +fun signAtt(bitstring, AttestationPrivatekey):bitstring. +reduc forall x:bitstring, y:AttestationPrivatekey; getmessAtt(signAtt(x, y)) = x. +reduc forall x:bitstring, y:AttestationPrivatekey; checksignAtt(signAtt(x, y), spkAtt(y)) = x. + +(*Symetric encryption*) + +fun senc(bitstring, key): bitstring. +reduc forall x: bitstring, k: key; sdec(senc(x,k),k) = x. + + +(*User's ID and Server's ID*) +const cert:bitstring [private]. +const a:RP_id [private]. +const credentialID2:bitstring [private]. + +(*Table*) +table tableAtt(AttestationPublicKey). + +(*Events and Queries*) + +event reachSameKey(AttestationPublicKey). + + +query N1:AttestationPublicKey; event(reachSameKey(N1)). + +free attskU: AttestationPrivatekey[private]. +query attacker(attskU). +free attpkU: AttestationPublicKey[private]. +query attacker(attpkU). + +const k:key. +let processUser ( k: key,cert:bitstring, attskA: AttestationPrivatekey, attpkA:AttestationPublicKey) = + (*Registration*) + in(c,s:bitstring); + let(challengeU:nonce, aU:RP_id)= sdec(s,k) in + new skU:skey; + let pkU = spk(skU) in + + out(c,senc(signAtt((pkU, attpkU, cert,challengeU), attskU),k)) + . + + +let processServer (k: key, a:RP_id) = + + (*Registration*) + new challenge:nonce; + + new attpkU3: AttestationPublicKey; + let ver1=(challenge,a) in + out(c, senc((challenge,a),k)); + + in(c, s:bitstring); + + let m= sdec(s,k) in + let (pkY1: pkey, attpkU1:AttestationPublicKey, cert: bitstring, Nt:nonce) = getmessAtt(m) in + let ver = checksignAtt(m, attpkU1) in + + get tableAtt(= attpkU1) in event reachSameKey(attpkU1) + else insert tableAtt(attpkU1). + +process + new attskU:AttestationPrivatekey; + let attpkU = spkAtt(attskU) in + ( + !processUser( k, cert, attskU,attpkU) | !processServer(k, a) + ) + From 1e97952446f0768de27f5e16c00b03dc9ce697f9 Mon Sep 17 00:00:00 2001 From: JeffH Date: Wed, 9 Jun 2021 13:40:04 -0700 Subject: [PATCH 012/131] who-signs-what musings... --- device-bound-key-pair.pv | 101 +++++++++++++++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 8 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index dc16ac5d6..97b99f6dd 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -52,20 +52,20 @@ Where: **** WebAuthn Signed Objects Hierarchy **** -Encompassing Attestation Object (https://www.w3.org/TR/webauthn/#sctn-generating-an-attestation-object) returned by Registration Operation when returning a device-bound public key. This `attObj` attests the user credential, and signs over `AuthDataForRegistrationReturningDevicePublicKey`. The latter object's `extensions` field contains the `devicePublicKey` extension output (see above): +Encompassing Attestation Object `attObj` (https://www.w3.org/TR/webauthn/#sctn-generating-an-attestation-object) returned by Registration Operation when returning a device-bound public key. This `attObj` attests the user credential, and signs over `AuthDataForRegistrationReturningDevicePublicKey`. The latter object's `extensions` field contains the `devicePublicKey` extension output (see above): - attObj = { + attObj = { AuthDataForRegistrationReturningDevicePublicKey: bytes, $$attStmtType - } + } - attStmtTemplate = ( + attStmtTemplate .within $$attStmtType + + attStmtTemplate = ( ; Every attestation statement format must have these fields: fmt: text, attStmt: { * tstr => any } ; Map is filled in by each concrete attStmtType ) - ; Every attestation statement format must have the above fields - attStmtTemplate .within $$attStmtType AuthData for Registration and Authentication Operations returning a devicePublicKey extension response (expressed in RFC5234 ABNF; https://www.w3.org/TR/webauthn/#sctn-authenticator-data ): @@ -83,25 +83,110 @@ AuthData for Registration and Authentication Operations returning a devicePublic attestedCredentialData = AAGUID CredIDLength CredentialID CredentialPublicKey AAGUID = 16OCTET ; 16 octets - CredIDLength = 2OCTET ; 2 octets + CredIDLength = 2OCTET ; 16-bit unsigned big-endian integer CredentialID = 16*OCTET ; CredIDLength octets, at least 16 bytes long - CredentialPublicKey = 1*OCTET ; self-describing variable length, COSE_Key format (CBOR-endoded) + CredentialPublicKey = 1*OCTET ; "user credential": self-describing variable length, + ; COSE_Key format (CBOR-endoded) extensions = 1*OCTET ; variable-length CBOR map of extension outputs ; `devicePublicKey` extension ouput is in here +EncompassingRegistrationAttestationToBeSigned: { // signed by the attestation private key + + AuthDataForRegistrationReturningDevicePublicKey: { + + rpIdHash + flags + signCount + + attestedCredentialData: { + AAGUID + CredIDLength + CredentialID // user credential ID + CredentialPublicKey // "user credential" + } + + extensions: { + + devicePublicKeyExtensionResult: { + + dpkSignatureValue { // signed by devicePrivateKey + // over... + clientDataHash || userCredentialId // userCredentialId is second because it is variable length + } + + AuthDataForDevicePublicKeyAttestationToBeSigned: { + rpIdHash + AAGUID + userCredentialIdLength + userCredentialId + devicePublicKey + } + + fmt: "...attestation statement format identifier..." + + attStmt: { // contains various items depending upon the attestation statement format, + // crucially containing a signature value, generated using + // the device's AttestationPrivatekey, over this to-be-signed data: + + AuthDataForDevicePublicKeyAttestation || clientDataHash + } + } + + // potentially other extension results... + + } // extensions + + } // AuthDataForRegistrationReturningDevicePublicKey + +} // EncompassingRegistrationAttestationToBeSigned + + + +EncompassingAuthenticationAssertionToBeSigned: { // signed by the user credential private key + + AuthDataForAuthenticationReturningDevicePublicKey: { + + rpIdHash + flags + signCount + + extensions: { + devicePublicKeyExtensionResult: { + dpkSignatureValue { // signed by devicePrivateKey + // over... + clientDataHash || userCredentialId // userCredentialId is second because it is variable length + } + AuthDataForDevicePublicKeyAttestationToBeSigned: { + rpIdHash + AAGUID // where do we get this from? + userCredentialIdLength // " " " " " " + userCredentialId // " " " " " " + devicePublicKey // " " " " " " + } + fmt: "...attestation statement format identifier..." + attStmt: { // contains various items depending upon the attestation statement format, + // crucially containing a signature value, generated using + // the device's AttestationPrivatekey, over this to-be-signed data: + AuthDataForDevicePublicKeyAttestation || clientDataHash + } + } + // potentially other extension results... + } // extensions + } // AuthDataForRegistrationReturningDevicePublicKey +} // EncompassingRegistrationAttestationToBeSigned From 8f0d66dc6ad2256d9f6736a7fd96d6f3f8105dfa Mon Sep 17 00:00:00 2001 From: JeffH Date: Mon, 14 Jun 2021 16:39:49 -0700 Subject: [PATCH 013/131] editorial polishing --- device-bound-key-pair.pv | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index 97b99f6dd..18eff8485 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -164,7 +164,7 @@ EncompassingAuthenticationAssertionToBeSigned: { // signed by the user crede AuthDataForDevicePublicKeyAttestationToBeSigned: { rpIdHash - AAGUID // where do we get this from? + AAGUID // where do we get this from if it is not conveyed in authData? userCredentialIdLength // " " " " " " userCredentialId // " " " " " " devicePublicKey // " " " " " " @@ -236,7 +236,7 @@ reduc forall x: bitstring, k: key; sdec(senc(x,k),k) = x. (*User's ID and Server's ID*) const cert:bitstring [private]. -const a:RP_id [private]. +const a:RP_id [private]. (* server's identity *) const credentialID2:bitstring [private]. (*Table*) @@ -254,10 +254,15 @@ query attacker(attskU). free attpkU: AttestationPublicKey[private]. query attacker(attpkU). -const k:key. +const k:key. (* TLS record layer symmetric encryption key *) + + let processUser ( k: key,cert:bitstring, attskA: AttestationPrivatekey, attpkA:AttestationPublicKey) = + (*Registration*) - in(c,s:bitstring); + + in(c,s:bitstring); (* server sent + let(challengeU:nonce, aU:RP_id)= sdec(s,k) in new skU:skey; let pkU = spk(skU) in @@ -269,11 +274,10 @@ let processUser ( k: key,cert:bitstring, attskA: AttestationPrivatekey, attpkA:A let processServer (k: key, a:RP_id) = (*Registration*) - new challenge:nonce; - new attpkU3: AttestationPublicKey; - let ver1=(challenge,a) in - out(c, senc((challenge,a),k)); + new challenge: nonce; + + out(c, senc( (challenge, a), k) ); (* send challenge & RP_id over TLS *) in(c, s:bitstring); From 43e03c8250598eb4b73372b650a4c19ee7ed19dd Mon Sep 17 00:00:00 2001 From: JeffH Date: Mon, 14 Jun 2021 17:10:16 -0700 Subject: [PATCH 014/131] editorial polishing --- device-bound-key-pair.pv | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index 18eff8485..c325be146 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -1,8 +1,5 @@ -(* ProVerif Web Authentication Formal Model - originally by Iness Ben Guirat and Harry Halpin - https://dl.acm.org/doi/10.1145/3190619.3190640 - github: https://github.com/hhalpin/weauthn-model -*) +(****** for the proverif model, search for "ProVerif Web Authentication Formal Model" below... ******) + (**** Collected syntax for devicePublicKey aka "dpk" **** @@ -50,7 +47,7 @@ Where: } -**** WebAuthn Signed Objects Hierarchy **** +**** WebAuthn Signed Objects Hierarchy: Encompassing Attestation Object `attObj` **** Encompassing Attestation Object `attObj` (https://www.w3.org/TR/webauthn/#sctn-generating-an-attestation-object) returned by Registration Operation when returning a device-bound public key. This `attObj` attests the user credential, and signs over `AuthDataForRegistrationReturningDevicePublicKey`. The latter object's `extensions` field contains the `devicePublicKey` extension output (see above): @@ -64,6 +61,8 @@ Encompassing Attestation Object `attObj` (https://www.w3.org/TR/webauthn/#sctn-g attStmtTemplate = ( ; Every attestation statement format must have these fields: fmt: text, attStmt: { * tstr => any } ; Map is filled in by each concrete attStmtType + + ; in most formats, the attStmt contains a signature value over: authData || clientDataHash ) @@ -93,6 +92,7 @@ AuthData for Registration and Authentication Operations returning a devicePublic + EncompassingRegistrationAttestationToBeSigned: { // signed by the attestation private key AuthDataForRegistrationReturningDevicePublicKey: { @@ -131,7 +131,10 @@ EncompassingRegistrationAttestationToBeSigned: { // signed by the attestatio // crucially containing a signature value, generated using // the device's AttestationPrivatekey, over this to-be-signed data: - AuthDataForDevicePublicKeyAttestation || clientDataHash + AuthDataForDevicePublicKeyAttestation // crucially, this to-be-signed data does not include + // clientDataHash because the latter is specific to these + // current request, e.g., the hashed clientData includes + // the request challenge } } @@ -153,6 +156,8 @@ EncompassingAuthenticationAssertionToBeSigned: { // signed by the user crede flags signCount + // NOTE: there is no `attestedCredentialData` in the authData conveyed in an assertion + extensions: { devicePublicKeyExtensionResult: { @@ -176,7 +181,10 @@ EncompassingAuthenticationAssertionToBeSigned: { // signed by the user crede // crucially containing a signature value, generated using // the device's AttestationPrivatekey, over this to-be-signed data: - AuthDataForDevicePublicKeyAttestation || clientDataHash + AuthDataForDevicePublicKeyAttestation // crucially, this to-be-signed data does not include + // clientDataHash because the latter is specific to these + // current request, e.g., the hashed clientData includes + // the request challenge } } @@ -197,6 +205,13 @@ EncompassingAuthenticationAssertionToBeSigned: { // signed by the user crede ****) +(* ProVerif Web Authentication Formal Model + originally by Iness Ben Guirat and Harry Halpin + https://dl.acm.org/doi/10.1145/3190619.3190640 + github: https://github.com/hhalpin/weauthn-model +*) + + free c:channel. type AttestationPublicKey. From 503a0271378e3237b70cc42853150186fe042501 Mon Sep 17 00:00:00 2001 From: JeffH Date: Tue, 15 Jun 2021 19:16:42 -0700 Subject: [PATCH 015/131] major reorg & clarifications --- device-bound-key-pair.pv | 183 +++++++++++++++++++++++++-------------- 1 file changed, 116 insertions(+), 67 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index c325be146..d8bcebe49 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -1,58 +1,56 @@ -(****** for the proverif model, search for "ProVerif Web Authentication Formal Model" below... ******) +(****** for the proverif model, find "ProVerif Web Authentication Formal Model" below... ******) -(**** Collected syntax for devicePublicKey aka "dpk" **** +(**** Device-bound Public Key | Device-bound key pair | Device key | Context Key **** -$$extensionOutput //= ( - devicePublicKey: AttObjForDevicePublicKey, -) -AuthDataForDevicePublicKeyAttestation = rpIdHash AAGUID userCredentialIdLength userCredentialId devicePublicKey - ; (expressed in RFC5234 ABNF) +==== WebAuthn Authenticator Response Messages, which include: ==== +==== Collected Client Data, Attestation Object, and Authenticator Data Structures ==== - rpIdHash = 32OCTET ; SHA-256 hash of the RP ID the credential is scoped to - AAGUID = 16OCTET ; 16 octets, authenticator's AAGUID - userCredentialIdLength = 2OCTET ; 2 octets, Credential ID length, 16-bit unsigned big-endian integer - userCredentialId = 16*OCTET ; credentialIdLength octets, at least 16 bytes long - devicePublicKey = 1*OCTET ; self-describing variable length, COSE_Key format (CBOR-endoded) -] + // Top-level WebAuthn response messages (in pared-down WebIDL): -AttObjForDevicePublicKey = { ; Note: This object conveys an attested device public key - ; and is analogous to `attObj`. + interface AuthenticatorResponse { + ArrayBuffer clientDataJSON; // serialized client data, included in both .create() and .get() responses + }; - dpkSignatureValue: bstr, ; result of sign(devicePrivateKey, (clientDataHash || userCredentialId)) + interface AuthenticatorAttestationResponse : AuthenticatorResponse { // .create() "Registration" response + ArrayBuffer attestationObject; // `attObj` goes here, it contains the `authData` and other components + [...] + }; - authDataForDevicePublicKeyAttestation: AuthDataForDevicePublicKeyAttestation, + interface AuthenticatorAssertionResponse : AuthenticatorResponse { // .get() "Authentication" response + ArrayBuffer authenticatorData; // `authData` goes here + ArrayBuffer signature; // ENCOMPASSING SIGNATURE VALUE over: authData || clientDataHash + [...] // userHandle + }; - $$attStmtType, ; see section 6.5.4 "Generating an Attestation Object". - ; Attestation statement formats define the `fmt` and `attStmt` members of - ; $$attStmtType. In summary, it contains a signature value calculated over - ; the bytes of authDataForDevicePublicKeyAttestation, the attestation - ; certificate or public key, and supporting certificates, if any. Note - ; that the details are dependent upon the particular attestation statement - ; format. See section 8 "Defined Attestation Statement Formats" -} -Where: + // Authenticator Data (authData) in RFC5234 ABNF : -`clientDataHash` is the hash of the serialized CollectedClientData object for the current registration or authentication operation (expressed in WebIDL): + authData = rpIdHash flags signCount [attestedCredentialData] [extensions] - dictionary CollectedClientData { - required DOMString type; - required DOMString challenge; - required DOMString origin; // https://html.spec.whatwg.org/#ascii-serialisation-of-an-origin - boolean crossOrigin; - // tokenBinding optionally goes here - } + rpIdHash = 32OCTET ; SHA-256 hash of the RP ID the credential is scoped to + flags = OCTET + signCount = 4OCTET ; 32-bit unsigned big-endian integer + + attestedCredentialData = AAGUID CredIDLength CredentialID CredentialPublicKey + + AAGUID = 16OCTET ; 16 octets + CredIDLength = 2OCTET ; 16-bit unsigned big-endian integer + CredentialID = 16*OCTET ; CredIDLength octets, at least 16 bytes long + CredentialPublicKey = 1*OCTET ; "user credential": self-describing variable length, + ; COSE_Key format (CBOR-endoded) + extensions = 1*OCTET ; variable-length CBOR map of extension outputs + ; `devicePublicKey` extension ouput is in here. -**** WebAuthn Signed Objects Hierarchy: Encompassing Attestation Object `attObj` **** -Encompassing Attestation Object `attObj` (https://www.w3.org/TR/webauthn/#sctn-generating-an-attestation-object) returned by Registration Operation when returning a device-bound public key. This `attObj` attests the user credential, and signs over `AuthDataForRegistrationReturningDevicePublicKey`. The latter object's `extensions` field contains the `devicePublicKey` extension output (see above): + + // Attestation Object (attObj) in CDDL attObj = { - AuthDataForRegistrationReturningDevicePublicKey: bytes, + authData: bytes, $$attStmtType } @@ -66,29 +64,80 @@ Encompassing Attestation Object `attObj` (https://www.w3.org/TR/webauthn/#sctn-g ) + // `clientDataHash` is the hash of the serialized CollectedClientData object for the current registration + // or authentication operation (expressed in WebIDL): + + dictionary CollectedClientData { + required DOMString type; // "webauthn.create" or "webauthn.get", as appropriate + required DOMString challenge; // from RP's server + required DOMString origin; // https://html.spec.whatwg.org/#ascii-serialisation-of-an-origin + boolean crossOrigin; + // tokenBinding optionally goes here + } + + + +==== Proposed Collected syntax for devicePublicKey (dpk) Extension Output ==== + + $$extensionOutput //= ( + devicePublicKey: AttObjForDevicePublicKey, + ) + + AttObjForDevicePublicKey = { ; Note: This object conveys an attested device public key + ; and is analogous to `attObj`. + + dpkSignatureValue: bstr, ; result of sign(devicePrivateKey, (clientDataHash || userCredentialId)) + + authDataForDevicePublicKeyAttestation: AuthDataForDevicePublicKeyAttestation, + + $$attStmtType, ; see . + ; + ; Attestation statement formats define the `fmt` and `attStmt` members of + ; $$attStmtType. In summary, it contains a signature value calculated over + ; the bytes of _authDataForDevicePublicKeyAttestation_, the attestation + ; certificate or public key, and supporting certificates, if any. Note + ; that the details are dependent upon the particular attestation statement + ; format. See . + } + + + AuthDataForDevicePublicKeyAttestation = rpIdHash AAGUID userCredentialIdLength userCredentialId devicePublicKey + ; (expressed in RFC5234 ABNF) + + rpIdHash = 32OCTET ; SHA-256 hash of the RP ID the credential is scoped to + AAGUID = 16OCTET ; 16 octets, authenticator's AAGUID + userCredentialIdLength = 2OCTET ; 2 octets, Credential ID length, 16-bit unsigned big-endian integer + userCredentialId = 16*OCTET ; credentialIdLength octets, at least 16 bytes long + devicePublicKey = 1*OCTET ; self-describing variable length, COSE_Key format (CBOR-endoded) + ] -AuthData for Registration and Authentication Operations returning a devicePublicKey extension response (expressed in RFC5234 ABNF; https://www.w3.org/TR/webauthn/#sctn-authenticator-data ): - AuthDataForRegistrationReturningDevicePublicKey = rpIdHash flags signCount attestedCredentialData extensions - AuthDataForAuthenticationReturningDevicePublicKey = rpIdHash flags signCount extensions +==== WebAuthn Signed Objects Hierarchy: Encompassing Attestation Object `attObj` ==== - ; Where: + AuthData for Registration and Authentication Operations returning a devicePublicKey extension response (expressed in RFC5234 ABNF; https://www.w3.org/TR/webauthn/#sctn-authenticator-data ): - rpIdHash = 32OCTET ; SHA-256 hash of the RP ID the credential is scoped to - flags = OCTET - signCount = 4OCTET ; 32-bit unsigned big-endian integer + ; See WebAuthn-standard `authData` (above) for details, though NOTE that in these two authData instantiations + ; `attestedCredentialData` and `extensions` components are always included (the AttObjForDevicePublicKey is conveyed + ; in the `extensions` component): + + AuthDataForRegistrationReturningDevicePublicKey = rpIdHash flags signCount attestedCredentialData extensions + + AuthDataForAuthenticationReturningDevicePublicKey = rpIdHash flags signCount extensions + + + Registration: + Encompassing Attestation Object `attObj` (https://www.w3.org/TR/webauthn/#sctn-generating-an-attestation-object) is returned by the Registration Operation when returning a device-bound public key. This `attObj` attests the user credential, and signs over `AuthDataForRegistrationReturningDevicePublicKey`. The latter object's `extensions` field contains the `devicePublicKey` extension output (see above): + + attObj = { + AuthDataForRegistrationReturningDevicePublicKey: bytes, + $$attStmtType ; as defined in WebAuthn + } + + Authentication: - attestedCredentialData = AAGUID CredIDLength CredentialID CredentialPublicKey - AAGUID = 16OCTET ; 16 octets - CredIDLength = 2OCTET ; 16-bit unsigned big-endian integer - CredentialID = 16*OCTET ; CredIDLength octets, at least 16 bytes long - CredentialPublicKey = 1*OCTET ; "user credential": self-describing variable length, - ; COSE_Key format (CBOR-endoded) - extensions = 1*OCTET ; variable-length CBOR map of extension outputs - ; `devicePublicKey` extension ouput is in here @@ -162,29 +211,29 @@ EncompassingAuthenticationAssertionToBeSigned: { // signed by the user crede devicePublicKeyExtensionResult: { - dpkSignatureValue { // signed by devicePrivateKey - // over... + dpkSignatureValue { // signed by devicePrivateKey.. + // ..over... clientDataHash || userCredentialId // userCredentialId is second because it is variable length } AuthDataForDevicePublicKeyAttestationToBeSigned: { rpIdHash - AAGUID // where do we get this from if it is not conveyed in authData? - userCredentialIdLength // " " " " " " - userCredentialId // " " " " " " - devicePublicKey // " " " " " " + AAGUID // where do we get this from if it is not conveyed in authData? Thus must be here. + userCredentialIdLength // " " " " " " " " " " " " " " " " " + userCredentialId // " " " " " " " " " " " " " " " " " + devicePublicKey // " " " " " " " " " " " " " " " " " } fmt: "...attestation statement format identifier..." attStmt: { // contains various items depending upon the attestation statement format, - // crucially containing a signature value, generated using - // the device's AttestationPrivatekey, over this to-be-signed data: - - AuthDataForDevicePublicKeyAttestation // crucially, this to-be-signed data does not include - // clientDataHash because the latter is specific to these - // current request, e.g., the hashed clientData includes - // the request challenge + // crucially containing a signature value, generated using the device's + // AttestationPrivatekey, over this to-be-signed data: + AuthDataForDevicePublicKeyAttestation + // crucially, this to-be-signed data does not include + // `clientDataHash` because the latter is specific to this + // current request, e.g., the hashed clientData includes + // the request challenge. } } @@ -192,9 +241,9 @@ EncompassingAuthenticationAssertionToBeSigned: { // signed by the user crede } // extensions - } // AuthDataForRegistrationReturningDevicePublicKey + } // AuthDataForAuthenticationReturningDevicePublicKey -} // EncompassingRegistrationAttestationToBeSigned +} // EncompassingAuthenticationAssertionToBeSigned From 87340d797855e780b3d1296e74529f284b57b5e0 Mon Sep 17 00:00:00 2001 From: JeffH Date: Thu, 17 Jun 2021 15:19:44 -0700 Subject: [PATCH 016/131] further reorg & polish --- device-bound-key-pair.pv | 221 +++++++++++++++++++++------------------ 1 file changed, 121 insertions(+), 100 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index d8bcebe49..fb391f52c 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -1,16 +1,18 @@ (****** for the proverif model, find "ProVerif Web Authentication Formal Model" below... ******) -(**** Device-bound Public Key | Device-bound key pair | Device key | Context Key **** +(**** Device-bound Public Key | Device-bound Key Pair | Device key | Context Key | Secondary Key **** ==== WebAuthn Authenticator Response Messages, which include: ==== -==== Collected Client Data, Attestation Object, and Authenticator Data Structures ==== +==== Collected Client Data, Attestation Object, Authenticator Data structures, ==== +==== and (nested) signature values calculated over portions of them. ==== - // Top-level WebAuthn response messages (in pared-down WebIDL): + // Top-level WebAuthn response messages (expressed in simplified WebIDL): interface AuthenticatorResponse { - ArrayBuffer clientDataJSON; // serialized client data, included in both .create() and .get() responses + ArrayBuffer clientDataJSON; // serialized collected client data, included + // in both .create() and .get() responses }; interface AuthenticatorAttestationResponse : AuthenticatorResponse { // .create() "Registration" response @@ -21,6 +23,7 @@ interface AuthenticatorAssertionResponse : AuthenticatorResponse { // .get() "Authentication" response ArrayBuffer authenticatorData; // `authData` goes here ArrayBuffer signature; // ENCOMPASSING SIGNATURE VALUE over: authData || clientDataHash + // Note that the signed-over authData conveys extension results [...] // userHandle }; @@ -77,9 +80,9 @@ -==== Proposed Collected syntax for devicePublicKey (dpk) Extension Output ==== +==== Proposed Collected Syntax for devicePublicKey (dpk) aka Secondary Key aka Device Key Extension Output ==== - $$extensionOutput //= ( + $$extensionOutput //= ( // Expressed in CDDL devicePublicKey: AttObjForDevicePublicKey, ) @@ -88,13 +91,15 @@ dpkSignatureValue: bstr, ; result of sign(devicePrivateKey, (clientDataHash || userCredentialId)) - authDataForDevicePublicKeyAttestation: AuthDataForDevicePublicKeyAttestation, + authDataForDevicePublicKeyAttestation: bstr, ; AuthDataForDevicePublicKeyAttestation goes here $$attStmtType, ; see . ; ; Attestation statement formats define the `fmt` and `attStmt` members of - ; $$attStmtType. In summary, it contains a signature value calculated over - ; the bytes of _authDataForDevicePublicKeyAttestation_, the attestation + ; $$attStmtType. + ; + ; In summary, it contains a signature value calculated over + ; the bytes of `authDataForDevicePublicKeyAttestation`, the attestation ; certificate or public key, and supporting certificates, if any. Note ; that the details are dependent upon the particular attestation statement ; format. See . @@ -102,24 +107,24 @@ AuthDataForDevicePublicKeyAttestation = rpIdHash AAGUID userCredentialIdLength userCredentialId devicePublicKey - ; (expressed in RFC5234 ABNF) + ; Expressed in RFC5234 ABNF rpIdHash = 32OCTET ; SHA-256 hash of the RP ID the credential is scoped to AAGUID = 16OCTET ; 16 octets, authenticator's AAGUID userCredentialIdLength = 2OCTET ; 2 octets, Credential ID length, 16-bit unsigned big-endian integer userCredentialId = 16*OCTET ; credentialIdLength octets, at least 16 bytes long devicePublicKey = 1*OCTET ; self-describing variable length, COSE_Key format (CBOR-endoded) - ] -==== WebAuthn Signed Objects Hierarchy: Encompassing Attestation Object `attObj` ==== + +==== WebAuthn Signed Objects Hierarchy for Device-bound Key Pair aka Device-bound Public Key aka Secondary Key aka Device Key ==== AuthData for Registration and Authentication Operations returning a devicePublicKey extension response (expressed in RFC5234 ABNF; https://www.w3.org/TR/webauthn/#sctn-authenticator-data ): - ; See WebAuthn-standard `authData` (above) for details, though NOTE that in these two authData instantiations - ; `attestedCredentialData` and `extensions` components are always included (the AttObjForDevicePublicKey is conveyed - ; in the `extensions` component): + ; See WebAuthn-standard `authData` (above) for details, though NOTE that in these two `authData` instantiations + ; the `extensions` component is ALWAYS included because the `AttObjForDevicePublicKey` is always conveyed + ; in the `extensions` component: AuthDataForRegistrationReturningDevicePublicKey = rpIdHash flags signCount attestedCredentialData extensions @@ -127,128 +132,144 @@ Registration: - Encompassing Attestation Object `attObj` (https://www.w3.org/TR/webauthn/#sctn-generating-an-attestation-object) is returned by the Registration Operation when returning a device-bound public key. This `attObj` attests the user credential, and signs over `AuthDataForRegistrationReturningDevicePublicKey`. The latter object's `extensions` field contains the `devicePublicKey` extension output (see above): - - attObj = { - AuthDataForRegistrationReturningDevicePublicKey: bytes, - $$attStmtType ; as defined in WebAuthn - } - - Authentication: - - - + Encompassing Attestation Object `attObj` is returned by the Registration Operation (in `AuthenticatorAttestationResponse.attestationObject`). This `attObj` attests the user credential, and signs over `AuthDataForRegistrationReturningDevicePublicKey`. The latter object's `extensions` field contains the `devicePublicKey` extension output (see above): + AuthenticatorAttestationResponse: { + clientDataJSON + attestationObject + } + attObj = { + AuthDataForRegistrationReturningDevicePublicKey: bytes, ; devicePublicKey extension result is here + $$attStmtType ; as defined in WebAuthn, contains ENCOMPASSING SIGNATURE VALUE over: + ; + } -EncompassingRegistrationAttestationToBeSigned: { // signed by the attestation private key - AuthDataForRegistrationReturningDevicePublicKey: { + Authentication: - rpIdHash - flags - signCount + Encompassing Authentication Assertion is returned by the Authentication Operation (as `AuthenticatorAssertionResponse`). The "assertion" is the `signature` value over the concatenation of the `AuthDataForAuthenticationReturningDevicePublicKey` and the hash of the collected client data containing the RP's challenge. `AuthDataForAuthenticationReturningDevicePublicKey`'s `extensions` field contains the `devicePublicKey` extension output: - attestedCredentialData: { - AAGUID - CredIDLength - CredentialID // user credential ID - CredentialPublicKey // "user credential" + AuthenticatorAssertionResponse: { + clientDataJSON + AuthDataForAuthenticationReturningDevicePublicKey + signature // ENCOMPASSING SIGNATURE VALUE over: + // AuthDataForAuthenticationReturningDevicePublicKey || clientDataHash } - extensions: { - devicePublicKeyExtensionResult: { - dpkSignatureValue { // signed by devicePrivateKey - // over... - clientDataHash || userCredentialId // userCredentialId is second because it is variable length - } - AuthDataForDevicePublicKeyAttestationToBeSigned: { - rpIdHash - AAGUID - userCredentialIdLength - userCredentialId - devicePublicKey - } + EncompassingRegistrationAttestationToBeSigned: { // SIGNED by the attestation private key - fmt: "...attestation statement format identifier..." + AuthDataForRegistrationReturningDevicePublicKey: { - attStmt: { // contains various items depending upon the attestation statement format, - // crucially containing a signature value, generated using - // the device's AttestationPrivatekey, over this to-be-signed data: + rpIdHash + flags + signCount - AuthDataForDevicePublicKeyAttestation // crucially, this to-be-signed data does not include - // clientDataHash because the latter is specific to these - // current request, e.g., the hashed clientData includes - // the request challenge - } + attestedCredentialData: { + AAGUID + CredIDLength + CredentialID // user credential ID + CredentialPublicKey // "user credential" } - // potentially other extension results... - - } // extensions - - } // AuthDataForRegistrationReturningDevicePublicKey - -} // EncompassingRegistrationAttestationToBeSigned - + extensions: { + + devicePublicKeyExtensionResult: { + + dpkSignatureValue { // SIGNED by devicePrivateKey. Is a UNIQUE value per response. + // over... + clientDataHash || userCredentialId // userCredentialId is second because it is variable length + } + + AuthDataForDevicePublicKeyAttestationToBeSigned: { + rpIdHash + AAGUID + userCredentialIdLength + userCredentialId + devicePublicKey + } + + fmt: "...attestation statement format identifier..." + + attStmt: { // contains various items depending upon the attestation statement format, + // crucially containing a signature value, generated using + // the device's AttestationPrivatekey, over this to-be-signed data: + + AuthDataForDevicePublicKeyAttestation // crucially, this to-be-signed data does not include + // clientDataHash because the latter is specific to these + // current request, e.g., the hashed clientData includes + // the request challenge. + // + // The `attStmt` is a CONSTANT value per context per device + // per account per RP. + } + } + // potentially other extension results... -EncompassingAuthenticationAssertionToBeSigned: { // signed by the user credential private key + } // extensions - AuthDataForAuthenticationReturningDevicePublicKey: { + } // AuthDataForRegistrationReturningDevicePublicKey - rpIdHash - flags - signCount + } // EncompassingRegistrationAttestationToBeSigned - // NOTE: there is no `attestedCredentialData` in the authData conveyed in an assertion - extensions: { - devicePublicKeyExtensionResult: { + EncompassingAuthenticationAssertionToBeSigned: { // SIGNED by the user credential private key - dpkSignatureValue { // signed by devicePrivateKey.. - // ..over... - clientDataHash || userCredentialId // userCredentialId is second because it is variable length - } + AuthDataForAuthenticationReturningDevicePublicKey: { - AuthDataForDevicePublicKeyAttestationToBeSigned: { - rpIdHash - AAGUID // where do we get this from if it is not conveyed in authData? Thus must be here. - userCredentialIdLength // " " " " " " " " " " " " " " " " " - userCredentialId // " " " " " " " " " " " " " " " " " - devicePublicKey // " " " " " " " " " " " " " " " " " - } + rpIdHash + flags + signCount - fmt: "...attestation statement format identifier..." + // NOTE: there is no `attestedCredentialData` in the authData conveyed in an assertion - attStmt: { // contains various items depending upon the attestation statement format, - // crucially containing a signature value, generated using the device's - // AttestationPrivatekey, over this to-be-signed data: - AuthDataForDevicePublicKeyAttestation - // crucially, this to-be-signed data does not include - // `clientDataHash` because the latter is specific to this - // current request, e.g., the hashed clientData includes - // the request challenge. - } - } + extensions: { - // potentially other extension results... + devicePublicKeyExtensionResult: { - } // extensions + dpkSignatureValue { // SIGNED by devicePrivateKey.. + // ..over... + clientDataHash || userCredentialId // userCredentialId is second because it is variable length + } - } // AuthDataForAuthenticationReturningDevicePublicKey + AuthDataForDevicePublicKeyAttestationToBeSigned: { + rpIdHash // i.e., the "audience" + AAGUID // where do we get this from if it is not conveyed in authData? Thus must be here. + userCredentialIdLength // " " " " " " " " " " " " " " " " " + userCredentialId // " " " " " " " " " " " " " " " " " + devicePublicKey // " " " " " " " " " " " " " " " " " + } -} // EncompassingAuthenticationAssertionToBeSigned + fmt: "...attestation statement format identifier..." + attStmt: { // contains various items depending upon the attestation statement format, + // crucially containing a signature value, generated using the device's + // AttestationPrivatekey, over this to-be-signed data: + AuthDataForDevicePublicKeyAttestation + // + // Crucially, this to-be-signed data DOES NOT INCLUDE + // `clientDataHash` because the latter is specific to this + // current request, e.g., the hashed clientData includes + // the request challenge. + // + // NOTE: The `attStmt` is a CONSTANT value per context per device + // per account per RP. + } + } + // potentially other extension results... + } // extensions + } // AuthDataForAuthenticationReturningDevicePublicKey + } // EncompassingAuthenticationAssertionToBeSigned From cbb066f1faa4f278b04583e80780f70b3278ffcd Mon Sep 17 00:00:00 2001 From: JeffH Date: Thu, 17 Jun 2021 19:02:50 -0700 Subject: [PATCH 017/131] proverif model cleanup --- device-bound-key-pair.pv | 77 +++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index fb391f52c..d4286016b 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -281,79 +281,82 @@ github: https://github.com/hhalpin/weauthn-model *) - -free c:channel. - type AttestationPublicKey. type AttestationPrivatekey. type nonce. type pkey. type skey. -type host. type key. type RP_id. +(* TLS channel *) + +free c: channel. (* TLS channel *) + +const k: key. (* TLS record layer symmetric encryption key. + This model assumes an error-free TLS connection establishment + resulting in this key |k| as the encryption key. *) + -(*For Digital Signatures *) +(* For Digital Signatures *) -fun spk(skey):pkey. -fun sign(bitstring, skey):bitstring. +fun spk(skey): pkey. +fun sign(bitstring, skey): bitstring. reduc forall x:bitstring, y:skey; getmess(sign(x, y)) = x. reduc forall x:bitstring, y:skey; checksign(sign(x, y), spk(y)) = x. -(*For nonces*) +(* For nonces *) fun nonce_to_bitstring ( nonce ) : bitstring [ data , typeConverter ] . table nonceTable(nonce) . -(*For Digital signature over Attestation Credentials*) +(* For Digital signature over Attestation Credentials *) fun spkAtt(AttestationPrivatekey):AttestationPublicKey. fun signAtt(bitstring, AttestationPrivatekey):bitstring. reduc forall x:bitstring, y:AttestationPrivatekey; getmessAtt(signAtt(x, y)) = x. reduc forall x:bitstring, y:AttestationPrivatekey; checksignAtt(signAtt(x, y), spkAtt(y)) = x. -(*Symetric encryption*) +(* Symetric encryption *) fun senc(bitstring, key): bitstring. reduc forall x: bitstring, k: key; sdec(senc(x,k),k) = x. -(*User's ID and Server's ID*) +(* User's ID and Server's ID *) const cert:bitstring [private]. -const a:RP_id [private]. (* server's identity *) -const credentialID2:bitstring [private]. +const a: RP_id [private]. (* server's identity *) -(*Table*) +(* Table *) table tableAtt(AttestationPublicKey). -(*Events and Queries*) +(* Events and Queries *) event reachSameKey(AttestationPublicKey). - query N1:AttestationPublicKey; event(reachSameKey(N1)). -free attskU: AttestationPrivatekey[private]. +free attskU: AttestationPrivatekey [private]. query attacker(attskU). -free attpkU: AttestationPublicKey[private]. -query attacker(attpkU). -const k:key. (* TLS record layer symmetric encryption key *) +free attpkU: AttestationPublicKey [private]. +query attacker(attpkU). -let processUser ( k: key,cert:bitstring, attskA: AttestationPrivatekey, attpkA:AttestationPublicKey) = +let processUser (k: key, cert: bitstring, attskA: AttestationPrivatekey, attpkA: AttestationPublicKey) = (*Registration*) - in(c,s:bitstring); (* server sent + in(c, s: bitstring); (* we expect server to send challenge & RP_id over encrypted TLS channel *) + + let (challengeU: nonce, aU: RP_id) = sdec(s, k) in - let(challengeU:nonce, aU:RP_id)= sdec(s,k) in - new skU:skey; + new skU: skey; (* user's authenticator mints new user credential key pair *) let pkU = spk(skU) in - out(c,senc(signAtt((pkU, attpkU, cert,challengeU), attskU),k)) - . + out(c, senc(signAtt((pkU, attpkU, cert, challengeU), attskU), k)). (* return signed registration response + containing attestation object, over + encrypted TLS channel. *) let processServer (k: key, a:RP_id) = @@ -362,21 +365,23 @@ let processServer (k: key, a:RP_id) = new challenge: nonce; - out(c, senc( (challenge, a), k) ); (* send challenge & RP_id over TLS *) + out(c, senc((challenge, a), k)); (* Reg request: send challenge & RP_id over TLS *) - in(c, s:bitstring); + in(c, s:bitstring); (* Registration response *) - let m= sdec(s,k) in - let (pkY1: pkey, attpkU1:AttestationPublicKey, cert: bitstring, Nt:nonce) = getmessAtt(m) in - let ver = checksignAtt(m, attpkU1) in + let m = sdec(s, k) in + let (pkY1: pkey, attpkU1: AttestationPublicKey, cert: bitstring, Nt: nonce) = getmessAtt(m) in + let ver = checksignAtt(m, attpkU1) in (* This verifies the attestation signature because `getmess()` does not. *) - get tableAtt(= attpkU1) in event reachSameKey(attpkU1) - else insert tableAtt(attpkU1). + get tableAtt(=attpkU1) in (* Have we seen this registration before? *) + event reachSameKey(attpkU1) (* Yes. *) + else + insert tableAtt(attpkU1). (* No, remember it. *) process - new attskU:AttestationPrivatekey; - let attpkU = spkAtt(attskU) in + new attskU: AttestationPrivatekey; (* The attestation key pair is a immutable property of *) + let attpkU = spkAtt(attskU) in (* the authenticator, so we mint it here at the beginning. *) ( - !processUser( k, cert, attskU,attpkU) | !processServer(k, a) + !processUser(k, cert, attskU, attpkU) | !processServer(k, a) ) From 1e72a00a870cff78f50bf74b49cdf3ca6adb1af7 Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 18 Jun 2021 14:48:17 -0700 Subject: [PATCH 018/131] remove unused 'cert' --- device-bound-key-pair.pv | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index d4286016b..e1ada22c9 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -323,8 +323,7 @@ fun senc(bitstring, key): bitstring. reduc forall x: bitstring, k: key; sdec(senc(x,k),k) = x. -(* User's ID and Server's ID *) -const cert:bitstring [private]. +(* Server's ID *) const a: RP_id [private]. (* server's identity *) (* Table *) @@ -343,7 +342,7 @@ free attpkU: AttestationPublicKey [private]. query attacker(attpkU). -let processUser (k: key, cert: bitstring, attskA: AttestationPrivatekey, attpkA: AttestationPublicKey) = +let processUser (k: key, attskA: AttestationPrivatekey, attpkA: AttestationPublicKey) = (*Registration*) @@ -354,7 +353,7 @@ let processUser (k: key, cert: bitstring, attskA: AttestationPrivatekey, attpkA: new skU: skey; (* user's authenticator mints new user credential key pair *) let pkU = spk(skU) in - out(c, senc(signAtt((pkU, attpkU, cert, challengeU), attskU), k)). (* return signed registration response + out(c, senc(signAtt((pkU, attpkU, challengeU), attskU), k)). (* return signed registration response containing attestation object, over encrypted TLS channel. *) @@ -370,11 +369,13 @@ let processServer (k: key, a:RP_id) = in(c, s:bitstring); (* Registration response *) let m = sdec(s, k) in - let (pkY1: pkey, attpkU1: AttestationPublicKey, cert: bitstring, Nt: nonce) = getmessAtt(m) in + let (pkY1: pkey, attpkU1: AttestationPublicKey, Nt: nonce) = getmessAtt(m) in let ver = checksignAtt(m, attpkU1) in (* This verifies the attestation signature because `getmess()` does not. *) get tableAtt(=attpkU1) in (* Have we seen this registration before? *) - event reachSameKey(attpkU1) (* Yes. *) + event reachSameKey(attpkU1) (* Yes, thus the user is re-identified, *) + (* modulo the authenticator batch size *) + (* having the same attestation key pair. *) else insert tableAtt(attpkU1). (* No, remember it. *) @@ -382,6 +383,6 @@ process new attskU: AttestationPrivatekey; (* The attestation key pair is a immutable property of *) let attpkU = spkAtt(attskU) in (* the authenticator, so we mint it here at the beginning. *) ( - !processUser(k, cert, attskU, attpkU) | !processServer(k, a) + !processUser(k, attskU, attpkU) | !processServer(k, a) ) From 99a6b79f90fb92015dea310023c4afea8693ee85 Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 18 Jun 2021 19:16:39 -0700 Subject: [PATCH 019/131] revise/correct objects hierarchy --- device-bound-key-pair.pv | 120 ++++++++++++++++++++++++++++----------- 1 file changed, 87 insertions(+), 33 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index e1ada22c9..11f1154f1 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -10,6 +10,12 @@ // Top-level WebAuthn response messages (expressed in simplified WebIDL): + interface PublicKeyCredential : Credential { + USVString id; // User Credential ID + AuthenticatorResponse response; + [...] + }; + interface AuthenticatorResponse { ArrayBuffer clientDataJSON; // serialized collected client data, included // in both .create() and .get() responses @@ -29,6 +35,24 @@ + Coalesced Registration PublicKeyCredential: { + USVString id; // User Credential ID + ArrayBuffer clientDataJSON; + ArrayBuffer attestationObject; // `attObj` goes here, it contains the `authData` and other component + } + + + Coalesced Authentication PublicKeyCredential: { + USVString id; // User Credential ID + ArrayBuffer clientDataJSON; + ArrayBuffer authenticatorData; // `authData` goes here + ArrayBuffer signature; // ENCOMPASSING SIGNATURE VALUE over: authData || clientDataHash + // Note that the signed-over authData conveys extension results + [...] + } + + + // Authenticator Data (authData) in RFC5234 ABNF : authData = rpIdHash flags signCount [attestedCredentialData] [extensions] @@ -80,7 +104,7 @@ -==== Proposed Collected Syntax for devicePublicKey (dpk) aka Secondary Key aka Device Key Extension Output ==== +==== Proposed Collected Syntax for devicePublicKey (dpk) (aka Secondary Key aka Device Key) Extension Output ==== $$extensionOutput //= ( // Expressed in CDDL devicePublicKey: AttObjForDevicePublicKey, @@ -90,6 +114,8 @@ ; and is analogous to `attObj`. dpkSignatureValue: bstr, ; result of sign(devicePrivateKey, (clientDataHash || userCredentialId)) + ; Note that this sig value is unique per-request because clientDataHash contains + ; the per-request challenge. authDataForDevicePublicKeyAttestation: bstr, ; AuthDataForDevicePublicKeyAttestation goes here @@ -98,16 +124,16 @@ ; Attestation statement formats define the `fmt` and `attStmt` members of ; $$attStmtType. ; - ; In summary, it contains a signature value calculated over - ; the bytes of `authDataForDevicePublicKeyAttestation`, the attestation + ; In summary, it contains (1) a signature value calculated (using the attestation private key) + ; over just the bytes of `authDataForDevicePublicKeyAttestation`, and (2) the attestation ; certificate or public key, and supporting certificates, if any. Note - ; that the details are dependent upon the particular attestation statement + ; that there are details dependent upon the particular attestation statement ; format. See . } AuthDataForDevicePublicKeyAttestation = rpIdHash AAGUID userCredentialIdLength userCredentialId devicePublicKey - ; Expressed in RFC5234 ABNF + ; Expressed in RFC5234 ABNF: rpIdHash = 32OCTET ; SHA-256 hash of the RP ID the credential is scoped to AAGUID = 16OCTET ; 16 octets, authenticator's AAGUID @@ -151,7 +177,7 @@ Encompassing Authentication Assertion is returned by the Authentication Operation (as `AuthenticatorAssertionResponse`). The "assertion" is the `signature` value over the concatenation of the `AuthDataForAuthenticationReturningDevicePublicKey` and the hash of the collected client data containing the RP's challenge. `AuthDataForAuthenticationReturningDevicePublicKey`'s `extensions` field contains the `devicePublicKey` extension output: - AuthenticatorAssertionResponse: { + AuthenticatorAssertionResponse: { // expressed in bespoke informal notation clientDataJSON AuthDataForAuthenticationReturningDevicePublicKey signature // ENCOMPASSING SIGNATURE VALUE over: @@ -159,9 +185,10 @@ } + ==== To-Be-Signed Data for the ENCOMPASSING SIGNATURE VALUEs (expressed in bespoke informal notation): - - EncompassingRegistrationAttestationToBeSigned: { // SIGNED by the attestation private key + EncompassingRegistrationAttestationToBeSigned: { // SIGNED by the attestation private key to form the signature + // value returned in the top-level attestation statement. AuthDataForRegistrationReturningDevicePublicKey: { @@ -180,32 +207,33 @@ devicePublicKeyExtensionResult: { - dpkSignatureValue { // SIGNED by devicePrivateKey. Is a UNIQUE value per response. - // over... - clientDataHash || userCredentialId // userCredentialId is second because it is variable length + dpkSignatureValue { // Is a UNIQUE value per response. + sign(devicePrivateKey, (clientDataHash || userCredentialId)) } - AuthDataForDevicePublicKeyAttestationToBeSigned: { - rpIdHash - AAGUID - userCredentialIdLength - userCredentialId - devicePublicKey + AuthDataForDevicePublicKeyAttestation: { + rpIdHash // i.e., the "audience" + AAGUID // This, and the next 3 data items, duplicate what's in + userCredentialIdLength // `attestedCredentialData`, but attested cred data is not present in + userCredentialId // authentication assertions, so it needs to be here IF we wish this + devicePublicKey // devicePublicKey extension result to be the same format whether its + // returned as result of a registration or authn request. } fmt: "...attestation statement format identifier..." attStmt: { // contains various items depending upon the attestation statement format, - // crucially containing a signature value, generated using - // the device's AttestationPrivatekey, over this to-be-signed data: + // Crucially containing a signature value, generated using + // the device's (effective) AttestationPrivatekey, over this to-be-signed data: - AuthDataForDevicePublicKeyAttestation // crucially, this to-be-signed data does not include + AuthDataForDevicePublicKeyAttestation // SIGNED by AttestationPrivatekey. + // Crucially, this to-be-signed data does not include // clientDataHash because the latter is specific to these // current request, e.g., the hashed clientData includes // the request challenge. // - // The `attStmt` is a CONSTANT value per context per device - // per account per RP. + // Note: This `attStmt` is a CONSTANT value per context + // per device per account per RP. } } @@ -215,11 +243,19 @@ } // AuthDataForRegistrationReturningDevicePublicKey + ClientDataHash: { // the following items are serialized then hashed to form this value: + type + challenge + origin + crossOrigin + } + } // EncompassingRegistrationAttestationToBeSigned - EncompassingAuthenticationAssertionToBeSigned: { // SIGNED by the user credential private key + EncompassingAuthenticationAssertionToBeSigned: { // SIGNED by the user credential private key yielding the + // `AuthenticatorAssertionResponse.signature` value. AuthDataForAuthenticationReturningDevicePublicKey: { @@ -239,17 +275,17 @@ } AuthDataForDevicePublicKeyAttestationToBeSigned: { - rpIdHash // i.e., the "audience" - AAGUID // where do we get this from if it is not conveyed in authData? Thus must be here. - userCredentialIdLength // " " " " " " " " " " " " " " " " " - userCredentialId // " " " " " " " " " " " " " " " " " - devicePublicKey // " " " " " " " " " " " " " " " " " + rpIdHash // i.e., the "audience" + AAGUID // where do we get this from if it is not conveyed in authData? Thus must be here? + userCredentialIdLength // " " " " " " " " " " " " " " " " " + userCredentialId // " " " " " " " " " " " " " " " " " + devicePublicKey // " " " " " " " " " " " " " " " " " } fmt: "...attestation statement format identifier..." - attStmt: { // contains various items depending upon the attestation statement format, - // crucially containing a signature value, generated using the device's + attStmt: { // Contains various items depending upon the attestation statement format, + // crucially containing a signature value, generated using the device's (effective) // AttestationPrivatekey, over this to-be-signed data: AuthDataForDevicePublicKeyAttestation // @@ -258,7 +294,7 @@ // current request, e.g., the hashed clientData includes // the request challenge. // - // NOTE: The `attStmt` is a CONSTANT value per context per device + // NOTE: This `attStmt` is a CONSTANT value per context per device // per account per RP. } } @@ -269,6 +305,13 @@ } // AuthDataForAuthenticationReturningDevicePublicKey + ClientDataHash: { // the following items are serialized then hashed to form this value: + type + challenge + origin + crossOrigin + } + } // EncompassingAuthenticationAssertionToBeSigned @@ -277,10 +320,19 @@ (* ProVerif Web Authentication Formal Model originally by Iness Ben Guirat and Harry Halpin - https://dl.acm.org/doi/10.1145/3190619.3190640 - github: https://github.com/hhalpin/weauthn-model + Paper: https://dl.acm.org/doi/10.1145/3190619.3190640 + github: https://github.com/hhalpin/weauthn-model [sic] *) +(**** unlinkability.pv **** + +This model simply proves that if the same authenticator is used to authenticate with a given server in the context of different accounts, the server can correlate the authenticator use across those accounts because the attestation public key is the same. See + +To counter this, it is advised by the FIDO Alliance (fidoalliance.org) that authenticators use the same attestation key pair for large batches (100K) of authenticators. + +This version editorially cleaned up, as well as technically: removed unused types, variables, etc. +****) + type AttestationPublicKey. type AttestationPrivatekey. type nonce. @@ -386,3 +438,5 @@ process !processUser(k, attskU, attpkU) | !processServer(k, a) ) +(* END *) + From e9db5235ba5bb78f7b7176f60326cb16c96e32f6 Mon Sep 17 00:00:00 2001 From: JeffH Date: Mon, 21 Jun 2021 16:11:01 -0700 Subject: [PATCH 020/131] clarifications --- device-bound-key-pair.pv | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index 11f1154f1..ae33b8988 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -189,6 +189,9 @@ EncompassingRegistrationAttestationToBeSigned: { // SIGNED by the attestation private key to form the signature // value returned in the top-level attestation statement. + // This is only depicting what gets signed by which signing key, + // NOTE that the e.g. credentialID is conveyed unsigned in the + // PublicKeyCredential object. AuthDataForRegistrationReturningDevicePublicKey: { @@ -213,11 +216,12 @@ AuthDataForDevicePublicKeyAttestation: { rpIdHash // i.e., the "audience" - AAGUID // This, and the next 3 data items, duplicate what's in - userCredentialIdLength // `attestedCredentialData`, but attested cred data is not present in - userCredentialId // authentication assertions, so it needs to be here IF we wish this - devicePublicKey // devicePublicKey extension result to be the same format whether its - // returned as result of a registration or authn request. + AAGUID // This is also in `attestedCredentialData`, but attested cred data + // is not present in authentication assertions, so it needs to be + // here IF we wish this devicePublicKey extension result to be the + // same format whether it is returned as result of a registration + // or authn request. + devicePublicKey // The "attested" device public key. } fmt: "...attestation statement format identifier..." @@ -256,6 +260,9 @@ EncompassingAuthenticationAssertionToBeSigned: { // SIGNED by the user credential private key yielding the // `AuthenticatorAssertionResponse.signature` value. + // This is only depicting what gets signed by which signing key, + // NOTE that the e.g. credentialID is conveyed unsigned in the + // PublicKeyCredential object. AuthDataForAuthenticationReturningDevicePublicKey: { From 68ebaa257639a21bda4221a28b1e21c0e56b00cf Mon Sep 17 00:00:00 2001 From: JeffH Date: Mon, 21 Jun 2021 16:15:44 -0700 Subject: [PATCH 021/131] clarifications --- device-bound-key-pair.pv | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index ae33b8988..3e3824ef0 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -212,6 +212,7 @@ dpkSignatureValue { // Is a UNIQUE value per response. sign(devicePrivateKey, (clientDataHash || userCredentialId)) + // userCredentialId is second because it is variable length } AuthDataForDevicePublicKeyAttestation: { @@ -276,17 +277,15 @@ devicePublicKeyExtensionResult: { - dpkSignatureValue { // SIGNED by devicePrivateKey.. - // ..over... - clientDataHash || userCredentialId // userCredentialId is second because it is variable length + dpkSignatureValue { // Is a UNIQUE value per response. SIGNED + sign(devicePrivateKey, (clientDataHash || userCredentialId)) + // userCredentialId is second because it is variable length } AuthDataForDevicePublicKeyAttestationToBeSigned: { rpIdHash // i.e., the "audience" - AAGUID // where do we get this from if it is not conveyed in authData? Thus must be here? - userCredentialIdLength // " " " " " " " " " " " " " " " " " - userCredentialId // " " " " " " " " " " " " " " " " " - devicePublicKey // " " " " " " " " " " " " " " " " " + AAGUID + devicePublicKey } fmt: "...attestation statement format identifier..." From 8b5702c5d2ef597dd31a5612d65722e629702788 Mon Sep 17 00:00:00 2001 From: JeffH Date: Mon, 21 Jun 2021 16:29:31 -0700 Subject: [PATCH 022/131] clarifications --- device-bound-key-pair.pv | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index 3e3824ef0..8e02eac8d 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -65,7 +65,7 @@ AAGUID = 16OCTET ; 16 octets CredIDLength = 2OCTET ; 16-bit unsigned big-endian integer - CredentialID = 16*OCTET ; CredIDLength octets, at least 16 bytes long + CredentialID = 16*OCTET ; CredIDLength octets, at least 16 octets long CredentialPublicKey = 1*OCTET ; "user credential": self-describing variable length, ; COSE_Key format (CBOR-endoded) @@ -114,7 +114,7 @@ ; and is analogous to `attObj`. dpkSignatureValue: bstr, ; result of sign(devicePrivateKey, (clientDataHash || userCredentialId)) - ; Note that this sig value is unique per-request because clientDataHash contains + ; Note that this sig value changes per-request because clientDataHash contains ; the per-request challenge. authDataForDevicePublicKeyAttestation: bstr, ; AuthDataForDevicePublicKeyAttestation goes here @@ -135,10 +135,8 @@ AuthDataForDevicePublicKeyAttestation = rpIdHash AAGUID userCredentialIdLength userCredentialId devicePublicKey ; Expressed in RFC5234 ABNF: - rpIdHash = 32OCTET ; SHA-256 hash of the RP ID the credential is scoped to - AAGUID = 16OCTET ; 16 octets, authenticator's AAGUID - userCredentialIdLength = 2OCTET ; 2 octets, Credential ID length, 16-bit unsigned big-endian integer - userCredentialId = 16*OCTET ; credentialIdLength octets, at least 16 bytes long + rpIdHash = 32OCTET ; SHA-256 hash of the RP ID the device key pair is scoped to + AAGUID = 16OCTET ; authenticator's AAGUID devicePublicKey = 1*OCTET ; self-describing variable length, COSE_Key format (CBOR-endoded) From da82c2e6192f1bf484ddc01d8907523c5ce1049d Mon Sep 17 00:00:00 2001 From: JeffH Date: Mon, 21 Jun 2021 16:45:36 -0700 Subject: [PATCH 023/131] switch model starting-point to webauthn-basic.pv --- device-bound-key-pair.pv | 218 ++++++++++++++++++++++++++++----------- 1 file changed, 155 insertions(+), 63 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index 8e02eac8d..dff14f046 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -325,122 +325,214 @@ (* ProVerif Web Authentication Formal Model originally by Iness Ben Guirat and Harry Halpin Paper: https://dl.acm.org/doi/10.1145/3190619.3190640 - github: https://github.com/hhalpin/weauthn-model [sic] + Slides: https://cps-vo.org/node/48511 + github: https://github.com/hhalpin/weauthn-model *) -(**** unlinkability.pv **** +(**** webauthn-basic.pv **** -This model simply proves that if the same authenticator is used to authenticate with a given server in the context of different accounts, the server can correlate the authenticator use across those accounts because the attestation public key is the same. See +See the paper section 5.2 for a detailed description of this model. -To counter this, it is advised by the FIDO Alliance (fidoalliance.org) that authenticators use the same attestation key pair for large batches (100K) of authenticators. +In summary: This models first registration of a WebAuthn user credential with a server (aka Relying Party) and then subsequent authentication using that registered credential. The model seeks to prove various properties of the protocol. -This version editorially cleaned up, as well as technically: removed unused types, variables, etc. +Updated by Jeff Hodges with corrections and comments, May 2021 +http://kingsmountain.com/people/jeff.hodges/ ****) + +free c:channel. (* TLS channel *) + type AttestationPublicKey. type AttestationPrivatekey. + type nonce. -type pkey. -type skey. -type key. + +type pkey. (* asymmetric public key *) +type skey. (* asymmetric secret key *) + type RP_id. +type key. (* symmetric key *) + -(* TLS channel *) -free c: channel. (* TLS channel *) +(***** For Digital Signatures *****) +(* *) +(* This model of signatures assumes that the signature is *) +(* always accompanied with the message x. *) -const k: key. (* TLS record layer symmetric encryption key. - This model assumes an error-free TLS connection establishment - resulting in this key |k| as the encryption key. *) +fun spk(skey):pkey. +fun sign(bitstring, skey):bitstring. +reduc forall x:bitstring, y:skey; getmess(sign(x, y)) = x. (* The destructor getmess allows + the attacker to get the message + x from the signature, even + without having the key. *) -(* For Digital Signatures *) +reduc forall x:bitstring, y:skey; checksign(sign(x, y), spk(y)) = x. (* The destructor + checksign checks the signature, + and returns x only when the + signature is correct. Honest + processes typically use only + checksign. *) -fun spk(skey): pkey. -fun sign(bitstring, skey): bitstring. -reduc forall x:bitstring, y:skey; getmess(sign(x, y)) = x. -reduc forall x:bitstring, y:skey; checksign(sign(x, y), spk(y)) = x. -(* For nonces *) +(***** For nonces *****) fun nonce_to_bitstring ( nonce ) : bitstring [ data , typeConverter ] . table nonceTable(nonce) . -(* For Digital signature over Attestation Credentials *) -fun spkAtt(AttestationPrivatekey):AttestationPublicKey. -fun signAtt(bitstring, AttestationPrivatekey):bitstring. -reduc forall x:bitstring, y:AttestationPrivatekey; getmessAtt(signAtt(x, y)) = x. -reduc forall x:bitstring, y:AttestationPrivatekey; checksignAtt(signAtt(x, y), spkAtt(y)) = x. +(***** For Digital signature over Attestation Credentials *****) +(* *) +(* This model of signatures assumes that the signature is *) +(* always accompanied with the message x. *) + +fun spkAtt(AttestationPrivatekey): AttestationPublicKey. +fun signAtt(bitstring, AttestationPrivatekey): bitstring. + +reduc forall x:bitstring, y:AttestationPrivatekey; getmessAtt(signAtt(x, y)) = x. (* The destructor getmess allows + the attacker to get the message + x from the signature, even + without having the key. *) + +reduc forall x:bitstring, y:AttestationPrivatekey; checksignAtt(signAtt(x, y), spkAtt(y)) = x. (* The destructor + checksign checks the signature, + and returns x only when the + signature is correct. Honest + processes typically use only + checksign. *) + +free attskU: AttestationPrivatekey [private]. +free attpkU: AttestationPublicKey [private]. + +(* originally: +free attpkU: AttestationPrivatekey [private]. -- BUG?: should be type AttestationPublicKey ? + Corrected above *) + + -(* Symetric encryption *) +free k: key [private]. (* TLS symmetric encryption key *) + + + +(***** Symetric encryption *****) fun senc(bitstring, key): bitstring. reduc forall x: bitstring, k: key; sdec(senc(x,k),k) = x. -(* Server's ID *) -const a: RP_id [private]. (* server's identity *) +(***** User's ID and Server's ID *****) -(* Table *) -table tableAtt(AttestationPublicKey). +const cookie:bitstring [private]. +const attestation_cert:bitstring [private]. +const a: RP_id [private]. -(* Events and Queries *) -event reachSameKey(AttestationPublicKey). -query N1:AttestationPublicKey; event(reachSameKey(N1)). +(***** Events and Queries *****) -free attskU: AttestationPrivatekey [private]. -query attacker(attskU). - -free attpkU: AttestationPublicKey [private]. -query attacker(attpkU). +event sentChallengeResponse(bitstring, bitstring). +event validChallengeResponse(bitstring, bitstring). +query N:bitstring, s:bitstring; event(validChallengeResponse(N, s)) ==> event(sentChallengeResponse(N, s)). +(* query N:bitstring; event(reachAuthentication(N)). +event reachAuthentication(bitstring). *) -let processUser (k: key, attskA: AttestationPrivatekey, attpkA: AttestationPublicKey) = +query attacker(attskU). +query attacker(attpkU). +query attacker(k). - (*Registration*) - in(c, s: bitstring); (* we expect server to send challenge & RP_id over encrypted TLS channel *) - let (challengeU: nonce, aU: RP_id) = sdec(s, k) in +(***** Processes *****) - new skU: skey; (* user's authenticator mints new user credential key pair *) +let processUser ( k: key, attskU: AttestationPrivatekey, attpkU:AttestationPublicKey, attestation_cert:bitstring) = + (* Registration *) + in(c, s:bitstring); + let(challengeU:nonce, a:RP_id) = sdec(s,k) in + new skU:skey; let pkU = spk(skU) in + out(c, + senc( ( attestation_cert, signAtt( (pkU, attpkU, challengeU), attskU ) ), k) + ); - out(c, senc(signAtt((pkU, attpkU, challengeU), attskU), k)). (* return signed registration response - containing attestation object, over - encrypted TLS channel. *) + (* Authentication *) + in(c, s1:bitstring); + let mess1 = sdec(s1, k) in -let processServer (k: key, a:RP_id) = + (* originally: + event sentChallengeResponse( mess1, sign( senc(mess1,k), skU ) ); -- AFAICT, this and.. + out(c, sign(senc(mess1,k), skU)). -- ..this are incorrectly constructed! The message needs to be enc'd + -- using `k`, with the sig (by skU) inside. (corrected below) + *) - (*Registration*) + event sentChallengeResponse( mess1, senc( sign(mess1, skU), k) ); + out(c, + senc( sign(mess1, skU), k) + ). - new challenge: nonce; - out(c, senc((challenge, a), k)); (* Reg request: send challenge & RP_id over TLS *) - in(c, s:bitstring); (* Registration response *) +let processServer (k: key, a:RP_id) = - let m = sdec(s, k) in - let (pkY1: pkey, attpkU1: AttestationPublicKey, Nt: nonce) = getmessAtt(m) in - let ver = checksignAtt(m, attpkU1) in (* This verifies the attestation signature because `getmess()` does not. *) + (* Registration *) + new challenge1:nonce; + out(c, senc((challenge1,a),k)); + in(c, s:bitstring); + let m = sdec(s,k) in + let (cert:bitstring, pkY1: pkey, attpkU1:AttestationPublicKey, credUser: bitstring, Nt:nonce) = getmessAtt(m) in + let ver = checksignAtt(m, attpkU1) in + + if (Nt=challenge1) then ( (* if we got here, then the signature on the registration response msg verified, + and if the challenge returned is the one for this user, then we will proceed + to challenge the user to authenticate (not an actually typical server behavior, + this just simplifies the model). *) + + (* Authentication *) + + new challenge2:nonce; + let mess = nonce_to_bitstring( challenge2 ) in + out(c, senc( (mess, a), k) ); (* authentication request with `challenge2` as `mess` *) + + (**** for each of next 4 lines: local var | what client sent and thus local var is set to *) + (* ------------|---------------------------------------------- *) + in(c, s2:bitstring); (* s2 | senc( sign(mess1, skU), k ) *) + let m1 = sdec(s2, k) in (* m1 | sign( mess1, skU ) *) + let m2 = checksign(m1, pkY1) in (* m2 | mess1 (_should_ be `(mess,a)` if sig is valid) *) + (* | *) + let m3 = getmess(m1) in (* m3 | mess1 (tho server sent `(mess,a)`) in out() above*) + + if ( m3 = m2 ) then ( (* This check is necessary (?) in order to determine if the *) + (* signed msg is actually the expected one. *) + (* Perhaps should use for the preceding "let" and this "if": *) + (* let (Nbitstr:bitstring, a1:RP_id) = getmess(m1) in *) + (* if ((Nbitstr = mess) && (a1 = a)) then ( ... ) (?) *) + event validChallengeResponse(m3, m2) + ) + + (* originally: + in(c, s2:bitstring); + let m1 = sdec(s2, k) in -- this does not match how the (incorrectly written) client is + -- constructing the msg to be rec'd here. (corrected above) + let m2 = checksign(m1, pkY1) in + let m3 = getmess(s2) in -- this does match the client's (incorrect) construction (corrected above) + if ( m3 = getmess(s2)) then ( + event validChallengeResponse(m3, s2) + ) + *) + ) + . - get tableAtt(=attpkU1) in (* Have we seen this registration before? *) - event reachSameKey(attpkU1) (* Yes, thus the user is re-identified, *) - (* modulo the authenticator batch size *) - (* having the same attestation key pair. *) - else - insert tableAtt(attpkU1). (* No, remember it. *) process - new attskU: AttestationPrivatekey; (* The attestation key pair is a immutable property of *) - let attpkU = spkAtt(attskU) in (* the authenticator, so we mint it here at the beginning. *) + new k:key; + new attskU:AttestationPrivatekey; + + let attpkU = spkAtt(attskU) in ( - !processUser(k, attskU, attpkU) | !processServer(k, a) + !processUser( k, attskU, attpkU, attestation_cert) | !processServer(k, a) ) -(* END *) +(***** END *****) From f84069bc7d10dfff48179e811bd15832dfc23487 Mon Sep 17 00:00:00 2001 From: JeffH Date: Mon, 21 Jun 2021 17:31:45 -0700 Subject: [PATCH 024/131] clarifications --- device-bound-key-pair.pv | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index dff14f046..ecaed9538 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -87,7 +87,8 @@ fmt: text, attStmt: { * tstr => any } ; Map is filled in by each concrete attStmtType - ; in most formats, the attStmt contains a signature value over: authData || clientDataHash + ; in most formats, the attStmt contains a signature value of: + ; sign (authData || clientDataHash ) @@ -447,12 +448,16 @@ query attacker(k). (***** Processes *****) let processUser ( k: key, attskU: AttestationPrivatekey, attpkU:AttestationPublicKey, attestation_cert:bitstring) = + (* Registration *) + in(c, s:bitstring); - let(challengeU:nonce, a:RP_id) = sdec(s,k) in - new skU:skey; - let pkU = spk(skU) in - out(c, + let (challengeU:nonce, a:RP_id) = sdec(s,k) in + + new skU: skey; (* mint new user credential "secret key user" |skU| *) + let pkU = spk(skU) in (* and "public key user" |pkU| *) + + out(c, (* return senc( ( attestation_cert, signAtt( (pkU, attpkU, challengeU), attskU ) ), k) ); @@ -477,15 +482,18 @@ let processUser ( k: key, attskU: AttestationPrivatekey, attpkU:AttestationPubli let processServer (k: key, a:RP_id) = (* Registration *) + new challenge1:nonce; - out(c, senc((challenge1,a),k)); - in(c, s:bitstring); + out(c, senc((challenge1, a), k)); (* send registration request *) + in(c, s:bitstring); (* receive possible registration response *) + let m = sdec(s,k) in let (cert:bitstring, pkY1: pkey, attpkU1:AttestationPublicKey, credUser: bitstring, Nt:nonce) = getmessAtt(m) in - let ver = checksignAtt(m, attpkU1) in + (* if the above succeeds, we received a registration response *) + let ver = checksignAtt(m, attpkU1) in (* verify signature on attestation response *) if (Nt=challenge1) then ( (* if we got here, then the signature on the registration response msg verified, - and if the challenge returned is the one for this user, then we will proceed + and if the challenge returned matches the one we sent, then we will proceed to challenge the user to authenticate (not an actually typical server behavior, this just simplifies the model). *) @@ -526,12 +534,13 @@ let processServer (k: key, a:RP_id) = process - new k:key; - new attskU:AttestationPrivatekey; + new k: key; (* assume TLS handshake is error-free and yields this symmetric channel encryption key *) + new attskU: AttestationPrivatekey; (* assume a particular authenticator with a particular attestation + private key *) let attpkU = spkAtt(attskU) in ( - !processUser( k, attskU, attpkU, attestation_cert) | !processServer(k, a) + !processUser(k, attskU, attpkU, attestation_cert) | !processServer(k, a) ) From f6663cbabff780bc8c967304a47429368816a2b2 Mon Sep 17 00:00:00 2001 From: JeffH Date: Tue, 22 Jun 2021 13:54:45 -0700 Subject: [PATCH 025/131] clarifications to both DPK stuff and PV model --- device-bound-key-pair.pv | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index ecaed9538..35d100987 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -114,7 +114,7 @@ AttObjForDevicePublicKey = { ; Note: This object conveys an attested device public key ; and is analogous to `attObj`. - dpkSignatureValue: bstr, ; result of sign(devicePrivateKey, (clientDataHash || userCredentialId)) + dpkSignatureValue: bstr, ; result of sign((clientDataHash || userCredentialId), devicePrivateKey) ; Note that this sig value changes per-request because clientDataHash contains ; the per-request challenge. @@ -210,13 +210,13 @@ devicePublicKeyExtensionResult: { dpkSignatureValue { // Is a UNIQUE value per response. - sign(devicePrivateKey, (clientDataHash || userCredentialId)) + sign((clientDataHash || userCredentialId), devicePrivateKey) // userCredentialId is second because it is variable length } AuthDataForDevicePublicKeyAttestation: { rpIdHash // i.e., the "audience" - AAGUID // This is also in `attestedCredentialData`, but attested cred data + AAGUID // This is in `attestedCredentialData`, but attested cred data // is not present in authentication assertions, so it needs to be // here IF we wish this devicePublicKey extension result to be the // same format whether it is returned as result of a registration @@ -277,14 +277,17 @@ devicePublicKeyExtensionResult: { dpkSignatureValue { // Is a UNIQUE value per response. SIGNED - sign(devicePrivateKey, (clientDataHash || userCredentialId)) - // userCredentialId is second because it is variable length + sign((clientDataHash || userCredentialId), devicePrivateKey) + // userCredentialId is second because it is variable length } AuthDataForDevicePublicKeyAttestationToBeSigned: { - rpIdHash // i.e., the "audience" - AAGUID - devicePublicKey + rpIdHash // i.e., the "audience" + AAGUID // Needs to be here because if user cred is synced to "new" device + // a registration is not done on that device and the RP will receive + // a new devicePublicKey extension result from a get() call, and + // this will attest to the provenance of the authenticator. + devicePublicKey // The "attested" device public key. } fmt: "...attestation statement format identifier..." @@ -434,6 +437,8 @@ const a: RP_id [private]. event sentChallengeResponse(bitstring, bitstring). event validChallengeResponse(bitstring, bitstring). + +(* we can only receive a valid response if the user sent a response message *) query N:bitstring, s:bitstring; event(validChallengeResponse(N, s)) ==> event(sentChallengeResponse(N, s)). (* query N:bitstring; event(reachAuthentication(N)). @@ -457,12 +462,13 @@ let processUser ( k: key, attskU: AttestationPrivatekey, attpkU:AttestationPubli new skU: skey; (* mint new user credential "secret key user" |skU| *) let pkU = spk(skU) in (* and "public key user" |pkU| *) - out(c, (* return + out(c, (* return attestation response *) senc( ( attestation_cert, signAtt( (pkU, attpkU, challengeU), attskU ) ), k) ); (* Authentication *) + in(c, s1:bitstring); let mess1 = sdec(s1, k) in @@ -534,7 +540,7 @@ let processServer (k: key, a:RP_id) = process - new k: key; (* assume TLS handshake is error-free and yields this symmetric channel encryption key *) + new k: key; (* assume TLS handshake is error-free and yields this symmetric channel-encryption key *) new attskU: AttestationPrivatekey; (* assume a particular authenticator with a particular attestation private key *) From 22e325d51a07388d8da2d0ddbbfe33a8dd2872d6 Mon Sep 17 00:00:00 2001 From: JeffH Date: Thu, 24 Jun 2021 13:38:24 -0700 Subject: [PATCH 026/131] revise model significantly revise model to have discrete message components and to leverage named_tuples.pvl and crypto.pvl. --- device-bound-key-pair.pv | 293 +++++++++++++++++++-------------------- 1 file changed, 140 insertions(+), 153 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index 35d100987..458609bf6 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -1,4 +1,4 @@ -(****** for the proverif model, find "ProVerif Web Authentication Formal Model" below... ******) +(****** for the proverif model, find section entitled "ProVerif Web Authentication Formal Model" below... ******) (**** Device-bound Public Key | Device-bound Key Pair | Device key | Context Key | Secondary Key **** @@ -8,6 +8,45 @@ ==== Collected Client Data, Attestation Object, Authenticator Data structures, ==== ==== and (nested) signature values calculated over portions of them. ==== + + // Coalesced informal expansion of PublicKeyCredential (showing all the important internal components + // and eliminating (irrelevant) nested containers): + + + Coalesced Registration PublicKeyCredential { + id // User CredentialID + clientDataJSON // serialized collected client data + attestationObject { // `attObj` goes here + authenticatorData { // `authData` goes here + rpIdHash + flags + signCount + AAGUID + CredIDLength + CredentialID + CredentialPublicKey // User Credential Public Key + extensions // devicePublicKey extension output will be in here + } + fmt + attStmt // typically contains the ENCOMPASSING SIGNATURE VALUE of + // sign((authData, clientDataHash), AttestationPrivatekey) + } + } + + Coalesced Authentication PublicKeyCredential { + id // User CredentialID + clientDataJSON // serialized collected client data + authenticatorData { // `authData` goes here + rpIdHash + flags + signCount + extensions // devicePublicKey extension output will be in here + } + signature // ENCOMPASSING SIGNATURE VALUE over: authData || clientDataHash + } + + + // Top-level WebAuthn response messages (expressed in simplified WebIDL): interface PublicKeyCredential : Credential { @@ -35,24 +74,6 @@ - Coalesced Registration PublicKeyCredential: { - USVString id; // User Credential ID - ArrayBuffer clientDataJSON; - ArrayBuffer attestationObject; // `attObj` goes here, it contains the `authData` and other component - } - - - Coalesced Authentication PublicKeyCredential: { - USVString id; // User Credential ID - ArrayBuffer clientDataJSON; - ArrayBuffer authenticatorData; // `authData` goes here - ArrayBuffer signature; // ENCOMPASSING SIGNATURE VALUE over: authData || clientDataHash - // Note that the signed-over authData conveys extension results - [...] - } - - - // Authenticator Data (authData) in RFC5234 ABNF : authData = rpIdHash flags signCount [attestedCredentialData] [extensions] @@ -133,7 +154,7 @@ } - AuthDataForDevicePublicKeyAttestation = rpIdHash AAGUID userCredentialIdLength userCredentialId devicePublicKey + AuthDataForDevicePublicKeyAttestation = rpIdHash AAGUID devicePublicKey ; Expressed in RFC5234 ABNF: rpIdHash = 32OCTET ; SHA-256 hash of the RP ID the device key pair is scoped to @@ -168,7 +189,7 @@ attObj = { AuthDataForRegistrationReturningDevicePublicKey: bytes, ; devicePublicKey extension result is here $$attStmtType ; as defined in WebAuthn, contains ENCOMPASSING SIGNATURE VALUE over: - ; + ; authData || clientDataHash } @@ -209,7 +230,7 @@ devicePublicKeyExtensionResult: { - dpkSignatureValue { // Is a UNIQUE value per response. + dpkSignatureValue { // Is a UNIQUE value per response. SIGNED by devicePrivateKey. sign((clientDataHash || userCredentialId), devicePrivateKey) // userCredentialId is second because it is variable length } @@ -276,7 +297,7 @@ devicePublicKeyExtensionResult: { - dpkSignatureValue { // Is a UNIQUE value per response. SIGNED + dpkSignatureValue { // Is a UNIQUE value per response. SIGNED by devicePrivateKey. sign((clientDataHash || userCredentialId), devicePrivateKey) // userCredentialId is second because it is variable length } @@ -343,93 +364,60 @@ Updated by Jeff Hodges with corrections and comments, May 2021 http://kingsmountain.com/people/jeff.hodges/ ****) +#include "crypto.pvl" +#include "named_tuples.pvl" free c:channel. (* TLS channel *) -type AttestationPublicKey. -type AttestationPrivatekey. type nonce. -type pkey. (* asymmetric public key *) -type skey. (* asymmetric secret key *) +(***** Server's ID *****) type RP_id. -type key. (* symmetric key *) - - - -(***** For Digital Signatures *****) -(* *) -(* This model of signatures assumes that the signature is *) -(* always accompanied with the message x. *) - -fun spk(skey):pkey. -fun sign(bitstring, skey):bitstring. - -reduc forall x:bitstring, y:skey; getmess(sign(x, y)) = x. (* The destructor getmess allows - the attacker to get the message - x from the signature, even - without having the key. *) - -reduc forall x:bitstring, y:skey; checksign(sign(x, y), spk(y)) = x. (* The destructor - checksign checks the signature, - and returns x only when the - signature is correct. Honest - processes typically use only - checksign. *) - - -(***** For nonces *****) - -fun nonce_to_bitstring ( nonce ) : bitstring [ data , typeConverter ] . -table nonceTable(nonce) . - +const a: RP_id [private]. -(***** For Digital signature over Attestation Credentials *****) -(* *) -(* This model of signatures assumes that the signature is *) -(* always accompanied with the message x. *) +(***** Attestation Key Pair ***** -fun spkAtt(AttestationPrivatekey): AttestationPublicKey. -fun signAtt(bitstring, AttestationPrivatekey): bitstring. +free attSecretKey: SKey [private]. +free attPublicKey: PKey [private]. -reduc forall x:bitstring, y:AttestationPrivatekey; getmessAtt(signAtt(x, y)) = x. (* The destructor getmess allows - the attacker to get the message - x from the signature, even - without having the key. *) -reduc forall x:bitstring, y:AttestationPrivatekey; checksignAtt(signAtt(x, y), spkAtt(y)) = x. (* The destructor - checksign checks the signature, - and returns x only when the - signature is correct. Honest - processes typically use only - checksign. *) +(***** Server's and authnr+clientPlatform's tuple messages *****) -free attskU: AttestationPrivatekey [private]. -free attpkU: AttestationPublicKey [private]. +(* authnr+clientPlatform returns this to server: *) +DEFINE_DATA_TYPE4(RegResponseMsg, + userCredPubKey, PKey, + attPublicKey, PKey, + receivedRegChallenge, nonce, + attSignature, bitstring). -(* originally: -free attpkU: AttestationPrivatekey [private]. -- BUG?: should be type AttestationPublicKey ? - Corrected above *) +fun RegResponseMsg2b( RegResponseMsg ): bitstring [data , typeConverter]. +fun B2RegResponseMsg( bitstring ): RegResponseMsg [data , typeConverter]. +(* server sends this: *) +DEFINE_DATA_TYPE2(AuthnRequestMsg, + authnChallenge, nonce, + serverName, RP_id). -free k: key [private]. (* TLS symmetric encryption key *) +fun AuthnRequestMsg2b( AuthnRequestMsg ): bitstring [data , typeConverter]. +fun B2AuthnRequestMsg( bitstring ): AuthnRequestMsg [data , typeConverter]. +(* user's authnr+clientPlatform returns this to server: *) +DEFINE_DATA_TYPE3(AuthnResponseMsg, + returnedAuthnChallenge, nonce, + serverName, RP_id, + assertSignature, bitstring). -(***** Symetric encryption *****) +fun AuthnResponseMsg2b( AuthnResponseMsg ): bitstring [data , typeConverter]. +fun B2AuthnResponseMsg( bitstring ): AuthnResponseMsg [data , typeConverter]. -fun senc(bitstring, key): bitstring. -reduc forall x: bitstring, k: key; sdec(senc(x,k),k) = x. -(***** User's ID and Server's ID *****) +free tlsKey: Key [private]. (* TLS symmetric encryption key *) -const cookie:bitstring [private]. -const attestation_cert:bitstring [private]. -const a: RP_id [private]. @@ -444,109 +432,108 @@ query N:bitstring, s:bitstring; event(validChallengeResponse(N, s)) ==> event(se (* query N:bitstring; event(reachAuthentication(N)). event reachAuthentication(bitstring). *) -query attacker(attskU). -query attacker(attpkU). -query attacker(k). +(* query attacker(attSecretKey). *) +(* query attacker(attPublicKey). *) +query attacker(tlsKey). (***** Processes *****) -let processUser ( k: key, attskU: AttestationPrivatekey, attpkU:AttestationPublicKey, attestation_cert:bitstring) = +let processUser ( k: Key, attskU: SKey, attpkU: PKey) = (* Registration *) in(c, s:bitstring); - let (challengeU:nonce, a:RP_id) = sdec(s,k) in + let (challengeU:nonce, a:RP_id) = dec(s,k) in - new skU: skey; (* mint new user credential "secret key user" |skU| *) - let pkU = spk(skU) in (* and "public key user" |pkU| *) + new skU: SKey; (* mint new user credential "secret key user" |skU| *) + let pkU = pk(skU) in (* and "public key user" |pkU| *) - out(c, (* return attestation response *) - senc( ( attestation_cert, signAtt( (pkU, attpkU, challengeU), attskU ) ), k) + let regResponseMsg = BuildRegResponseMsg(pkU, attpkU, challengeU, sign((pkU, challengeU), attskU)) in + let regResponseMsgb = RegResponseMsg2b(regResponseMsg) in + + out(c, (* return registration response with attestation *) + enc(regResponseMsgb, k) ); (* Authentication *) - in(c, s1:bitstring); - let mess1 = sdec(s1, k) in + in(c, encAuthnRequestMsg: bitstring); (* receive server's challenge. *) + let authnRequestMsgb = dec(encAuthnRequestMsg, k) in + let authnRequestMsg = B2AuthnRequestMsg(authnRequestMsgb) in + let receivedAuthnChallenge = AuthnRequestMsg_authnChallenge(authnRequestMsg) in + let receivedServerName = AuthnRequestMsg_serverName(authnRequestMsg) in - (* originally: - event sentChallengeResponse( mess1, sign( senc(mess1,k), skU ) ); -- AFAICT, this and.. - out(c, sign(senc(mess1,k), skU)). -- ..this are incorrectly constructed! The message needs to be enc'd - -- using `k`, with the sig (by skU) inside. (corrected below) - *) + let assertionSignature = sign((receivedAuthnChallenge, receivedServerName), skU) in + let authnResponseMsg = BuildAuthnResponseMsg(receivedAuthnChallenge, receivedServerName, assertionSignature) in + let authnResponseMsgb = AuthnResponseMsg2b(authnResponseMsg) in - event sentChallengeResponse( mess1, senc( sign(mess1, skU), k) ); + event sentChallengeResponse( authnResponseMsgb, enc(authnResponseMsgb, k) ); out(c, - senc( sign(mess1, skU), k) + enc(authnResponseMsgb, k) ). -let processServer (k: key, a:RP_id) = +let processServer (k: Key, a: RP_id) = (* Registration *) - new challenge1:nonce; - out(c, senc((challenge1, a), k)); (* send registration request *) - in(c, s:bitstring); (* receive possible registration response *) + new regChallenge: nonce; + + out(c, enc((regChallenge, a), k)); (* send registration request *) + in(c, encRegResponseMsg: bitstring); (* receive possible registration response *) + + let regResponseMsgb = dec(encRegResponseMsg, k) in (* decrypt and parse |encRegResponseMsg| *) + let regResponseMsg = B2RegResponseMsg(regResponseMsgb) in + let userPK = RegResponseMsg_userCredPubKey(regResponseMsg) in + let attPK = RegResponseMsg_attPublicKey(regResponseMsg) in + let returnedRegChallenge = RegResponseMsg_receivedRegChallenge(regResponseMsg) in + let attSig = RegResponseMsg_attSignature(regResponseMsg) in - let m = sdec(s,k) in - let (cert:bitstring, pkY1: pkey, attpkU1:AttestationPublicKey, credUser: bitstring, Nt:nonce) = getmessAtt(m) in - (* if the above succeeds, we received a registration response *) - let ver = checksignAtt(m, attpkU1) in (* verify signature on attestation response *) + if checkSign(attSig, (userPK, returnedRegChallenge), attPK) then (* we have a registration response msg with + a valid signature... *) - if (Nt=challenge1) then ( (* if we got here, then the signature on the registration response msg verified, - and if the challenge returned matches the one we sent, then we will proceed - to challenge the user to authenticate (not an actually typical server behavior, - this just simplifies the model). *) + if (regChallenge = returnedRegChallenge) then ( (* ...and if the challenge returned matches the then we + one we sent, will proceed to challenge the user to + authenticate. *) (* Authentication *) - new challenge2:nonce; - let mess = nonce_to_bitstring( challenge2 ) in - out(c, senc( (mess, a), k) ); (* authentication request with `challenge2` as `mess` *) - - (**** for each of next 4 lines: local var | what client sent and thus local var is set to *) - (* ------------|---------------------------------------------- *) - in(c, s2:bitstring); (* s2 | senc( sign(mess1, skU), k ) *) - let m1 = sdec(s2, k) in (* m1 | sign( mess1, skU ) *) - let m2 = checksign(m1, pkY1) in (* m2 | mess1 (_should_ be `(mess,a)` if sig is valid) *) - (* | *) - let m3 = getmess(m1) in (* m3 | mess1 (tho server sent `(mess,a)`) in out() above*) - - if ( m3 = m2 ) then ( (* This check is necessary (?) in order to determine if the *) - (* signed msg is actually the expected one. *) - (* Perhaps should use for the preceding "let" and this "if": *) - (* let (Nbitstr:bitstring, a1:RP_id) = getmess(m1) in *) - (* if ((Nbitstr = mess) && (a1 = a)) then ( ... ) (?) *) - event validChallengeResponse(m3, m2) - ) - - (* originally: - in(c, s2:bitstring); - let m1 = sdec(s2, k) in -- this does not match how the (incorrectly written) client is - -- constructing the msg to be rec'd here. (corrected above) - let m2 = checksign(m1, pkY1) in - let m3 = getmess(s2) in -- this does match the client's (incorrect) construction (corrected above) - if ( m3 = getmess(s2)) then ( - event validChallengeResponse(m3, s2) - ) - *) - ) - . + new authnChallenge: nonce; + + (* let authnChallengeBitstr = nonce_to_bitstring( authnChallenge ) in -- do we need to do this type conversion? *) + + let authnRequestMsg = BuildAuthnRequestMsg(authnChallenge, a) in + let authnRequestMsgb = AuthnRequestMsg2b(authnRequestMsg) in + + out(c, enc( authnRequestMsgb, k) ); (* send authentication request *) + + in(c, encAuthnResponseMsg: bitstring); + let authnResponseMsgb = dec(encAuthnResponseMsg, k) in + let authnResponseMsg = B2AuthnResponseMsg(authnResponseMsgb) in + let retAuthnChallenge = AuthnResponseMsg_returnedAuthnChallenge(authnResponseMsg) in + let retServerName = AuthnResponseMsg_serverName(authnResponseMsg) in + let retAssertSignature = AuthnResponseMsg_assertSignature(authnResponseMsg) in + + if (authnChallenge = retAuthnChallenge) && (a = retServerName) then + if checkSign(retAssertSignature, (retAuthnChallenge, retServerName), userPK) then + + event validChallengeResponse(authnResponseMsgb, encAuthnResponseMsg) + ). + process - new k: key; (* assume TLS handshake is error-free and yields this symmetric channel-encryption key *) - new attskU: AttestationPrivatekey; (* assume a particular authenticator with a particular attestation - private key *) + new tlsKey: Key; (* assume TLS handshake is error-free and yields this symmetric channel-encryption key *) + new attSecretKey: SKey; (* assume a particular authenticator with a particular attestation + private key *) - let attpkU = spkAtt(attskU) in + let attPublicKey = pk(attSecretKey) in ( - !processUser(k, attskU, attpkU, attestation_cert) | !processServer(k, a) + !processUser(tlsKey, attSecretKey, attPublicKey) | !processServer(tlsKey, a) ) From a3ed05bfa659f01e7c5bb3b913b00b62983c78b0 Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 25 Jun 2021 14:39:23 -0700 Subject: [PATCH 027/131] further clarifications and musings this is the stage of development I first shared with internal colleagues post the original hand-wavy prose writeup. --- device-bound-key-pair.pv | 46 ++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index 458609bf6..30dd162f4 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -29,7 +29,7 @@ } fmt attStmt // typically contains the ENCOMPASSING SIGNATURE VALUE of - // sign((authData, clientDataHash), AttestationPrivatekey) + // sign((authData || clientDataHash), AttestationPrivatekey) } } @@ -42,7 +42,8 @@ signCount extensions // devicePublicKey extension output will be in here } - signature // ENCOMPASSING SIGNATURE VALUE over: authData || clientDataHash + signature // ENCOMPASSING SIGNATURE VALUE of + // sign((authData || clientDataHash), CredentialPublicKey) } @@ -126,7 +127,7 @@ -==== Proposed Collected Syntax for devicePublicKey (dpk) (aka Secondary Key aka Device Key) Extension Output ==== +==== Proposed Syntax for devicePublicKey (dpk) (aka Secondary Key aka Device Key) Extension Output ==== $$extensionOutput //= ( // Expressed in CDDL devicePublicKey: AttObjForDevicePublicKey, @@ -136,31 +137,50 @@ ; and is analogous to `attObj`. dpkSignatureValue: bstr, ; result of sign((clientDataHash || userCredentialId), devicePrivateKey) - ; Note that this sig value changes per-request because clientDataHash contains + ; Note that this sig value is unique per-request because clientDataHash contains ; the per-request challenge. - authDataForDevicePublicKeyAttestation: bstr, ; AuthDataForDevicePublicKeyAttestation goes here + authDataForDevicePublicKeyAttestation: bstr, ; AuthDataForDevicePublicKeyAttestation goes here (see below) $$attStmtType, ; see . ; ; Attestation statement formats define the `fmt` and `attStmt` members of ; $$attStmtType. ; - ; In summary, it contains (1) a signature value calculated (using the attestation private key) - ; over just the bytes of `authDataForDevicePublicKeyAttestation`, and (2) the attestation - ; certificate or public key, and supporting certificates, if any. Note - ; that there are details dependent upon the particular attestation statement + ; In summary, the `attStmt` contains: + ; (1) a SIGNATURE value calculated (using the attestation private key) + ; over the bytes of (authDataForDevicePublicKeyAttestation || rpIdHash), and + ; (2) the attestation certificate or public key, and supporting certificates, if any. + ; + ; Note that there are details dependent upon the particular attestation statement ; format. See . } - AuthDataForDevicePublicKeyAttestation = rpIdHash AAGUID devicePublicKey - ; Expressed in RFC5234 ABNF: + AuthDataForDevicePublicKeyAttestation = AAGUID devicePublicKey ; Expressed in RFC5234 ABNF: + + ; A rationale for also including `rpIdHash` here is so `AttObjForDevicePublicKey.attStmt` + ; is "self contained", i.e., so that the verifier would not need to "look elsewhere" in order + ; to verify the signature. + ; rpIdHash = 32OCTET ; SHA-256 hash of the RP ID the device key pair is scoped to + ; HOWEVER, including rpIdHash here can make a substitution of the authDataForDevicePublicKeyAttestation "easier" because + ; then it could differ from the rpIdHash in the top-level message. + + ; Do we also include these next two items here such that the `AttObjForDevicePublicKey.attStmt` is self-contained? + ; I.e., the verifier would not need to "look elsewhere" in order to have the data necessary for signature + ; verification + ; + ; CredIDLength = 2OCTET ; 16-bit unsigned big-endian integer + ; CredentialID = 16*OCTET ; CredIDLength octets, at least 16 octets long + ; + ; note-to-self: ANSWER TO ABOVE IS "no" wrt CredID because otherwise we'd also need to include the clientDataHash + ; and this is just plain getting unnecessarily too large. plus credID is only needed to validate dpkSignatureValue. - rpIdHash = 32OCTET ; SHA-256 hash of the RP ID the device key pair is scoped to AAGUID = 16OCTET ; authenticator's AAGUID - devicePublicKey = 1*OCTET ; self-describing variable length, COSE_Key format (CBOR-endoded) + devicePublicKey = 1*OCTET ; self-describing variable length, COSE_Key format (CBOR-encoded) + ; Another question here is whether for this `AuthDataForDevicePublicKeyAttestation` continues the tradition of an + ; undelimited bespoke binary format, or whether we encode it in CBOR ? From 6382444f375e8cbd1d502e0f0e8991c7318e588b Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 25 Jun 2021 14:41:38 -0700 Subject: [PATCH 028/131] editorial --- device-bound-key-pair.pv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index 30dd162f4..72c5fe972 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -129,7 +129,7 @@ ==== Proposed Syntax for devicePublicKey (dpk) (aka Secondary Key aka Device Key) Extension Output ==== - $$extensionOutput //= ( // Expressed in CDDL + $$extensionOutput //= ( ; Expressed in CDDL devicePublicKey: AttObjForDevicePublicKey, ) From 768d90067f3bdee5b38e700264f617798c143f97 Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 25 Jun 2021 17:00:02 -0700 Subject: [PATCH 029/131] revised dpk syntax per agl review --- device-bound-key-pair.pv | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index 72c5fe972..d9e872d5d 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -184,6 +184,41 @@ +==== Proposed Syntax for devicePublicKey (dpk) (aka Secondary Key aka Device Key) Extension Output ==== + + $$extensionOutput //= ( ; Expressed in CDDL + devicePublicKey: AttObjForDevicePublicKey, + ) + + AttObjForDevicePublicKey = { ; Note: This object conveys an attested device public key + ; and is analogous to `attObj`. + + sig: bstr, ; result of sign((clientDataHash || userCredentialId), devicePrivateKey) + ; Note that this sig value is unique per-response because clientDataHash contains + ; the per-request challenge. + + dpkAuthData: bstr, ; AuthDataForDevicePublicKeyAttestation goes here (see below) + + $$attStmtType, ; see . + ; + ; Attestation statement formats define the `fmt` and `attStmt` members of + ; $$attStmtType. + ; + ; In summary, the `attStmt` (typically) contains: + ; (1) a SIGNATURE value calculated (using the attestation private key) + ; over the bytes of `authDataForDevicePublicKeyAttestation` and + ; (2) the attestation certificate or public key, and supporting certificates, if any. + ; + ; Note that there are details dependent upon the particular attestation statement + ; format. See . + } + + AuthDataForDevicePublicKeyAttestation = { + aaguid: bstr ; authenticator's AAGUID + dPK: bstr ; self-describing variable length, COSE_Key format (CBOR-encoded) + } + + ==== WebAuthn Signed Objects Hierarchy for Device-bound Key Pair aka Device-bound Public Key aka Secondary Key aka Device Key ==== AuthData for Registration and Authentication Operations returning a devicePublicKey extension response (expressed in RFC5234 ABNF; https://www.w3.org/TR/webauthn/#sctn-authenticator-data ): From fd9ea008dffdb5588cdc1a6bc3d032ccf47a7697 Mon Sep 17 00:00:00 2001 From: JeffH Date: Mon, 28 Jun 2021 12:04:58 -0700 Subject: [PATCH 030/131] further refined dpk syntax per feedback --- device-bound-key-pair.pv | 68 +++++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index d9e872d5d..90cd7a373 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -127,7 +127,8 @@ -==== Proposed Syntax for devicePublicKey (dpk) (aka Secondary Key aka Device Key) Extension Output ==== +==== (OLD, SUPERSEDED) Proposed Syntax for devicePublicKey (dpk) (aka Secondary Key aka Device Key) Extension Output +==== (EXPRESSES RATIONALE) (see further below for current actual dpk proposed syntax... $$extensionOutput //= ( ; Expressed in CDDL devicePublicKey: AttObjForDevicePublicKey, @@ -193,32 +194,61 @@ AttObjForDevicePublicKey = { ; Note: This object conveys an attested device public key ; and is analogous to `attObj`. - sig: bstr, ; result of sign((clientDataHash || userCredentialId), devicePrivateKey) - ; Note that this sig value is unique per-response because clientDataHash contains - ; the per-request challenge. + sig: bstr, ; result of sign((clientDataHash || userCredentialId), devicePrivateKey) + ; Note that this sig value is unique per-response because the client data + ; contains the per-request challenge. - dpkAuthData: bstr, ; AuthDataForDevicePublicKeyAttestation goes here (see below) + dpkAuthData: { + aaguid: bstr ; authenticator's AAGUID + dpk: bstr ; the Device Public Key + ; (self-describing variable length, COSE_Key format (CBOR-encoded) + } $$attStmtType, ; see . - ; - ; Attestation statement formats define the `fmt` and `attStmt` members of - ; $$attStmtType. - ; - ; In summary, the `attStmt` (typically) contains: - ; (1) a SIGNATURE value calculated (using the attestation private key) - ; over the bytes of `authDataForDevicePublicKeyAttestation` and - ; (2) the attestation certificate or public key, and supporting certificates, if any. - ; - ; Note that there are details dependent upon the particular attestation statement - ; format. See . + ; + ; Attestation statement formats define the `fmt` and `attStmt` members of + ; $$attStmtType. + ; + ; In summary, the `attStmt` (typically) contains: + ; (1) a SIGNATURE value calculated (using the attestation private key) + ; over the bytes of `dpkAuthData` and + ; (2) the attestation certificate or public key, and supporting certificates, if any. + ; + ; Note that there are details dependent upon the particular attestation statement + ; format. See . } - AuthDataForDevicePublicKeyAttestation = { - aaguid: bstr ; authenticator's AAGUID - dPK: bstr ; self-describing variable length, COSE_Key format (CBOR-encoded) +...OR... + + AttObjForDevicePublicKey = { ; Note: This object conveys an attested device public key + ; and is analogous to `attObj`. + + sig: bstr, ; result of sign((clientDataHash || userCredentialId), devicePrivateKey) + ; Note that this sig value is unique per-response because the client data + ; contains the per-request challenge. + + aaguid: bstr, ; authenticator's AAGUID + + dpk: bstr, ; the Device Public Key + ; (self-describing variable length, COSE_Key format (CBOR-encoded) + + $$attStmtType, ; see . + ; + ; Attestation statement formats define the `fmt` and `attStmt` members of + ; $$attStmtType. + ; + ; In summary, the `attStmt` (typically) contains: + ; (1) a SIGNATURE value calculated (using the attestation private key) + ; over (aaguid || dpk). + ; (2) the attestation certificate or public key, and supporting certificates, if any. + ; + ; Note that there are details dependent upon the particular attestation statement + ; format. See . } + + ==== WebAuthn Signed Objects Hierarchy for Device-bound Key Pair aka Device-bound Public Key aka Secondary Key aka Device Key ==== AuthData for Registration and Authentication Operations returning a devicePublicKey extension response (expressed in RFC5234 ABNF; https://www.w3.org/TR/webauthn/#sctn-authenticator-data ): From a34b48938ffd24e645017139aa7c37a3edb28367 Mon Sep 17 00:00:00 2001 From: JeffH Date: Wed, 30 Jun 2021 16:38:44 -0700 Subject: [PATCH 031/131] select the more simple AttObjForDevicePublicKey --- device-bound-key-pair.pv | 90 ++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 58 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index 90cd7a373..99ef3f22b 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -187,64 +187,38 @@ ==== Proposed Syntax for devicePublicKey (dpk) (aka Secondary Key aka Device Key) Extension Output ==== - $$extensionOutput //= ( ; Expressed in CDDL - devicePublicKey: AttObjForDevicePublicKey, - ) - - AttObjForDevicePublicKey = { ; Note: This object conveys an attested device public key - ; and is analogous to `attObj`. - - sig: bstr, ; result of sign((clientDataHash || userCredentialId), devicePrivateKey) - ; Note that this sig value is unique per-response because the client data - ; contains the per-request challenge. - - dpkAuthData: { - aaguid: bstr ; authenticator's AAGUID - dpk: bstr ; the Device Public Key - ; (self-describing variable length, COSE_Key format (CBOR-encoded) - } - - $$attStmtType, ; see . - ; - ; Attestation statement formats define the `fmt` and `attStmt` members of - ; $$attStmtType. - ; - ; In summary, the `attStmt` (typically) contains: - ; (1) a SIGNATURE value calculated (using the attestation private key) - ; over the bytes of `dpkAuthData` and - ; (2) the attestation certificate or public key, and supporting certificates, if any. - ; - ; Note that there are details dependent upon the particular attestation statement - ; format. See . - } - -...OR... - - AttObjForDevicePublicKey = { ; Note: This object conveys an attested device public key - ; and is analogous to `attObj`. - - sig: bstr, ; result of sign((clientDataHash || userCredentialId), devicePrivateKey) - ; Note that this sig value is unique per-response because the client data - ; contains the per-request challenge. - - aaguid: bstr, ; authenticator's AAGUID - - dpk: bstr, ; the Device Public Key - ; (self-describing variable length, COSE_Key format (CBOR-encoded) - - $$attStmtType, ; see . - ; - ; Attestation statement formats define the `fmt` and `attStmt` members of - ; $$attStmtType. - ; - ; In summary, the `attStmt` (typically) contains: - ; (1) a SIGNATURE value calculated (using the attestation private key) - ; over (aaguid || dpk). - ; (2) the attestation certificate or public key, and supporting certificates, if any. - ; - ; Note that there are details dependent upon the particular attestation statement - ; format. See . - } +$$extensionOutput //= ( ; Expressed in CDDL + devicePubKey: AttObjForDevicePublicKey, +) + +AttObjForDevicePublicKey = { ; Note: This object conveys an attested device public key + ; and is analogous to `attObj`. + + sig: bstr, ; result of sign((clientDataHash || userCredentialId), devicePrivateKey) + ; Note that this sig value is unique per-response because the client data + ; contains the per-request challenge. + + aaguid: bstr, ; authenticator's AAGUID + ; (16 bytes fixed-length ). + + dpk: bstr, ; the Device Public Key + ; (self-describing variable length, COSE_Key format (CBOR-encoded)). + + $$attStmtType, ; see . + ; + ; Attestation statement formats define the `fmt` and `attStmt` members of + ; $$attStmtType. + ; + ; In summary, the `attStmt` will (typically) contain: + ; (1) a SIGNATURE value calculated (using the attestation private key) + ; over (aaguid || dpk). + ; (2) the attestation certificate or public key, and supporting certificates, + ; if any. + ; + ; Note that there are details dependent upon the particular attestation + ; statement format. + ; See . +} From 2832b5e5f36181e2b67b804a88ff04a8a03098f5 Mon Sep 17 00:00:00 2001 From: JeffH Date: Thu, 1 Jul 2021 13:06:38 -0700 Subject: [PATCH 032/131] begin reworking devicePubKey extension --- device-bound-key-pair.pv | 65 +++++++++++++++++++--------------------- index.bs | 28 +++++++++-------- 2 files changed, 45 insertions(+), 48 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index 99ef3f22b..eb7caba35 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -287,22 +287,19 @@ AttObjForDevicePublicKey = { ; Note: This object conveys an attested device publ extensions: { - devicePublicKeyExtensionResult: { + devicePubKey: { - dpkSignatureValue { // Is a UNIQUE value per response. SIGNED by devicePrivateKey. + sig: { // Is a UNIQUE value per response. SIGNED by devicePrivateKey. sign((clientDataHash || userCredentialId), devicePrivateKey) // userCredentialId is second because it is variable length } - AuthDataForDevicePublicKeyAttestation: { - rpIdHash // i.e., the "audience" - AAGUID // This is in `attestedCredentialData`, but attested cred data - // is not present in authentication assertions, so it needs to be - // here IF we wish this devicePublicKey extension result to be the - // same format whether it is returned as result of a registration - // or authn request. - devicePublicKey // The "attested" device public key. - } + aaguid // This is in `attestedCredentialData`, but attested cred data + // is not present in authentication assertions, so it needs to be + // here IF we wish this devicePublicKey extension result to be the + // same format whether it is returned as result of a registration + // or authn request. + dpk // The "attested" device public key. fmt: "...attestation statement format identifier..." @@ -310,7 +307,7 @@ AttObjForDevicePublicKey = { ; Note: This object conveys an attested device publ // Crucially containing a signature value, generated using // the device's (effective) AttestationPrivatekey, over this to-be-signed data: - AuthDataForDevicePublicKeyAttestation // SIGNED by AttestationPrivatekey. + ( aaguid || dpk ) // SIGNED by AttestationPrivatekey. // Crucially, this to-be-signed data does not include // clientDataHash because the latter is specific to these // current request, e.g., the hashed clientData includes @@ -354,36 +351,34 @@ AttObjForDevicePublicKey = { ; Note: This object conveys an attested device publ extensions: { - devicePublicKeyExtensionResult: { + devicePubKey: { - dpkSignatureValue { // Is a UNIQUE value per response. SIGNED by devicePrivateKey. + sig: { // Is a UNIQUE value per response. SIGNED by devicePrivateKey. sign((clientDataHash || userCredentialId), devicePrivateKey) - // userCredentialId is second because it is variable length + // userCredentialId is second because it is variable length } - AuthDataForDevicePublicKeyAttestationToBeSigned: { - rpIdHash // i.e., the "audience" - AAGUID // Needs to be here because if user cred is synced to "new" device - // a registration is not done on that device and the RP will receive - // a new devicePublicKey extension result from a get() call, and - // this will attest to the provenance of the authenticator. - devicePublicKey // The "attested" device public key. - } + aaguid // This is in `attestedCredentialData`, but attested cred data + // is not present in authentication assertions, so it needs to be + // here IF we wish this devicePublicKey extension result to be the + // same format whether it is returned as result of a registration + // or authn request. + dpk // The "attested" device public key. fmt: "...attestation statement format identifier..." - attStmt: { // Contains various items depending upon the attestation statement format, - // crucially containing a signature value, generated using the device's (effective) - // AttestationPrivatekey, over this to-be-signed data: - AuthDataForDevicePublicKeyAttestation - // - // Crucially, this to-be-signed data DOES NOT INCLUDE - // `clientDataHash` because the latter is specific to this - // current request, e.g., the hashed clientData includes - // the request challenge. - // - // NOTE: This `attStmt` is a CONSTANT value per context per device - // per account per RP. + attStmt: { // contains various items depending upon the attestation statement format, + // Crucially containing a signature value, generated using + // the device's (effective) AttestationPrivatekey, over this to-be-signed data: + + ( aaguid || dpk ) // SIGNED by AttestationPrivatekey. + // Crucially, this to-be-signed data does not include + // clientDataHash because the latter is specific to these + // current request, e.g., the hashed clientData includes + // the request challenge. + // + // Note: This `attStmt` is a CONSTANT value per context + // per device per account per RP. } } diff --git a/index.bs b/index.bs index 0bed8ea62..7ed78ecb6 100644 --- a/index.bs +++ b/index.bs @@ -3,7 +3,7 @@ Notes: * the h1 tag is spec title that is rendered within the document window. Wrapping may be controlled with break tags. The spec 'Level' value is not appended. * the Title metadata value is what is rendered in the browser's titlebar, any break - tags will be rendered. It also has the spec 'Level' value (gratuitously) appended. + tags will be rendered. It also has the spec 'Level' value (gratuitously) appended. -->

Web Authentication:
An API for accessing Public Key Credentials
Level 3

@@ -1019,7 +1019,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S : \[DEPRECATED] Resident Credential : \[DEPRECATED] Resident Key :: Note: Historically, [=client-side discoverable credentials=] have been known as [=resident credentials=] or [=resident keys=]. - Due to the phrases `ResidentKey` and `residentKey` being widely used in both the [=web authentication api|WebAuthn + Due to the phrases `ResidentKey` and `residentKey` being widely used in both the [=web authentication api|WebAuthn API=] and also in the [=Authenticator Model=] (e.g., in dictionary member names, algorithm variable names, and operation parameters) the usage of `resident` within their names has not been changed for backwards compatibility purposes. Also, the term [=resident key=] is @@ -1090,6 +1090,8 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S : Device-bound Key : Device Private Key : Device Public Key +: Device Key +: Secondary Key :: A [=hardware-bound device key pair=], also known as a [=device-bound key=], is a [=[RP]=]-specific and [=user credential=]-specific public key pair created upon a [=[RP]=]'s request via the [=devicePublicKey=] [=WebAuthn extension=]. The [=[RP]=] may supply this extension during both [=registration=] and [=authentication=]. The [=authenticator=] that a [=hardware-bound device key pair=] is created upon guarantees that the [=device private key=] is securely stored in hardware, i.e., it is unextractable. The [=device public key=] is returned only to the [=[RP]=] that created the [=hardware-bound device key pair=]. See [[#sctn-device-publickey-extension]]. @@ -2354,7 +2356,7 @@ Note: Invoking this method from a [=browsing context=] where the [=Web Authentic
: clientDataJSON :: This attribute contains a [[#clientdatajson-serialization|JSON-compatible serialization]] of the [=client data=], the [=hash of the serialized client data|hash of which=] is passed to the - authenticator by the client in its call to either {{CredentialsContainer/create()}} or {{CredentialsContainer/get()}} (i.e., the + authenticator by the client in its call to either {{CredentialsContainer/create()}} or {{CredentialsContainer/get()}} (i.e., the [=client data=] itself is not sent to the authenticator).
@@ -4276,10 +4278,10 @@ Note: This specification does not define any data structures explicitly expressi calling {{CredentialsContainer/create()|navigator.credentials.create()}} they select an [=attestation conveyance=] other than {{AttestationConveyancePreference/none}} and verify the received [=attestation statement=] — will determine the employed [=attestation type=] as a part of [=verification procedure|verification=]. See the "Verification procedure" subsections of -[[#sctn-defined-attestation-formats]]. See also [[#sctn-attestation-privacy]]. For all [=attestation types=] defined in this +[[#sctn-defined-attestation-formats]]. See also [[#sctn-attestation-privacy]]. For all [=attestation types=] defined in this section other than [=self attestation|Self=] and [=None=], [=[RP]=] [=verification procedure|verification=] is followed by matching the [=attestation trust path|trust path=] to an acceptable root certificate per step 21 of [[#sctn-registering-a-new-credential]]. -Differentiating these [=attestation types=] becomes useful primarily as a means for determining if the [=attestation=] is acceptable +Differentiating these [=attestation types=] becomes useful primarily as a means for determining if the [=attestation=] is acceptable under [=[RP]=] policy. : Basic Attestation (Basic) @@ -4371,7 +4373,7 @@ the [=authenticator=] MUST: It is RECOMMENDED that any new attestation formats defined not use ASN.1 encodings, but instead represent signatures as equivalent fixed-length byte arrays without internal structure, - using the same representations as used by COSE signatures as defined in [[!RFC8152]] and [[!RFC8230]]. + using the same representations as used by COSE signatures as defined in [[!RFC8152]] and [[!RFC8230]]. The below signature format definitions satisfy this requirement and serve as examples for deriving the same for other signature algorithms not explicitly mentioned here: @@ -4407,7 +4409,7 @@ In order to perform a [=registration ceremony=], the [=[RP]=] MUST proceed as fo Let |credential| be the result of the successfully resolved promise. If the promise is rejected, abort the ceremony with a user-visible error, or otherwise guide the user experience as might be determinable from the context available in the rejected promise. For example if the promise is rejected with - an error code equivalent to "{{InvalidStateError}}", the user might be instructed to use a different [=authenticator=]. + an error code equivalent to "{{InvalidStateError}}", the user might be instructed to use a different [=authenticator=]. For information on different error contexts and the circumstances leading to them, see [[#sctn-op-make-cred]]. 1. Let |response| be |credential|.{{PublicKeyCredential/response}}. @@ -4546,8 +4548,8 @@ In order to perform an [=authentication ceremony=], the [=[RP]=] MUST proceed as 1. Call {{CredentialsContainer/get()|navigator.credentials.get()}} and pass |options| as the {{CredentialRequestOptions/publicKey}} option. Let |credential| be the result of the successfully resolved promise. - If the promise is rejected, abort the ceremony with a user-visible error, or otherwise guide the user experience as might - be determinable from the context available in the rejected promise. For information on different error contexts and the + If the promise is rejected, abort the ceremony with a user-visible error, or otherwise guide the user experience as might + be determinable from the context available in the rejected promise. For information on different error contexts and the circumstances leading to them, see [[#sctn-op-get-assertion]]. 1. Let |response| be |credential|.{{PublicKeyCredential/response}}. @@ -5393,7 +5395,7 @@ New extensions SHOULD follow the above convention. publicKey: { // Other members omitted for brevity extensions: { - // An "entry key" identifying the "webauthnExample_foobar" extension, + // An "entry key" identifying the "webauthnExample_foobar" extension, // whose value is a map with two input parameters: "webauthnExample_foobar": { foo: 42, @@ -5926,7 +5928,7 @@ Note: In order to interoperate, user agents storing large blobs on authenticator :: [=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]=]. -## Device-bound public key extension (devicePublicKey) ## {#sctn-device-publickey-extension} +## Device-bound public key extension (devicePubKey) ## {#sctn-device-publickey-extension} This [=client extension|client=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal. This is done by creating a [=[RP]=]-specific [=hardware-bound device key pair=] on the [=client device=], if such a key pair does not already exist for the [=user credential=] being created or exercised, and returning the attested [=device public key=] along with a signature by the [=device private key=] to the [=[RP]=]. This is done each time this [=devicePublicKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/create()|navigator.credentials.get()}} call. @@ -5934,7 +5936,7 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke : Extension identifier -:: `devicePublicKey` +:: `devicePubKey` : Operation applicability :: [=registration extension|Registration=] and [=authentication extension|authentication=] @@ -5972,7 +5974,7 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke :: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: 1. Create or select the [=user credential=] as usual. 1. If a [=hardware-bound device key pair=] does not already exist for this [=user credential=] on the [=authenticator=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing [=device-bound key=]. - 1. Let `devicePublicKey` be the newly created or existing [=device public key=], in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=] when conveyed in [=attested credential data=]. + 1. Let `devicePublicKey` be the newly created or existing [=device public key=], in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=] when the latter is conveyed in [=attested credential data=]. 1. Let `devicePrivateKey` be the newly created or existing [=device private key=]. 1. Let `clientDataHash` be the [=hash of the serialized client data=]. 1. Let `dpkSignatureValue` be the result of `sign(devicePrivateKey, (clientDataHash || devicePublicKey))` using the signature algorithm appropriate for the `devicePrivateKey`'s public key algorithm. From e47c5f8ed448e0025d6388d9e098c1813f0bec4e Mon Sep 17 00:00:00 2001 From: JeffH Date: Thu, 1 Jul 2021 15:09:50 -0700 Subject: [PATCH 033/131] editorial --- device-bound-key-pair.pv | 272 ++++++--------------------------------- 1 file changed, 41 insertions(+), 231 deletions(-) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.pv index eb7caba35..f79756445 100644 --- a/device-bound-key-pair.pv +++ b/device-bound-key-pair.pv @@ -1,9 +1,47 @@ -(****** for the proverif model, find section entitled "ProVerif Web Authentication Formal Model" below... ******) -(**** Device-bound Public Key | Device-bound Key Pair | Device key | Context Key | Secondary Key **** + +==== Proposed Syntax for devicePublicKey (dpk) (aka Secondary Key aka Device Key) Extension Output ==== + +$$extensionOutput //= ( ; Expressed in CDDL + devicePubKey: AttObjForDevicePublicKey, +) + +AttObjForDevicePublicKey = { ; Note: This object conveys an attested device public key + ; and is analogous to `attObj`. + + sig: bstr, ; result of sign((clientDataHash || userCredentialId), devicePrivateKey) + ; Note that this sig value is unique per-response because the client data + ; contains the per-request challenge. + + aaguid: bstr, ; authenticator's AAGUID + ; (16 bytes fixed-length ). + + dpk: bstr, ; the Device Public Key + ; (self-describing variable length, COSE_Key format (CBOR-encoded)). + + $$attStmtType, ; see . + ; + ; Attestation statement formats define the `fmt` and `attStmt` members of + ; $$attStmtType. + ; + ; In summary, the `attStmt` will (typically) contain: + ; (1) a SIGNATURE value calculated (using the attestation private key) + ; over (aaguid || dpk). + ; (2) the attestation certificate or public key, and supporting certificates, + ; if any. + ; + ; Note that there are details dependent upon the particular attestation + ; statement format. + ; See . +} + + + +==== SUPPORTING INFORMATION and MUSINGS: + ==== WebAuthn Authenticator Response Messages, which include: ==== ==== Collected Client Data, Attestation Object, Authenticator Data structures, ==== ==== and (nested) signature values calculated over portions of them. ==== @@ -185,43 +223,6 @@ -==== Proposed Syntax for devicePublicKey (dpk) (aka Secondary Key aka Device Key) Extension Output ==== - -$$extensionOutput //= ( ; Expressed in CDDL - devicePubKey: AttObjForDevicePublicKey, -) - -AttObjForDevicePublicKey = { ; Note: This object conveys an attested device public key - ; and is analogous to `attObj`. - - sig: bstr, ; result of sign((clientDataHash || userCredentialId), devicePrivateKey) - ; Note that this sig value is unique per-response because the client data - ; contains the per-request challenge. - - aaguid: bstr, ; authenticator's AAGUID - ; (16 bytes fixed-length ). - - dpk: bstr, ; the Device Public Key - ; (self-describing variable length, COSE_Key format (CBOR-encoded)). - - $$attStmtType, ; see . - ; - ; Attestation statement formats define the `fmt` and `attStmt` members of - ; $$attStmtType. - ; - ; In summary, the `attStmt` will (typically) contain: - ; (1) a SIGNATURE value calculated (using the attestation private key) - ; over (aaguid || dpk). - ; (2) the attestation certificate or public key, and supporting certificates, - ; if any. - ; - ; Note that there are details dependent upon the particular attestation - ; statement format. - ; See . -} - - - ==== WebAuthn Signed Objects Hierarchy for Device-bound Key Pair aka Device-bound Public Key aka Secondary Key aka Device Key ==== @@ -399,196 +400,5 @@ AttObjForDevicePublicKey = { ; Note: This object conveys an attested device publ -****) - -(* ProVerif Web Authentication Formal Model - originally by Iness Ben Guirat and Harry Halpin - Paper: https://dl.acm.org/doi/10.1145/3190619.3190640 - Slides: https://cps-vo.org/node/48511 - github: https://github.com/hhalpin/weauthn-model -*) - -(**** webauthn-basic.pv **** - -See the paper section 5.2 for a detailed description of this model. - -In summary: This models first registration of a WebAuthn user credential with a server (aka Relying Party) and then subsequent authentication using that registered credential. The model seeks to prove various properties of the protocol. - -Updated by Jeff Hodges with corrections and comments, May 2021 -http://kingsmountain.com/people/jeff.hodges/ -****) - -#include "crypto.pvl" -#include "named_tuples.pvl" - -free c:channel. (* TLS channel *) - - -type nonce. - -(***** Server's ID *****) - -type RP_id. -const a: RP_id [private]. - -(***** Attestation Key Pair ***** - -free attSecretKey: SKey [private]. -free attPublicKey: PKey [private]. - - -(***** Server's and authnr+clientPlatform's tuple messages *****) - -(* authnr+clientPlatform returns this to server: *) -DEFINE_DATA_TYPE4(RegResponseMsg, - userCredPubKey, PKey, - attPublicKey, PKey, - receivedRegChallenge, nonce, - attSignature, bitstring). - -fun RegResponseMsg2b( RegResponseMsg ): bitstring [data , typeConverter]. -fun B2RegResponseMsg( bitstring ): RegResponseMsg [data , typeConverter]. - - -(* server sends this: *) -DEFINE_DATA_TYPE2(AuthnRequestMsg, - authnChallenge, nonce, - serverName, RP_id). - -fun AuthnRequestMsg2b( AuthnRequestMsg ): bitstring [data , typeConverter]. -fun B2AuthnRequestMsg( bitstring ): AuthnRequestMsg [data , typeConverter]. - - -(* user's authnr+clientPlatform returns this to server: *) -DEFINE_DATA_TYPE3(AuthnResponseMsg, - returnedAuthnChallenge, nonce, - serverName, RP_id, - assertSignature, bitstring). - -fun AuthnResponseMsg2b( AuthnResponseMsg ): bitstring [data , typeConverter]. -fun B2AuthnResponseMsg( bitstring ): AuthnResponseMsg [data , typeConverter]. - - - -free tlsKey: Key [private]. (* TLS symmetric encryption key *) - - - - -(***** Events and Queries *****) - -event sentChallengeResponse(bitstring, bitstring). -event validChallengeResponse(bitstring, bitstring). - -(* we can only receive a valid response if the user sent a response message *) -query N:bitstring, s:bitstring; event(validChallengeResponse(N, s)) ==> event(sentChallengeResponse(N, s)). - -(* query N:bitstring; event(reachAuthentication(N)). -event reachAuthentication(bitstring). *) - -(* query attacker(attSecretKey). *) -(* query attacker(attPublicKey). *) -query attacker(tlsKey). - - - -(***** Processes *****) - -let processUser ( k: Key, attskU: SKey, attpkU: PKey) = - - (* Registration *) - - in(c, s:bitstring); - let (challengeU:nonce, a:RP_id) = dec(s,k) in - - new skU: SKey; (* mint new user credential "secret key user" |skU| *) - let pkU = pk(skU) in (* and "public key user" |pkU| *) - - let regResponseMsg = BuildRegResponseMsg(pkU, attpkU, challengeU, sign((pkU, challengeU), attskU)) in - let regResponseMsgb = RegResponseMsg2b(regResponseMsg) in - - out(c, (* return registration response with attestation *) - enc(regResponseMsgb, k) - ); - - - (* Authentication *) - - in(c, encAuthnRequestMsg: bitstring); (* receive server's challenge. *) - let authnRequestMsgb = dec(encAuthnRequestMsg, k) in - let authnRequestMsg = B2AuthnRequestMsg(authnRequestMsgb) in - let receivedAuthnChallenge = AuthnRequestMsg_authnChallenge(authnRequestMsg) in - let receivedServerName = AuthnRequestMsg_serverName(authnRequestMsg) in - - let assertionSignature = sign((receivedAuthnChallenge, receivedServerName), skU) in - let authnResponseMsg = BuildAuthnResponseMsg(receivedAuthnChallenge, receivedServerName, assertionSignature) in - let authnResponseMsgb = AuthnResponseMsg2b(authnResponseMsg) in - - event sentChallengeResponse( authnResponseMsgb, enc(authnResponseMsgb, k) ); - out(c, - enc(authnResponseMsgb, k) - ). - - - -let processServer (k: Key, a: RP_id) = - - (* Registration *) - - new regChallenge: nonce; - - out(c, enc((regChallenge, a), k)); (* send registration request *) - in(c, encRegResponseMsg: bitstring); (* receive possible registration response *) - - let regResponseMsgb = dec(encRegResponseMsg, k) in (* decrypt and parse |encRegResponseMsg| *) - let regResponseMsg = B2RegResponseMsg(regResponseMsgb) in - let userPK = RegResponseMsg_userCredPubKey(regResponseMsg) in - let attPK = RegResponseMsg_attPublicKey(regResponseMsg) in - let returnedRegChallenge = RegResponseMsg_receivedRegChallenge(regResponseMsg) in - let attSig = RegResponseMsg_attSignature(regResponseMsg) in - - if checkSign(attSig, (userPK, returnedRegChallenge), attPK) then (* we have a registration response msg with - a valid signature... *) - - if (regChallenge = returnedRegChallenge) then ( (* ...and if the challenge returned matches the then we - one we sent, will proceed to challenge the user to - authenticate. *) - - (* Authentication *) - - new authnChallenge: nonce; - - (* let authnChallengeBitstr = nonce_to_bitstring( authnChallenge ) in -- do we need to do this type conversion? *) - - let authnRequestMsg = BuildAuthnRequestMsg(authnChallenge, a) in - let authnRequestMsgb = AuthnRequestMsg2b(authnRequestMsg) in - - out(c, enc( authnRequestMsgb, k) ); (* send authentication request *) - - in(c, encAuthnResponseMsg: bitstring); - let authnResponseMsgb = dec(encAuthnResponseMsg, k) in - let authnResponseMsg = B2AuthnResponseMsg(authnResponseMsgb) in - let retAuthnChallenge = AuthnResponseMsg_returnedAuthnChallenge(authnResponseMsg) in - let retServerName = AuthnResponseMsg_serverName(authnResponseMsg) in - let retAssertSignature = AuthnResponseMsg_assertSignature(authnResponseMsg) in - - if (authnChallenge = retAuthnChallenge) && (a = retServerName) then - if checkSign(retAssertSignature, (retAuthnChallenge, retServerName), userPK) then - - event validChallengeResponse(authnResponseMsgb, encAuthnResponseMsg) - ). - - - -process - new tlsKey: Key; (* assume TLS handshake is error-free and yields this symmetric channel-encryption key *) - new attSecretKey: SKey; (* assume a particular authenticator with a particular attestation - private key *) - - let attPublicKey = pk(attSecretKey) in - ( - !processUser(tlsKey, attSecretKey, attPublicKey) | !processServer(tlsKey, a) - ) - -(***** END *****) +==== end ==== \ No newline at end of file From 75c8f251574709c98b8800809cae0fc564a5d7ad Mon Sep 17 00:00:00 2001 From: JeffH Date: Thu, 1 Jul 2021 15:14:31 -0700 Subject: [PATCH 034/131] device-bound-key-pair.pv -> device-bound-key-pair.txt --- device-bound-key-pair.pv => device-bound-key-pair.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename device-bound-key-pair.pv => device-bound-key-pair.txt (100%) diff --git a/device-bound-key-pair.pv b/device-bound-key-pair.txt similarity index 100% rename from device-bound-key-pair.pv rename to device-bound-key-pair.txt From 4515d630af96ad939b7cffa0e72eef2887960f68 Mon Sep 17 00:00:00 2001 From: JeffH Date: Thu, 1 Jul 2021 15:16:58 -0700 Subject: [PATCH 035/131] add separate webauthn.pv file --- webauthn.pv | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 webauthn.pv diff --git a/webauthn.pv b/webauthn.pv new file mode 100644 index 000000000..554b3a3d5 --- /dev/null +++ b/webauthn.pv @@ -0,0 +1,191 @@ +(* ProVerif Web Authentication Formal Model + originally by Iness Ben Guirat and Harry Halpin + Paper: https://dl.acm.org/doi/10.1145/3190619.3190640 + Slides: https://cps-vo.org/node/48511 + github: https://github.com/hhalpin/weauthn-model +*) + +(**** webauthn-basic.pv **** + +See the paper section 5.2 for a detailed description of this model. + +In summary: This models first registration of a WebAuthn user credential with a server (aka Relying Party) and then subsequent authentication using that registered credential. The model seeks to prove various properties of the protocol. + +Updated by Jeff Hodges with corrections and comments, May 2021 +http://kingsmountain.com/people/jeff.hodges/ +****) + +#include "crypto.pvl" +#include "named_tuples.pvl" + +free c:channel. (* TLS channel *) + + +type nonce. + +(***** Server's ID *****) + +type RP_id. +const a: RP_id [private]. + +(***** Attestation Key Pair ***** + +free attSecretKey: SKey [private]. +free attPublicKey: PKey [private]. + + +(***** Server's and authnr+clientPlatform's tuple messages *****) + +(* authnr+clientPlatform returns this to server: *) +DEFINE_DATA_TYPE4(RegResponseMsg, + userCredPubKey, PKey, + attPublicKey, PKey, + receivedRegChallenge, nonce, + attSignature, bitstring). + +fun RegResponseMsg2b( RegResponseMsg ): bitstring [data , typeConverter]. +fun B2RegResponseMsg( bitstring ): RegResponseMsg [data , typeConverter]. + + +(* server sends this: *) +DEFINE_DATA_TYPE2(AuthnRequestMsg, + authnChallenge, nonce, + serverName, RP_id). + +fun AuthnRequestMsg2b( AuthnRequestMsg ): bitstring [data , typeConverter]. +fun B2AuthnRequestMsg( bitstring ): AuthnRequestMsg [data , typeConverter]. + + +(* user's authnr+clientPlatform returns this to server: *) +DEFINE_DATA_TYPE3(AuthnResponseMsg, + returnedAuthnChallenge, nonce, + serverName, RP_id, + assertSignature, bitstring). + +fun AuthnResponseMsg2b( AuthnResponseMsg ): bitstring [data , typeConverter]. +fun B2AuthnResponseMsg( bitstring ): AuthnResponseMsg [data , typeConverter]. + + + +free tlsKey: Key [private]. (* TLS symmetric encryption key *) + + + + +(***** Events and Queries *****) + +event sentChallengeResponse(bitstring, bitstring). +event validChallengeResponse(bitstring, bitstring). + +(* we can only receive a valid response if the user sent a response message *) +query N:bitstring, s:bitstring; event(validChallengeResponse(N, s)) ==> event(sentChallengeResponse(N, s)). + +(* query N:bitstring; event(reachAuthentication(N)). +event reachAuthentication(bitstring). *) + +(* query attacker(attSecretKey). *) +(* query attacker(attPublicKey). *) +query attacker(tlsKey). + + + +(***** Processes *****) + +let processUser ( k: Key, attskU: SKey, attpkU: PKey) = + + (* Registration *) + + in(c, s:bitstring); + let (challengeU:nonce, a:RP_id) = dec(s,k) in + + new skU: SKey; (* mint new user credential "secret key user" |skU| *) + let pkU = pk(skU) in (* and "public key user" |pkU| *) + + let regResponseMsg = BuildRegResponseMsg(pkU, attpkU, challengeU, sign((pkU, challengeU), attskU)) in + let regResponseMsgb = RegResponseMsg2b(regResponseMsg) in + + out(c, (* return registration response with attestation *) + enc(regResponseMsgb, k) + ); + + + (* Authentication *) + + in(c, encAuthnRequestMsg: bitstring); (* receive server's challenge. *) + let authnRequestMsgb = dec(encAuthnRequestMsg, k) in + let authnRequestMsg = B2AuthnRequestMsg(authnRequestMsgb) in + let receivedAuthnChallenge = AuthnRequestMsg_authnChallenge(authnRequestMsg) in + let receivedServerName = AuthnRequestMsg_serverName(authnRequestMsg) in + + let assertionSignature = sign((receivedAuthnChallenge, receivedServerName), skU) in + let authnResponseMsg = BuildAuthnResponseMsg(receivedAuthnChallenge, receivedServerName, assertionSignature) in + let authnResponseMsgb = AuthnResponseMsg2b(authnResponseMsg) in + + event sentChallengeResponse( authnResponseMsgb, enc(authnResponseMsgb, k) ); + out(c, + enc(authnResponseMsgb, k) + ). + + + +let processServer (k: Key, a: RP_id) = + + (* Registration *) + + new regChallenge: nonce; + + out(c, enc((regChallenge, a), k)); (* send registration request *) + in(c, encRegResponseMsg: bitstring); (* receive possible registration response *) + + let regResponseMsgb = dec(encRegResponseMsg, k) in (* decrypt and parse |encRegResponseMsg| *) + let regResponseMsg = B2RegResponseMsg(regResponseMsgb) in + let userPK = RegResponseMsg_userCredPubKey(regResponseMsg) in + let attPK = RegResponseMsg_attPublicKey(regResponseMsg) in + let returnedRegChallenge = RegResponseMsg_receivedRegChallenge(regResponseMsg) in + let attSig = RegResponseMsg_attSignature(regResponseMsg) in + + if checkSign(attSig, (userPK, returnedRegChallenge), attPK) then (* we have a registration response msg with + a valid signature... *) + + if (regChallenge = returnedRegChallenge) then ( (* ...and if the challenge returned matches the then we + one we sent, will proceed to challenge the user to + authenticate. *) + + (* Authentication *) + + new authnChallenge: nonce; + + (* let authnChallengeBitstr = nonce_to_bitstring( authnChallenge ) in -- do we need to do this type conversion? *) + + let authnRequestMsg = BuildAuthnRequestMsg(authnChallenge, a) in + let authnRequestMsgb = AuthnRequestMsg2b(authnRequestMsg) in + + out(c, enc( authnRequestMsgb, k) ); (* send authentication request *) + + in(c, encAuthnResponseMsg: bitstring); + let authnResponseMsgb = dec(encAuthnResponseMsg, k) in + let authnResponseMsg = B2AuthnResponseMsg(authnResponseMsgb) in + let retAuthnChallenge = AuthnResponseMsg_returnedAuthnChallenge(authnResponseMsg) in + let retServerName = AuthnResponseMsg_serverName(authnResponseMsg) in + let retAssertSignature = AuthnResponseMsg_assertSignature(authnResponseMsg) in + + if (authnChallenge = retAuthnChallenge) && (a = retServerName) then + if checkSign(retAssertSignature, (retAuthnChallenge, retServerName), userPK) then + + event validChallengeResponse(authnResponseMsgb, encAuthnResponseMsg) + ). + + + +process + new tlsKey: Key; (* assume TLS handshake is error-free and yields this symmetric channel-encryption key *) + new attSecretKey: SKey; (* assume a particular authenticator with a particular attestation + private key *) + + let attPublicKey = pk(attSecretKey) in + ( + !processUser(tlsKey, attSecretKey, attPublicKey) | !processServer(tlsKey, a) + ) + + +(***** END *****) From c208e19e149dd98544521eead0beafe71c1e2756 Mon Sep 17 00:00:00 2001 From: JeffH Date: Thu, 1 Jul 2021 15:23:32 -0700 Subject: [PATCH 036/131] editorial --- webauthn.pv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webauthn.pv b/webauthn.pv index 554b3a3d5..860f92a0f 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -1,6 +1,6 @@ (* ProVerif Web Authentication Formal Model - originally by Iness Ben Guirat and Harry Halpin - Paper: https://dl.acm.org/doi/10.1145/3190619.3190640 + based on webauthn-basic.pv by Iness Ben Guirat + Paper: https://dl.acm.org/doi/10.1145/3190619.3190640 (by Iness Ben Guirat and Harry Halpin) Slides: https://cps-vo.org/node/48511 github: https://github.com/hhalpin/weauthn-model *) From c3487a217de7a7647226269924322fdda1eeb02a Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 2 Jul 2021 10:46:49 -0700 Subject: [PATCH 037/131] fix attSecretKey in pv model --- webauthn.pv | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webauthn.pv b/webauthn.pv index 860f92a0f..77540c18c 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -28,7 +28,7 @@ type nonce. type RP_id. const a: RP_id [private]. -(***** Attestation Key Pair ***** +(***** Attestation Key Pair *****) free attSecretKey: SKey [private]. free attPublicKey: PKey [private]. @@ -83,8 +83,8 @@ query N:bitstring, s:bitstring; event(validChallengeResponse(N, s)) ==> event(se (* query N:bitstring; event(reachAuthentication(N)). event reachAuthentication(bitstring). *) -(* query attacker(attSecretKey). *) -(* query attacker(attPublicKey). *) +query attacker(attSecretKey). +query attacker(attPublicKey). query attacker(tlsKey). From 0e8d3b362c602e23cb4a2335a4aa21d0e0dca10a Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 2 Jul 2021 10:47:41 -0700 Subject: [PATCH 038/131] add README.pv.md file --- README.pv.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 README.pv.md diff --git a/README.pv.md b/README.pv.md new file mode 100644 index 000000000..1ac039c21 --- /dev/null +++ b/README.pv.md @@ -0,0 +1,50 @@ +# WebAuthn protocol verification effort + +This directory uses the C Preprocessor to modularize ProVerif models. It also +introduces `*_test.pv` files that do testing in the applied pi calculus by +setting up processes that send out either `Success` or `Failure` bitstrings and +querying ProVerif to see if the adversary can get a `Failure` message. See +`list_test.pv` for an example. + +## Running ProVerif + +To run a file in ProVerif, use the script `run_proverif.sh`. This script assumes +that ProVerif is installed on the local machine. The typical usage is: + +```bash +$ ./run_proverif.sh list_test.pv +``` + +NOTE: You can only run `.pv` files, and not `.pvl` (library) files. + +### Debugging syntax errors + +If the ProVerif script has a syntax error, the output will refer to a line in +one of the generated files. The temporary files will be automatically cleaned up +after the script finishes. To disable this automatic cleanup, set the +environment variable `PROVERIF_NO_CLEANUP`. For example: + +```bash +$ PROVERIF_NO_CLEANUP=1 ./run_proverif.sh list_test.pv +``` + +## Running ProVerif Interactively + +The `run_proverif.sh` script supports an environment variable +`PROVERIF_INTERACT`. If this variable is set, then `run_proverif.sh` will call +`proverif_interact` on the generated file instead of `proverif`. This brings up +an interactive GUI that allows the user to act as the adversary in a protocol. +For example: + +```bash +$ PROVERIF_INTERACT=1 ./run_proverif.sh ekep.pv +``` + +To facilitate the job of the adversary in interactive mode, the `ekep.pvl` file +conditionally defines helper functions that support generating some of the +messages that would otherwise be tedious and error-prone to type. See the +`ifdef ENABLE_DEBUG_FUNCTIONS` block in `ekep.pvl` for these functions. + +The `run_proverif.sh` script always defines `ENABLE_DEBUG_FUNCTIONS` when +`PROVERIF_INTERACT` is set. + From 6b216db9d7dcbbafc20ff20f02313093ef73b822 Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 2 Jul 2021 11:10:39 -0700 Subject: [PATCH 039/131] editorial cleanups --- webauthn.pv | 52 +++++++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/webauthn.pv b/webauthn.pv index 77540c18c..2bb70ca56 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -1,42 +1,53 @@ -(* ProVerif Web Authentication Formal Model +(* ProVerif Web Authentication Formal Model: webauthn.pv based on webauthn-basic.pv by Iness Ben Guirat Paper: https://dl.acm.org/doi/10.1145/3190619.3190640 (by Iness Ben Guirat and Harry Halpin) Slides: https://cps-vo.org/node/48511 github: https://github.com/hhalpin/weauthn-model *) -(**** webauthn-basic.pv **** +(* INTRODUCTION: -See the paper section 5.2 for a detailed description of this model. +See the paper section 5.2 for a detailed description of the original model. -In summary: This models first registration of a WebAuthn user credential with a server (aka Relying Party) and then subsequent authentication using that registered credential. The model seeks to prove various properties of the protocol. +This models a regostration of a WebAuthn user credential with a server (aka Relying Party) and then models subsequent authentication using that registered credential. The model seeks to prove various properties of the protocol. -Updated by Jeff Hodges with corrections and comments, May 2021 +This current model is by Jeff Hodges, July 2021 http://kingsmountain.com/people/jeff.hodges/ -****) + +*) + #include "crypto.pvl" #include "named_tuples.pvl" -free c:channel. (* TLS channel *) +(** Comms Channel **) + +free c: channel. (* TLS channel *) + +free tlsKey: Key [private]. (* TLS symmetric encryption key *) + + +(* Various locally-defined types *) +(* (see crypto.pvl and named_tuples.pvl for types not defined in this file) *) type nonce. -(***** Server's ID *****) + +(** Server's ID **) type RP_id. const a: RP_id [private]. -(***** Attestation Key Pair *****) +(** Attestation Key Pair **) free attSecretKey: SKey [private]. free attPublicKey: PKey [private]. -(***** Server's and authnr+clientPlatform's tuple messages *****) +(** Server's and authnr+clientPlatform's tuple messages **) -(* authnr+clientPlatform returns this to server: *) +(* Registration Response message -- authnr+clientPlatform returns this to server: *) DEFINE_DATA_TYPE4(RegResponseMsg, userCredPubKey, PKey, attPublicKey, PKey, @@ -47,7 +58,7 @@ fun RegResponseMsg2b( RegResponseMsg ): bitstring [data , typeConverter]. fun B2RegResponseMsg( bitstring ): RegResponseMsg [data , typeConverter]. -(* server sends this: *) +(* Authentication Request message -- server sends this to the client: *) DEFINE_DATA_TYPE2(AuthnRequestMsg, authnChallenge, nonce, serverName, RP_id). @@ -56,7 +67,7 @@ fun AuthnRequestMsg2b( AuthnRequestMsg ): bitstring [data , typeConverter]. fun B2AuthnRequestMsg( bitstring ): AuthnRequestMsg [data , typeConverter]. -(* user's authnr+clientPlatform returns this to server: *) +(* Authentication Response message -- user's authnr+clientPlatform returns this to server: *) DEFINE_DATA_TYPE3(AuthnResponseMsg, returnedAuthnChallenge, nonce, serverName, RP_id, @@ -67,12 +78,7 @@ fun B2AuthnResponseMsg( bitstring ): AuthnResponseMsg [data , typeConverter]. -free tlsKey: Key [private]. (* TLS symmetric encryption key *) - - - - -(***** Events and Queries *****) +(** Events and Queries **) event sentChallengeResponse(bitstring, bitstring). event validChallengeResponse(bitstring, bitstring). @@ -89,7 +95,7 @@ query attacker(tlsKey). -(***** Processes *****) +(** Processes **) let processUser ( k: Key, attskU: SKey, attpkU: PKey) = @@ -148,7 +154,7 @@ let processServer (k: Key, a: RP_id) = a valid signature... *) if (regChallenge = returnedRegChallenge) then ( (* ...and if the challenge returned matches the then we - one we sent, will proceed to challenge the user to + one we sent, we will proceed to challenge the user to authenticate. *) (* Authentication *) @@ -173,7 +179,7 @@ let processServer (k: Key, a: RP_id) = if checkSign(retAssertSignature, (retAuthnChallenge, retServerName), userPK) then event validChallengeResponse(authnResponseMsgb, encAuthnResponseMsg) - ). + ). @@ -188,4 +194,4 @@ process ) -(***** END *****) +(** END **) From 25b07e6ea75c9a94f80965aa0544388d6ccdbf16 Mon Sep 17 00:00:00 2001 From: JeffH Date: Mon, 5 Jul 2021 14:37:12 -0700 Subject: [PATCH 040/131] processUser -> processClientAndAuthnr --- webauthn.pv | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/webauthn.pv b/webauthn.pv index 2bb70ca56..266e74a62 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -9,7 +9,7 @@ See the paper section 5.2 for a detailed description of the original model. -This models a regostration of a WebAuthn user credential with a server (aka Relying Party) and then models subsequent authentication using that registered credential. The model seeks to prove various properties of the protocol. +This models a registration of a WebAuthn user credential with a server (aka Relying Party) and then models subsequent authentication using that registered credential. The model seeks to prove various properties of the protocol. This current model is by Jeff Hodges, July 2021 http://kingsmountain.com/people/jeff.hodges/ @@ -83,7 +83,7 @@ fun B2AuthnResponseMsg( bitstring ): AuthnResponseMsg [data , typeConverter]. event sentChallengeResponse(bitstring, bitstring). event validChallengeResponse(bitstring, bitstring). -(* we can only receive a valid response if the user sent a response message *) +(* we can only receive a valid response if the user's authnr+clientPlatform sent a response message *) query N:bitstring, s:bitstring; event(validChallengeResponse(N, s)) ==> event(sentChallengeResponse(N, s)). (* query N:bitstring; event(reachAuthentication(N)). @@ -97,7 +97,7 @@ query attacker(tlsKey). (** Processes **) -let processUser ( k: Key, attskU: SKey, attpkU: PKey) = +let processClientAndAuthnr ( k: Key, attskU: SKey, attpkU: PKey) = (* Registration *) @@ -190,7 +190,7 @@ process let attPublicKey = pk(attSecretKey) in ( - !processUser(tlsKey, attSecretKey, attPublicKey) | !processServer(tlsKey, a) + !processClientAndAuthnr(tlsKey, attSecretKey, attPublicKey) | !processServer(tlsKey, a) ) From 2da450453c77a10d0c90cc55345951762668160b Mon Sep 17 00:00:00 2001 From: JeffH Date: Mon, 5 Jul 2021 15:08:04 -0700 Subject: [PATCH 041/131] define formal RegRequestMsg --- webauthn.pv | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/webauthn.pv b/webauthn.pv index 266e74a62..5fdf792f9 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -47,6 +47,15 @@ free attPublicKey: PKey [private]. (** Server's and authnr+clientPlatform's tuple messages **) +(* Registration Request message -- server sends this to the user's authnr+clientPlatform: *) +DEFINE_DATA_TYPE2(RegRequestMsg, + regChallenge, nonce, + serverName, RP_id). + +fun RegRequestMsg2b( RegRequestMsg ): bitstring [data, typeConverter]. +fun B2RegRequestMsg( bitstring ): RegRequestMsg [data, typeConverter]. + + (* Registration Response message -- authnr+clientPlatform returns this to server: *) DEFINE_DATA_TYPE4(RegResponseMsg, userCredPubKey, PKey, @@ -54,8 +63,9 @@ DEFINE_DATA_TYPE4(RegResponseMsg, receivedRegChallenge, nonce, attSignature, bitstring). -fun RegResponseMsg2b( RegResponseMsg ): bitstring [data , typeConverter]. -fun B2RegResponseMsg( bitstring ): RegResponseMsg [data , typeConverter]. +fun RegResponseMsg2b( RegResponseMsg ): bitstring [data, typeConverter]. +fun B2RegResponseMsg( bitstring ): RegResponseMsg [data, typeConverter]. + (* Authentication Request message -- server sends this to the client: *) @@ -63,8 +73,8 @@ DEFINE_DATA_TYPE2(AuthnRequestMsg, authnChallenge, nonce, serverName, RP_id). -fun AuthnRequestMsg2b( AuthnRequestMsg ): bitstring [data , typeConverter]. -fun B2AuthnRequestMsg( bitstring ): AuthnRequestMsg [data , typeConverter]. +fun AuthnRequestMsg2b( AuthnRequestMsg ): bitstring [data, typeConverter]. +fun B2AuthnRequestMsg( bitstring ): AuthnRequestMsg [data, typeConverter]. (* Authentication Response message -- user's authnr+clientPlatform returns this to server: *) @@ -73,9 +83,8 @@ DEFINE_DATA_TYPE3(AuthnResponseMsg, serverName, RP_id, assertSignature, bitstring). -fun AuthnResponseMsg2b( AuthnResponseMsg ): bitstring [data , typeConverter]. -fun B2AuthnResponseMsg( bitstring ): AuthnResponseMsg [data , typeConverter]. - +fun AuthnResponseMsg2b( AuthnResponseMsg ): bitstring [data, typeConverter]. +fun B2AuthnResponseMsg( bitstring ): AuthnResponseMsg [data, typeConverter]. (** Events and Queries **) @@ -101,13 +110,16 @@ let processClientAndAuthnr ( k: Key, attskU: SKey, attpkU: PKey) = (* Registration *) - in(c, s:bitstring); - let (challengeU:nonce, a:RP_id) = dec(s,k) in + in(c, encRegRequestMsg:bitstring); + let regRequestMsgb = dec(encRegRequestMsg, k) in + let regRequestMsg = B2RegRequestMsg(regRequestMsgb) in + let receivedRegChallenge = RegRequestMsg_regChallenge(regRequestMsg) in + let receivedServerName = RegRequestMsg_serverName(regRequestMsg) in new skU: SKey; (* mint new user credential "secret key user" |skU| *) let pkU = pk(skU) in (* and "public key user" |pkU| *) - let regResponseMsg = BuildRegResponseMsg(pkU, attpkU, challengeU, sign((pkU, challengeU), attskU)) in + let regResponseMsg = BuildRegResponseMsg(pkU, attpkU, receivedRegChallenge, sign((pkU, receivedRegChallenge), attskU)) in let regResponseMsgb = RegResponseMsg2b(regResponseMsg) in out(c, (* return registration response with attestation *) From f943bbcff7e3b5546a36cde6943bb7d39059ff14 Mon Sep 17 00:00:00 2001 From: JeffH Date: Mon, 5 Jul 2021 15:18:33 -0700 Subject: [PATCH 042/131] editorial --- webauthn.pv | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/webauthn.pv b/webauthn.pv index 5fdf792f9..262064c76 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -21,7 +21,7 @@ http://kingsmountain.com/people/jeff.hodges/ #include "named_tuples.pvl" -(** Comms Channel **) +(* Comms Channel *) free c: channel. (* TLS channel *) @@ -34,20 +34,21 @@ free tlsKey: Key [private]. (* TLS symmetric encryption key *) type nonce. -(** Server's ID **) +(* Server's ID *) type RP_id. const a: RP_id [private]. -(** Attestation Key Pair **) + +(* Attestation Key Pair *) free attSecretKey: SKey [private]. free attPublicKey: PKey [private]. -(** Server's and authnr+clientPlatform's tuple messages **) +(* Server's and authnr+clientPlatform's tuple messages *) -(* Registration Request message -- server sends this to the user's authnr+clientPlatform: *) +(** Registration Request message -- server sends this to the user's authnr+clientPlatform: **) DEFINE_DATA_TYPE2(RegRequestMsg, regChallenge, nonce, serverName, RP_id). @@ -56,7 +57,7 @@ fun RegRequestMsg2b( RegRequestMsg ): bitstring [data, typeConverter]. fun B2RegRequestMsg( bitstring ): RegRequestMsg [data, typeConverter]. -(* Registration Response message -- authnr+clientPlatform returns this to server: *) +(** Registration Response message -- authnr+clientPlatform returns this to server: **) DEFINE_DATA_TYPE4(RegResponseMsg, userCredPubKey, PKey, attPublicKey, PKey, @@ -68,7 +69,7 @@ fun B2RegResponseMsg( bitstring ): RegResponseMsg [data, typeConverter]. -(* Authentication Request message -- server sends this to the client: *) +(** Authentication Request message -- server sends this to the client: **) DEFINE_DATA_TYPE2(AuthnRequestMsg, authnChallenge, nonce, serverName, RP_id). @@ -77,7 +78,7 @@ fun AuthnRequestMsg2b( AuthnRequestMsg ): bitstring [data, typeConverter]. fun B2AuthnRequestMsg( bitstring ): AuthnRequestMsg [data, typeConverter]. -(* Authentication Response message -- user's authnr+clientPlatform returns this to server: *) +(** Authentication Response message -- user's authnr+clientPlatform returns this to server: **) DEFINE_DATA_TYPE3(AuthnResponseMsg, returnedAuthnChallenge, nonce, serverName, RP_id, @@ -87,16 +88,17 @@ fun AuthnResponseMsg2b( AuthnResponseMsg ): bitstring [data, typeConverter]. fun B2AuthnResponseMsg( bitstring ): AuthnResponseMsg [data, typeConverter]. -(** Events and Queries **) +(* Events and Queries *) event sentChallengeResponse(bitstring, bitstring). event validChallengeResponse(bitstring, bitstring). -(* we can only receive a valid response if the user's authnr+clientPlatform sent a response message *) +(** we can only receive a valid response if the user's authnr+clientPlatform sent a response message **) query N:bitstring, s:bitstring; event(validChallengeResponse(N, s)) ==> event(sentChallengeResponse(N, s)). -(* query N:bitstring; event(reachAuthentication(N)). -event reachAuthentication(bitstring). *) +(** This was in the original model, am not sure what the intent was... +query N:bitstring; event(reachAuthentication(N)). +event reachAuthentication(bitstring). **) query attacker(attSecretKey). query attacker(attPublicKey). @@ -104,11 +106,11 @@ query attacker(tlsKey). -(** Processes **) +(* Processes *) let processClientAndAuthnr ( k: Key, attskU: SKey, attpkU: PKey) = - (* Registration *) + (** Registration **) in(c, encRegRequestMsg:bitstring); let regRequestMsgb = dec(encRegRequestMsg, k) in @@ -127,7 +129,7 @@ let processClientAndAuthnr ( k: Key, attskU: SKey, attpkU: PKey) = ); - (* Authentication *) + (** Authentication **) in(c, encAuthnRequestMsg: bitstring); (* receive server's challenge. *) let authnRequestMsgb = dec(encAuthnRequestMsg, k) in @@ -148,7 +150,7 @@ let processClientAndAuthnr ( k: Key, attskU: SKey, attpkU: PKey) = let processServer (k: Key, a: RP_id) = - (* Registration *) + (** Registration **) new regChallenge: nonce; @@ -169,7 +171,7 @@ let processServer (k: Key, a: RP_id) = one we sent, we will proceed to challenge the user to authenticate. *) - (* Authentication *) + (** Authentication **) new authnChallenge: nonce; @@ -196,9 +198,8 @@ let processServer (k: Key, a: RP_id) = process - new tlsKey: Key; (* assume TLS handshake is error-free and yields this symmetric channel-encryption key *) - new attSecretKey: SKey; (* assume a particular authenticator with a particular attestation - private key *) + new tlsKey: Key; (** assume TLS handshake is error-free and yields this symmetric channel-encryption key **) + new attSecretKey: SKey; (** assume a particular authenticator with a particular attestation private key **) let attPublicKey = pk(attSecretKey) in ( From e23ccfec1b2df64f89b37ad29a16be743b10eb6c Mon Sep 17 00:00:00 2001 From: JeffH Date: Mon, 5 Jul 2021 15:35:08 -0700 Subject: [PATCH 043/131] attPublicKey is public --- webauthn.pv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webauthn.pv b/webauthn.pv index 262064c76..bc71d6ac4 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -37,13 +37,13 @@ type nonce. (* Server's ID *) type RP_id. -const a: RP_id [private]. +const a: RP_id (* [private] *). (* Attestation Key Pair *) free attSecretKey: SKey [private]. -free attPublicKey: PKey [private]. +free attPublicKey: PKey (* [private] *). (* an attacker can obtain the attPublicKey from (eg) metadata *) (* Server's and authnr+clientPlatform's tuple messages *) From 7a1e2eeeb8fe57102c8cbc56c4cfdc324aef3315 Mon Sep 17 00:00:00 2001 From: JeffH Date: Tue, 6 Jul 2021 17:17:05 -0700 Subject: [PATCH 044/131] WIP: refine attestation object construction --- webauthn.pv | 84 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 18 deletions(-) diff --git a/webauthn.pv b/webauthn.pv index bc71d6ac4..5767bf786 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -57,16 +57,46 @@ fun RegRequestMsg2b( RegRequestMsg ): bitstring [data, typeConverter]. fun B2RegRequestMsg( bitstring ): RegRequestMsg [data, typeConverter]. -(** Registration Response message -- authnr+clientPlatform returns this to server: **) -DEFINE_DATA_TYPE4(RegResponseMsg, +(** Attestation Statement, Authenticator Data, and Attestation Object: **) + +DEFINE_DATA_TYPE2(AttStatement, + attSignature, bitstring, + attPublicKey, PKey). + +fun AttStatement2b( AttStatement ): bitstring [data, typeConverter]. +fun B2AttStatement( bitstring ): AttStatement [data, typeConverter]. + + +type Extensions. (* placeholder for now *) +free extensions: Extensions [data, private]. + + +DEFINE_DATA_TYPE3(AuthData, + serverName, RP_id, userCredPubKey, PKey, - attPublicKey, PKey, - receivedRegChallenge, nonce, - attSignature, bitstring). + extensions, Extensions). + +fun AuthData2b( AuthData ): bitstring [data, typeConverter]. +fun B2AuthData( bitstring ): AuthData [data, typeConverter]. + + +DEFINE_DATA_TYPE2(AttObject, + attStatement, AttStatement, + authData, AuthData). + +fun AttObject2b( AttObject ): bitstring [data, typeConverter]. +fun B2AttObject( bitstring ): AttObject [data, typeConverter]. + + -fun RegResponseMsg2b( RegResponseMsg ): bitstring [data, typeConverter]. -fun B2RegResponseMsg( bitstring ): RegResponseMsg [data, typeConverter]. +(** Registration Response message: AuthenticatorAttestationResponse -- authnr+clientPlatform returns this to server: **) +DEFINE_DATA_TYPE2(AuthnrAttResponseMsg, + challenge, nonce, + attObject, AttObject). + +fun AuthnrAttResponseMsg2b( AuthnrAttResponseMsg ): bitstring [data, typeConverter]. +fun B2AuthnrAttResponseMsg( bitstring ): AuthnrAttResponseMsg [data, typeConverter]. (** Authentication Request message -- server sends this to the client: **) @@ -121,11 +151,20 @@ let processClientAndAuthnr ( k: Key, attskU: SKey, attpkU: PKey) = new skU: SKey; (* mint new user credential "secret key user" |skU| *) let pkU = pk(skU) in (* and "public key user" |pkU| *) - let regResponseMsg = BuildRegResponseMsg(pkU, attpkU, receivedRegChallenge, sign((pkU, receivedRegChallenge), attskU)) in - let regResponseMsgb = RegResponseMsg2b(regResponseMsg) in + let authData = BuildAuthData(receivedServerName, pkU, extensions) in + + let attSignature = sign( (authData, receivedRegChallenge), attskU) in + + let attStatement = BuildAttStatement(attSignature, attpkU) in + + let attObject = BuildAttObject(attStatement, authData) in + + let authnrAttResponseMsg = BuildAuthnrAttResponseMsg(receivedRegChallenge, attObject) in + + let authnrAttResponseMsgb = AuthnrAttResponseMsg2b(authnrAttResponseMsg) in out(c, (* return registration response with attestation *) - enc(regResponseMsgb, k) + enc(authnrAttResponseMsgb, k) ); @@ -155,14 +194,23 @@ let processServer (k: Key, a: RP_id) = new regChallenge: nonce; out(c, enc((regChallenge, a), k)); (* send registration request *) - in(c, encRegResponseMsg: bitstring); (* receive possible registration response *) - - let regResponseMsgb = dec(encRegResponseMsg, k) in (* decrypt and parse |encRegResponseMsg| *) - let regResponseMsg = B2RegResponseMsg(regResponseMsgb) in - let userPK = RegResponseMsg_userCredPubKey(regResponseMsg) in - let attPK = RegResponseMsg_attPublicKey(regResponseMsg) in - let returnedRegChallenge = RegResponseMsg_receivedRegChallenge(regResponseMsg) in - let attSig = RegResponseMsg_attSignature(regResponseMsg) in + + (* ----- *) + + in(c, encAuthnrAttResponseMsg: bitstring); (* receive possible registration response *) + + let authnrAttResponseMsgb = dec(encAuthnrAttResponseMsg, k) in (* decrypt and parse |encAuthnrAttResponseMsg| *) + let authnrAttResponseMsg = B2AuthnrAttResponseMsg(authnrAttResponseMsgb) in + + let returnedRegChallenge = AuthnrAttResponseMsg_challenge(authnrAttResponseMsg) in + let returnedAttObject = AuthnrAttResponseMsg_attObject(authnrAttResponseMsg) in + + let returnedAttStatement = AttObject_attStatement( + + + let userPK = AuthnrAttResponseMsg_userCredPubKey(authnrAttResponseMsg) in + let attPK = AuthnrAttResponseMsg_attPublicKey(authnrAttResponseMsg) in + let attSig = AuthnrAttResponseMsg_attSignature(authnrAttResponseMsg) in if checkSign(attSig, (userPK, returnedRegChallenge), attPK) then (* we have a registration response msg with a valid signature... *) From 8a420eb77695a8d950ba7cd4f01fe8723271ef8e Mon Sep 17 00:00:00 2001 From: JeffH Date: Wed, 7 Jul 2021 07:54:41 -0700 Subject: [PATCH 045/131] WIP: attObject parsing --- webauthn.pv | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/webauthn.pv b/webauthn.pv index 5767bf786..0cd10e614 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -203,17 +203,20 @@ let processServer (k: Key, a: RP_id) = let authnrAttResponseMsg = B2AuthnrAttResponseMsg(authnrAttResponseMsgb) in let returnedRegChallenge = AuthnrAttResponseMsg_challenge(authnrAttResponseMsg) in - let returnedAttObject = AuthnrAttResponseMsg_attObject(authnrAttResponseMsg) in + let returnedAttObject = AuthnrAttResponseMsg_attObject(authnrAttResponseMsg) in (* extract AttObject *) - let returnedAttStatement = AttObject_attStatement( + let returnedAttStatement = AttObject_attStatement(returnedAttObject) in (* extract and parse AttStatement *) + let attSig = AttStatement_attSignature(returnedAttStatement) in + let attPK = AttStatement_attPublicKey(returnedAttStatement) in + let returnedAuthData = AttObject_authData(returnedAttObject) in (* extract and parse AuthData *) + let returnedServerName = AuthData_serverName(returnedAuthData) in + let userPK = AuthData_userCredPubKey(returnedAuthData) in + let returnedExtensions = AuthData_extensions(returnedAuthData) in - let userPK = AuthnrAttResponseMsg_userCredPubKey(authnrAttResponseMsg) in - let attPK = AuthnrAttResponseMsg_attPublicKey(authnrAttResponseMsg) in - let attSig = AuthnrAttResponseMsg_attSignature(authnrAttResponseMsg) in - if checkSign(attSig, (userPK, returnedRegChallenge), attPK) then (* we have a registration response msg with - a valid signature... *) + if checkSign(attSig, (returnedAuthData, returnedRegChallenge), attPK) then (* we have a registration response msg with + a valid signature... *) if (regChallenge = returnedRegChallenge) then ( (* ...and if the challenge returned matches the then we one we sent, we will proceed to challenge the user to From d2b529b3a117c9f19c16bc6ffa7ffbe7208579b3 Mon Sep 17 00:00:00 2001 From: JeffH Date: Wed, 7 Jul 2021 08:16:54 -0700 Subject: [PATCH 046/131] WIP: add Extensions. --- webauthn.pv | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/webauthn.pv b/webauthn.pv index 0cd10e614..2c7142cae 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -68,7 +68,9 @@ fun B2AttStatement( bitstring ): AttStatement [data, typeConverter]. type Extensions. (* placeholder for now *) -free extensions: Extensions [data, private]. +fun BuildExtensions(bitstring): Extensions [data]. +reduc forall devicePublicKey: bitstring; + Extensions_devicePublicKey(BuildExtensions(devicePublicKey)) = devicePublicKey. DEFINE_DATA_TYPE3(AuthData, From 666718a01d330bbd8622afc445436871117e3b02 Mon Sep 17 00:00:00 2001 From: JeffH Date: Wed, 7 Jul 2021 08:43:37 -0700 Subject: [PATCH 047/131] editorial --- webauthn.pv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webauthn.pv b/webauthn.pv index 2c7142cae..ce4a254c5 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -155,7 +155,7 @@ let processClientAndAuthnr ( k: Key, attskU: SKey, attpkU: PKey) = let authData = BuildAuthData(receivedServerName, pkU, extensions) in - let attSignature = sign( (authData, receivedRegChallenge), attskU) in + let attSignature = sign( (authData, receivedRegChallenge), attskU ) in let attStatement = BuildAttStatement(attSignature, attpkU) in From f9e861cd9e5316ec0e843028128ffe6e62e28c30 Mon Sep 17 00:00:00 2001 From: JeffH Date: Wed, 7 Jul 2021 18:41:03 -0700 Subject: [PATCH 048/131] COMPLETED: refine attestation object construction --- webauthn.pv | 78 ++++++++++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/webauthn.pv b/webauthn.pv index ce4a254c5..1f5b3cb1a 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -38,7 +38,7 @@ type nonce. type RP_id. const a: RP_id (* [private] *). - +type Extensions. (* placeholder for now *) (* Attestation Key Pair *) @@ -49,15 +49,17 @@ free attPublicKey: PKey (* [private] *). (* an attacker can obtain the attPublic (* Server's and authnr+clientPlatform's tuple messages *) (** Registration Request message -- server sends this to the user's authnr+clientPlatform: **) -DEFINE_DATA_TYPE2(RegRequestMsg, + +DEFINE_DATA_TYPE3(RegRequestMsg, regChallenge, nonce, - serverName, RP_id). + serverName, RP_id, + extensions, Extensions). fun RegRequestMsg2b( RegRequestMsg ): bitstring [data, typeConverter]. fun B2RegRequestMsg( bitstring ): RegRequestMsg [data, typeConverter]. -(** Attestation Statement, Authenticator Data, and Attestation Object: **) +(** Registration Response message -- Attestation Statement, Authenticator Data, and Attestation Object: **) DEFINE_DATA_TYPE2(AttStatement, attSignature, bitstring, @@ -67,11 +69,11 @@ fun AttStatement2b( AttStatement ): bitstring [data, typeConverter]. fun B2AttStatement( bitstring ): AttStatement [data, typeConverter]. -type Extensions. (* placeholder for now *) +(* fun BuildExtensions(bitstring): Extensions [data]. reduc forall devicePublicKey: bitstring; Extensions_devicePublicKey(BuildExtensions(devicePublicKey)) = devicePublicKey. - +*) DEFINE_DATA_TYPE3(AuthData, serverName, RP_id, @@ -122,15 +124,12 @@ fun B2AuthnResponseMsg( bitstring ): AuthnResponseMsg [data, typeConverter]. (* Events and Queries *) -event sentChallengeResponse(bitstring, bitstring). -event validChallengeResponse(bitstring, bitstring). +event sendingAuthnResponse(bitstring, bitstring). +event receivedValidAuthnResponse(bitstring, bitstring). (** we can only receive a valid response if the user's authnr+clientPlatform sent a response message **) -query N:bitstring, s:bitstring; event(validChallengeResponse(N, s)) ==> event(sentChallengeResponse(N, s)). +query N:bitstring, s:bitstring; event(receivedValidAuthnResponse(N, s)) ==> event(sendingAuthnResponse(N, s)). -(** This was in the original model, am not sure what the intent was... -query N:bitstring; event(reachAuthentication(N)). -event reachAuthentication(bitstring). **) query attacker(attSecretKey). query attacker(attPublicKey). @@ -144,48 +143,49 @@ let processClientAndAuthnr ( k: Key, attskU: SKey, attpkU: PKey) = (** Registration **) - in(c, encRegRequestMsg:bitstring); + in(c, encRegRequestMsg: bitstring); (* receive and parse registration request msg *) let regRequestMsgb = dec(encRegRequestMsg, k) in let regRequestMsg = B2RegRequestMsg(regRequestMsgb) in let receivedRegChallenge = RegRequestMsg_regChallenge(regRequestMsg) in let receivedServerName = RegRequestMsg_serverName(regRequestMsg) in + let receivedExtensions = RegRequestMsg_extensions(regRequestMsg) in - new skU: SKey; (* mint new user credential "secret key user" |skU| *) - let pkU = pk(skU) in (* and "public key user" |pkU| *) - - let authData = BuildAuthData(receivedServerName, pkU, extensions) in + new skU: SKey; (* mint new user credential "secret key user" |skU| *) + let pkU = pk(skU) in (* and "public key user" |pkU| *) + new extensionsOutputs: Extensions; + let authData = BuildAuthData(receivedServerName, pkU, extensionsOutputs) in (* construct attestation object *) let attSignature = sign( (authData, receivedRegChallenge), attskU ) in - let attStatement = BuildAttStatement(attSignature, attpkU) in - let attObject = BuildAttObject(attStatement, authData) in let authnrAttResponseMsg = BuildAuthnrAttResponseMsg(receivedRegChallenge, attObject) in - let authnrAttResponseMsgb = AuthnrAttResponseMsg2b(authnrAttResponseMsg) in - out(c, (* return registration response with attestation *) + out(c, (* return registration response with attestation *) enc(authnrAttResponseMsgb, k) ); (** Authentication **) - in(c, encAuthnRequestMsg: bitstring); (* receive server's challenge. *) + in(c, encAuthnRequestMsg: bitstring); (* receive server's challenge. *) + let authnRequestMsgb = dec(encAuthnRequestMsg, k) in let authnRequestMsg = B2AuthnRequestMsg(authnRequestMsgb) in let receivedAuthnChallenge = AuthnRequestMsg_authnChallenge(authnRequestMsg) in let receivedServerName = AuthnRequestMsg_serverName(authnRequestMsg) in let assertionSignature = sign((receivedAuthnChallenge, receivedServerName), skU) in + let authnResponseMsg = BuildAuthnResponseMsg(receivedAuthnChallenge, receivedServerName, assertionSignature) in let authnResponseMsgb = AuthnResponseMsg2b(authnResponseMsg) in - event sentChallengeResponse( authnResponseMsgb, enc(authnResponseMsgb, k) ); - out(c, - enc(authnResponseMsgb, k) - ). + let encAuthnResponseMsgFromAuthnr = enc(authnResponseMsgb, k) in + + event sendingAuthnResponse( authnResponseMsgb, encAuthnResponseMsgFromAuthnr ); + + out(c, encAuthnResponseMsgFromAuthnr). (* return authn response *) @@ -194,14 +194,18 @@ let processServer (k: Key, a: RP_id) = (** Registration **) new regChallenge: nonce; + new requestedExtensions: Extensions; + + let regRequestMsg = BuildRegRequestMsg(regChallenge, a, requestedExtensions) in + let regRequestMsgb = RegRequestMsg2b(regRequestMsg) in - out(c, enc((regChallenge, a), k)); (* send registration request *) + out(c, enc(regRequestMsgb, k)); (* send registration request *) (* ----- *) in(c, encAuthnrAttResponseMsg: bitstring); (* receive possible registration response *) - let authnrAttResponseMsgb = dec(encAuthnrAttResponseMsg, k) in (* decrypt and parse |encAuthnrAttResponseMsg| *) + let authnrAttResponseMsgb = dec(encAuthnrAttResponseMsg, k) in (* decrypt and parse |encAuthnrAttResponseMsg| *) let authnrAttResponseMsg = B2AuthnrAttResponseMsg(authnrAttResponseMsgb) in let returnedRegChallenge = AuthnrAttResponseMsg_challenge(authnrAttResponseMsg) in @@ -220,10 +224,9 @@ let processServer (k: Key, a: RP_id) = if checkSign(attSig, (returnedAuthData, returnedRegChallenge), attPK) then (* we have a registration response msg with a valid signature... *) - if (regChallenge = returnedRegChallenge) then ( (* ...and if the challenge returned matches the then we - one we sent, we will proceed to challenge the user to - authenticate. *) - + if (regChallenge = returnedRegChallenge) then ( (* ...and if the challenge returned matches the + one we sent, we will proceed to challenge + the user to authenticate. *) (** Authentication **) new authnChallenge: nonce; @@ -233,10 +236,13 @@ let processServer (k: Key, a: RP_id) = let authnRequestMsg = BuildAuthnRequestMsg(authnChallenge, a) in let authnRequestMsgb = AuthnRequestMsg2b(authnRequestMsg) in - out(c, enc( authnRequestMsgb, k) ); (* send authentication request *) + out(c, enc(authnRequestMsgb, k) ); (* send authentication request *) + + (* ----- *) + + in(c, encAuthnResponseMsg: bitstring); (* receive authn response *) - in(c, encAuthnResponseMsg: bitstring); - let authnResponseMsgb = dec(encAuthnResponseMsg, k) in + let authnResponseMsgb = dec(encAuthnResponseMsg, k) in (* decrypt and parse |encAuthnResponseMsg| *) let authnResponseMsg = B2AuthnResponseMsg(authnResponseMsgb) in let retAuthnChallenge = AuthnResponseMsg_returnedAuthnChallenge(authnResponseMsg) in let retServerName = AuthnResponseMsg_serverName(authnResponseMsg) in @@ -245,7 +251,7 @@ let processServer (k: Key, a: RP_id) = if (authnChallenge = retAuthnChallenge) && (a = retServerName) then if checkSign(retAssertSignature, (retAuthnChallenge, retServerName), userPK) then - event validChallengeResponse(authnResponseMsgb, encAuthnResponseMsg) + event receivedValidAuthnResponse(authnResponseMsgb, encAuthnResponseMsg) ). From e1a4383a396eafd99dadb66666cbff58b174aa88 Mon Sep 17 00:00:00 2001 From: JeffH Date: Wed, 7 Jul 2021 19:20:42 -0700 Subject: [PATCH 049/131] refine events --- webauthn.pv | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/webauthn.pv b/webauthn.pv index 1f5b3cb1a..04e234a96 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -124,11 +124,12 @@ fun B2AuthnResponseMsg( bitstring ): AuthnResponseMsg [data, typeConverter]. (* Events and Queries *) -event sendingAuthnResponse(bitstring, bitstring). -event receivedValidAuthnResponse(bitstring, bitstring). +event sendingAuthnResponse(bitstring). +event receivedValidAuthnResponse(bitstring). (** we can only receive a valid response if the user's authnr+clientPlatform sent a response message **) -query N:bitstring, s:bitstring; event(receivedValidAuthnResponse(N, s)) ==> event(sendingAuthnResponse(N, s)). +query msg:bitstring, msg':bitstring; + inj-event(receivedValidAuthnResponse(msg)) ==> inj-event(sendingAuthnResponse(msg')). query attacker(attSecretKey). @@ -183,7 +184,7 @@ let processClientAndAuthnr ( k: Key, attskU: SKey, attpkU: PKey) = let encAuthnResponseMsgFromAuthnr = enc(authnResponseMsgb, k) in - event sendingAuthnResponse( authnResponseMsgb, encAuthnResponseMsgFromAuthnr ); + event sendingAuthnResponse( encAuthnResponseMsgFromAuthnr ); out(c, encAuthnResponseMsgFromAuthnr). (* return authn response *) @@ -251,7 +252,7 @@ let processServer (k: Key, a: RP_id) = if (authnChallenge = retAuthnChallenge) && (a = retServerName) then if checkSign(retAssertSignature, (retAuthnChallenge, retServerName), userPK) then - event receivedValidAuthnResponse(authnResponseMsgb, encAuthnResponseMsg) + event receivedValidAuthnResponse(encAuthnResponseMsg) ). From 9182fa1cc9d53bb5f1b600d14bf2c833b6c63ec3 Mon Sep 17 00:00:00 2001 From: JeffH Date: Thu, 8 Jul 2021 14:47:14 -0700 Subject: [PATCH 050/131] editorial --- webauthn.pv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webauthn.pv b/webauthn.pv index 04e234a96..cc8983b9c 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -202,7 +202,7 @@ let processServer (k: Key, a: RP_id) = out(c, enc(regRequestMsgb, k)); (* send registration request *) - (* ----- *) + (* --- wait for client -- *) in(c, encAuthnrAttResponseMsg: bitstring); (* receive possible registration response *) @@ -239,7 +239,7 @@ let processServer (k: Key, a: RP_id) = out(c, enc(authnRequestMsgb, k) ); (* send authentication request *) - (* ----- *) + (* --- wait for client -- *) in(c, encAuthnResponseMsg: bitstring); (* receive authn response *) From 89e26609eac2098b06d71be0a826160e40b37357 Mon Sep 17 00:00:00 2001 From: JeffH Date: Thu, 8 Jul 2021 17:08:55 -0700 Subject: [PATCH 051/131] editorial --- webauthn.pv | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/webauthn.pv b/webauthn.pv index cc8983b9c..2830cc9b0 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -3,6 +3,8 @@ Paper: https://dl.acm.org/doi/10.1145/3190619.3190640 (by Iness Ben Guirat and Harry Halpin) Slides: https://cps-vo.org/node/48511 github: https://github.com/hhalpin/weauthn-model + ------ + [MANUAL]: The proverif manual is here: https://prosecco.gforge.inria.fr/personal/bblanche/proverif/manual.pdf *) (* INTRODUCTION: @@ -43,7 +45,8 @@ type Extensions. (* placeholder for now *) (* Attestation Key Pair *) free attSecretKey: SKey [private]. -free attPublicKey: PKey (* [private] *). (* an attacker can obtain the attPublicKey from (eg) metadata *) +free attPublicKey: PKey (* [private] *). (* an attacker can obtain the attPublicKey from several sources, + e.g.: an authenticator itself, or authenticator metadata *) (* Server's and authnr+clientPlatform's tuple messages *) @@ -132,9 +135,12 @@ query msg:bitstring, msg':bitstring; inj-event(receivedValidAuthnResponse(msg)) ==> inj-event(sendingAuthnResponse(msg')). -query attacker(attSecretKey). -query attacker(attPublicKey). -query attacker(tlsKey). +query attacker(attSecretKey). +query attacker(attPublicKey). +query attacker(tlsKey). + +query secret regChallengeQ; (* secrecy queries (see "5.1.2 Security properties" in [MANUAL]) *) + secret authnChallengeQ. @@ -163,9 +169,9 @@ let processClientAndAuthnr ( k: Key, attskU: SKey, attpkU: PKey) = let authnrAttResponseMsg = BuildAuthnrAttResponseMsg(receivedRegChallenge, attObject) in let authnrAttResponseMsgb = AuthnrAttResponseMsg2b(authnrAttResponseMsg) in - out(c, (* return registration response with attestation *) - enc(authnrAttResponseMsgb, k) - ); + let encAuthnrAttResponseMsg = enc(authnrAttResponseMsgb, k) in + + out(c, encAuthnrAttResponseMsg); (* return registration response with attestation *) (** Authentication **) @@ -252,7 +258,12 @@ let processServer (k: Key, a: RP_id) = if (authnChallenge = retAuthnChallenge) && (a = retServerName) then if checkSign(retAssertSignature, (retAuthnChallenge, retServerName), userPK) then - event receivedValidAuthnResponse(encAuthnResponseMsg) + event receivedValidAuthnResponse(encAuthnResponseMsg); + + (* set up for secrecy queries (see "5.1.2 Security properties" in [MANUAL]) *) + let regChallengeQ = regChallenge in + let authnChallengeQ = authnChallenge + ). From 54eb767405346c6c63ffabfd270bd9dd425a5061 Mon Sep 17 00:00:00 2001 From: JeffH Date: Tue, 20 Jul 2021 02:57:15 -0700 Subject: [PATCH 052/131] more meaningful query wrt response msg. --- webauthn.pv | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webauthn.pv b/webauthn.pv index 2830cc9b0..fa107a3e3 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -130,9 +130,10 @@ fun B2AuthnResponseMsg( bitstring ): AuthnResponseMsg [data, typeConverter]. event sendingAuthnResponse(bitstring). event receivedValidAuthnResponse(bitstring). -(** we can only receive a valid response if the user's authnr+clientPlatform sent a response message **) +(** we can only receive a valid response if the user's authnr+clientPlatform sent the response message, + and we have to receive the same message as was sent. **) query msg:bitstring, msg':bitstring; - inj-event(receivedValidAuthnResponse(msg)) ==> inj-event(sendingAuthnResponse(msg')). + event(receivedValidAuthnResponse(msg)) && event(sendingAuthnResponse(msg')) ==> msg = msg'. query attacker(attSecretKey). From f6fcee8aa8c67a6f14d0a5e6fe911acdf207d7fc Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 13 Aug 2021 05:00:13 -0700 Subject: [PATCH 053/131] add: set traceDisplay long --- webauthn.pv | 1 + 1 file changed, 1 insertion(+) diff --git a/webauthn.pv b/webauthn.pv index fa107a3e3..b3f1c424d 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -17,6 +17,7 @@ This current model is by Jeff Hodges, July 2021 http://kingsmountain.com/people/jeff.hodges/ *) +set traceDisplay = long. #include "crypto.pvl" From 1747dff04cdc3b44b1ba4879e98fefae8a35c79c Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 13 Aug 2021 22:37:59 -0700 Subject: [PATCH 054/131] fix var rebindings, trim queries --- webauthn.pv | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/webauthn.pv b/webauthn.pv index b3f1c424d..2292084d9 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -28,7 +28,7 @@ set traceDisplay = long. free c: channel. (* TLS channel *) -free tlsKey: Key [private]. (* TLS symmetric encryption key *) +(* free tlsKey: Key [private]. *) (* TLS symmetric encryption key *) (* Various locally-defined types *) @@ -45,8 +45,8 @@ type Extensions. (* placeholder for now *) (* Attestation Key Pair *) -free attSecretKey: SKey [private]. -free attPublicKey: PKey (* [private] *). (* an attacker can obtain the attPublicKey from several sources, +(* free attSecretKey: SKey [private]. *) +(* free attPublicKey: PKey *) (* [private] .*) (* an attacker can obtain the attPublicKey from several sources, e.g.: an authenticator itself, or authenticator metadata *) @@ -67,7 +67,7 @@ fun B2RegRequestMsg( bitstring ): RegRequestMsg [data, typeConverter]. DEFINE_DATA_TYPE2(AttStatement, attSignature, bitstring, - attPublicKey, PKey). + attPubKey, PKey). fun AttStatement2b( AttStatement ): bitstring [data, typeConverter]. fun B2AttStatement( bitstring ): AttStatement [data, typeConverter]. @@ -136,10 +136,11 @@ event receivedValidAuthnResponse(bitstring). query msg:bitstring, msg':bitstring; event(receivedValidAuthnResponse(msg)) && event(sendingAuthnResponse(msg')) ==> msg = msg'. - +(* query attacker(attSecretKey). query attacker(attPublicKey). query attacker(tlsKey). +*) query secret regChallengeQ; (* secrecy queries (see "5.1.2 Security properties" in [MANUAL]) *) secret authnChallengeQ. @@ -156,14 +157,14 @@ let processClientAndAuthnr ( k: Key, attskU: SKey, attpkU: PKey) = let regRequestMsgb = dec(encRegRequestMsg, k) in let regRequestMsg = B2RegRequestMsg(regRequestMsgb) in let receivedRegChallenge = RegRequestMsg_regChallenge(regRequestMsg) in - let receivedServerName = RegRequestMsg_serverName(regRequestMsg) in + let receivedServerNameReg = RegRequestMsg_serverName(regRequestMsg) in let receivedExtensions = RegRequestMsg_extensions(regRequestMsg) in new skU: SKey; (* mint new user credential "secret key user" |skU| *) let pkU = pk(skU) in (* and "public key user" |pkU| *) new extensionsOutputs: Extensions; - let authData = BuildAuthData(receivedServerName, pkU, extensionsOutputs) in (* construct attestation object *) + let authData = BuildAuthData(receivedServerNameReg, pkU, extensionsOutputs) in (* construct attestation object *) let attSignature = sign( (authData, receivedRegChallenge), attskU ) in let attStatement = BuildAttStatement(attSignature, attpkU) in let attObject = BuildAttObject(attStatement, authData) in @@ -183,11 +184,11 @@ let processClientAndAuthnr ( k: Key, attskU: SKey, attpkU: PKey) = let authnRequestMsgb = dec(encAuthnRequestMsg, k) in let authnRequestMsg = B2AuthnRequestMsg(authnRequestMsgb) in let receivedAuthnChallenge = AuthnRequestMsg_authnChallenge(authnRequestMsg) in - let receivedServerName = AuthnRequestMsg_serverName(authnRequestMsg) in + let receivedServerNameAuthn = AuthnRequestMsg_serverName(authnRequestMsg) in - let assertionSignature = sign((receivedAuthnChallenge, receivedServerName), skU) in + let assertionSignature = sign((receivedAuthnChallenge, receivedServerNameAuthn), skU) in - let authnResponseMsg = BuildAuthnResponseMsg(receivedAuthnChallenge, receivedServerName, assertionSignature) in + let authnResponseMsg = BuildAuthnResponseMsg(receivedAuthnChallenge, receivedServerNameAuthn, assertionSignature) in let authnResponseMsgb = AuthnResponseMsg2b(authnResponseMsg) in let encAuthnResponseMsgFromAuthnr = enc(authnResponseMsgb, k) in @@ -222,7 +223,7 @@ let processServer (k: Key, a: RP_id) = let returnedAttStatement = AttObject_attStatement(returnedAttObject) in (* extract and parse AttStatement *) let attSig = AttStatement_attSignature(returnedAttStatement) in - let attPK = AttStatement_attPublicKey(returnedAttStatement) in + let attPK = AttStatement_attPubKey(returnedAttStatement) in let returnedAuthData = AttObject_authData(returnedAttObject) in (* extract and parse AuthData *) let returnedServerName = AuthData_serverName(returnedAuthData) in From 12ec079fdf7bbeeb990968ce4ee48c246341a059 Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 13 Aug 2021 23:35:52 -0700 Subject: [PATCH 055/131] update README.pv.md --- README.pv.md | 73 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/README.pv.md b/README.pv.md index 1ac039c21..7a2963680 100644 --- a/README.pv.md +++ b/README.pv.md @@ -1,21 +1,74 @@ -# WebAuthn protocol verification effort +# WebAuthn protocol verification using Proverif -This directory uses the C Preprocessor to modularize ProVerif models. It also -introduces `*_test.pv` files that do testing in the applied pi calculus by -setting up processes that send out either `Success` or `Failure` bitstrings and -querying ProVerif to see if the adversary can get a `Failure` message. See -`list_test.pv` for an example. +ProVerif Web Authentication Formal Model: webauthn.pv + based on webauthn-basic.pv by Iness Ben Guirat + Paper: https://dl.acm.org/doi/10.1145/3190619.3190640 (by Iness Ben Guirat and Harry Halpin) + Slides: https://cps-vo.org/node/48511 + github: https://github.com/hhalpin/weauthn-model + + See the [Paper] section 5.2 for a detailed description of the original model + + MANUAL: The proverif manual is here: https://prosecco.gforge.inria.fr/personal/bblanche/proverif/manual.pdf -## Running ProVerif +`webauthn.pv` models a registration of a WebAuthn user credential with a server (aka Relying Party) and then models subsequent authentication using that registered credential. + + + +## STATUS + +This is WORK IN PROGRESS by Jeff Hodges as of Aug-2021. + + +## NOTES + +This directory uses the C Preprocessor to modularize ProVerif models (this +is why `run_proverif.sh` is used rather than invoking `proverif` directly). + +Specifically, `webauthn.pv` relies upon `crypto.pvl` and `named_tuples.pvl`. + +These pvl ("Proverif library") files were obtained from: + + `//google3/cloud/security/virtsec/proverif_rg/seems_legit/` ..and.. + + `//google3/cloud/security/virtsec/proverif_rg/ekep/` + + +`crypto.pvl` models sophisticated signature proofs based on the recent "Seems Legit" +paper , see `go/seems-legit-proverif` for details. + +`named_tuples.pvl` very cleverly uses the C-preprocessor to make it easy to model +nested datastructures in Proverif. `webauthn.pv` uses this liberally to create a +fine-grained model. + +See also: ProVerif Reading Group `//google3/cloud/security/virtsec/proverif_rg/` + + +## Running Proverif To run a file in ProVerif, use the script `run_proverif.sh`. This script assumes that ProVerif is installed on the local machine. The typical usage is: ```bash -$ ./run_proverif.sh list_test.pv +$ ./run_proverif.sh webauthn.pv +``` + +NOTE: You can only run `.pv` files, and not `.pvl` (library) files. Also, + `run_proverif.sh` generates colorized output which looks nice in a + terminal but is lousy to use when captured in a file and examined in + an editor (see the next section). + + +### Running Proverif with no color ouput + +`run_proverif.sh`'s standard output is colorized. If you wish to generate output +without colorization, e.g., for examining the output in an editor (doing so can be +useful for groveling thru the output in order to figure out just what Proverif did +to find an attack) then run Proverif like so: + +```bash +$ ./run_proverif-no-color.sh webauthn.pv > some.output.file.name.txt ``` -NOTE: You can only run `.pv` files, and not `.pvl` (library) files. ### Debugging syntax errors @@ -25,7 +78,7 @@ after the script finishes. To disable this automatic cleanup, set the environment variable `PROVERIF_NO_CLEANUP`. For example: ```bash -$ PROVERIF_NO_CLEANUP=1 ./run_proverif.sh list_test.pv +$ PROVERIF_NO_CLEANUP=1 ./run_proverif.sh webauthn.pv ``` ## Running ProVerif Interactively From 59f29091e04aeb3742397d9dfafffa97cb9eb994 Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 13 Aug 2021 23:43:43 -0700 Subject: [PATCH 056/131] edit README.pv.md --- README.pv.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.pv.md b/README.pv.md index 7a2963680..55f7075ed 100644 --- a/README.pv.md +++ b/README.pv.md @@ -28,13 +28,13 @@ Specifically, `webauthn.pv` relies upon `crypto.pvl` and `named_tuples.pvl`. These pvl ("Proverif library") files were obtained from: - `//google3/cloud/security/virtsec/proverif_rg/seems_legit/` ..and.. + `//google3/cloud/security/virtsec/proverif_rg/seems_legit/` (`crypto.pvl`) - `//google3/cloud/security/virtsec/proverif_rg/ekep/` + `//google3/cloud/security/virtsec/proverif_rg/ekep/` (`named_tuples.pvl`) `crypto.pvl` models sophisticated signature proofs based on the recent "Seems Legit" -paper , see `go/seems-legit-proverif` for details. +paper , SEE `go/seems-legit-proverif` FOR DETAILS. `named_tuples.pvl` very cleverly uses the C-preprocessor to make it easy to model nested datastructures in Proverif. `webauthn.pv` uses this liberally to create a @@ -81,7 +81,9 @@ environment variable `PROVERIF_NO_CLEANUP`. For example: $ PROVERIF_NO_CLEANUP=1 ./run_proverif.sh webauthn.pv ``` -## Running ProVerif Interactively +## Running ProVerif Interactively (TODO) + +[NOTE: I have not experimented with this as yet] The `run_proverif.sh` script supports an environment variable `PROVERIF_INTERACT`. If this variable is set, then `run_proverif.sh` will call From 8b4d51ce2f72d1b50e25c5fbe8428bd717f66771 Mon Sep 17 00:00:00 2001 From: JeffH Date: Sun, 15 Aug 2021 01:16:01 -0700 Subject: [PATCH 057/131] rename server name, plus other clieanups --- README.pv.md | 35 ++++++++++++++++++++++++++--------- webauthn.pv | 41 +++++++++++++++++++++++++---------------- 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/README.pv.md b/README.pv.md index 55f7075ed..b67171f28 100644 --- a/README.pv.md +++ b/README.pv.md @@ -1,16 +1,19 @@ # WebAuthn protocol verification using Proverif ProVerif Web Authentication Formal Model: webauthn.pv - based on webauthn-basic.pv by Iness Ben Guirat + Originally based on webauthn-basic.pv by Iness Ben Guirat + Paper: https://dl.acm.org/doi/10.1145/3190619.3190640 (by Iness Ben Guirat and Harry Halpin) Slides: https://cps-vo.org/node/48511 github: https://github.com/hhalpin/weauthn-model See the [Paper] section 5.2 for a detailed description of the original model - MANUAL: The proverif manual is here: https://prosecco.gforge.inria.fr/personal/bblanche/proverif/manual.pdf + MANUAL: The proverif manual is here: + https://prosecco.gforge.inria.fr/personal/bblanche/proverif/manual.pdf -`webauthn.pv` models a registration of a WebAuthn user credential with a server (aka Relying Party) and then models subsequent authentication using that registered credential. +`webauthn.pv` models a registration of a WebAuthn user credential with a server +(aka Relying Party) and then models subsequent authentication using that registered credential. @@ -21,8 +24,9 @@ This is WORK IN PROGRESS by Jeff Hodges as of Aug-2021. ## NOTES -This directory uses the C Preprocessor to modularize ProVerif models (this -is why `run_proverif.sh` is used rather than invoking `proverif` directly). +Proverif models (`*.pv` files) in this directory are modularized using the +C Preprocessor (this is why `run_proverif.sh` is used rather than invoking +the `proverif` command directly). Specifically, `webauthn.pv` relies upon `crypto.pvl` and `named_tuples.pvl`. @@ -38,9 +42,9 @@ paper , SEE `go/seems-legit-proverif` FOR DETA `named_tuples.pvl` very cleverly uses the C-preprocessor to make it easy to model nested datastructures in Proverif. `webauthn.pv` uses this liberally to create a -fine-grained model. +fine-grained model datastructure-wise. -See also: ProVerif Reading Group `//google3/cloud/security/virtsec/proverif_rg/` +SEE ALSO: ProVerif Reading Group `//google3/cloud/security/virtsec/proverif_rg/` ## Running Proverif @@ -61,12 +65,12 @@ NOTE: You can only run `.pv` files, and not `.pvl` (library) files. Also, ### Running Proverif with no color ouput `run_proverif.sh`'s standard output is colorized. If you wish to generate output -without colorization, e.g., for examining the output in an editor (doing so can be +without colorization, e.g., for examining the output in an editor (e.g., doing so can be useful for groveling thru the output in order to figure out just what Proverif did to find an attack) then run Proverif like so: ```bash -$ ./run_proverif-no-color.sh webauthn.pv > some.output.file.name.txt +$ ./run_proverif-no-color.sh webauthn.pv > some.output.file.name ``` @@ -80,6 +84,16 @@ environment variable `PROVERIF_NO_CLEANUP`. For example: ```bash $ PROVERIF_NO_CLEANUP=1 ./run_proverif.sh webauthn.pv ``` +In this case, the first line of the Proverif console output will be akin to: + +```bash +Output in /tmp/tmp.Lxv1mdTe7y +``` +..where `/tmp/tmp.Lxv1mdTe7y` is a directory containing a single file `webauthn.pv`, +which is the C preprocessor output (ie was the latter that was fed to the `proverif` +command). + + ## Running ProVerif Interactively (TODO) @@ -103,3 +117,6 @@ messages that would otherwise be tedious and error-prone to type. See the The `run_proverif.sh` script always defines `ENABLE_DEBUG_FUNCTIONS` when `PROVERIF_INTERACT` is set. + +end + diff --git a/webauthn.pv b/webauthn.pv index 2292084d9..0aea680a6 100644 --- a/webauthn.pv +++ b/webauthn.pv @@ -1,15 +1,17 @@ (* ProVerif Web Authentication Formal Model: webauthn.pv - based on webauthn-basic.pv by Iness Ben Guirat + Originally based on webauthn-basic.pv by Iness Ben Guirat + Paper: https://dl.acm.org/doi/10.1145/3190619.3190640 (by Iness Ben Guirat and Harry Halpin) Slides: https://cps-vo.org/node/48511 github: https://github.com/hhalpin/weauthn-model ------ - [MANUAL]: The proverif manual is here: https://prosecco.gforge.inria.fr/personal/bblanche/proverif/manual.pdf + [MANUAL]: The proverif manual is here: + https://prosecco.gforge.inria.fr/personal/bblanche/proverif/manual.pdf *) (* INTRODUCTION: -See the paper section 5.2 for a detailed description of the original model. +See the [Paper[ section 5.2 for a detailed description of the original model. This models a registration of a WebAuthn user credential with a server (aka Relying Party) and then models subsequent authentication using that registered credential. The model seeks to prove various properties of the protocol. @@ -40,8 +42,9 @@ type nonce. (* Server's ID *) type RP_id. -const a: RP_id (* [private] *). -type Extensions. (* placeholder for now *) +const server: RP_id (* This is not [private] because the server's name is not + necessarily a secret in the real world. *). + (* Attestation Key Pair *) @@ -52,6 +55,9 @@ type Extensions. (* placeholder for now *) (* Server's and authnr+clientPlatform's tuple messages *) +type Extensions. (* TODO: placeholder for now *) + + (** Registration Request message -- server sends this to the user's authnr+clientPlatform: **) DEFINE_DATA_TYPE3(RegRequestMsg, @@ -73,7 +79,7 @@ fun AttStatement2b( AttStatement ): bitstring [data, typeConverter]. fun B2AttStatement( bitstring ): AttStatement [data, typeConverter]. -(* +(* TODO: fun BuildExtensions(bitstring): Extensions [data]. reduc forall devicePublicKey: bitstring; Extensions_devicePublicKey(BuildExtensions(devicePublicKey)) = devicePublicKey. @@ -131,8 +137,9 @@ fun B2AuthnResponseMsg( bitstring ): AuthnResponseMsg [data, typeConverter]. event sendingAuthnResponse(bitstring). event receivedValidAuthnResponse(bitstring). -(** we can only receive a valid response if the user's authnr+clientPlatform sent the response message, - and we have to receive the same message as was sent. **) +(** we can only receive a valid authn response if the user's authnr+clientPlatform + sent the response message, and we have to receive the same message as was sent. **) + query msg:bitstring, msg':bitstring; event(receivedValidAuthnResponse(msg)) && event(sendingAuthnResponse(msg')) ==> msg = msg'. @@ -232,7 +239,7 @@ let processServer (k: Key, a: RP_id) = if checkSign(attSig, (returnedAuthData, returnedRegChallenge), attPK) then (* we have a registration response msg with - a valid signature... *) + a (supposedly) valid signature... *) if (regChallenge = returnedRegChallenge) then ( (* ...and if the challenge returned matches the one we sent, we will proceed to challenge @@ -241,16 +248,17 @@ let processServer (k: Key, a: RP_id) = new authnChallenge: nonce; - (* let authnChallengeBitstr = nonce_to_bitstring( authnChallenge ) in -- do we need to do this type conversion? *) + (* let authnChallengeBitstr = nonce_to_bitstring( authnChallenge ) in -- do we need to do this type conversion? + seems like we do not. *) let authnRequestMsg = BuildAuthnRequestMsg(authnChallenge, a) in let authnRequestMsgb = AuthnRequestMsg2b(authnRequestMsg) in out(c, enc(authnRequestMsgb, k) ); (* send authentication request *) - (* --- wait for client -- *) + (* --- wait for client --- *) - in(c, encAuthnResponseMsg: bitstring); (* receive authn response *) + in(c, encAuthnResponseMsg: bitstring); (* receive encrypted authn response *) let authnResponseMsgb = dec(encAuthnResponseMsg, k) in (* decrypt and parse |encAuthnResponseMsg| *) let authnResponseMsg = B2AuthnResponseMsg(authnResponseMsgb) in @@ -261,7 +269,8 @@ let processServer (k: Key, a: RP_id) = if (authnChallenge = retAuthnChallenge) && (a = retServerName) then if checkSign(retAssertSignature, (retAuthnChallenge, retServerName), userPK) then - event receivedValidAuthnResponse(encAuthnResponseMsg); + event receivedValidAuthnResponse(encAuthnResponseMsg); (* signal receipt of ostensibly valid authn + authn response *) (* set up for secrecy queries (see "5.1.2 Security properties" in [MANUAL]) *) let regChallengeQ = regChallenge in @@ -273,11 +282,11 @@ let processServer (k: Key, a: RP_id) = process new tlsKey: Key; (** assume TLS handshake is error-free and yields this symmetric channel-encryption key **) - new attSecretKey: SKey; (** assume a particular authenticator with a particular attestation private key **) + new attSecretKey: SKey; (** assume a particular authenticator with a particular attestation private key.. **) - let attPublicKey = pk(attSecretKey) in + let attPublicKey = pk(attSecretKey) in (** ..and attestation public key. **) ( - !processClientAndAuthnr(tlsKey, attSecretKey, attPublicKey) | !processServer(tlsKey, a) + !processClientAndAuthnr(tlsKey, attSecretKey, attPublicKey) | !processServer(tlsKey, server) ) From c5f3b2de40fc926c49a2e8844e35ba0f7e3c39e8 Mon Sep 17 00:00:00 2001 From: JeffH Date: Sun, 15 Aug 2021 01:27:50 -0700 Subject: [PATCH 058/131] edit README.pv.md --- README.pv.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.pv.md b/README.pv.md index b67171f28..192826730 100644 --- a/README.pv.md +++ b/README.pv.md @@ -16,11 +16,23 @@ ProVerif Web Authentication Formal Model: webauthn.pv (aka Relying Party) and then models subsequent authentication using that registered credential. - ## STATUS This is WORK IN PROGRESS by Jeff Hodges as of Aug-2021. +Presently, the model seeks to prove that the ostensibly valid authentication response message +(i.e., the signatures validate) received by the server is the same as the one sent by the +authnr+clientPlatform. Proverif finds this to be FALSE, meaning it found a way such that an +attacker can forge an authentication response message that passes signature verification by +the server (if I understand correctly). + +I'm presently suspecting this result is due to the sophisticated signature algorithm model +ensconced in `crypto.pvl`. Digging into this result and determining its source and +significance is a TODO. + +Further, the model ought to be updated to model the authenticator, client platform, and server +as distinct entities, in order to more closely model the actual end-to-end behavior of the system. + ## NOTES From 4ebd028f0d05e0938f5d53a628a845fcbe811665 Mon Sep 17 00:00:00 2001 From: JeffH Date: Tue, 17 Aug 2021 12:29:45 -0700 Subject: [PATCH 059/131] remove pv files from this branch --- README.pv.md | 134 ----------------------- webauthn.pv | 293 --------------------------------------------------- 2 files changed, 427 deletions(-) delete mode 100644 README.pv.md delete mode 100644 webauthn.pv diff --git a/README.pv.md b/README.pv.md deleted file mode 100644 index 192826730..000000000 --- a/README.pv.md +++ /dev/null @@ -1,134 +0,0 @@ -# WebAuthn protocol verification using Proverif - -ProVerif Web Authentication Formal Model: webauthn.pv - Originally based on webauthn-basic.pv by Iness Ben Guirat - - Paper: https://dl.acm.org/doi/10.1145/3190619.3190640 (by Iness Ben Guirat and Harry Halpin) - Slides: https://cps-vo.org/node/48511 - github: https://github.com/hhalpin/weauthn-model - - See the [Paper] section 5.2 for a detailed description of the original model - - MANUAL: The proverif manual is here: - https://prosecco.gforge.inria.fr/personal/bblanche/proverif/manual.pdf - -`webauthn.pv` models a registration of a WebAuthn user credential with a server -(aka Relying Party) and then models subsequent authentication using that registered credential. - - -## STATUS - -This is WORK IN PROGRESS by Jeff Hodges as of Aug-2021. - -Presently, the model seeks to prove that the ostensibly valid authentication response message -(i.e., the signatures validate) received by the server is the same as the one sent by the -authnr+clientPlatform. Proverif finds this to be FALSE, meaning it found a way such that an -attacker can forge an authentication response message that passes signature verification by -the server (if I understand correctly). - -I'm presently suspecting this result is due to the sophisticated signature algorithm model -ensconced in `crypto.pvl`. Digging into this result and determining its source and -significance is a TODO. - -Further, the model ought to be updated to model the authenticator, client platform, and server -as distinct entities, in order to more closely model the actual end-to-end behavior of the system. - - -## NOTES - -Proverif models (`*.pv` files) in this directory are modularized using the -C Preprocessor (this is why `run_proverif.sh` is used rather than invoking -the `proverif` command directly). - -Specifically, `webauthn.pv` relies upon `crypto.pvl` and `named_tuples.pvl`. - -These pvl ("Proverif library") files were obtained from: - - `//google3/cloud/security/virtsec/proverif_rg/seems_legit/` (`crypto.pvl`) - - `//google3/cloud/security/virtsec/proverif_rg/ekep/` (`named_tuples.pvl`) - - -`crypto.pvl` models sophisticated signature proofs based on the recent "Seems Legit" -paper , SEE `go/seems-legit-proverif` FOR DETAILS. - -`named_tuples.pvl` very cleverly uses the C-preprocessor to make it easy to model -nested datastructures in Proverif. `webauthn.pv` uses this liberally to create a -fine-grained model datastructure-wise. - -SEE ALSO: ProVerif Reading Group `//google3/cloud/security/virtsec/proverif_rg/` - - -## Running Proverif - -To run a file in ProVerif, use the script `run_proverif.sh`. This script assumes -that ProVerif is installed on the local machine. The typical usage is: - -```bash -$ ./run_proverif.sh webauthn.pv -``` - -NOTE: You can only run `.pv` files, and not `.pvl` (library) files. Also, - `run_proverif.sh` generates colorized output which looks nice in a - terminal but is lousy to use when captured in a file and examined in - an editor (see the next section). - - -### Running Proverif with no color ouput - -`run_proverif.sh`'s standard output is colorized. If you wish to generate output -without colorization, e.g., for examining the output in an editor (e.g., doing so can be -useful for groveling thru the output in order to figure out just what Proverif did -to find an attack) then run Proverif like so: - -```bash -$ ./run_proverif-no-color.sh webauthn.pv > some.output.file.name -``` - - -### Debugging syntax errors - -If the ProVerif script has a syntax error, the output will refer to a line in -one of the generated files. The temporary files will be automatically cleaned up -after the script finishes. To disable this automatic cleanup, set the -environment variable `PROVERIF_NO_CLEANUP`. For example: - -```bash -$ PROVERIF_NO_CLEANUP=1 ./run_proverif.sh webauthn.pv -``` -In this case, the first line of the Proverif console output will be akin to: - -```bash -Output in /tmp/tmp.Lxv1mdTe7y -``` -..where `/tmp/tmp.Lxv1mdTe7y` is a directory containing a single file `webauthn.pv`, -which is the C preprocessor output (ie was the latter that was fed to the `proverif` -command). - - - -## Running ProVerif Interactively (TODO) - -[NOTE: I have not experimented with this as yet] - -The `run_proverif.sh` script supports an environment variable -`PROVERIF_INTERACT`. If this variable is set, then `run_proverif.sh` will call -`proverif_interact` on the generated file instead of `proverif`. This brings up -an interactive GUI that allows the user to act as the adversary in a protocol. -For example: - -```bash -$ PROVERIF_INTERACT=1 ./run_proverif.sh ekep.pv -``` - -To facilitate the job of the adversary in interactive mode, the `ekep.pvl` file -conditionally defines helper functions that support generating some of the -messages that would otherwise be tedious and error-prone to type. See the -`ifdef ENABLE_DEBUG_FUNCTIONS` block in `ekep.pvl` for these functions. - -The `run_proverif.sh` script always defines `ENABLE_DEBUG_FUNCTIONS` when -`PROVERIF_INTERACT` is set. - - -end - diff --git a/webauthn.pv b/webauthn.pv deleted file mode 100644 index 0aea680a6..000000000 --- a/webauthn.pv +++ /dev/null @@ -1,293 +0,0 @@ -(* ProVerif Web Authentication Formal Model: webauthn.pv - Originally based on webauthn-basic.pv by Iness Ben Guirat - - Paper: https://dl.acm.org/doi/10.1145/3190619.3190640 (by Iness Ben Guirat and Harry Halpin) - Slides: https://cps-vo.org/node/48511 - github: https://github.com/hhalpin/weauthn-model - ------ - [MANUAL]: The proverif manual is here: - https://prosecco.gforge.inria.fr/personal/bblanche/proverif/manual.pdf -*) - -(* INTRODUCTION: - -See the [Paper[ section 5.2 for a detailed description of the original model. - -This models a registration of a WebAuthn user credential with a server (aka Relying Party) and then models subsequent authentication using that registered credential. The model seeks to prove various properties of the protocol. - -This current model is by Jeff Hodges, July 2021 -http://kingsmountain.com/people/jeff.hodges/ - -*) -set traceDisplay = long. - - -#include "crypto.pvl" -#include "named_tuples.pvl" - - -(* Comms Channel *) - -free c: channel. (* TLS channel *) - -(* free tlsKey: Key [private]. *) (* TLS symmetric encryption key *) - - -(* Various locally-defined types *) -(* (see crypto.pvl and named_tuples.pvl for types not defined in this file) *) - -type nonce. - - -(* Server's ID *) - -type RP_id. -const server: RP_id (* This is not [private] because the server's name is not - necessarily a secret in the real world. *). - - -(* Attestation Key Pair *) - -(* free attSecretKey: SKey [private]. *) -(* free attPublicKey: PKey *) (* [private] .*) (* an attacker can obtain the attPublicKey from several sources, - e.g.: an authenticator itself, or authenticator metadata *) - - -(* Server's and authnr+clientPlatform's tuple messages *) - -type Extensions. (* TODO: placeholder for now *) - - -(** Registration Request message -- server sends this to the user's authnr+clientPlatform: **) - -DEFINE_DATA_TYPE3(RegRequestMsg, - regChallenge, nonce, - serverName, RP_id, - extensions, Extensions). - -fun RegRequestMsg2b( RegRequestMsg ): bitstring [data, typeConverter]. -fun B2RegRequestMsg( bitstring ): RegRequestMsg [data, typeConverter]. - - -(** Registration Response message -- Attestation Statement, Authenticator Data, and Attestation Object: **) - -DEFINE_DATA_TYPE2(AttStatement, - attSignature, bitstring, - attPubKey, PKey). - -fun AttStatement2b( AttStatement ): bitstring [data, typeConverter]. -fun B2AttStatement( bitstring ): AttStatement [data, typeConverter]. - - -(* TODO: -fun BuildExtensions(bitstring): Extensions [data]. -reduc forall devicePublicKey: bitstring; - Extensions_devicePublicKey(BuildExtensions(devicePublicKey)) = devicePublicKey. -*) - -DEFINE_DATA_TYPE3(AuthData, - serverName, RP_id, - userCredPubKey, PKey, - extensions, Extensions). - -fun AuthData2b( AuthData ): bitstring [data, typeConverter]. -fun B2AuthData( bitstring ): AuthData [data, typeConverter]. - - -DEFINE_DATA_TYPE2(AttObject, - attStatement, AttStatement, - authData, AuthData). - -fun AttObject2b( AttObject ): bitstring [data, typeConverter]. -fun B2AttObject( bitstring ): AttObject [data, typeConverter]. - - - -(** Registration Response message: AuthenticatorAttestationResponse -- authnr+clientPlatform returns this to server: **) - -DEFINE_DATA_TYPE2(AuthnrAttResponseMsg, - challenge, nonce, - attObject, AttObject). - -fun AuthnrAttResponseMsg2b( AuthnrAttResponseMsg ): bitstring [data, typeConverter]. -fun B2AuthnrAttResponseMsg( bitstring ): AuthnrAttResponseMsg [data, typeConverter]. - - -(** Authentication Request message -- server sends this to the client: **) -DEFINE_DATA_TYPE2(AuthnRequestMsg, - authnChallenge, nonce, - serverName, RP_id). - -fun AuthnRequestMsg2b( AuthnRequestMsg ): bitstring [data, typeConverter]. -fun B2AuthnRequestMsg( bitstring ): AuthnRequestMsg [data, typeConverter]. - - -(** Authentication Response message -- user's authnr+clientPlatform returns this to server: **) -DEFINE_DATA_TYPE3(AuthnResponseMsg, - returnedAuthnChallenge, nonce, - serverName, RP_id, - assertSignature, bitstring). - -fun AuthnResponseMsg2b( AuthnResponseMsg ): bitstring [data, typeConverter]. -fun B2AuthnResponseMsg( bitstring ): AuthnResponseMsg [data, typeConverter]. - - -(* Events and Queries *) - -event sendingAuthnResponse(bitstring). -event receivedValidAuthnResponse(bitstring). - -(** we can only receive a valid authn response if the user's authnr+clientPlatform - sent the response message, and we have to receive the same message as was sent. **) - -query msg:bitstring, msg':bitstring; - event(receivedValidAuthnResponse(msg)) && event(sendingAuthnResponse(msg')) ==> msg = msg'. - -(* -query attacker(attSecretKey). -query attacker(attPublicKey). -query attacker(tlsKey). -*) - -query secret regChallengeQ; (* secrecy queries (see "5.1.2 Security properties" in [MANUAL]) *) - secret authnChallengeQ. - - - -(* Processes *) - -let processClientAndAuthnr ( k: Key, attskU: SKey, attpkU: PKey) = - - (** Registration **) - - in(c, encRegRequestMsg: bitstring); (* receive and parse registration request msg *) - let regRequestMsgb = dec(encRegRequestMsg, k) in - let regRequestMsg = B2RegRequestMsg(regRequestMsgb) in - let receivedRegChallenge = RegRequestMsg_regChallenge(regRequestMsg) in - let receivedServerNameReg = RegRequestMsg_serverName(regRequestMsg) in - let receivedExtensions = RegRequestMsg_extensions(regRequestMsg) in - - new skU: SKey; (* mint new user credential "secret key user" |skU| *) - let pkU = pk(skU) in (* and "public key user" |pkU| *) - new extensionsOutputs: Extensions; - - let authData = BuildAuthData(receivedServerNameReg, pkU, extensionsOutputs) in (* construct attestation object *) - let attSignature = sign( (authData, receivedRegChallenge), attskU ) in - let attStatement = BuildAttStatement(attSignature, attpkU) in - let attObject = BuildAttObject(attStatement, authData) in - - let authnrAttResponseMsg = BuildAuthnrAttResponseMsg(receivedRegChallenge, attObject) in - let authnrAttResponseMsgb = AuthnrAttResponseMsg2b(authnrAttResponseMsg) in - - let encAuthnrAttResponseMsg = enc(authnrAttResponseMsgb, k) in - - out(c, encAuthnrAttResponseMsg); (* return registration response with attestation *) - - - (** Authentication **) - - in(c, encAuthnRequestMsg: bitstring); (* receive server's challenge. *) - - let authnRequestMsgb = dec(encAuthnRequestMsg, k) in - let authnRequestMsg = B2AuthnRequestMsg(authnRequestMsgb) in - let receivedAuthnChallenge = AuthnRequestMsg_authnChallenge(authnRequestMsg) in - let receivedServerNameAuthn = AuthnRequestMsg_serverName(authnRequestMsg) in - - let assertionSignature = sign((receivedAuthnChallenge, receivedServerNameAuthn), skU) in - - let authnResponseMsg = BuildAuthnResponseMsg(receivedAuthnChallenge, receivedServerNameAuthn, assertionSignature) in - let authnResponseMsgb = AuthnResponseMsg2b(authnResponseMsg) in - - let encAuthnResponseMsgFromAuthnr = enc(authnResponseMsgb, k) in - - event sendingAuthnResponse( encAuthnResponseMsgFromAuthnr ); - - out(c, encAuthnResponseMsgFromAuthnr). (* return authn response *) - - - -let processServer (k: Key, a: RP_id) = - - (** Registration **) - - new regChallenge: nonce; - new requestedExtensions: Extensions; - - let regRequestMsg = BuildRegRequestMsg(regChallenge, a, requestedExtensions) in - let regRequestMsgb = RegRequestMsg2b(regRequestMsg) in - - out(c, enc(regRequestMsgb, k)); (* send registration request *) - - (* --- wait for client -- *) - - in(c, encAuthnrAttResponseMsg: bitstring); (* receive possible registration response *) - - let authnrAttResponseMsgb = dec(encAuthnrAttResponseMsg, k) in (* decrypt and parse |encAuthnrAttResponseMsg| *) - let authnrAttResponseMsg = B2AuthnrAttResponseMsg(authnrAttResponseMsgb) in - - let returnedRegChallenge = AuthnrAttResponseMsg_challenge(authnrAttResponseMsg) in - let returnedAttObject = AuthnrAttResponseMsg_attObject(authnrAttResponseMsg) in (* extract AttObject *) - - let returnedAttStatement = AttObject_attStatement(returnedAttObject) in (* extract and parse AttStatement *) - let attSig = AttStatement_attSignature(returnedAttStatement) in - let attPK = AttStatement_attPubKey(returnedAttStatement) in - - let returnedAuthData = AttObject_authData(returnedAttObject) in (* extract and parse AuthData *) - let returnedServerName = AuthData_serverName(returnedAuthData) in - let userPK = AuthData_userCredPubKey(returnedAuthData) in - let returnedExtensions = AuthData_extensions(returnedAuthData) in - - - if checkSign(attSig, (returnedAuthData, returnedRegChallenge), attPK) then (* we have a registration response msg with - a (supposedly) valid signature... *) - - if (regChallenge = returnedRegChallenge) then ( (* ...and if the challenge returned matches the - one we sent, we will proceed to challenge - the user to authenticate. *) - (** Authentication **) - - new authnChallenge: nonce; - - (* let authnChallengeBitstr = nonce_to_bitstring( authnChallenge ) in -- do we need to do this type conversion? - seems like we do not. *) - - let authnRequestMsg = BuildAuthnRequestMsg(authnChallenge, a) in - let authnRequestMsgb = AuthnRequestMsg2b(authnRequestMsg) in - - out(c, enc(authnRequestMsgb, k) ); (* send authentication request *) - - (* --- wait for client --- *) - - in(c, encAuthnResponseMsg: bitstring); (* receive encrypted authn response *) - - let authnResponseMsgb = dec(encAuthnResponseMsg, k) in (* decrypt and parse |encAuthnResponseMsg| *) - let authnResponseMsg = B2AuthnResponseMsg(authnResponseMsgb) in - let retAuthnChallenge = AuthnResponseMsg_returnedAuthnChallenge(authnResponseMsg) in - let retServerName = AuthnResponseMsg_serverName(authnResponseMsg) in - let retAssertSignature = AuthnResponseMsg_assertSignature(authnResponseMsg) in - - if (authnChallenge = retAuthnChallenge) && (a = retServerName) then - if checkSign(retAssertSignature, (retAuthnChallenge, retServerName), userPK) then - - event receivedValidAuthnResponse(encAuthnResponseMsg); (* signal receipt of ostensibly valid authn - authn response *) - - (* set up for secrecy queries (see "5.1.2 Security properties" in [MANUAL]) *) - let regChallengeQ = regChallenge in - let authnChallengeQ = authnChallenge - - ). - - - -process - new tlsKey: Key; (** assume TLS handshake is error-free and yields this symmetric channel-encryption key **) - new attSecretKey: SKey; (** assume a particular authenticator with a particular attestation private key.. **) - - let attPublicKey = pk(attSecretKey) in (** ..and attestation public key. **) - ( - !processClientAndAuthnr(tlsKey, attSecretKey, attPublicKey) | !processServer(tlsKey, server) - ) - - -(** END **) From 66e67bdc62673007ddae06ea9df582f100390f26 Mon Sep 17 00:00:00 2001 From: JeffH Date: Tue, 17 Aug 2021 14:38:37 -0700 Subject: [PATCH 060/131] update Device-bound public key extension --- index.bs | 97 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 32 deletions(-) diff --git a/index.bs b/index.bs index 170dcc9ef..73e83c1c1 100644 --- a/index.bs +++ b/index.bs @@ -5943,18 +5943,18 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke :: The Boolean value [TRUE] to indicate that this extension is requested by the [=[RP]=]. partial dictionary AuthenticationExtensionsClientInputs { - boolean devicePublicKey; // Explicitly set this to \`true\`! + boolean devicePubKey; // Explicitly set this to \`true\`! }; : Client extension processing -:: If {{AuthenticationExtensionsClientInputs/devicePublicKey}} is [TRUE], then there is no processing, except for creating the authenticator extension input from the client extension input. Otherwise, the platform ignores this extension. +:: If {{AuthenticationExtensionsClientInputs/devicePublicKey}} is [TRUE], the client creates the authenticator extension input from the client extension input. If {{AuthenticationExtensionsClientInputs/devicePublicKey}} is [FALSE], the client ignores this extension. : Client extension output :: An ArrayBuffer containing the [=device public key attestation object=] that was returned in the authenticator extension output. partial dictionary AuthenticationExtensionsClientOutputs { - ArrayBuffer devicePublicKeyAttestationObject; + ArrayBuffer devicePubKey; }; @@ -5968,32 +5968,73 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke ) ``` +: Authenticator extension output +:: The device public key attestation object constructed above (i.e., `attObjForDevicePublicKey`): + + ``` + $$extensionOutput //= ( + devicePubKey: attObjForDevicePublicKey, + ) + + AttObjForDevicePublicKey = { ; Note: This object conveys an attested + ; device public key and is analogous to `attObj`. + + sig: bstr, ; Result of sign((clientDataHash || userCredentialId), devicePrivateKey) + ; Note that this signature value is unique per-response + ; because the client data contains the per-request challenge. + + aaguid: bstr, ; Authenticator's AAGUID (16 bytes fixed-length) + ; https://www.w3.org/TR/webauthn/#aaguid + + dpk: bstr, ; The Device Public Key (self-describing variable length, + ; COSE_Key format, CBOR-encoded)). + + ; Whether this key is scoped to the entire device, or a loosely-defined, + ; narrower scope called "app". For example, a "device"-scoped key is expected + ; to be the same between an app and a browser on the same device, while + ; an "app"-scoped key would probably not be. + ; + ; Whatever the scope, a device key is still specific to a given credential + ; and does not provide any ability to link credentials. + ; + ; Whether device-scoped or not, keys are still device-bound. I.e. an + ; app-scoped key does not enjoy lesser protection from extraction. + + scope: uint .size 1, ; a value of '0' means "entire device" ("all apps") scope. + ; '1' means "per-app" scope. + ; Values other than '0' or '1' are reserved for future use. + + ; See https://www.w3.org/TR/webauthn/#sctn-generating-an-attestation-object + ; + ; Attestation statement formats define the `fmt` and `attStmt` members of + ; $$attStmtType. + ; + ; In summary, the `attStmt` will (typically) contain: + ; (1) a SIGNATURE value calculated (using the attestation private key) + ; over (aaguid || dpk). + ; (2) the attestation certificate or public key, and supporting certificates, + ; if any. + ; + ; Note that there are details dependent upon the particular attestation + ; statement format. + ; See https://www.w3.org/TR/webauthn/#sctn-defined-attestation-formats. + + $$attStmtType, + } + + ``` + : Authenticator extension processing :: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: - 1. Create or select the [=user credential=] as usual. + 1. Create or select the [=user credential=] as usual, and let `userCredentialId` be its [=credentialId=]. 1. If a [=hardware-bound device key pair=] does not already exist for this [=user credential=] on the [=authenticator=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing [=device-bound key=]. - 1. Let `devicePublicKey` be the newly created or existing [=device public key=], in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=] when the latter is conveyed in [=attested credential data=]. + 1. Let `dpk` be the newly created or existing [=device public key=], in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=] when the latter is conveyed in [=attested credential data=]. 1. Let `devicePrivateKey` be the newly created or existing [=device private key=]. 1. Let `clientDataHash` be the [=hash of the serialized client data=]. - 1. Let `dpkSignatureValue` be the result of `sign(devicePrivateKey, (clientDataHash || devicePublicKey))` using the signature algorithm appropriate for the `devicePrivateKey`'s public key algorithm. - 1. Let `rpIdHash` be the [=user credential=]'s [=rpIdHash=]. - 1. Let `credentialId` be [=user credential=]'s [=credentialId=]. - 1. Let `AuthDataForDevicePublicKeyAttestation` be a [=CBOR=] record containing `rpIdHash`, `credentialId`, `devicePublicKey`, and `dpkSignatureValue`. This is expressed in [=CDDL=] as: - - ``` - AuthDataForDevicePublicKeyAttestation = [ ; Note: The element labels are annotations - ; and do not appear in an encoded - ; record. - - rpIdHash: bstr, ; SHA-256 hash of the user credential's RP ID - credentialId: bstr, ; CredentialId of the user credential - devicePublicKey: bstr, ; COSE_Key format - dpkSignatureValue: bstr, ; sign(devicePrivateKey, - ; (clientDataHash || devicePublicKey)) - ] - ``` - - 10. Let `$$attStmtType` be the result of generating the appropriate attestation statement, using `AuthDataForDevicePublicKeyAttestation`, see [[#sctn-generating-an-attestation-object]]. Attestation statement formats define the `fmt` and `attStmt` members of `$$attStmtType`'s value. In summary, it typically contains a signature value calculated over the bytes of `AuthDataForDevicePublicKeyAttestation`, the attestation certificate or public key, and supporting certificates, if any. Note that the details are dependent upon the particular attestation statement format. See [[#sctn-attestation-formats]]. + 1. Let `sig` be the result of signing the concatenation of `clientDataHash` and `userCredentialId` using the `devicePrivateKey` and the signature algorithm appropriate for the `devicePrivateKey`'s public key algorithm: `sign((clientDataHash || userCredentialId), devicePrivateKey)`. + 1. Let `scope` have the value zero (0x00) if this is an "entrie-device" device public key. Otherwise, let `scope` have the value one (0x01), indicating a more narrow per-app scope. + 1. Let `$$attStmtType` be the result of generating an attestation statement in the [=attestation statement format=] appropriate for the [=user credential=], although substituting the authenticator's [=aaguid=] for |authenticatorData|, and substituting `userCredentialId` for |clientDataHash| in the [=attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]). Attestation statement formats define the `fmt` and `attStmt` members of `$$attStmtType`'s value. In summary, it typically contains a signature value calculated over the bytes of `(clientDataHash || userCredentialId)`, the attestation certificate or public key, and supporting certificates, if any. Note that the details are dependent upon the particular attestation statement format. See [[#sctn-attestation-formats]]. + 1. Let `AttObjForDevicePublicKey` be a [=CBOR=] object, as defined above (in "Authenticator extension output"), 1. Let `attObjForDevicePublicKey` be a CBOR object containing a `dpkAuthData` member whose value is the `AuthDataForDevicePublicKeyAttestation` record constructed above, and the attestation statement constructed in the prior step as the value of `$$attStmtType`: ``` @@ -6007,14 +6048,6 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke } ``` -: Authenticator extension output -:: The device public key attestation object constructed above (i.e., `attObjForDevicePublicKey`): - - ``` - $$extensionOutput //= ( - devicePublicKey: attObjForDevicePublicKey, - ) - ``` # User Agent Automation # {#sctn-automation} From 9ac274aaa5cf6f505e0c5cd861b24706c128626f Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 20 Aug 2021 09:35:40 -0700 Subject: [PATCH 061/131] work in progress --- index.bs | 62 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/index.bs b/index.bs index 73e83c1c1..3849ea248 100644 --- a/index.bs +++ b/index.bs @@ -1094,7 +1094,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S : Device Public Key : Device Key : Secondary Key -:: A [=hardware-bound device key pair=], also known as a [=device-bound key=], is a [=[RP]=]-specific and [=user credential=]-specific public key pair created upon a [=[RP]=]'s request via the [=devicePublicKey=] [=WebAuthn extension=]. +:: A [=hardware-bound device key pair=], also known as a [=device-bound key=], is a [=[RP]=]-specific and [=user credential=]-specific public key pair created upon a [=[RP]=]'s request via the [=devicePubKey=] [=WebAuthn extension=]. The [=[RP]=] may supply this extension during both [=registration=] and [=authentication=]. The [=authenticator=] that a [=hardware-bound device key pair=] is created upon guarantees that the [=device private key=] is securely stored in hardware, i.e., it is unextractable. The [=device public key=] is returned only to the [=[RP]=] that created the [=hardware-bound device key pair=]. See [[#sctn-device-publickey-extension]]. @@ -5928,11 +5928,23 @@ Note: In order to interoperate, user agents storing large blobs on authenticator ## Device-bound public key extension (devicePubKey) ## {#sctn-device-publickey-extension} -This [=client extension|client=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal. This is done by creating a [=[RP]=]-specific [=hardware-bound device key pair=] on the [=client device=], if such a key pair does not already exist for the [=user credential=] being created or exercised, and returning the attested [=device public key=] along with a signature by the [=device private key=] to the [=[RP]=]. This is done each time this [=devicePublicKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/create()|navigator.credentials.get()}} call. +This [=client extension|client=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal. This is done by creating a [=[RP]=]-specific [=hardware-bound device key pair=] on the [=client device=], if such a key pair does not already exist for the [=user credential=] being created or exercised, and returning the attested [=device public key=] along with a signature by the [=device private key=] to the [=[RP]=]. This is done each time this [=devicePubKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/create()|navigator.credentials.get()}} call. -If the [=client device=] is incapable of generating a [=hardware-bound device key pair=], or the registration or authentication operation fails for any reason, this extension is ignored and no [=hardware-bound device key pair=] is created. In this case, there is no [=devicePublicKey=] extension output generated. +If the [=client device=] is incapable of generating a [=hardware-bound device key pair=], or the registration or authentication operation fails for any reason, this extension is ignored and no [=hardware-bound device key pair=] is created. In this case, there is no [=devicePubKey=] extension output generated. +
+ Note 1: This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension provides a "device continuity" signal when used consistently with both {{CredentialsContainer/get()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations. When a user's credential is synced to a new device, the [=[RP]=] will receive a new [=device public key=] on the initial {{CredentialsContainer/get()|navigator.credentials.get()}} invocation on the new device. Note that a synced credential will be exercised for authentication on a new device without a {{CredentialsContainer/get()|navigator.credentials.create()}} having been performed on that new device). + + A usage example is thus: + + Say that a sign-in request appears along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical working hours observed for the account. The risk may be deemed high enough not to allow the request, even with a synced credential. But if a device-bound signature can also be presented and it's a [=device-bound key=] that is well established for this user, then that may tip the balance. + + Note 2: A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `AttObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [= attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `context`, and `$$attStmtType` values. Upon receiving subsequent +
+ +Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDevicePublicKey` ? + : Extension identifier :: `devicePubKey` @@ -5948,7 +5960,7 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke : Client extension processing -:: If {{AuthenticationExtensionsClientInputs/devicePublicKey}} is [TRUE], the client creates the authenticator extension input from the client extension input. If {{AuthenticationExtensionsClientInputs/devicePublicKey}} is [FALSE], the client ignores this extension. +:: If {{AuthenticationExtensionsClientInputs/devicePubKey}} is [TRUE], the client creates the authenticator extension input from the client extension input. If {{AuthenticationExtensionsClientInputs/devicePubKey}} is [FALSE], the client ignores this extension. : Client extension output :: An ArrayBuffer containing the [=device public key attestation object=] that was returned in the authenticator extension output. @@ -5964,12 +5976,12 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke ``` $$extensionInput //= ( - devicePublicKey: true + devicePubKey: true ) ``` : Authenticator extension output -:: The device public key attestation object constructed above (i.e., `attObjForDevicePublicKey`): +:: The device public key attestation object, `attObjForDevicePublicKey`: ``` $$extensionOutput //= ( @@ -5977,7 +5989,7 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke ) AttObjForDevicePublicKey = { ; Note: This object conveys an attested - ; device public key and is analogous to `attObj`. + ; device public key and is analogous to \`attObj\`. sig: bstr, ; Result of sign((clientDataHash || userCredentialId), devicePrivateKey) ; Note that this signature value is unique per-response @@ -6000,16 +6012,16 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke ; Whether device-scoped or not, keys are still device-bound. I.e. an ; app-scoped key does not enjoy lesser protection from extraction. - scope: uint .size 1, ; a value of '0' means "entire device" ("all apps") scope. - ; '1' means "per-app" scope. - ; Values other than '0' or '1' are reserved for future use. + scope: uint .size 1, ; a value of 0x00 means "entire device" ("all apps") scope. + ; 0x01 means "per-app" scope. + ; Values other than 0x00 or 0x01 are reserved for future use. ; See https://www.w3.org/TR/webauthn/#sctn-generating-an-attestation-object ; - ; Attestation statement formats define the `fmt` and `attStmt` members of + ; Attestation statement formats define the \`fmt\` and \`attStmt\` members of ; $$attStmtType. ; - ; In summary, the `attStmt` will (typically) contain: + ; In summary, the \`attStmt\` will (typically) contain: ; (1) a SIGNATURE value calculated (using the attestation private key) ; over (aaguid || dpk). ; (2) the attestation certificate or public key, and supporting certificates, @@ -6027,26 +6039,28 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke : Authenticator extension processing :: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: 1. Create or select the [=user credential=] as usual, and let `userCredentialId` be its [=credentialId=]. + 1. If a [=hardware-bound device key pair=] does not already exist for this [=user credential=] on the [=authenticator=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing [=device-bound key=]. + 1. Let `dpk` be the newly created or existing [=device public key=], in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=] when the latter is conveyed in [=attested credential data=]. + 1. Let `devicePrivateKey` be the newly created or existing [=device private key=]. + 1. Let `clientDataHash` be the [=hash of the serialized client data=]. + 1. Let `sig` be the result of signing the concatenation of `clientDataHash` and `userCredentialId` using the `devicePrivateKey` and the signature algorithm appropriate for the `devicePrivateKey`'s public key algorithm: `sign((clientDataHash || userCredentialId), devicePrivateKey)`. - 1. Let `scope` have the value zero (0x00) if this is an "entrie-device" device public key. Otherwise, let `scope` have the value one (0x01), indicating a more narrow per-app scope. - 1. Let `$$attStmtType` be the result of generating an attestation statement in the [=attestation statement format=] appropriate for the [=user credential=], although substituting the authenticator's [=aaguid=] for |authenticatorData|, and substituting `userCredentialId` for |clientDataHash| in the [=attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]). Attestation statement formats define the `fmt` and `attStmt` members of `$$attStmtType`'s value. In summary, it typically contains a signature value calculated over the bytes of `(clientDataHash || userCredentialId)`, the attestation certificate or public key, and supporting certificates, if any. Note that the details are dependent upon the particular attestation statement format. See [[#sctn-attestation-formats]]. - 1. Let `AttObjForDevicePublicKey` be a [=CBOR=] object, as defined above (in "Authenticator extension output"), - 1. Let `attObjForDevicePublicKey` be a CBOR object containing a `dpkAuthData` member whose value is the `AuthDataForDevicePublicKeyAttestation` record constructed above, and the attestation statement constructed in the prior step as the value of `$$attStmtType`: - ``` - attObjForDevicePublicKey = { ; Note: This object conveys an attested - ; device public key and is - ; analogous to \`attObj\`. + 1. Let `scope` have the value zero (0x00) if this is an "entrie-device" [=device public key=]. Otherwise, let `scope` have the value one (0x01), indicating a more narrow per-app scope. - dpkAuthData: AuthDataForDevicePublicKeyAttestation, + 1. Let `$$attStmtType` be the result of generating an attestation statement in the [=attestation statement format=] appropriate for the [=user credential=], although substituting the authenticator's [=aaguid=] for |authenticatorData|, and substituting `userCredentialId` for |clientDataHash| in the [=attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]). Attestation statement formats define the `fmt` and `attStmt` members of `$$attStmtType`'s value. - $$attStmtType, - } - ``` + In summary, `$$attStmtType`'s value typically contains a signature value calculated over the bytes of `(clientDataHash || userCredentialId)`, the attestation certificate or public key, and supporting certificates, if any. + + Note: The details of `$$attStmtType`'s value are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. + + 1. Let `AttObjForDevicePublicKey` be a [=CBOR=] object, as defined above (in "Authenticator extension output"), whose values for its `sig`, `aaguid`, `dpk`, `scope`, and `$$attStmtType` members are as calculated in the prior steps. + + 1. Let `AttObjForDevicePublicKey` be the value for the `devicePubKey` member of `$$extensionOutput`, and return the extension output. From fcc6a68857f6c9fa0f3f50b2a4dbf8ad99ae54d1 Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 20 Aug 2021 23:00:39 -0700 Subject: [PATCH 062/131] finish Notes -- nominally complete for Draft PR --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 3849ea248..551d56eff 100644 --- a/index.bs +++ b/index.bs @@ -5940,10 +5940,10 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke Say that a sign-in request appears along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical working hours observed for the account. The risk may be deemed high enough not to allow the request, even with a synced credential. But if a device-bound signature can also be presented and it's a [=device-bound key=] that is well established for this user, then that may tip the balance. - Note 2: A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `AttObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [= attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `context`, and `$$attStmtType` values. Upon receiving subsequent + Note 2: A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `AttObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [= attestation statement format=]'s signing procedure, see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `context`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrance, do byte-level comparisons of the cached `aaguid`, `dpk`, `context`, and `$$attStmtType` values against those returned in the extension output. -Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDevicePublicKey` ? +Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDevicePublicKey`" ? (i.e., analogous to steps 7 through 19 in [[#sctn-verifying-assertion]]) : Extension identifier :: `devicePubKey` From 7c5393c8eba0da3f9f19f6a62805af86903027d2 Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 20 Aug 2021 23:16:15 -0700 Subject: [PATCH 063/131] untraced device-bound-key-pair.txt --- device-bound-key-pair.txt | 404 -------------------------------------- 1 file changed, 404 deletions(-) delete mode 100644 device-bound-key-pair.txt diff --git a/device-bound-key-pair.txt b/device-bound-key-pair.txt deleted file mode 100644 index f79756445..000000000 --- a/device-bound-key-pair.txt +++ /dev/null @@ -1,404 +0,0 @@ - - - -==== Proposed Syntax for devicePublicKey (dpk) (aka Secondary Key aka Device Key) Extension Output ==== - -$$extensionOutput //= ( ; Expressed in CDDL - devicePubKey: AttObjForDevicePublicKey, -) - -AttObjForDevicePublicKey = { ; Note: This object conveys an attested device public key - ; and is analogous to `attObj`. - - sig: bstr, ; result of sign((clientDataHash || userCredentialId), devicePrivateKey) - ; Note that this sig value is unique per-response because the client data - ; contains the per-request challenge. - - aaguid: bstr, ; authenticator's AAGUID - ; (16 bytes fixed-length ). - - dpk: bstr, ; the Device Public Key - ; (self-describing variable length, COSE_Key format (CBOR-encoded)). - - $$attStmtType, ; see . - ; - ; Attestation statement formats define the `fmt` and `attStmt` members of - ; $$attStmtType. - ; - ; In summary, the `attStmt` will (typically) contain: - ; (1) a SIGNATURE value calculated (using the attestation private key) - ; over (aaguid || dpk). - ; (2) the attestation certificate or public key, and supporting certificates, - ; if any. - ; - ; Note that there are details dependent upon the particular attestation - ; statement format. - ; See . -} - - - - - -==== SUPPORTING INFORMATION and MUSINGS: - -==== WebAuthn Authenticator Response Messages, which include: ==== -==== Collected Client Data, Attestation Object, Authenticator Data structures, ==== -==== and (nested) signature values calculated over portions of them. ==== - - - // Coalesced informal expansion of PublicKeyCredential (showing all the important internal components - // and eliminating (irrelevant) nested containers): - - - Coalesced Registration PublicKeyCredential { - id // User CredentialID - clientDataJSON // serialized collected client data - attestationObject { // `attObj` goes here - authenticatorData { // `authData` goes here - rpIdHash - flags - signCount - AAGUID - CredIDLength - CredentialID - CredentialPublicKey // User Credential Public Key - extensions // devicePublicKey extension output will be in here - } - fmt - attStmt // typically contains the ENCOMPASSING SIGNATURE VALUE of - // sign((authData || clientDataHash), AttestationPrivatekey) - } - } - - Coalesced Authentication PublicKeyCredential { - id // User CredentialID - clientDataJSON // serialized collected client data - authenticatorData { // `authData` goes here - rpIdHash - flags - signCount - extensions // devicePublicKey extension output will be in here - } - signature // ENCOMPASSING SIGNATURE VALUE of - // sign((authData || clientDataHash), CredentialPublicKey) - } - - - - // Top-level WebAuthn response messages (expressed in simplified WebIDL): - - interface PublicKeyCredential : Credential { - USVString id; // User Credential ID - AuthenticatorResponse response; - [...] - }; - - interface AuthenticatorResponse { - ArrayBuffer clientDataJSON; // serialized collected client data, included - // in both .create() and .get() responses - }; - - interface AuthenticatorAttestationResponse : AuthenticatorResponse { // .create() "Registration" response - ArrayBuffer attestationObject; // `attObj` goes here, it contains the `authData` and other components - [...] - }; - - interface AuthenticatorAssertionResponse : AuthenticatorResponse { // .get() "Authentication" response - ArrayBuffer authenticatorData; // `authData` goes here - ArrayBuffer signature; // ENCOMPASSING SIGNATURE VALUE over: authData || clientDataHash - // Note that the signed-over authData conveys extension results - [...] // userHandle - }; - - - - // Authenticator Data (authData) in RFC5234 ABNF : - - authData = rpIdHash flags signCount [attestedCredentialData] [extensions] - - rpIdHash = 32OCTET ; SHA-256 hash of the RP ID the credential is scoped to - flags = OCTET - signCount = 4OCTET ; 32-bit unsigned big-endian integer - - attestedCredentialData = AAGUID CredIDLength CredentialID CredentialPublicKey - - AAGUID = 16OCTET ; 16 octets - CredIDLength = 2OCTET ; 16-bit unsigned big-endian integer - CredentialID = 16*OCTET ; CredIDLength octets, at least 16 octets long - CredentialPublicKey = 1*OCTET ; "user credential": self-describing variable length, - ; COSE_Key format (CBOR-endoded) - - extensions = 1*OCTET ; variable-length CBOR map of extension outputs - ; `devicePublicKey` extension ouput is in here. - - - - // Attestation Object (attObj) in CDDL - - attObj = { - authData: bytes, - $$attStmtType - } - - attStmtTemplate .within $$attStmtType - - attStmtTemplate = ( ; Every attestation statement format must have these fields: - fmt: text, - attStmt: { * tstr => any } ; Map is filled in by each concrete attStmtType - - ; in most formats, the attStmt contains a signature value of: - ; sign (authData || clientDataHash - ) - - - // `clientDataHash` is the hash of the serialized CollectedClientData object for the current registration - // or authentication operation (expressed in WebIDL): - - dictionary CollectedClientData { - required DOMString type; // "webauthn.create" or "webauthn.get", as appropriate - required DOMString challenge; // from RP's server - required DOMString origin; // https://html.spec.whatwg.org/#ascii-serialisation-of-an-origin - boolean crossOrigin; - // tokenBinding optionally goes here - } - - - -==== (OLD, SUPERSEDED) Proposed Syntax for devicePublicKey (dpk) (aka Secondary Key aka Device Key) Extension Output -==== (EXPRESSES RATIONALE) (see further below for current actual dpk proposed syntax... - - $$extensionOutput //= ( ; Expressed in CDDL - devicePublicKey: AttObjForDevicePublicKey, - ) - - AttObjForDevicePublicKey = { ; Note: This object conveys an attested device public key - ; and is analogous to `attObj`. - - dpkSignatureValue: bstr, ; result of sign((clientDataHash || userCredentialId), devicePrivateKey) - ; Note that this sig value is unique per-request because clientDataHash contains - ; the per-request challenge. - - authDataForDevicePublicKeyAttestation: bstr, ; AuthDataForDevicePublicKeyAttestation goes here (see below) - - $$attStmtType, ; see . - ; - ; Attestation statement formats define the `fmt` and `attStmt` members of - ; $$attStmtType. - ; - ; In summary, the `attStmt` contains: - ; (1) a SIGNATURE value calculated (using the attestation private key) - ; over the bytes of (authDataForDevicePublicKeyAttestation || rpIdHash), and - ; (2) the attestation certificate or public key, and supporting certificates, if any. - ; - ; Note that there are details dependent upon the particular attestation statement - ; format. See . - } - - - AuthDataForDevicePublicKeyAttestation = AAGUID devicePublicKey ; Expressed in RFC5234 ABNF: - - ; A rationale for also including `rpIdHash` here is so `AttObjForDevicePublicKey.attStmt` - ; is "self contained", i.e., so that the verifier would not need to "look elsewhere" in order - ; to verify the signature. - ; rpIdHash = 32OCTET ; SHA-256 hash of the RP ID the device key pair is scoped to - ; HOWEVER, including rpIdHash here can make a substitution of the authDataForDevicePublicKeyAttestation "easier" because - ; then it could differ from the rpIdHash in the top-level message. - - ; Do we also include these next two items here such that the `AttObjForDevicePublicKey.attStmt` is self-contained? - ; I.e., the verifier would not need to "look elsewhere" in order to have the data necessary for signature - ; verification - ; - ; CredIDLength = 2OCTET ; 16-bit unsigned big-endian integer - ; CredentialID = 16*OCTET ; CredIDLength octets, at least 16 octets long - ; - ; note-to-self: ANSWER TO ABOVE IS "no" wrt CredID because otherwise we'd also need to include the clientDataHash - ; and this is just plain getting unnecessarily too large. plus credID is only needed to validate dpkSignatureValue. - - AAGUID = 16OCTET ; authenticator's AAGUID - devicePublicKey = 1*OCTET ; self-describing variable length, COSE_Key format (CBOR-encoded) - - ; Another question here is whether for this `AuthDataForDevicePublicKeyAttestation` continues the tradition of an - ; undelimited bespoke binary format, or whether we encode it in CBOR ? - - - - -==== WebAuthn Signed Objects Hierarchy for Device-bound Key Pair aka Device-bound Public Key aka Secondary Key aka Device Key ==== - - AuthData for Registration and Authentication Operations returning a devicePublicKey extension response (expressed in RFC5234 ABNF; https://www.w3.org/TR/webauthn/#sctn-authenticator-data ): - - ; See WebAuthn-standard `authData` (above) for details, though NOTE that in these two `authData` instantiations - ; the `extensions` component is ALWAYS included because the `AttObjForDevicePublicKey` is always conveyed - ; in the `extensions` component: - - AuthDataForRegistrationReturningDevicePublicKey = rpIdHash flags signCount attestedCredentialData extensions - - AuthDataForAuthenticationReturningDevicePublicKey = rpIdHash flags signCount extensions - - - Registration: - - Encompassing Attestation Object `attObj` is returned by the Registration Operation (in `AuthenticatorAttestationResponse.attestationObject`). This `attObj` attests the user credential, and signs over `AuthDataForRegistrationReturningDevicePublicKey`. The latter object's `extensions` field contains the `devicePublicKey` extension output (see above): - - AuthenticatorAttestationResponse: { - clientDataJSON - attestationObject - } - - attObj = { - AuthDataForRegistrationReturningDevicePublicKey: bytes, ; devicePublicKey extension result is here - $$attStmtType ; as defined in WebAuthn, contains ENCOMPASSING SIGNATURE VALUE over: - ; authData || clientDataHash - } - - - Authentication: - - Encompassing Authentication Assertion is returned by the Authentication Operation (as `AuthenticatorAssertionResponse`). The "assertion" is the `signature` value over the concatenation of the `AuthDataForAuthenticationReturningDevicePublicKey` and the hash of the collected client data containing the RP's challenge. `AuthDataForAuthenticationReturningDevicePublicKey`'s `extensions` field contains the `devicePublicKey` extension output: - - AuthenticatorAssertionResponse: { // expressed in bespoke informal notation - clientDataJSON - AuthDataForAuthenticationReturningDevicePublicKey - signature // ENCOMPASSING SIGNATURE VALUE over: - // AuthDataForAuthenticationReturningDevicePublicKey || clientDataHash - } - - - ==== To-Be-Signed Data for the ENCOMPASSING SIGNATURE VALUEs (expressed in bespoke informal notation): - - EncompassingRegistrationAttestationToBeSigned: { // SIGNED by the attestation private key to form the signature - // value returned in the top-level attestation statement. - // This is only depicting what gets signed by which signing key, - // NOTE that the e.g. credentialID is conveyed unsigned in the - // PublicKeyCredential object. - - AuthDataForRegistrationReturningDevicePublicKey: { - - rpIdHash - flags - signCount - - attestedCredentialData: { - AAGUID - CredIDLength - CredentialID // user credential ID - CredentialPublicKey // "user credential" - } - - extensions: { - - devicePubKey: { - - sig: { // Is a UNIQUE value per response. SIGNED by devicePrivateKey. - sign((clientDataHash || userCredentialId), devicePrivateKey) - // userCredentialId is second because it is variable length - } - - aaguid // This is in `attestedCredentialData`, but attested cred data - // is not present in authentication assertions, so it needs to be - // here IF we wish this devicePublicKey extension result to be the - // same format whether it is returned as result of a registration - // or authn request. - dpk // The "attested" device public key. - - fmt: "...attestation statement format identifier..." - - attStmt: { // contains various items depending upon the attestation statement format, - // Crucially containing a signature value, generated using - // the device's (effective) AttestationPrivatekey, over this to-be-signed data: - - ( aaguid || dpk ) // SIGNED by AttestationPrivatekey. - // Crucially, this to-be-signed data does not include - // clientDataHash because the latter is specific to these - // current request, e.g., the hashed clientData includes - // the request challenge. - // - // Note: This `attStmt` is a CONSTANT value per context - // per device per account per RP. - } - } - - // potentially other extension results... - - } // extensions - - } // AuthDataForRegistrationReturningDevicePublicKey - - ClientDataHash: { // the following items are serialized then hashed to form this value: - type - challenge - origin - crossOrigin - } - - } // EncompassingRegistrationAttestationToBeSigned - - - - EncompassingAuthenticationAssertionToBeSigned: { // SIGNED by the user credential private key yielding the - // `AuthenticatorAssertionResponse.signature` value. - // This is only depicting what gets signed by which signing key, - // NOTE that the e.g. credentialID is conveyed unsigned in the - // PublicKeyCredential object. - - AuthDataForAuthenticationReturningDevicePublicKey: { - - rpIdHash - flags - signCount - - // NOTE: there is no `attestedCredentialData` in the authData conveyed in an assertion - - extensions: { - - devicePubKey: { - - sig: { // Is a UNIQUE value per response. SIGNED by devicePrivateKey. - sign((clientDataHash || userCredentialId), devicePrivateKey) - // userCredentialId is second because it is variable length - } - - aaguid // This is in `attestedCredentialData`, but attested cred data - // is not present in authentication assertions, so it needs to be - // here IF we wish this devicePublicKey extension result to be the - // same format whether it is returned as result of a registration - // or authn request. - dpk // The "attested" device public key. - - fmt: "...attestation statement format identifier..." - - attStmt: { // contains various items depending upon the attestation statement format, - // Crucially containing a signature value, generated using - // the device's (effective) AttestationPrivatekey, over this to-be-signed data: - - ( aaguid || dpk ) // SIGNED by AttestationPrivatekey. - // Crucially, this to-be-signed data does not include - // clientDataHash because the latter is specific to these - // current request, e.g., the hashed clientData includes - // the request challenge. - // - // Note: This `attStmt` is a CONSTANT value per context - // per device per account per RP. - } - } - - // potentially other extension results... - - } // extensions - - } // AuthDataForAuthenticationReturningDevicePublicKey - - ClientDataHash: { // the following items are serialized then hashed to form this value: - type - challenge - origin - crossOrigin - } - - } // EncompassingAuthenticationAssertionToBeSigned - - - - -==== end ==== \ No newline at end of file From 3d16662af306e7169efea910ce75d7b6cd82b287 Mon Sep 17 00:00:00 2001 From: =JeffH Date: Thu, 11 Nov 2021 16:27:45 -0800 Subject: [PATCH 064/131] context is now scope Co-authored-by: Emil Lundberg --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index b25d0cd46..0fe93d0d7 100644 --- a/index.bs +++ b/index.bs @@ -5946,7 +5946,7 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke Say that a sign-in request appears along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical working hours observed for the account. The risk may be deemed high enough not to allow the request, even with a synced credential. But if a device-bound signature can also be presented and it's a [=device-bound key=] that is well established for this user, then that may tip the balance. - Note 2: A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `AttObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [= attestation statement format=]'s signing procedure, see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `context`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrance, do byte-level comparisons of the cached `aaguid`, `dpk`, `context`, and `$$attStmtType` values against those returned in the extension output. + Note 2: A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `AttObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [= attestation statement format=]'s signing procedure, see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `scope`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrance, do byte-level comparisons of the cached `aaguid`, `dpk`, `scope`, and `$$attStmtType` values against those returned in the extension output. Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDevicePublicKey`" ? (i.e., analogous to steps 7 through 19 in [[#sctn-verifying-assertion]]) From aee534c36d84eae84681865f839924d601da3558 Mon Sep 17 00:00:00 2001 From: JeffH Date: Thu, 11 Nov 2021 16:36:09 -0800 Subject: [PATCH 065/131] do binary equality checks --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 0fe93d0d7..9a1daf34a 100644 --- a/index.bs +++ b/index.bs @@ -5946,7 +5946,7 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke Say that a sign-in request appears along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical working hours observed for the account. The risk may be deemed high enough not to allow the request, even with a synced credential. But if a device-bound signature can also be presented and it's a [=device-bound key=] that is well established for this user, then that may tip the balance. - Note 2: A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `AttObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [= attestation statement format=]'s signing procedure, see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `scope`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrance, do byte-level comparisons of the cached `aaguid`, `dpk`, `scope`, and `$$attStmtType` values against those returned in the extension output. + Note 2: A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `AttObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [= attestation statement format=]'s signing procedure, see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `scope`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrance, do binary equality checks against the cached `aaguid`, `dpk`, `scope`, and `$$attStmtType` values against those returned in the extension output. Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDevicePublicKey`" ? (i.e., analogous to steps 7 through 19 in [[#sctn-verifying-assertion]]) From 7c3e2e8d43518e0e9bb4c822f934713db6e2b2f3 Mon Sep 17 00:00:00 2001 From: =JeffH Date: Fri, 12 Nov 2021 11:28:31 -0800 Subject: [PATCH 066/131] Apply suggestions from emlun's code review, thanks! Co-authored-by: Emil Lundberg --- index.bs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/index.bs b/index.bs index 9a1daf34a..e1eed5ce4 100644 --- a/index.bs +++ b/index.bs @@ -5987,14 +5987,14 @@ Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDeviceP ``` : Authenticator extension output -:: The device public key attestation object, `attObjForDevicePublicKey`: +:: The device public key attestation object, defined by the `attObjForDevicePublicKey` type: ``` $$extensionOutput //= ( devicePubKey: attObjForDevicePublicKey, ) - AttObjForDevicePublicKey = { ; Note: This object conveys an attested + attObjForDevicePublicKey = { ; Note: This object conveys an attested ; device public key and is analogous to \`attObj\`. sig: bstr, ; Result of sign((clientDataHash || userCredentialId), devicePrivateKey) @@ -6058,15 +6058,13 @@ Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDeviceP 1. Let `scope` have the value zero (0x00) if this is an "entrie-device" [=device public key=]. Otherwise, let `scope` have the value one (0x01), indicating a more narrow per-app scope. - 1. Let `$$attStmtType` be the result of generating an attestation statement in the [=attestation statement format=] appropriate for the [=user credential=], although substituting the authenticator's [=aaguid=] for |authenticatorData|, and substituting `userCredentialId` for |clientDataHash| in the [=attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]). Attestation statement formats define the `fmt` and `attStmt` members of `$$attStmtType`'s value. + 1. Let the `$$attStmtType` values be the result of generating an attestation statement in the [=attestation statement format=] appropriate for the [=user credential=], although substituting the authenticator's [=aaguid=] for |authenticatorData|, and substituting `userCredentialId` for |clientDataHash| in the [=attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]). Attestation statement formats define the `fmt` and `attStmt` members of the `$$attStmtType` expansion. - In summary, `$$attStmtType`'s value typically contains a signature value calculated over the bytes of `(clientDataHash || userCredentialId)`, the attestation certificate or public key, and supporting certificates, if any. + In summary, the `$$attStmtType` values typically contain a signature value calculated over the bytes of `(clientDataHash || userCredentialId)`, the attestation certificate or public key, and supporting certificates, if any. - Note: The details of `$$attStmtType`'s value are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. + Note: The details of the `$$attStmtType` values are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. - 1. Let `AttObjForDevicePublicKey` be a [=CBOR=] object, as defined above (in "Authenticator extension output"), whose values for its `sig`, `aaguid`, `dpk`, `scope`, and `$$attStmtType` members are as calculated in the prior steps. - - 1. Let `AttObjForDevicePublicKey` be the value for the `devicePubKey` member of `$$extensionOutput`, and return the extension output. + 1. Let the `devicePubKey` [=authenticator extension output=] be a [=CBOR=] map as defined by `attObjForDevicePublicKey` above, whose values for its `sig`, `aaguid`, `dpk`, `scope`, and `$$attStmtType` members are as calculated in the prior steps. From 90593b9cc35d3c5deb3072fdb4c5c80ab10b7ded Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 12 Nov 2021 11:33:45 -0800 Subject: [PATCH 067/131] fixes inspired by emlun's review --- index.bs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.bs b/index.bs index 9a1daf34a..610d9850b 100644 --- a/index.bs +++ b/index.bs @@ -5946,10 +5946,10 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke Say that a sign-in request appears along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical working hours observed for the account. The risk may be deemed high enough not to allow the request, even with a synced credential. But if a device-bound signature can also be presented and it's a [=device-bound key=] that is well established for this user, then that may tip the balance. - Note 2: A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `AttObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [= attestation statement format=]'s signing procedure, see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `scope`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrance, do binary equality checks against the cached `aaguid`, `dpk`, `scope`, and `$$attStmtType` values against those returned in the extension output. + Note 2: A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `attObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [= attestation statement format=]'s signing procedure, see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `scope`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrance, do binary equality checks against the cached `aaguid`, `dpk`, `scope`, and `$$attStmtType` values against those returned in the extension output. -Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDevicePublicKey`" ? (i.e., analogous to steps 7 through 19 in [[#sctn-verifying-assertion]]) +Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDevicePublicKey`" ? (i.e., analogous to the "verification procedue" steps provided in [[#sctn-defined-attestation-formats|attestation statement format definitions]], and also steps 7 through 19 in [[#sctn-verifying-assertion]]) : Extension identifier :: `devicePubKey` @@ -6064,9 +6064,9 @@ Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDeviceP Note: The details of `$$attStmtType`'s value are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. - 1. Let `AttObjForDevicePublicKey` be a [=CBOR=] object, as defined above (in "Authenticator extension output"), whose values for its `sig`, `aaguid`, `dpk`, `scope`, and `$$attStmtType` members are as calculated in the prior steps. + 1. Let `attObjForDevicePublicKey` be a [=CBOR=] object, as defined above (in "Authenticator extension output"), whose values for its `sig`, `aaguid`, `dpk`, `scope`, and `$$attStmtType` members are as calculated in the prior steps. - 1. Let `AttObjForDevicePublicKey` be the value for the `devicePubKey` member of `$$extensionOutput`, and return the extension output. + 1. Let `attObjForDevicePublicKey` be the value for the `devicePubKey` member of `$$extensionOutput`, and return the extension output. From db63d69cc46af5c662c9f3811a1d37e6f1334dc8 Mon Sep 17 00:00:00 2001 From: =JeffH Date: Mon, 15 Nov 2021 13:47:21 -0800 Subject: [PATCH 068/131] incorp pascoej's correction, thx! Co-authored-by: J Pascoe <2867699+pascoej@users.noreply.github.com> --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 585e7eb83..e8d45e603 100644 --- a/index.bs +++ b/index.bs @@ -6086,7 +6086,7 @@ Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDeviceP 1. Let `sig` be the result of signing the concatenation of `clientDataHash` and `userCredentialId` using the `devicePrivateKey` and the signature algorithm appropriate for the `devicePrivateKey`'s public key algorithm: `sign((clientDataHash || userCredentialId), devicePrivateKey)`. - 1. Let `scope` have the value zero (0x00) if this is an "entrie-device" [=device public key=]. Otherwise, let `scope` have the value one (0x01), indicating a more narrow per-app scope. + 1. Let `scope` have the value zero (0x00) if this is an "entire-device" [=device public key=]. Otherwise, let `scope` have the value one (0x01), indicating a more narrow per-app scope. 1. Let the `$$attStmtType` values be the result of generating an attestation statement in the [=attestation statement format=] appropriate for the [=user credential=], although substituting the authenticator's [=aaguid=] for |authenticatorData|, and substituting `userCredentialId` for |clientDataHash| in the [=attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]). Attestation statement formats define the `fmt` and `attStmt` members of the `$$attStmtType` expansion. From 9a786838e11adabed6f0c562645006ce01ee7854 Mon Sep 17 00:00:00 2001 From: JeffH Date: Mon, 15 Nov 2021 17:07:28 -0800 Subject: [PATCH 069/131] fix bug emlun caught (thx) & apply polish --- index.bs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/index.bs b/index.bs index e8d45e603..6acb89c37 100644 --- a/index.bs +++ b/index.bs @@ -1096,8 +1096,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S : Secondary Key :: A [=hardware-bound device key pair=], also known as a [=device-bound key=], is a [=[RP]=]-specific and [=user credential=]-specific public key pair created upon a [=[RP]=]'s request via the [=devicePubKey=] [=WebAuthn extension=]. The [=[RP]=] may supply this extension during both [=registration=] and [=authentication=]. - The [=authenticator=] that a [=hardware-bound device key pair=] is created upon guarantees that the [=device private key=] is securely stored in hardware, i.e., it is unextractable. The [=device public key=] is returned only to the [=[RP]=] that created the [=hardware-bound device key pair=]. See [[#sctn-device-publickey-extension]]. - + The [=authenticator=] that a [=hardware-bound device key pair=] is created upon guarantees that the [=device private key=] is securely stored in hardware, i.e., it is unextractable. The [=device public key=], along with a signature generated using the corresponding [=device private key=], is returned only to the [=[RP]=] that created the [=hardware-bound device key pair=]. This creates a "device continuity" signal that the [=[RP]=] can use as an input to their risk-analysis system. See [[#sctn-device-publickey-extension]]. : Human Palatability :: An identifier that is [=human palatability|human-palatable=] is intended to be rememberable and reproducible by typical human @@ -5974,11 +5973,13 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke A usage example is thus: - Say that a sign-in request appears along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical working hours observed for the account. The risk may be deemed high enough not to allow the request, even with a synced credential. But if a device-bound signature can also be presented and it's a [=device-bound key=] that is well established for this user, then that may tip the balance. + > Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with a synced credential. But if a [=device-bound/signature=], by a [=device-bound key=] that is well established for this user, can also be presented then that may tip the balance. - Note 2: A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `attObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [= attestation statement format=]'s signing procedure, see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `scope`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrance, do binary equality checks against the cached `aaguid`, `dpk`, `scope`, and `$$attStmtType` values against those returned in the extension output. + Note 2: A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `attObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [=attestation statement format=]'s "signing procedure", see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `scope`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrance, do binary equality checks of the cached `aaguid`, `dpk`, `scope`, and `$$attStmtType` values against those returned in the extension output. +Issue(1665): TODO: explicitly add the notion of "synced user credentials" to the spec. + Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDevicePublicKey`" ? (i.e., analogous to the "verification procedue" steps provided in [[#sctn-defined-attestation-formats|attestation statement format definitions]], and also steps 7 through 19 in [[#sctn-verifying-assertion]]) : Extension identifier @@ -6088,9 +6089,9 @@ Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDeviceP 1. Let `scope` have the value zero (0x00) if this is an "entire-device" [=device public key=]. Otherwise, let `scope` have the value one (0x01), indicating a more narrow per-app scope. - 1. Let the `$$attStmtType` values be the result of generating an attestation statement in the [=attestation statement format=] appropriate for the [=user credential=], although substituting the authenticator's [=aaguid=] for |authenticatorData|, and substituting `userCredentialId` for |clientDataHash| in the [=attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]). Attestation statement formats define the `fmt` and `attStmt` members of the `$$attStmtType` expansion. + 1. Let the `$$attStmtType` values be the result of generating an attestation statement in the [=attestation statement format=] appropriate for the [=user credential=], although substituting the authenticator's [=aaguid=] for |authenticatorData|, and substituting `dpk` for |clientDataHash| in the [=attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]). Attestation statement formats define the `fmt` and `attStmt` members of the `$$attStmtType` expansion. - In summary, the `$$attStmtType` values typically contain a signature value calculated over the bytes of `(clientDataHash || userCredentialId)`, the attestation certificate or public key, and supporting certificates, if any. + In summary, the `$$attStmtType` values typically contain a signature value calculated over the bytes of `(aaguid || dpk)`, the attestation certificate or public key, and supporting certificates, if any. Note: The details of the `$$attStmtType` values are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. From d52342ceefed784c747cf9e262770b076b409d4d Mon Sep 17 00:00:00 2001 From: =JeffH Date: Tue, 11 Jan 2022 11:16:26 -0800 Subject: [PATCH 070/131] Apply emlun's suggestions, thx! Co-authored-by: Emil Lundberg --- index.bs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/index.bs b/index.bs index 6acb89c37..cdc73d458 100644 --- a/index.bs +++ b/index.bs @@ -5967,6 +5967,8 @@ This [=client extension|client=] [=registration extension=] and [=authentication If the [=client device=] is incapable of generating a [=hardware-bound device key pair=], or the registration or authentication operation fails for any reason, this extension is ignored and no [=hardware-bound device key pair=] is created. In this case, there is no [=devicePubKey=] extension output generated. +Note: The [=hardware-bound device key=] is not on its own a [=user credential=] and does not have its own [=credential ID=]. Instead the [=hardware-bound device key=] works like a contextual attribute of its associated [=user credential=] when that [=user credential=] is used on a particular [=client device=]. +
Note 1: This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension provides a "device continuity" signal when used consistently with both {{CredentialsContainer/get()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations. When a user's credential is synced to a new device, the [=[RP]=] will receive a new [=device public key=] on the initial {{CredentialsContainer/get()|navigator.credentials.get()}} invocation on the new device. Note that a synced credential will be exercised for authentication on a new device without a {{CredentialsContainer/get()|navigator.credentials.create()}} having been performed on that new device). @@ -6075,27 +6077,34 @@ Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDeviceP : Authenticator extension processing :: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: - 1. Create or select the [=user credential=] as usual, and let `userCredentialId` be its [=credentialId=]. + 1. Create or select the [=user credential=] as usual, and let |userCredentialId| be its [=credentialId=]. 1. If a [=hardware-bound device key pair=] does not already exist for this [=user credential=] on the [=authenticator=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing [=device-bound key=]. - 1. Let `dpk` be the newly created or existing [=device public key=], in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=] when the latter is conveyed in [=attested credential data=]. + 1. Let |dpk| be the newly created or existing [=device public key=], in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=] when the latter is conveyed in [=attested credential data=]. + + 1. Let |aaguid| be the [=authenticator=]'s [=AAGUID=]. + + 1. Let |devicePrivateKey| be the newly created or existing [=device private key=]. + + 1. Let |clientDataHash| be the [=hash of the serialized client data=]. + + 1. Let the `devicePubKey` [=authenticator extension output=] be a [=CBOR=] map as defined by `attObjForDevicePublicKey` above, with members as follows: - 1. Let `devicePrivateKey` be the newly created or existing [=device private key=]. + 1. Let the `aaguid` member be |aaguid|. - 1. Let `clientDataHash` be the [=hash of the serialized client data=]. + 1. Let the `dpk` member be |dpk|. - 1. Let `sig` be the result of signing the concatenation of `clientDataHash` and `userCredentialId` using the `devicePrivateKey` and the signature algorithm appropriate for the `devicePrivateKey`'s public key algorithm: `sign((clientDataHash || userCredentialId), devicePrivateKey)`. + 1. Let the `sig` member be the result of signing the concatenation of |clientDataHash| and |userCredentialId| using the |devicePrivateKey| and the signature algorithm appropriate for the |devicePrivateKey|'s public key algorithm: sign((|clientDataHash| || |userCredentialId|), |devicePrivateKey|). - 1. Let `scope` have the value zero (0x00) if this is an "entire-device" [=device public key=]. Otherwise, let `scope` have the value one (0x01), indicating a more narrow per-app scope. + 1. Let the `scope` member have the value zero (0x00) if this is an "entire-device" [=device public key=]. Otherwise, let `scope` have the value one (0x01), indicating a more narrow per-app scope. - 1. Let the `$$attStmtType` values be the result of generating an attestation statement in the [=attestation statement format=] appropriate for the [=user credential=], although substituting the authenticator's [=aaguid=] for |authenticatorData|, and substituting `dpk` for |clientDataHash| in the [=attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]). Attestation statement formats define the `fmt` and `attStmt` members of the `$$attStmtType` expansion. + 1. Let the `$$attStmtType` values be the result of generating an attestation statement in the [=attestation statement format=] appropriate for the [=user credential=], although substituting |aaguid| for |authenticatorData|, and substituting |dpk| for |clientDataHash| in the [=attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]). Attestation statement formats define the `fmt` and `attStmt` members of the `$$attStmtType` expansion. - In summary, the `$$attStmtType` values typically contain a signature value calculated over the bytes of `(aaguid || dpk)`, the attestation certificate or public key, and supporting certificates, if any. + In summary, the `$$attStmtType` values typically contain a signature value calculated over the bytes of (|aaguid| || |dpk|), the attestation certificate or public key, and supporting certificates, if any. - Note: The details of the `$$attStmtType` values are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. + Note: The details of the `$$attStmtType` values are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. - 1. Let the `devicePubKey` [=authenticator extension output=] be a [=CBOR=] map as defined by `attObjForDevicePublicKey` above, whose values for its `sig`, `aaguid`, `dpk`, `scope`, and `$$attStmtType` members are as calculated in the prior steps. From e23c4b997edc4f90cd948dd33c6c768689642e72 Mon Sep 17 00:00:00 2001 From: JeffH Date: Tue, 11 Jan 2022 12:06:22 -0800 Subject: [PATCH 071/131] polish emlun's suggestion to not be a Note --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index cdc73d458..acf3be446 100644 --- a/index.bs +++ b/index.bs @@ -5967,7 +5967,7 @@ This [=client extension|client=] [=registration extension=] and [=authentication If the [=client device=] is incapable of generating a [=hardware-bound device key pair=], or the registration or authentication operation fails for any reason, this extension is ignored and no [=hardware-bound device key pair=] is created. In this case, there is no [=devicePubKey=] extension output generated. -Note: The [=hardware-bound device key=] is not on its own a [=user credential=] and does not have its own [=credential ID=]. Instead the [=hardware-bound device key=] works like a contextual attribute of its associated [=user credential=] when that [=user credential=] is used on a particular [=client device=]. +The [=hardware-bound device key pair=] is not on its own a [=user credential=] and does not have its own [=credential ID=]. Instead, the returned [=device public key=] is a device-specific contextual attribute of its associated [=user credential=]. That is, when that [=user credential=] is used—along with the [=devicePubKey=] extension—on a particular [=client device=], a particular [=device public key=] is returned by the extension.
@@ -5982,7 +5982,7 @@ Note: The [=hardware-bound device key=] is not on its own a [=user credential=] Issue(1665): TODO: explicitly add the notion of "synced user credentials" to the spec. -Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDevicePublicKey`" ? (i.e., analogous to the "verification procedue" steps provided in [[#sctn-defined-attestation-formats|attestation statement format definitions]], and also steps 7 through 19 in [[#sctn-verifying-assertion]]) +Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDevicePublicKey`" ? (i.e., analogous to the "verification procedure" steps provided in [[#sctn-defined-attestation-formats|attestation statement format definitions]], and also steps 7 through 19 in [[#sctn-verifying-assertion]]) : Extension identifier :: `devicePubKey` From b8ec5b80fd8686460e8a22410705bdc70b86fdd6 Mon Sep 17 00:00:00 2001 From: JeffH Date: Wed, 12 Jan 2022 11:20:01 -0800 Subject: [PATCH 072/131] polish Authenticator extension processing --- index.bs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/index.bs b/index.bs index acf3be446..7aea11340 100644 --- a/index.bs +++ b/index.bs @@ -5982,7 +5982,7 @@ The [=hardware-bound device key pair=] is not on its own a [=user credential=] a Issue(1665): TODO: explicitly add the notion of "synced user credentials" to the spec. -Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDevicePublicKey`" ? (i.e., analogous to the "verification procedure" steps provided in [[#sctn-defined-attestation-formats|attestation statement format definitions]], and also steps 7 through 19 in [[#sctn-verifying-assertion]]) +Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDevicePublicKey`". (i.e., analogous to the "verification procedure" steps provided in [[#sctn-defined-attestation-formats|attestation statement format definitions]], and also steps 7 through 19 in [[#sctn-verifying-assertion]]) : Extension identifier :: `devicePubKey` @@ -6089,23 +6089,21 @@ Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDeviceP 1. Let |clientDataHash| be the [=hash of the serialized client data=]. - 1. Let the `devicePubKey` [=authenticator extension output=] be a [=CBOR=] map as defined by `attObjForDevicePublicKey` above, with members as follows: + 1. Let the `devicePubKey` [=authenticator extension output=] value be a [=CBOR=] map as defined by `attObjForDevicePublicKey` above, with keys and values as follows: - 1. Let the `aaguid` member be |aaguid|. + 1. Let the `aaguid` key's value be |aaguid|. - 1. Let the `dpk` member be |dpk|. + 1. Let the `dpk` key's value be |dpk|. - 1. Let the `sig` member be the result of signing the concatenation of |clientDataHash| and |userCredentialId| using the |devicePrivateKey| and the signature algorithm appropriate for the |devicePrivateKey|'s public key algorithm: sign((|clientDataHash| || |userCredentialId|), |devicePrivateKey|). + 1. Let the `sig` key's value be the result of signing the concatenation of |clientDataHash| and |userCredentialId| using the |devicePrivateKey| and the signature algorithm appropriate for the |devicePrivateKey|'s public key algorithm: sign((|clientDataHash| || |userCredentialId|), |devicePrivateKey|). - 1. Let the `scope` member have the value zero (0x00) if this is an "entire-device" [=device public key=]. Otherwise, let `scope` have the value one (0x01), indicating a more narrow per-app scope. + 1. Let the `scope` key have the value zero (0x00) if this is an "entire device" [=device public key=]. Otherwise, let `scope` have the value one (0x01), indicating a more narrow per-app scope. - 1. Let the `$$attStmtType` values be the result of generating an attestation statement in the [=attestation statement format=] appropriate for the [=user credential=], although substituting |aaguid| for |authenticatorData|, and substituting |dpk| for |clientDataHash| in the [=attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]). Attestation statement formats define the `fmt` and `attStmt` members of the `$$attStmtType` expansion. - - In summary, the `$$attStmtType` values typically contain a signature value calculated over the bytes of (|aaguid| || |dpk|), the attestation certificate or public key, and supporting certificates, if any. - - Note: The details of the `$$attStmtType` values are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. + 1. Let the values of the `$$attStmtType` "group socket" [[=CDDL=]] be the result of generating an [=attestation statement=] in the [=attestation statement format=] appropriate for this [=authenticator=] (see the Note below), although substituting |aaguid| for `authenticatorData`, and substituting |dpk| for `clientDataHash` in the [=attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]). Attestation statement formats define the `fmt` and `attStmt` members of the `$$attStmtType` group socket. + In summary, the `$$attStmtType` values generated by the foregoing procedure typically contain a signature value calculated over the bytes of (|aaguid| || |dpk|), the attestation certificate or public key, and supporting certificates, if any. + Note: The details of the `$$attStmtType` values are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust. This is in contrast to the associated [=user credential=]'s attestation. # User Agent Automation # {#sctn-automation} From 0bb9aaac10cedaa68ea085d8774aa6b6c86e0989 Mon Sep 17 00:00:00 2001 From: JeffH Date: Wed, 12 Jan 2022 12:09:18 -0800 Subject: [PATCH 073/131] authnr extension rather than client extension --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 7aea11340..25f175fdd 100644 --- a/index.bs +++ b/index.bs @@ -5963,7 +5963,7 @@ However, [=authenticators=] that do not utilize [[!FIDO-CTAP]] do not necessaril ## Device-bound public key extension (devicePubKey) ## {#sctn-device-publickey-extension} -This [=client extension|client=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal. This is done by creating a [=[RP]=]-specific [=hardware-bound device key pair=] on the [=client device=], if such a key pair does not already exist for the [=user credential=] being created or exercised, and returning the attested [=device public key=] along with a signature by the [=device private key=] to the [=[RP]=]. This is done each time this [=devicePubKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/create()|navigator.credentials.get()}} call. +This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal. This is done by creating a [=[RP]=]-specific [=hardware-bound device key pair=] on the [=client device=], if such a key pair does not already exist for the [=user credential=] being created or exercised, and returning the attested [=device public key=] along with a signature by the [=device private key=] to the [=[RP]=]. This is done each time this [=devicePubKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/create()|navigator.credentials.get()}} call. If the [=client device=] is incapable of generating a [=hardware-bound device key pair=], or the registration or authentication operation fails for any reason, this extension is ignored and no [=hardware-bound device key pair=] is created. In this case, there is no [=devicePubKey=] extension output generated. From 32378966857e501f43356036abe697f514f70a24 Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 14 Jan 2022 11:19:59 -0800 Subject: [PATCH 074/131] minor editorial fixes --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 25f175fdd..a256674a2 100644 --- a/index.bs +++ b/index.bs @@ -5971,13 +5971,13 @@ The [=hardware-bound device key pair=] is not on its own a [=user credential=] a
- Note 1: This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension provides a "device continuity" signal when used consistently with both {{CredentialsContainer/get()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations. When a user's credential is synced to a new device, the [=[RP]=] will receive a new [=device public key=] on the initial {{CredentialsContainer/get()|navigator.credentials.get()}} invocation on the new device. Note that a synced credential will be exercised for authentication on a new device without a {{CredentialsContainer/get()|navigator.credentials.create()}} having been performed on that new device). + Note 1: This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension provides a "device continuity" signal when used consistently with both {{CredentialsContainer/get()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations. When a user's credential is synced to a new device, the [=[RP]=] will receive a new [=device public key=] on the initial {{CredentialsContainer/get()|navigator.credentials.get()}} invocation on the new device. Note that a synced credential will be exercised for authentication on a new device without a {{CredentialsContainer/get()|navigator.credentials.create()}} having been performed on that new device. A usage example is thus: > Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with a synced credential. But if a [=device-bound/signature=], by a [=device-bound key=] that is well established for this user, can also be presented then that may tip the balance. - Note 2: A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `attObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [=attestation statement format=]'s "signing procedure", see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `scope`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrance, do binary equality checks of the cached `aaguid`, `dpk`, `scope`, and `$$attStmtType` values against those returned in the extension output. + Note 2: A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `attObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [=attestation statement format=]'s "signing procedure", see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `scope`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrence, do binary equality checks of the cached `aaguid`, `dpk`, `scope`, and `$$attStmtType` values against those returned in the extension output.
Issue(1665): TODO: explicitly add the notion of "synced user credentials" to the spec. From 55e64c94a385474952b3330b771d91ddc47929d9 Mon Sep 17 00:00:00 2001 From: JeffH Date: Mon, 17 Jan 2022 19:09:30 -0800 Subject: [PATCH 075/131] revise intro and define most of verification procedure --- index.bs | 138 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 94 insertions(+), 44 deletions(-) diff --git a/index.bs b/index.bs index cc064f991..260088204 100644 --- a/index.bs +++ b/index.bs @@ -1078,7 +1078,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S : 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 thus forms the central part of a [=public key credential source=]. + and [=scoped=] to a specific [=[WRP]=]. It is the central part of a [=public key credential=]. A [=credential public key=] is the public key portion of a [=credential key pair=]. The [=credential public key=] is returned to the [=[RP]=] during a [=registration ceremony=]. @@ -1087,10 +1087,12 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S The [=credential private key=] is bound to a particular [=authenticator=] - its [=managing authenticator=] - and is expected to never be exposed to any other party, not even to the owner of the [=authenticator=]. - Note: In the case of [=self attestation=], the [=credential key pair=] is also used as the [=attestation key pair=], see [=self attestation=] for details. + Note that in the case of [=self + attestation=], the [=credential key pair=] is also used as the [=attestation key pair=], see [=self attestation=] + for details. 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. Also, the term [=user credential=] is occasionally used to synonymously refer to [=credential public key=] and [=user public key=]. + [[FIDO-U2F-Message-Formats]] and some parts of this specification that relate to it. : Credential Properties :: A [=credential property=] is some characteristic property of a [=public key credential source=], such as whether it is a @@ -1542,6 +1544,10 @@ This [=internal method=] accepts three arguments: :: This argument is a Boolean value which is [TRUE] if and only if the caller's [=environment settings object=] is [=same-origin with its ancestors=]. It is [FALSE] if caller is cross-origin. + Note: Invocation of this [=internal method=] indicates that it was allowed by + [=permissions policy=], which is evaluated at the [[!CREDENTIAL-MANAGEMENT-1]] level. + See [[#sctn-permissions-policy]]. + Note: This algorithm is synchronous: the {{Promise}} resolution/rejection is handled by @@ -4593,22 +4599,24 @@ In order to perform a [=registration ceremony=], the [=[RP]=] MUST proceed as fo matches the {{PublicKeyCredentialParameters/alg}} attribute of one of the [=list/items=] in |options|.{{PublicKeyCredentialCreationOptions/pubKeyCredParams}}. -1. Verify that the values of the [=client extension outputs=] in |clientExtensionResults| and the [=authenticator extension - outputs=] in the [=authdataextensions|extensions=] in |authData| are as expected, considering the [=client - extension input=] values that were given in |options|.{{PublicKeyCredentialCreationOptions/extensions}} - and any specific policy of the [=[RP]=] regarding unsolicited extensions, i.e., those that were not specified as part of - |options|.{{PublicKeyCredentialCreationOptions/extensions}}. - In the general case, the meaning of "are as expected" is specific to the [=[RP]=] and which extensions are in use. - - Note: [=Client platforms=] MAY enact local policy that sets additional [=authenticator extensions=] or - [=client extensions=] and thus cause values to appear in the [=authenticator extension outputs=] or - [=client extension outputs=] that were not originally specified as part of - |options|.{{PublicKeyCredentialCreationOptions/extensions}}. [=[RPS]=] MUST be prepared to handle such - situations, whether it be to ignore the unsolicited extensions or reject the attestation. The [=[RP]=] can make this - decision based on local policy and the extensions in use. - - Note: Since all extensions are OPTIONAL for both the [=client=] and the [=authenticator=], the [=[RP]=] MUST also be - prepared to handle cases where none or not all of the requested extensions were acted upon. +
  • + Verify that the values of the [=client extension outputs=] in |clientExtensionResults| and the [=authenticator extension + outputs=] in the [=authdataextensions|extensions=] in |authData| are as expected, considering the [=client + extension input=] values that were given in |options|.{{PublicKeyCredentialCreationOptions/extensions}} + and any specific policy of the [=[RP]=] regarding unsolicited extensions, i.e., those that were not specified as part of + |options|.{{PublicKeyCredentialCreationOptions/extensions}}. + In the general case, the meaning of "are as expected" is specific to the [=[RP]=] and which extensions are in use. + + Note: [=Client platforms=] MAY enact local policy that sets additional [=authenticator extensions=] or + [=client extensions=] and thus cause values to appear in the [=authenticator extension outputs=] or + [=client extension outputs=] that were not originally specified as part of + |options|.{{PublicKeyCredentialCreationOptions/extensions}}. [=[RPS]=] MUST be prepared to handle such + situations, whether it be to ignore the unsolicited extensions or reject the attestation. The [=[RP]=] can make this + decision based on local policy and the extensions in use. + + Note: Since all extensions are OPTIONAL for both the [=client=] and the [=authenticator=], the [=[RP]=] MUST also be + prepared to handle cases where none or not all of the requested extensions were acted upon. +
  • 1. Determine the attestation statement format by performing a USASCII case-sensitive match on |fmt| against the set of supported WebAuthn Attestation Statement Format Identifier values. @@ -4756,22 +4764,24 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o 1. If [=user verification=] is required for this assertion, verify that the [=User Verified=] bit of the [=flags=] in |authData| is set. -1. Verify that the values of the [=client extension outputs=] in |clientExtensionResults| and the [=authenticator extension - outputs=] in the [=authdataextensions|extensions=] in |authData| are as expected, considering the [=client - extension input=] values that were given in |options|.{{PublicKeyCredentialRequestOptions/extensions}} - and any specific policy of the [=[RP]=] regarding unsolicited extensions, i.e., those that were not specified as part of - |options|.{{PublicKeyCredentialRequestOptions/extensions}}. - In the general case, the meaning of "are as expected" is specific to the [=[RP]=] and which extensions are in use. - - Note: [=Client platforms=] MAY enact local policy that sets additional [=authenticator extensions=] or - [=client extensions=] and thus cause values to appear in the [=authenticator extension outputs=] or - [=client extension outputs=] that were not originally specified as part of - |options|.{{PublicKeyCredentialRequestOptions/extensions}}. [=[RPS]=] MUST be prepared to handle such - situations, whether it be to ignore the unsolicited extensions or reject the assertion. The [=[RP]=] can make this - decision based on local policy and the extensions in use. - - Note: Since all extensions are OPTIONAL for both the [=client=] and the [=authenticator=], the [=[RP]=] MUST also be - prepared to handle cases where none or not all of the requested extensions were acted upon. +
  • + Verify that the values of the [=client extension outputs=] in |clientExtensionResults| and the [=authenticator extension + outputs=] in the [=authdataextensions|extensions=] in |authData| are as expected, considering the [=client + extension input=] values that were given in |options|.{{PublicKeyCredentialRequestOptions/extensions}} + and any specific policy of the [=[RP]=] regarding unsolicited extensions, i.e., those that were not specified as part of + |options|.{{PublicKeyCredentialRequestOptions/extensions}}. + In the general case, the meaning of "are as expected" is specific to the [=[RP]=] and which extensions are in use. + + Note: [=Client platforms=] MAY enact local policy that sets additional [=authenticator extensions=] or + [=client extensions=] and thus cause values to appear in the [=authenticator extension outputs=] or + [=client extension outputs=] that were not originally specified as part of + |options|.{{PublicKeyCredentialRequestOptions/extensions}}. [=[RPS]=] MUST be prepared to handle such + situations, whether it be to ignore the unsolicited extensions or reject the assertion. The [=[RP]=] can make this + decision based on local policy and the extensions in use. + + Note: Since all extensions are OPTIONAL for both the [=client=] and the [=authenticator=], the [=[RP]=] MUST also be + prepared to handle cases where none or not all of the requested extensions were acted upon. +
  • 1. Let |hash| be the result of computing a hash over the |cData| using SHA-256. @@ -6075,26 +6085,31 @@ However, [=authenticators=] that do not utilize [[!FIDO-CTAP]] do not necessaril ## Device-bound public key extension (devicePubKey) ## {#sctn-device-publickey-extension} -This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal. This is done by creating a [=[RP]=]-specific [=hardware-bound device key pair=] on the [=client device=], if such a key pair does not already exist for the [=user credential=] being created or exercised, and returning the attested [=device public key=] along with a signature by the [=device private key=] to the [=[RP]=]. This is done each time this [=devicePubKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/create()|navigator.credentials.get()}} call. +This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal. This is done by creating a [=[RP]=]-specific [=hardware-bound device key pair=] on the [=client device=], if such a key pair does not already exist for the [=user credential=] being created or exercised, and returning the attested [=device public key=] along with a signature by the [=device private key=] to the [=[RP]=]. This is done each time this [=devicePubKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/get()|navigator.credentials.get()}} call. If the [=client device=] is incapable of generating a [=hardware-bound device key pair=], or the registration or authentication operation fails for any reason, this extension is ignored and no [=hardware-bound device key pair=] is created. In this case, there is no [=devicePubKey=] extension output generated. The [=hardware-bound device key pair=] is not on its own a [=user credential=] and does not have its own [=credential ID=]. Instead, the returned [=device public key=] is a device-specific contextual attribute of its associated [=user credential=]. That is, when that [=user credential=] is used—along with the [=devicePubKey=] extension—on a particular [=client device=], a particular [=device public key=] is returned by the extension. +Issue(1665): TODO: explicitly add the notion of "synced user credentials" to the spec. -
    - Note 1: This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension provides a "device continuity" signal when used consistently with both {{CredentialsContainer/get()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations. When a user's credential is synced to a new device, the [=[RP]=] will receive a new [=device public key=] on the initial {{CredentialsContainer/get()|navigator.credentials.get()}} invocation on the new device. Note that a synced credential will be exercised for authentication on a new device without a {{CredentialsContainer/get()|navigator.credentials.create()}} having been performed on that new device. - A usage example is thus: +### Relying Party Usage ### {#sctn-device-publickey-extension-usage} - > Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with a synced credential. But if a [=device-bound/signature=], by a [=device-bound key=] that is well established for this user, can also be presented then that may tip the balance. +This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension provides a "device continuity" signal when used consistently with both {{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations: - Note 2: A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `attObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [=attestation statement format=]'s "signing procedure", see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `scope`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrence, do binary equality checks of the cached `aaguid`, `dpk`, `scope`, and `$$attStmtType` values against those returned in the extension output. -
    +A new [=device public key=] is returned to the [=[RP]=] (along with a new [=credential=]) when the `devicePubKey` extension is used with a {{CredentialsContainer/create()|navigator.credentials.create()}} call. The same [=device public key=] is returned to the [=[RP]=] along with [=assertions=] generated using the latter credential—i.e., via {{CredentialsContainer/get()|navigator.credentials.get()}} calls—if those {{CredentialsContainer/get()|get()}} calls occur on the same device as the {{CredentialsContainer/create()|create()}} call that created the credential. + +However, when a user's credential is synced to a different device, the [=[RP]=] will receive a new [=device public key=] on the initial {{CredentialsContainer/get()|navigator.credentials.get()}} invocation on the other device. Note that a synced credential will be exercised for authentication on a different device without a {{CredentialsContainer/create()|navigator.credentials.create()}} having been performed on that new device. + +A usage example is thus: + +> Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with a synced credential. But if a [=device-bound/signature=], by a [=device-bound key=] that is well established for this user, can also be presented then that may tip the balance. + +A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `attObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [=attestation statement format=]'s "signing procedure", see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value: see [[#sctn-device-publickey-extension-verification]]. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `scope`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrence, do binary equality checks of the cached `aaguid`, `dpk`, `scope`, and `$$attStmtType` values against those returned in the extension output. -Issue(1665): TODO: explicitly add the notion of "synced user credentials" to the spec. -Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDevicePublicKey`". (i.e., analogous to the "verification procedure" steps provided in [[#sctn-defined-attestation-formats|attestation statement format definitions]], and also steps 7 through 19 in [[#sctn-verifying-assertion]]) +### Extension Definition ### {#sctn-device-publickey-extension-definition} : Extension identifier :: `devicePubKey` @@ -6217,6 +6232,41 @@ Issue: TODO: write an explicit subsection wrt "how to verify a `attObjForDeviceP Note: The details of the `$$attStmtType` values are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust. This is in contrast to the associated [=user credential=]'s attestation. +### `devicePubKey` Extension Output Verification Procedures ### {#sctn-device-publickey-extension-verification} + +Verifying the [=devicePubKey=] extension output is performed by the [=[RP]=] whenever a new [=device public key=] is returned within the extension output. As explained in [[#sctn-device-publickey-extension-usage]], a new [=device public key=] is always returned as a result of a {{CredentialsContainer/create()|navigator.credentials.create()}} call (i.e., as part of a [=registration ceremony=]). In contrast, a new [=device public key=] may be returned as a result of a {{CredentialsContainer/get()|navigator.credentials.get()}} call (i.e., as part of an [=authentication ceremony=]). + +#### Registration (`create()`) #### {#sctn-device-publickey-extension-verification-create} + +If the `devicePubKey` extension was included on a {{CredentialsContainer/create()|navigator.credentials.create()}} call, then 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 |attObjForDevicePublicKey| be the value of the `devicePubKey` member of |clientExtensionResults|. + +1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |sig|, |aaguid|, |dpk|, |scope|, |fmt|, |attStmt|. + +1. Verify that |attObjForDevicePublicKey|.|aaguid| matches the [=aaguid=] in the [=attestedCredentialData=] in |authData|. + +1. Verify that |credential|.{{PublicKeyCredential/rawId}} matches the [=credentialId=] in the [=attestedCredentialData=] in |authData|. + +1. Verify that |sig| is a valid signature over the concatenation of |hash| and [=credentialId=] using the [=device public key=] |dpk| (the signature algorithm is indicated by |dpk|'s "alg" {{COSEAlgorithmIdentifier}} value). + +1. Verify that |attObjForDevicePublicKey|.|attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |attObjForDevicePublicKey|.|fmt|'s [=verification procedure=] given |attObjForDevicePublicKey|.|attStmt|, although substituting |attObjForDevicePublicKey|.|aaguid| for `authenticatorData`, and substituting |dpk| for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. + + Note: If |attObjForDevicePublicKey|.|fmt|'s value is "[=none=]", there is no attestation signature to verify. + +See also [[#sctn-device-publickey-extension-usage]] for guidance on [=[RP]=] processing upon successful verification. + + +#### Authentication (`get()`) #### {#sctn-device-publickey-extension-verification-get} + +If the `devicePubKey` extension was included on 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|. + +1. Let |attObjForDevicePublicKey| be the value of the `devicePubKey` member of |clientExtensionResults|. + +1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |sig|, |aaguid|, |dpk|, |scope|, |fmt|, |attStmt|. + +1. + # User Agent Automation # {#sctn-automation} From 41ffcbf8a44a1f0be4adc9f29a1c47a1b6f5a0c5 Mon Sep 17 00:00:00 2001 From: JeffH Date: Mon, 17 Jan 2022 22:36:27 -0800 Subject: [PATCH 076/131] finish roughing-out verification procedures --- index.bs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/index.bs b/index.bs index 260088204..eee2c0729 100644 --- a/index.bs +++ b/index.bs @@ -6244,17 +6244,15 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/create( 1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |sig|, |aaguid|, |dpk|, |scope|, |fmt|, |attStmt|. -1. Verify that |attObjForDevicePublicKey|.|aaguid| matches the [=aaguid=] in the [=attestedCredentialData=] in |authData|. - -1. Verify that |credential|.{{PublicKeyCredential/rawId}} matches the [=credentialId=] in the [=attestedCredentialData=] in |authData|. - 1. Verify that |sig| is a valid signature over the concatenation of |hash| and [=credentialId=] using the [=device public key=] |dpk| (the signature algorithm is indicated by |dpk|'s "alg" {{COSEAlgorithmIdentifier}} value). 1. Verify that |attObjForDevicePublicKey|.|attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |attObjForDevicePublicKey|.|fmt|'s [=verification procedure=] given |attObjForDevicePublicKey|.|attStmt|, although substituting |attObjForDevicePublicKey|.|aaguid| for `authenticatorData`, and substituting |dpk| for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. Note: If |attObjForDevicePublicKey|.|fmt|'s value is "[=none=]", there is no attestation signature to verify. -See also [[#sctn-device-publickey-extension-usage]] for guidance on [=[RP]=] processing upon successful verification. +1. Store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. + +See also [[#sctn-device-publickey-extension-usage]] for further details. #### Authentication (`get()`) #### {#sctn-device-publickey-extension-verification-get} @@ -6265,7 +6263,23 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n 1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |sig|, |aaguid|, |dpk|, |scope|, |fmt|, |attStmt|. -1. +1. Verify that |sig| is a valid signature over the concatenation of |hash| and [=credentialId=] using the [=device public key=] |dpk| (the signature algorithm is indicated by |dpk|'s "alg" {{COSEAlgorithmIdentifier}} value). + +1. If the [=[RP]=]'s [=user account|account=] mapped to the |credential|.{{Credential/id}} in play (i.e., for the user being authenticated) holds values corresponding to the extracted |attObjForDevicePublicKey| fields: perform binary equality checks between the corresponding stored and extracted values, then if the results are: + +
    + : successful + :: This is a known [=device public key=] and thus a known device. Terminate these verification steps. + + : unsuccessful + :: This is a new [=device public key=] signifying a new device: + 1. Verify that |attObjForDevicePublicKey|.|attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |attObjForDevicePublicKey|.|fmt|'s [=verification procedure=] given |attObjForDevicePublicKey|.|attStmt|, although substituting |attObjForDevicePublicKey|.|aaguid| for `authenticatorData`, and substituting |dpk| for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. + 1. Store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. Terminate these verification steps. +
    + +1. Otherwise, |attObjForDevicePublicKey| fields are not presently mapped to this [=user account=] and |credential|.{{Credential/id}} pair. Verify the |attObjForDevicePublicKey|.|attStmt| as given above, and if successful then store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. + +See also [[#sctn-device-publickey-extension-usage]] for further details. # User Agent Automation # {#sctn-automation} From f131d687993333f296c2fb11762f829584fb0ddb Mon Sep 17 00:00:00 2001 From: JeffH Date: Tue, 18 Jan 2022 14:49:05 -0800 Subject: [PATCH 077/131] remove extraneous Note on permissions policy that crept in somehow --- index.bs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/index.bs b/index.bs index eee2c0729..2df5ae717 100644 --- a/index.bs +++ b/index.bs @@ -1544,10 +1544,6 @@ This [=internal method=] accepts three arguments: :: This argument is a Boolean value which is [TRUE] if and only if the caller's [=environment settings object=] is [=same-origin with its ancestors=]. It is [FALSE] if caller is cross-origin. - Note: Invocation of this [=internal method=] indicates that it was allowed by - [=permissions policy=], which is evaluated at the [[!CREDENTIAL-MANAGEMENT-1]] level. - See [[#sctn-permissions-policy]]. - Note: This algorithm is synchronous: the {{Promise}} resolution/rejection is handled by From e1e6d94d26147e1a8e0caa82c06a2b29498447ae Mon Sep 17 00:00:00 2001 From: JeffH Date: Tue, 18 Jan 2022 14:56:49 -0800 Subject: [PATCH 078/131] incorp emlun's suggestion on hardware-bound device key pair definition --- index.bs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/index.bs b/index.bs index 2df5ae717..cb3d3673c 100644 --- a/index.bs +++ b/index.bs @@ -1102,11 +1102,8 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S : Device-bound Key : Device Private Key : Device Public Key -: Device Key -: Secondary Key -:: A [=hardware-bound device key pair=], also known as a [=device-bound key=], is a [=[RP]=]-specific and [=user credential=]-specific public key pair created upon a [=[RP]=]'s request via the [=devicePubKey=] [=WebAuthn extension=]. - The [=[RP]=] may supply this extension during both [=registration=] and [=authentication=]. - The [=authenticator=] that a [=hardware-bound device key pair=] is created upon guarantees that the [=device private key=] is securely stored in hardware, i.e., it is unextractable. The [=device public key=], along with a signature generated using the corresponding [=device private key=], is returned only to the [=[RP]=] that created the [=hardware-bound device key pair=]. This creates a "device continuity" signal that the [=[RP]=] can use as an input to their risk-analysis system. See [[#sctn-device-publickey-extension]]. +:: A [=hardware-bound device key pair=], also known as a [=device-bound key=], is an [=authenticator=]-, [=[RP]=]-, and [=user credential=]-specific public key pair created upon a [=[RP]=]'s request via the [=devicePubKey=] [=WebAuthn extension=]. + The [=authenticator=] that a [=hardware-bound device key pair=] is created upon guarantees that the [=device private key=] is securely stored in hardware, i.e., it is unextractable. See also [[#sctn-device-publickey-extension]]. : Human Palatability :: An identifier that is [=human palatability|human-palatable=] is intended to be rememberable and reproducible by typical human @@ -6100,7 +6097,7 @@ However, when a user's credential is synced to a different device, the [=[RP]=] A usage example is thus: -> Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with a synced credential. But if a [=device-bound/signature=], by a [=device-bound key=] that is well established for this user, can also be presented then that may tip the balance. +> Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with a synced credential. But if a signature by a [=device-bound key=] that is well established for this user can also be presented, then that may tip the balance. A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `attObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [=attestation statement format=]'s "signing procedure", see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value: see [[#sctn-device-publickey-extension-verification]]. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `scope`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrence, do binary equality checks of the cached `aaguid`, `dpk`, `scope`, and `$$attStmtType` values against those returned in the extension output. From 23ea3eff1b5939e6d90236563bc6ebc31724a427 Mon Sep 17 00:00:00 2001 From: JeffH Date: Thu, 17 Feb 2022 17:30:49 -0800 Subject: [PATCH 079/131] add Notes to RP verification steps linking to DPK extension verification procedures --- index.bs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/index.bs b/index.bs index cb3d3673c..04a607e5b 100644 --- a/index.bs +++ b/index.bs @@ -4609,6 +4609,8 @@ In order to perform a [=registration ceremony=], the [=[RP]=] MUST proceed as fo Note: Since all extensions are OPTIONAL for both the [=client=] and the [=authenticator=], the [=[RP]=] MUST also be prepared to handle cases where none or not all of the requested extensions were acted upon. + + Note: The [=devicePubKey=] extension has explicit verification procedures, see [[#sctn-device-publickey-extension-verification-create]]. 1. Determine the attestation statement format by performing a USASCII case-sensitive match on |fmt| against the set of @@ -4774,6 +4776,8 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o Note: Since all extensions are OPTIONAL for both the [=client=] and the [=authenticator=], the [=[RP]=] MUST also be prepared to handle cases where none or not all of the requested extensions were acted upon. + + Note: The [=devicePubKey=] extension has explicit verification procedures, see [[#sctn-device-publickey-extension-verification-get]]. 1. Let |hash| be the result of computing a hash over the |cData| using SHA-256. From 683ad4d8fba9ea09b5ee27fa58fda3d6d32cf626 Mon Sep 17 00:00:00 2001 From: JeffH Date: Thu, 17 Feb 2022 17:37:46 -0800 Subject: [PATCH 080/131] do not use 'synced' user cred term per TimC --- index.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index 04a607e5b..5fe18c1bf 100644 --- a/index.bs +++ b/index.bs @@ -6088,7 +6088,7 @@ If the [=client device=] is incapable of generating a [=hardware-bound device ke The [=hardware-bound device key pair=] is not on its own a [=user credential=] and does not have its own [=credential ID=]. Instead, the returned [=device public key=] is a device-specific contextual attribute of its associated [=user credential=]. That is, when that [=user credential=] is used—along with the [=devicePubKey=] extension—on a particular [=client device=], a particular [=device public key=] is returned by the extension. -Issue(1665): TODO: explicitly add the notion of "synced user credentials" to the spec. +Issue(1665): TODO: explicitly add the notion of "Synced/multi-device user Credentials" to the spec. ### Relying Party Usage ### {#sctn-device-publickey-extension-usage} @@ -6097,11 +6097,11 @@ This extension is intended for use by those [=[RPS]=] employing risk-analysis sy A new [=device public key=] is returned to the [=[RP]=] (along with a new [=credential=]) when the `devicePubKey` extension is used with a {{CredentialsContainer/create()|navigator.credentials.create()}} call. The same [=device public key=] is returned to the [=[RP]=] along with [=assertions=] generated using the latter credential—i.e., via {{CredentialsContainer/get()|navigator.credentials.get()}} calls—if those {{CredentialsContainer/get()|get()}} calls occur on the same device as the {{CredentialsContainer/create()|create()}} call that created the credential. -However, when a user's credential is synced to a different device, the [=[RP]=] will receive a new [=device public key=] on the initial {{CredentialsContainer/get()|navigator.credentials.get()}} invocation on the other device. Note that a synced credential will be exercised for authentication on a different device without a {{CredentialsContainer/create()|navigator.credentials.create()}} having been performed on that new device. +However, when a user's credential is copied or moved to a different device, the [=[RP]=] will receive a new [=device public key=] on the initial {{CredentialsContainer/get()|navigator.credentials.get()}} invocation on the other device. Note that a multi-device credential will be exercised for authentication on a different device without a {{CredentialsContainer/create()|navigator.credentials.create()}} having been performed on that new device. A usage example is thus: -> Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with a synced credential. But if a signature by a [=device-bound key=] that is well established for this user can also be presented, then that may tip the balance. +> Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with a multi-device credential. But if a signature by a [=device-bound key=] that is well established for this user can also be presented, then that may tip the balance. A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `attObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [=attestation statement format=]'s "signing procedure", see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value: see [[#sctn-device-publickey-extension-verification]]. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `scope`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrence, do binary equality checks of the cached `aaguid`, `dpk`, `scope`, and `$$attStmtType` values against those returned in the extension output. From 17f3aa2fb46ac36be5fbb185f02a98ea1952ec01 Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 4 Mar 2022 11:33:16 -0800 Subject: [PATCH 081/131] update 'Relying Party Usage' section and note current issues --- index.bs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/index.bs b/index.bs index 5fe18c1bf..8dc2e7c7a 100644 --- a/index.bs +++ b/index.bs @@ -6086,22 +6086,24 @@ This [=authenticator extension|authenticator=] [=registration extension=] and [= If the [=client device=] is incapable of generating a [=hardware-bound device key pair=], or the registration or authentication operation fails for any reason, this extension is ignored and no [=hardware-bound device key pair=] is created. In this case, there is no [=devicePubKey=] extension output generated. -The [=hardware-bound device key pair=] is not on its own a [=user credential=] and does not have its own [=credential ID=]. Instead, the returned [=device public key=] is a device-specific contextual attribute of its associated [=user credential=]. That is, when that [=user credential=] is used—along with the [=devicePubKey=] extension—on a particular [=client device=], a particular [=device public key=] is returned by the extension. - -Issue(1665): TODO: explicitly add the notion of "Synced/multi-device user Credentials" to the spec. +The [=hardware-bound device key pair=] is not on its own a [=user credential=] and does not have its own [=credential ID=]. Instead, the returned [=device public key=] is a device-specific contextual attribute of its associated [=user credential=]. That is, when that [=user credential=] is used—along with the [=devicePubKey=] extension—on a particular [=client device=], a particular [=device public key=] is returned by the extension, along with a signature demonstrating proof-of-possession of the [=device private key=] by that device. ### Relying Party Usage ### {#sctn-device-publickey-extension-usage} -This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension provides a "device continuity" signal when used consistently with both {{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations: +Issue(w3c/webauthn#1665): TODO: explicitly add the notion of "Synced/multi-device User Credentials" to the spec. This will involve adding proper definitions for "single-device (user) credentials" and "multi-device (user) credentials" in the terminology section, updating the text of this "Device-bound public key extension" section, and perhaps other approapriate changes or additions to other portions of the spec. + +This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension provides a "device continuity" signal when used consistently with both {{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations: -A new [=device public key=] is returned to the [=[RP]=] (along with a new [=credential=]) when the `devicePubKey` extension is used with a {{CredentialsContainer/create()|navigator.credentials.create()}} call. The same [=device public key=] is returned to the [=[RP]=] along with [=assertions=] generated using the latter credential—i.e., via {{CredentialsContainer/get()|navigator.credentials.get()}} calls—if those {{CredentialsContainer/get()|get()}} calls occur on the same device as the {{CredentialsContainer/create()|create()}} call that created the credential. +A new signature by a new [=device-bound key=] ("sig 1 + dpk 1") is returned to the [=[RP]=], along with a new [=user credential=], when the `devicePubKey` extension is used with a {{CredentialsContainer/create()|navigator.credentials.create()}} call on a given device ("device 1"). For example, as part of a [=registration ceremony=]. Further signatures by the same [=device-bound key=] ("sig n + dpk 1") are returned to the [=[RP]=] along with [=assertions=] generated using the latter user credential when the [=[RP]=] subsequently invokes {{CredentialsContainer/get()|navigator.credentials.get()}} calls including the `devicePubKey` extension, on that same device ("device 1") upon which the user credential was created. This behavior (on "device 1") is independent of whether this user credential has been copied to any other devices. -However, when a user's credential is copied or moved to a different device, the [=[RP]=] will receive a new [=device public key=] on the initial {{CredentialsContainer/get()|navigator.credentials.get()}} invocation on the other device. Note that a multi-device credential will be exercised for authentication on a different device without a {{CredentialsContainer/create()|navigator.credentials.create()}} having been performed on that new device. +Then, if this user credential is copied or moved to a different device ("device 2"), the [=[RP]=] will receive a new signature by a new [=device-bound key=] ("sig x + dpk 2") on the initial {{CredentialsContainer/get()|navigator.credentials.get()}} invocation (which must include the `devicePubKey` extension) on "device 2". Note that such a "multi-device user credential" can be exercised for authenticating its user on a different device without a {{CredentialsContainer/create()|navigator.credentials.create()}} having been performed on that new device. A usage example is thus: -> Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with a multi-device credential. But if a signature by a [=device-bound key=] that is well established for this user can also be presented, then that may tip the balance. +> Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with an assertion by a multi-device user credential on its own. But if a signature by a [=device-bound key=] that is well established for this user can also be presented, then that may tip the balance. + +Issue(w3c/webauthn#1701): TODO: **The below paragraph is presently inaccurate and will be revised or removed as part of the resolution to Issue #1701.** RPs will likely need to verify the device-bound key's signature on a per-request basis. A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `attObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [=attestation statement format=]'s "signing procedure", see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value: see [[#sctn-device-publickey-extension-verification]]. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `scope`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrence, do binary equality checks of the cached `aaguid`, `dpk`, `scope`, and `$$attStmtType` values against those returned in the extension output. From b4e8d0ec9121de61dd94b861b7bfef448423354d Mon Sep 17 00:00:00 2001 From: JeffH Date: Fri, 4 Mar 2022 18:28:42 -0800 Subject: [PATCH 082/131] clarification --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 8dc2e7c7a..0093dfb0f 100644 --- a/index.bs +++ b/index.bs @@ -6097,7 +6097,7 @@ This extension is intended for use by those [=[RPS]=] employing risk-analysis sy A new signature by a new [=device-bound key=] ("sig 1 + dpk 1") is returned to the [=[RP]=], along with a new [=user credential=], when the `devicePubKey` extension is used with a {{CredentialsContainer/create()|navigator.credentials.create()}} call on a given device ("device 1"). For example, as part of a [=registration ceremony=]. Further signatures by the same [=device-bound key=] ("sig n + dpk 1") are returned to the [=[RP]=] along with [=assertions=] generated using the latter user credential when the [=[RP]=] subsequently invokes {{CredentialsContainer/get()|navigator.credentials.get()}} calls including the `devicePubKey` extension, on that same device ("device 1") upon which the user credential was created. This behavior (on "device 1") is independent of whether this user credential has been copied to any other devices. -Then, if this user credential is copied or moved to a different device ("device 2"), the [=[RP]=] will receive a new signature by a new [=device-bound key=] ("sig x + dpk 2") on the initial {{CredentialsContainer/get()|navigator.credentials.get()}} invocation (which must include the `devicePubKey` extension) on "device 2". Note that such a "multi-device user credential" can be exercised for authenticating its user on a different device without a {{CredentialsContainer/create()|navigator.credentials.create()}} having been performed on that new device. +Then, if this user credential is copied or moved to a different device ("device 2"), the [=[RP]=] will receive a new signature by a new [=device-bound key=] ("sig x + dpk 2") on the initial {{CredentialsContainer/get()|navigator.credentials.get()}} invocation (that includes the `devicePubKey` extension) on "device 2". Note that such a "multi-device user credential" can be exercised for authenticating its user on a different device without a {{CredentialsContainer/create()|navigator.credentials.create()}} having been performed on that new device. A usage example is thus: From 619ebb98d28c0243c2a6343ea8c6c4ea877c2cb4 Mon Sep 17 00:00:00 2001 From: JeffH Date: Tue, 8 Mar 2022 21:21:14 -0800 Subject: [PATCH 083/131] wordsmithing, thx emlun! --- index.bs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 0093dfb0f..a54c3496b 100644 --- a/index.bs +++ b/index.bs @@ -6095,9 +6095,10 @@ Issue(w3c/webauthn#1665): TODO: explicitly add the notion of "Synced/multi-devic This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension provides a "device continuity" signal when used consistently with both {{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations: -A new signature by a new [=device-bound key=] ("sig 1 + dpk 1") is returned to the [=[RP]=], along with a new [=user credential=], when the `devicePubKey` extension is used with a {{CredentialsContainer/create()|navigator.credentials.create()}} call on a given device ("device 1"). For example, as part of a [=registration ceremony=]. Further signatures by the same [=device-bound key=] ("sig n + dpk 1") are returned to the [=[RP]=] along with [=assertions=] generated using the latter user credential when the [=[RP]=] subsequently invokes {{CredentialsContainer/get()|navigator.credentials.get()}} calls including the `devicePubKey` extension, on that same device ("device 1") upon which the user credential was created. This behavior (on "device 1") is independent of whether this user credential has been copied to any other devices. -Then, if this user credential is copied or moved to a different device ("device 2"), the [=[RP]=] will receive a new signature by a new [=device-bound key=] ("sig x + dpk 2") on the initial {{CredentialsContainer/get()|navigator.credentials.get()}} invocation (that includes the `devicePubKey` extension) on "device 2". Note that such a "multi-device user credential" can be exercised for authenticating its user on a different device without a {{CredentialsContainer/create()|navigator.credentials.create()}} having been performed on that new device. +When the `devicePubKey` extension is used with a {{CredentialsContainer/create()|navigator.credentials.create()}} call on a given device ("device 1") causing the successful creation of a new [=user credential=], a new signature ("sig 1") by a new [=device-bound key=] ("dpk 1") is returned to the [=[RP]=] (along with the [=device public key=]). For example, as part of a [=registration ceremony=]. Further signatures by the same [=device-bound key=] ("sig n + dpk 1") are returned to the [=[RP]=] along with [=assertions=] generated using the same user credential when the [=[RP]=] subsequently invokes {{CredentialsContainer/get()|navigator.credentials.get()}} calls including the `devicePubKey` extension, on that same device ("device 1"). This behavior (on "device 1") is independent of whether this user credential has been copied to any other devices. + +Then, if this same user credential is copied or moved to a different device ("device 2"), the [=[RP]=] will receive a new signature by a new [=device-bound key=] ("sig x + dpk 2") on the initial {{CredentialsContainer/get()|navigator.credentials.get()}} invocation (that includes the `devicePubKey` extension) on "device 2". Note that such a "multi-device user credential" can be exercised for authenticating its user on a different device without a {{CredentialsContainer/create()|navigator.credentials.create()}} having been performed on "device 2". Subsequent invocations of {{CredentialsContainer/get()|navigator.credentials.get()}} involving the same user credential and including the `devicePubKey` extension on "device 2" yield further signatures by "dpk 2". A usage example is thus: From 2730294db2b62b46fa960070159052b983c2acfe Mon Sep 17 00:00:00 2001 From: JeffH Date: Thu, 17 Mar 2022 14:10:48 -0700 Subject: [PATCH 084/131] incop & massage Emlun's suggestion, thx! --- index.bs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index a54c3496b..b4fd8ec44 100644 --- a/index.bs +++ b/index.bs @@ -6095,10 +6095,9 @@ Issue(w3c/webauthn#1665): TODO: explicitly add the notion of "Synced/multi-devic This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension provides a "device continuity" signal when used consistently with both {{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations: +When a [=[RP]=] uses the `devicePubKey` extension with a {{CredentialsContainer/create()|create()}} call on a given [=client device=] ("device 1") to create a new [=user credential=], a new signature ("sig 1") by a new [=device-bound key=] ("dpk 1") is returned along with the new [=device public key=]. The [=[RP]=]'s subsequent {{CredentialsContainer/get()|get()}} operations using the same user credential on that same [=client device=] ("device 1") generate [=assertions=] including further signatures by the same [=device-bound key=] ("sig n + dpk 1"). This behavior on "device 1" is independent of whether this user credential has been copied to any other [=client devices=]. -When the `devicePubKey` extension is used with a {{CredentialsContainer/create()|navigator.credentials.create()}} call on a given device ("device 1") causing the successful creation of a new [=user credential=], a new signature ("sig 1") by a new [=device-bound key=] ("dpk 1") is returned to the [=[RP]=] (along with the [=device public key=]). For example, as part of a [=registration ceremony=]. Further signatures by the same [=device-bound key=] ("sig n + dpk 1") are returned to the [=[RP]=] along with [=assertions=] generated using the same user credential when the [=[RP]=] subsequently invokes {{CredentialsContainer/get()|navigator.credentials.get()}} calls including the `devicePubKey` extension, on that same device ("device 1"). This behavior (on "device 1") is independent of whether this user credential has been copied to any other devices. - -Then, if this same user credential is copied or moved to a different device ("device 2"), the [=[RP]=] will receive a new signature by a new [=device-bound key=] ("sig x + dpk 2") on the initial {{CredentialsContainer/get()|navigator.credentials.get()}} invocation (that includes the `devicePubKey` extension) on "device 2". Note that such a "multi-device user credential" can be exercised for authenticating its user on a different device without a {{CredentialsContainer/create()|navigator.credentials.create()}} having been performed on "device 2". Subsequent invocations of {{CredentialsContainer/get()|navigator.credentials.get()}} involving the same user credential and including the `devicePubKey` extension on "device 2" yield further signatures by "dpk 2". +Then, if this same user credential is copied or moved to a different [=client device=] ("device 2"), the [=[RP]=]'s initial {{CredentialsContainer/get()|get()}} call on "device 2", that includes the `devicePubKey` extension, will produce an [=assertion=] including a new signature by a new [=device-bound key=] ("sig x + dpk 2"). Note that such a "multi-device user credential" can be exercised on "device 2" without a {{CredentialsContainer/create()|create()}} having been performed on "device 2". The [=[RP]=]'s subsequent {{CredentialsContainer/get()|get()}} calls on "device 2", using the `devicePubKey` extension and the same user credential, yield further signatures by "dpk 2". A usage example is thus: From f0fe8f25b198d3ebc9f1ac10ae13d1eabfca364d Mon Sep 17 00:00:00 2001 From: JeffH Date: Sat, 19 Mar 2022 16:54:37 -0700 Subject: [PATCH 085/131] rough WIP to fix issue #1701 side-channel attack --- index.bs | 98 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 83 insertions(+), 15 deletions(-) diff --git a/index.bs b/index.bs index b4fd8ec44..a5bc3fe04 100644 --- a/index.bs +++ b/index.bs @@ -6105,7 +6105,10 @@ A usage example is thus: Issue(w3c/webauthn#1701): TODO: **The below paragraph is presently inaccurate and will be revised or removed as part of the resolution to Issue #1701.** RPs will likely need to verify the device-bound key's signature on a per-request basis. -A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `attObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [=attestation statement format=]'s "signing procedure", see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value: see [[#sctn-device-publickey-extension-verification]]. Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `scope`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrence, do binary equality checks of the cached `aaguid`, `dpk`, `scope`, and `$$attStmtType` values against those returned in the extension output. +A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `attObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [=attestation statement format=]'s "signing procedure", see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value: see [[#sctn-device-publickey-extension-verification]]. + + +Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `scope`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrence, do binary equality checks of the cached `aaguid`, `dpk`, `scope`, and `$$attStmtType` values against those returned in the extension output. If any of `aaguid`, `dpk`, `scope` do not compare as equal, then the response is invalid. Otherwise, they are all equal, and if `$$attStmtType` does not compare as equal, then the [=[RP]=] should verify the attestation signature. If ### Extension Definition ### {#sctn-device-publickey-extension-definition} @@ -6181,6 +6184,12 @@ A [=[RP]=] utilizing this extension will only need to perform thorough verificat ; 0x01 means "per-app" scope. ; Values other than 0x00 or 0x01 are reserved for future use. + ; An authenticator-generated random nonce for inclusion in the attestation + ; signature. If the authenticator chooses to not generate a nonce, it sets this + ; to a zero-length bytestring. + + nonce: bstr .size (0..32), + ; See https://www.w3.org/TR/webauthn/#sctn-generating-an-attestation-object ; ; Attestation statement formats define the \`fmt\` and \`attStmt\` members of @@ -6188,7 +6197,7 @@ A [=[RP]=] utilizing this extension will only need to perform thorough verificat ; ; In summary, the \`attStmt\` will (typically) contain: ; (1) a SIGNATURE value calculated (using the attestation private key) - ; over (aaguid || dpk). + ; over (aaguid || dpk || nonce). ; (2) the attestation certificate or public key, and supporting certificates, ; if any. ; @@ -6215,21 +6224,27 @@ A [=[RP]=] utilizing this extension will only need to perform thorough verificat 1. Let |clientDataHash| be the [=hash of the serialized client data=]. + 1. Let |randomNonce| be a fresh randomly-generated bytestring of 32 bytes maximum length, or a zero length bytestring if the authenticator chooses to not generate a nonce. + + Note: |randomNonce|'s purpose is to randomize the `devicePubKey` extension's attestation signature value. If this is not done, then the `devicePubKey` extension's attestation signature value remains constant for all such signatures issued on behalf of this user credential, possibly exposing the authenticator's attestation private key to side-channel attacks. The randomness-generation mechanism should be carefully chosen by the authenticator implementer. + 1. Let the `devicePubKey` [=authenticator extension output=] value be a [=CBOR=] map as defined by `attObjForDevicePublicKey` above, with keys and values as follows: 1. Let the `aaguid` key's value be |aaguid|. 1. Let the `dpk` key's value be |dpk|. + 1. Let the `nonce` key's value be |randomNonce|. + 1. Let the `sig` key's value be the result of signing the concatenation of |clientDataHash| and |userCredentialId| using the |devicePrivateKey| and the signature algorithm appropriate for the |devicePrivateKey|'s public key algorithm: sign((|clientDataHash| || |userCredentialId|), |devicePrivateKey|). 1. Let the `scope` key have the value zero (0x00) if this is an "entire device" [=device public key=]. Otherwise, let `scope` have the value one (0x01), indicating a more narrow per-app scope. - 1. Let the values of the `$$attStmtType` "group socket" [[=CDDL=]] be the result of generating an [=attestation statement=] in the [=attestation statement format=] appropriate for this [=authenticator=] (see the Note below), although substituting |aaguid| for `authenticatorData`, and substituting |dpk| for `clientDataHash` in the [=attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]). Attestation statement formats define the `fmt` and `attStmt` members of the `$$attStmtType` group socket. + 1. Let the values of the `$$attStmtType` "group socket" [[=CDDL=]] be the result of generating an [=attestation statement=] in the [=attestation statement format=] appropriate for this [=authenticator=] (see the Note below), although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value (in that order) for `clientDataHash` in the [=attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]). If |nonce|'s value is a zero length bytestring, then there is no nonce value to concatenate. Attestation statement formats define the `fmt` and `attStmt` members of the `$$attStmtType` group socket. - In summary, the `$$attStmtType` values generated by the foregoing procedure typically contain a signature value calculated over the bytes of (|aaguid| || |dpk|), the attestation certificate or public key, and supporting certificates, if any. + In summary, the `$$attStmtType` values generated by the foregoing procedure typically contain a signature value calculated over the bytes of (|aaguid| || |dpk| || |nonce|), the attestation certificate or public key, and supporting certificates, if any. - Note: The details of the `$$attStmtType` values are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust. This is in contrast to the associated [=user credential=]'s attestation. + Note: The details of the `$$attStmtType` values are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust (although they do not have to be). This is in contrast to the associated [=user credential=]'s attestation. ### `devicePubKey` Extension Output Verification Procedures ### {#sctn-device-publickey-extension-verification} @@ -6241,13 +6256,15 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/create( 1. Let |attObjForDevicePublicKey| be the value of the `devicePubKey` member of |clientExtensionResults|. -1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |sig|, |aaguid|, |dpk|, |scope|, |fmt|, |attStmt|. +1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |sig|, |aaguid|, |dpk|, |scope|, |nonce|, |fmt|, |attStmt|. + + Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of [=authenticatorData=]. 1. Verify that |sig| is a valid signature over the concatenation of |hash| and [=credentialId=] using the [=device public key=] |dpk| (the signature algorithm is indicated by |dpk|'s "alg" {{COSEAlgorithmIdentifier}} value). -1. Verify that |attObjForDevicePublicKey|.|attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |attObjForDevicePublicKey|.|fmt|'s [=verification procedure=] given |attObjForDevicePublicKey|.|attStmt|, although substituting |attObjForDevicePublicKey|.|aaguid| for `authenticatorData`, and substituting |dpk| for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. +1. Verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. - Note: If |attObjForDevicePublicKey|.|fmt|'s value is "[=none=]", there is no attestation signature to verify. + Note: If |fmt|'s value is "[=none=]" there is no attestation signature to verify. 1. Store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. @@ -6260,23 +6277,74 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n 1. Let |attObjForDevicePublicKey| be the value of the `devicePubKey` member of |clientExtensionResults|. -1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |sig|, |aaguid|, |dpk|, |scope|, |fmt|, |attStmt|. +1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |sig|, |aaguid|, |dpk|, |scope|, |nonce|, |fmt|, |attStmt|. + + Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of [=authenticatorData=]. 1. Verify that |sig| is a valid signature over the concatenation of |hash| and [=credentialId=] using the [=device public key=] |dpk| (the signature algorithm is indicated by |dpk|'s "alg" {{COSEAlgorithmIdentifier}} value). -1. If the [=[RP]=]'s [=user account|account=] mapped to the |credential|.{{Credential/id}} in play (i.e., for the user being authenticated) holds values corresponding to the extracted |attObjForDevicePublicKey| fields: perform binary equality checks between the corresponding stored and extracted values, then if the results are: +1. If the [=[RP]=]'s [=user account=] mapped to the |credential|.{{Credential/id}} in play (i.e., for the user being authenticated) holds |aaguid|, |dpk|, |scope|, |fmt|, and |attStmt| values corresponding to the extracted |attObjForDevicePublicKey| fields, then perform binary equality checks between the corresponding stored {|aaguid|, |dpk|, |scope|, |fmt|} values and the extracted |aaguid|, |dpk|, |scope|, |fmt| values. The [=[RP]=] may have more than one set of {|aaguid|, |dpk|, |scope|, |fmt|, |attStmt|} values mapped to the [=user account=] and each set must be checked until one matches. + + If the results are:
    : successful - :: This is a known [=device public key=] and thus a known device. Terminate these verification steps. + :: This is likely a known device: + + If |fmt|'s value is "none" then there is no attestation signature to verify and this is a known [=device public key=] with a valid |sig| and thus a known device. Terminate these verification steps. + + Otherwise, check |attObjForDevicePublicKey|'s |attStmt| by performing a binary equality check between the corresponding stored and extracted |attStmt| values. If the result is: +
    + : successful + :: This is a known [=device public key=] with a valid |sig| and valid attestation and thus a known device. Terminate these verification steps. + + Note: This authenticator is not generating a fresh per-response random nonce. + + : unsuccessful + :: Verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. + + If the result is: + +
    + : successful + :: This is a known [=device public key=] with a valid |sig| and valid attestation and thus a known device. Terminate these verification steps. + + Note: This authenticator is generating a fresh per-response random nonce. + + : unsuccessful + :: Some form of error has occurred. It is indeterminate whether this is a known device. Terminate these verification steps. +
    +
    : unsuccessful - :: This is a new [=device public key=] signifying a new device: - 1. Verify that |attObjForDevicePublicKey|.|attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |attObjForDevicePublicKey|.|fmt|'s [=verification procedure=] given |attObjForDevicePublicKey|.|attStmt|, although substituting |attObjForDevicePublicKey|.|aaguid| for `authenticatorData`, and substituting |dpk| for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. - 1. Store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. Terminate these verification steps. + :: This is possibly a new [=device public key=] signifying a new device: + + 1. If |attObjForDevicePublicKey|.|dpk| did not match any of the [=[RP]=]'s stored |dpk| values for this [=user account=] and |credential|.{{Credential/id}} pair then: + +
    + : If |fmt|'s value is "none" then there is no attestation signature to verify and this is a new device. + :: Store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. Terminate these verification steps. + + : Otherwise: + :: Verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. + + If the result is: + +
    + : successful + :: This is a new [=device public key=] signifying a new device. Store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. Terminate these verification steps. + + : unsuccessful + :: Some form of error has occurred. It is indeterminate whether this is a valid new device. Terminate these verification steps. +
    +
    + 1. Otherwise something weird is going on because we recieved a known |dpk| value, but one or more of the accompanying |aaguid|, |scope|, or |fmt| values did not match what the [=[RP]=] as stored along with that |dpk| value.
    -1. Otherwise, |attObjForDevicePublicKey| fields are not presently mapped to this [=user account=] and |credential|.{{Credential/id}} pair. Verify the |attObjForDevicePublicKey|.|attStmt| as given above, and if successful then store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. +1. Otherwise, the [=[RP]=] does not have |attObjForDevicePublicKey| fields presently mapped to this [=user account=] and |credential|.{{Credential/id}} pair, + + 1. If |fmt|'s value is "none" then there is no attestation signature to verify. Otherwise, verify the |attStmt| as given above. + 1. If successful then store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. See also [[#sctn-device-publickey-extension-usage]] for further details. From f1452343557651be7569627a75a41709768d281c Mon Sep 17 00:00:00 2001 From: JeffH Date: Wed, 23 Mar 2022 11:59:40 -0700 Subject: [PATCH 086/131] further WIP re fixing #1701 authnr nonce, & noting #1711 --- index.bs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/index.bs b/index.bs index a5bc3fe04..fc58d50c3 100644 --- a/index.bs +++ b/index.bs @@ -6091,7 +6091,7 @@ The [=hardware-bound device key pair=] is not on its own a [=user credential=] a ### Relying Party Usage ### {#sctn-device-publickey-extension-usage} -Issue(w3c/webauthn#1665): TODO: explicitly add the notion of "Synced/multi-device User Credentials" to the spec. This will involve adding proper definitions for "single-device (user) credentials" and "multi-device (user) credentials" in the terminology section, updating the text of this "Device-bound public key extension" section, and perhaps other approapriate changes or additions to other portions of the spec. +Issue(w3c/webauthn#1665): TODO: explicitly add the notion of "Synced/multi-device User Credentials" to the spec. This will involve adding proper definitions for "single-device (user) credentials" and "multi-device (user) credentials" in the terminology section, updating the text of this "Device-bound public key extension" section, and perhaps other appropriate changes or additions to other portions of the spec. See also [issue #1692](https://github.com/w3c/webauthn/issues/1692) and [PR #1695](https://github.com/w3c/webauthn/pull/1695) regarding public key credential source "backup". This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension provides a "device continuity" signal when used consistently with both {{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations: @@ -6103,12 +6103,7 @@ A usage example is thus: > Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with an assertion by a multi-device user credential on its own. But if a signature by a [=device-bound key=] that is well established for this user can also be presented, then that may tip the balance. -Issue(w3c/webauthn#1701): TODO: **The below paragraph is presently inaccurate and will be revised or removed as part of the resolution to Issue #1701.** RPs will likely need to verify the device-bound key's signature on a per-request basis. - -A [=[RP]=] utilizing this extension will only need to perform thorough verification of the [=device public key=]'s `attObjForDevicePublicKey` once: i.e., the first time a new [=device public key=] is received. "Thorough verification" means verifying the attestation statement `$$attStmtType` (per the [=attestation statement format=]'s "signing procedure", see [[#sctn-generating-an-attestation-object]]) as well as the `sig` value: see [[#sctn-device-publickey-extension-verification]]. - - -Upon successful verification, the [=[RP]=] SHOULD cache the `aaguid`, `dpk`, `scope`, and `$$attStmtType` values. Upon receiving subsequent `devicePubKey` extension output values, the [=[RP]=] can, along with verifying the `sig` value of each extension output occurrence, do binary equality checks of the cached `aaguid`, `dpk`, `scope`, and `$$attStmtType` values against those returned in the extension output. If any of `aaguid`, `dpk`, `scope` do not compare as equal, then the response is invalid. Otherwise, they are all equal, and if `$$attStmtType` does not compare as equal, then the [=[RP]=] should verify the attestation signature. If +Note: The `devicePubKey` extension output value contains one or more signature values. [=[RPS]=] need to take care to verify these signatures before associating and storing extension in conjunction with the [=user account=]. See [[#sctn-device-publickey-extension-verification]]. ### Extension Definition ### {#sctn-device-publickey-extension-definition} @@ -6258,7 +6253,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/create( 1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |sig|, |aaguid|, |dpk|, |scope|, |nonce|, |fmt|, |attStmt|. - Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of [=authenticatorData=]. + Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of [=authenticator data=]. 1. Verify that |sig| is a valid signature over the concatenation of |hash| and [=credentialId=] using the [=device public key=] |dpk| (the signature algorithm is indicated by |dpk|'s "alg" {{COSEAlgorithmIdentifier}} value). @@ -6268,6 +6263,8 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/create( 1. Store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. +Issue(w3c/webauthn#1711): Storing this information ought to only occur if the overall registration operation is successful (i.e., crucially, the "encompassing signature" verified correctly). + See also [[#sctn-device-publickey-extension-usage]] for further details. @@ -6279,7 +6276,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n 1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |sig|, |aaguid|, |dpk|, |scope|, |nonce|, |fmt|, |attStmt|. - Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of [=authenticatorData=]. + Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of [=authenticator data=]. 1. Verify that |sig| is a valid signature over the concatenation of |hash| and [=credentialId=] using the [=device public key=] |dpk| (the signature algorithm is indicated by |dpk|'s "alg" {{COSEAlgorithmIdentifier}} value). @@ -6325,6 +6322,8 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n : If |fmt|'s value is "none" then there is no attestation signature to verify and this is a new device. :: Store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. Terminate these verification steps. + Issue(w3c/webauthn#1711): Storing this information ought to only occur if the overall registration operation is successful (i.e., crucially, the "encompassing signature" verified correctly). + : Otherwise: :: Verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. @@ -6334,6 +6333,8 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n : successful :: This is a new [=device public key=] signifying a new device. Store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. Terminate these verification steps. + Issue(w3c/webauthn#1711): Storing this information ought to only occur if the overall registration operation is successful (i.e., crucially, the "encompassing signature" verified correctly). + : unsuccessful :: Some form of error has occurred. It is indeterminate whether this is a valid new device. Terminate these verification steps. From b8d8567a5bf26e287e0dc378cc6bee940bc9ebed Mon Sep 17 00:00:00 2001 From: JeffH Date: Wed, 23 Mar 2022 15:17:15 -0700 Subject: [PATCH 087/131] attempt at polishing various portions of devicePubKey --- index.bs | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/index.bs b/index.bs index fc58d50c3..b8627a486 100644 --- a/index.bs +++ b/index.bs @@ -6103,7 +6103,7 @@ A usage example is thus: > Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with an assertion by a multi-device user credential on its own. But if a signature by a [=device-bound key=] that is well established for this user can also be presented, then that may tip the balance. -Note: The `devicePubKey` extension output value contains one or more signature values. [=[RPS]=] need to take care to verify these signatures before associating and storing extension in conjunction with the [=user account=]. See [[#sctn-device-publickey-extension-verification]]. +Note: The `devicePubKey` extension output value contains one or more signature values. [=[RPS]=] need to take care to verify these signatures before associating and storing extension output value fields in conjunction with the [=user account=]. See [[#sctn-device-publickey-extension-verification]]. ### Extension Definition ### {#sctn-device-publickey-extension-definition} @@ -6207,9 +6207,9 @@ Note: The `devicePubKey` extension output value contains one or more signature v : Authenticator extension processing :: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: - 1. Create or select the [=user credential=] as usual, and let |userCredentialId| be its [=credentialId=]. + 1. Create or select the [=public key credential source=] as usual (see [[#sctn-op-make-cred]], or [[#sctn-op-get-assertion]] as appropriate), and let |userCredentialId| be its [=public key credential source/id|Credential ID=]. - 1. If a [=hardware-bound device key pair=] does not already exist for this [=user credential=] on the [=authenticator=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing [=device-bound key=]. + 1. If a [=hardware-bound device key pair=] does not already exist for this {[=public key credential source/id|Credential ID=], [=public key credential source/rpId|RP ID=], [=public key credential source/rpId|userHandle=]} tuple on the [=authenticator=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing [=device-bound key=]. 1. Let |dpk| be the newly created or existing [=device public key=], in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=] when the latter is conveyed in [=attested credential data=]. @@ -6221,7 +6221,7 @@ Note: The `devicePubKey` extension output value contains one or more signature v 1. Let |randomNonce| be a fresh randomly-generated bytestring of 32 bytes maximum length, or a zero length bytestring if the authenticator chooses to not generate a nonce. - Note: |randomNonce|'s purpose is to randomize the `devicePubKey` extension's attestation signature value. If this is not done, then the `devicePubKey` extension's attestation signature value remains constant for all such signatures issued on behalf of this user credential, possibly exposing the authenticator's attestation private key to side-channel attacks. The randomness-generation mechanism should be carefully chosen by the authenticator implementer. + Note: |randomNonce|'s purpose is to randomize the `devicePubKey` extension's [=attestation signature=] value. If this is not done, then the `devicePubKey` extension's [=attestation signature=] value remains constant for all such signatures issued on behalf of this [=user credential=], possibly exposing the [=authenticator=]'s [=attestation private key=] to side-channel attacks. The randomness-generation mechanism should be carefully chosen by the authenticator implementer. 1. Let the `devicePubKey` [=authenticator extension output=] value be a [=CBOR=] map as defined by `attObjForDevicePublicKey` above, with keys and values as follows: @@ -6263,7 +6263,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/create( 1. Store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. -Issue(w3c/webauthn#1711): Storing this information ought to only occur if the overall registration operation is successful (i.e., crucially, the "encompassing signature" verified correctly). +Issue(w3c/webauthn#1711): Storing this information ought to only occur if the overall registration operation is successful, i.e., crucially, the "encompassing signature" verified correctly. See also [[#sctn-device-publickey-extension-usage]] for further details. @@ -6280,9 +6280,13 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n 1. Verify that |sig| is a valid signature over the concatenation of |hash| and [=credentialId=] using the [=device public key=] |dpk| (the signature algorithm is indicated by |dpk|'s "alg" {{COSEAlgorithmIdentifier}} value). -1. If the [=[RP]=]'s [=user account=] mapped to the |credential|.{{Credential/id}} in play (i.e., for the user being authenticated) holds |aaguid|, |dpk|, |scope|, |fmt|, and |attStmt| values corresponding to the extracted |attObjForDevicePublicKey| fields, then perform binary equality checks between the corresponding stored {|aaguid|, |dpk|, |scope|, |fmt|} values and the extracted |aaguid|, |dpk|, |scope|, |fmt| values. The [=[RP]=] may have more than one set of {|aaguid|, |dpk|, |scope|, |fmt|, |attStmt|} values mapped to the [=user account=] and each set must be checked until one matches. +1. If the [=[RP]=]'s [=user account=] mapped to the |credential|.{{Credential/id}} in play (i.e., for the user being authenticated) holds `aaguid`, `dpk`, `scope`, `fmt`, and `attStmt` values corresponding to the extracted |attObjForDevicePublicKey| fields, then perform binary equality checks between the corresponding stored values and the extracted field values. The [=[RP]=] may have more than one set of {`aaguid`, `dpk`, `scope`, `fmt`, `attStmt`} values mapped to the [=user account=] and |credential|.{{Credential/id}} pair and each set must be checked. - If the results are: + Issue(w3c/webauthn#1711): In the below steps, where storing the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values is called for, it ought to only occur if the overall authentication operation is successful, i.e., crucially, the "encompassing signature" verified correctly. + + 1. If more than one stored {`aaguid`, `dpk`, `scope`, `fmt`, `attStmt`} value set matched with the extracted |attObjForDevicePublicKey| fields, then some form of error has occurred. It is indeterminate whether this is a known device. Terminate these verification steps. + + 1. Otherwise, if the results are:
    : successful @@ -6320,9 +6324,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n
    : If |fmt|'s value is "none" then there is no attestation signature to verify and this is a new device. - :: Store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. Terminate these verification steps. - - Issue(w3c/webauthn#1711): Storing this information ought to only occur if the overall registration operation is successful (i.e., crucially, the "encompassing signature" verified correctly). + :: Store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. Terminate these verification steps. : Otherwise: :: Verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. @@ -6331,23 +6333,32 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n
    : successful - :: This is a new [=device public key=] signifying a new device. Store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. Terminate these verification steps. - - Issue(w3c/webauthn#1711): Storing this information ought to only occur if the overall registration operation is successful (i.e., crucially, the "encompassing signature" verified correctly). + :: This is a new [=device public key=] signifying a new device. Store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. Terminate these verification steps. : unsuccessful :: Some form of error has occurred. It is indeterminate whether this is a valid new device. Terminate these verification steps.
    - 1. Otherwise something weird is going on because we recieved a known |dpk| value, but one or more of the accompanying |aaguid|, |scope|, or |fmt| values did not match what the [=[RP]=] as stored along with that |dpk| value. + 1. Otherwise there is some form of error: we recieved a known |dpk| value, but one or more of the accompanying |aaguid|, |scope|, or |fmt| values did not match what the [=[RP]=] has stored along with that |dpk| value. Terminate these verification steps.
    -1. Otherwise, the [=[RP]=] does not have |attObjForDevicePublicKey| fields presently mapped to this [=user account=] and |credential|.{{Credential/id}} pair, +1. Otherwise, the [=[RP]=] does not have |attObjForDevicePublicKey| fields presently mapped to this [=user account=] and |credential|.{{Credential/id}} pair: - 1. If |fmt|'s value is "none" then there is no attestation signature to verify. Otherwise, verify the |attStmt| as given above. - 1. If successful then store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. + 1. If |fmt|'s value is "none" there is no attestation signature to verify. Store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. Terminate these verification steps. -See also [[#sctn-device-publickey-extension-usage]] for further details. + 1. Otherwise, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. + + If the result is: + +
    + : successful + :: This is the first [=device public key=]—and thus device—mapped to this [=user account=] and |credential|.{{Credential/id}} pair. Store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. Terminate these verification steps. + + : unsuccessful + :: Some form of error has occurred. It is indeterminate whether this is a valid new device. Terminate these verification steps. +
    + +See also [[#sctn-device-publickey-extension-usage]]. # User Agent Automation # {#sctn-automation} From d92bad2a3f5925b4dd4d0cb39544393e7f3a491b Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Thu, 19 May 2022 10:40:00 -0700 Subject: [PATCH 088/131] The DPK is stored on the authenticator. The text said that the DPK was stored on the client device, but the client device is the device that the browser is running on, not the authenticator. That _might_ be the same device, but it's unclear. Clarify that the DPKs come from the authenticator. --- index.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index b8627a486..9e9a478e6 100644 --- a/index.bs +++ b/index.bs @@ -6082,11 +6082,11 @@ However, [=authenticators=] that do not utilize [[!FIDO-CTAP]] do not necessaril ## Device-bound public key extension (devicePubKey) ## {#sctn-device-publickey-extension} -This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal. This is done by creating a [=[RP]=]-specific [=hardware-bound device key pair=] on the [=client device=], if such a key pair does not already exist for the [=user credential=] being created or exercised, and returning the attested [=device public key=] along with a signature by the [=device private key=] to the [=[RP]=]. This is done each time this [=devicePubKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/get()|navigator.credentials.get()}} call. +This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal for backup eligible credentials. This is done by creating a [=[RP]=]-specific [=hardware-bound device key pair=] on the [=authenticator=], if such a key pair does not already exist for the [=user credential=] being created or exercised, and returning the attested [=device public key=] along with a signature by the [=device private key=] to the [=[RP]=]. This is done each time this [=devicePubKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/get()|navigator.credentials.get()}} call. -If the [=client device=] is incapable of generating a [=hardware-bound device key pair=], or the registration or authentication operation fails for any reason, this extension is ignored and no [=hardware-bound device key pair=] is created. In this case, there is no [=devicePubKey=] extension output generated. +If the [=authenticator=] is incapable of generating a [=hardware-bound device key pair=], or the registration or authentication operation fails for any reason, this extension is ignored and no [=hardware-bound device key pair=] is created. In this case, there is no [=devicePubKey=] extension output generated. -The [=hardware-bound device key pair=] is not on its own a [=user credential=] and does not have its own [=credential ID=]. Instead, the returned [=device public key=] is a device-specific contextual attribute of its associated [=user credential=]. That is, when that [=user credential=] is used—along with the [=devicePubKey=] extension—on a particular [=client device=], a particular [=device public key=] is returned by the extension, along with a signature demonstrating proof-of-possession of the [=device private key=] by that device. +The [=hardware-bound device key pair=] is not on its own a [=user credential=] and does not have its own [=credential ID=]. Instead, the returned [=device public key=] is a device-specific contextual attribute of its associated [=user credential=]. That is, when that [=user credential=] is used—along with the [=devicePubKey=] extension—on a particular [=authenticator=], a particular [=device public key=] is returned by the extension, along with a signature demonstrating proof-of-possession of the [=device private key=] by that device. ### Relying Party Usage ### {#sctn-device-publickey-extension-usage} From 6d45aba6a5088cac743f85c33fec48dec60bd010 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Thu, 19 May 2022 12:04:21 -0700 Subject: [PATCH 089/131] Provide attestation controls. Mirror the attestation controls for user credentials into the DPK extension. --- index.bs | 64 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/index.bs b/index.bs index 9e9a478e6..862c09e1a 100644 --- a/index.bs +++ b/index.bs @@ -6095,15 +6095,15 @@ Issue(w3c/webauthn#1665): TODO: explicitly add the notion of "Synced/multi-devic This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension provides a "device continuity" signal when used consistently with both {{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations: -When a [=[RP]=] uses the `devicePubKey` extension with a {{CredentialsContainer/create()|create()}} call on a given [=client device=] ("device 1") to create a new [=user credential=], a new signature ("sig 1") by a new [=device-bound key=] ("dpk 1") is returned along with the new [=device public key=]. The [=[RP]=]'s subsequent {{CredentialsContainer/get()|get()}} operations using the same user credential on that same [=client device=] ("device 1") generate [=assertions=] including further signatures by the same [=device-bound key=] ("sig n + dpk 1"). This behavior on "device 1" is independent of whether this user credential has been copied to any other [=client devices=]. +When a [=[RP]=] uses the `devicePubKey` extension with a {{CredentialsContainer/create()|create()}} call to create a new [=user credential=], a signature by a new [=device-bound key=] ("dpk 1") is returned along with the new [=device public key=]. Even if the [=user credential=] is backed up, "dpk 1" never leaves the generating authenticator ("authenticator 1"). The [=[RP]=]'s subsequent {{CredentialsContainer/get()|get()}} operations using the same user credential with that same [=authenticator=] generate [=assertions=] including further signatures by the same [=device-bound key=] ("dpk 1"). This behavior on "authenticator  1" is independent of whether this user credential has been copied to any other [=authenticator=]. -Then, if this same user credential is copied or moved to a different [=client device=] ("device 2"), the [=[RP]=]'s initial {{CredentialsContainer/get()|get()}} call on "device 2", that includes the `devicePubKey` extension, will produce an [=assertion=] including a new signature by a new [=device-bound key=] ("sig x + dpk 2"). Note that such a "multi-device user credential" can be exercised on "device 2" without a {{CredentialsContainer/create()|create()}} having been performed on "device 2". The [=[RP]=]'s subsequent {{CredentialsContainer/get()|get()}} calls on "device 2", using the `devicePubKey` extension and the same user credential, yield further signatures by "dpk 2". +Then, if this same user credential is copied to a different [=authenticator=] ("authenticator 2"), the [=[RP]=]'s first {{CredentialsContainer/get()|get()}} call on "authenticator 2" (that includes the `devicePubKey` extension) will produce an [=assertion=] including a signature by a new [=device-bound key=] ("dpk 2"). Note that such a "multi-device user credential" can be exercised on "authenticator 2" without a {{CredentialsContainer/create()|create()}} having been performed on "authenticator 2". The [=[RP]=]'s subsequent {{CredentialsContainer/get()|get()}} calls on "authenticator 2", using the `devicePubKey` extension and the same user credential, yield further signatures by "dpk 2". A usage example is thus: > Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with an assertion by a multi-device user credential on its own. But if a signature by a [=device-bound key=] that is well established for this user can also be presented, then that may tip the balance. -Note: The `devicePubKey` extension output value contains one or more signature values. [=[RPS]=] need to take care to verify these signatures before associating and storing extension output value fields in conjunction with the [=user account=]. See [[#sctn-device-publickey-extension-verification]]. +Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures before associating and storing extension output value fields in conjunction with the [=user account=]. See [[#sctn-device-publickey-extension-verification]]. ### Extension Definition ### {#sctn-device-publickey-extension-definition} @@ -6117,13 +6117,25 @@ Note: The `devicePubKey` extension output value contains one or more signature v : Client extension input :: The Boolean value [TRUE] to indicate that this extension is requested by the [=[RP]=]. + dictionary AuthenticationExtensionsDevicePublicKeyInputs { + DOMString attestation = "none"; + }; + partial dictionary AuthenticationExtensionsClientInputs { - boolean devicePubKey; // Explicitly set this to \`true\`! + AuthenticationExtensionsDevicePublicKeyInputs devicePubKey; }; +
    + : attestation + :: The [=[RP]=] MAY use this OPTIONAL member to specify a preference regarding [=attestation conveyance=]. + Its value SHOULD be a member of {{AttestationConveyancePreference}}. + [=Client platforms=] MUST ignore unknown values, treating an unknown value as if the [=map/exist|member does not exist=]. + + The default value is {{AttestationConveyancePreference/none}}. +
    : Client extension processing -:: If {{AuthenticationExtensionsClientInputs/devicePubKey}} is [TRUE], the client creates the authenticator extension input from the client extension input. If {{AuthenticationExtensionsClientInputs/devicePubKey}} is [FALSE], the client ignores this extension. +:: If {{AuthenticationExtensionsClientInputs/devicePubKey}} is present, the client creates the authenticator extension input from the client extension input. : Client extension output :: An ArrayBuffer containing the [=device public key attestation object=] that was returned in the authenticator extension output. @@ -6135,11 +6147,14 @@ Note: The `devicePubKey` extension output value contains one or more signature v : Authenticator extension input -:: The Boolean value [TRUE], encoded in CBOR (major type 7, value 21). +:: A CBOR expression of the client extension input ``` + devicePublicKeyInputs = { + attestation: "none" / "indirect" / "direct" / "enterprise", + } $$extensionInput //= ( - devicePubKey: true + devicePubKey: devicePublicKeyInputs, ) ``` @@ -6201,6 +6216,14 @@ Note: The `devicePubKey` extension output value contains one or more signature v ; See https://www.w3.org/TR/webauthn/#sctn-defined-attestation-formats. $$attStmtType, + + ; An optional boolean that indicates whether the attestation statement + ; contains uniquely identifying information. This can only be true + ; when the \`attestation\` field of the extension input is "enterprise" + ; an either the user-agent or the authenticator permits uniquely + ; identifying attestation for the requested RP ID. + + ? epAtt: bool .default false, } ``` @@ -6211,9 +6234,22 @@ Note: The `devicePubKey` extension output value contains one or more signature v 1. If a [=hardware-bound device key pair=] does not already exist for this {[=public key credential source/id|Credential ID=], [=public key credential source/rpId|RP ID=], [=public key credential source/rpId|userHandle=]} tuple on the [=authenticator=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing [=device-bound key=]. - 1. Let |dpk| be the newly created or existing [=device public key=], in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=] when the latter is conveyed in [=attested credential data=]. + 1. Let |attFormat| be the chosen [=attestation statement format=], and |aaguid| be a 16-byte value, based on the value of {{AuthenticationExtensionsDevicePublicKeyInputs/attestation}} in the extension input: - 1. Let |aaguid| be the [=authenticator=]'s [=AAGUID=]. +
    + : none + :: |attFormat| is "none" or "self", at the authenticator's discretion, and |aaguid| is 16 zero bytes. + + : indirect, direct + :: |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] and |aaguid| is the [=authenticator's=] [=AAGUID=]. (Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust, although they do not have to be. This is in contrast to the associated [=user credential=]'s attestation.) + + : enterprise + :: The [=[RP]=] wants to receive an [=attestation statement=] that may include uniquely identifying information. This is intended for controlled deployments within an enterprise where the organization wishes to tie registrations to specific authenticators. [=Authenticators=] MUST NOT provide such an attestation unless the user agent or authenticator configuration expressly permits it for the requested [=RP ID=]. If not permitted, |attFormat| is "none" and |aaguid| is 16 zero bytes. Otherwise |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] and |aaguid| is the [=authenticator's=] [=AAGUID=]. (Again, since the [=hardware-bound device key pair=] is specific to a particular authenticator, the attestation may be tied to hardware roots of trust.) + + Note: CTAP2 does not currently provide for an enterpriseAttestation signal during an authenticatorGetAssertion call. Until that is changed, platform-managed enterprise attestation will not work in that context with CTAP2 [=authenticators=]. +
    + + 1. Let |dpk| be the newly created or existing [=device public key=], in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=] when the latter is conveyed in [=attested credential data=]. 1. Let |devicePrivateKey| be the newly created or existing [=device private key=]. @@ -6235,12 +6271,14 @@ Note: The `devicePubKey` extension output value contains one or more signature v 1. Let the `scope` key have the value zero (0x00) if this is an "entire device" [=device public key=]. Otherwise, let `scope` have the value one (0x01), indicating a more narrow per-app scope. - 1. Let the values of the `$$attStmtType` "group socket" [[=CDDL=]] be the result of generating an [=attestation statement=] in the [=attestation statement format=] appropriate for this [=authenticator=] (see the Note below), although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value (in that order) for `clientDataHash` in the [=attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]). If |nonce|'s value is a zero length bytestring, then there is no nonce value to concatenate. Attestation statement formats define the `fmt` and `attStmt` members of the `$$attStmtType` group socket. + 1. Let the values of the `$$attStmtType` "group socket" [[=CDDL=]] be the result of generating an [=attestation statement=] with the [=attestation statement format=], |attFormat|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value (in that order) for `clientDataHash` in the [=attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]). If |nonce|'s value is a zero length bytestring, then there is no nonce value to concatenate. Attestation statement formats define the `fmt` and `attStmt` members of the `$$attStmtType` group socket. In summary, the `$$attStmtType` values generated by the foregoing procedure typically contain a signature value calculated over the bytes of (|aaguid| || |dpk| || |nonce|), the attestation certificate or public key, and supporting certificates, if any. Note: The details of the `$$attStmtType` values are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust (although they do not have to be). This is in contrast to the associated [=user credential=]'s attestation. + 1. If the `$$attStmtType` "group socket" contains uniquely identifying information then let `epAtt` be [TRUE]. Otherwise omit the `epAtt` field. (This field can only be [TRUE] when the {{AuthenticationExtensionsDevicePublicKeyInputs/attestation}} field of the extension input is "enterprise" and either the user-agent or the authenticator permits uniquely identifying attestation for the requested RP ID.) + ### `devicePubKey` Extension Output Verification Procedures ### {#sctn-device-publickey-extension-verification} Verifying the [=devicePubKey=] extension output is performed by the [=[RP]=] whenever a new [=device public key=] is returned within the extension output. As explained in [[#sctn-device-publickey-extension-usage]], a new [=device public key=] is always returned as a result of a {{CredentialsContainer/create()|navigator.credentials.create()}} call (i.e., as part of a [=registration ceremony=]). In contrast, a new [=device public key=] may be returned as a result of a {{CredentialsContainer/get()|navigator.credentials.get()}} call (i.e., as part of an [=authentication ceremony=]). @@ -6257,7 +6295,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/create( 1. Verify that |sig| is a valid signature over the concatenation of |hash| and [=credentialId=] using the [=device public key=] |dpk| (the signature algorithm is indicated by |dpk|'s "alg" {{COSEAlgorithmIdentifier}} value). -1. Verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. +1. Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. Note: If |fmt|'s value is "[=none=]" there is no attestation signature to verify. @@ -6302,7 +6340,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n Note: This authenticator is not generating a fresh per-response random nonce. : unsuccessful - :: Verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. + :: Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. If the result is: @@ -6327,7 +6365,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n :: Store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. Terminate these verification steps. : Otherwise: - :: Verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. + :: Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. If the result is: From eb598ff6a4d08ce35262ee2de3537a8989809cf9 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Thu, 19 May 2022 13:05:11 -0700 Subject: [PATCH 090/131] Pull out DPK attestation rules and add signature prefix. This change adds a section about calculating DPK attestations and references that each time rather than duplicating the rules. It also adds a prefix to the signed messages to ensure that DPK and user credential attestations clearly cannot be confused. --- index.bs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/index.bs b/index.bs index 862c09e1a..1b6134a0b 100644 --- a/index.bs +++ b/index.bs @@ -6207,7 +6207,9 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo ; ; In summary, the \`attStmt\` will (typically) contain: ; (1) a SIGNATURE value calculated (using the attestation private key) - ; over (aaguid || dpk || nonce). + ; over (prefix || aaguid || dpk || nonce) where \`prefix\` is + ; h'64657669636520626f756e64206b6579206174746573746174696f6e20736967 + ; 00ffffffff' ; (2) the attestation certificate or public key, and supporting certificates, ; if any. ; @@ -6271,14 +6273,23 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo 1. Let the `scope` key have the value zero (0x00) if this is an "entire device" [=device public key=]. Otherwise, let `scope` have the value one (0x01), indicating a more narrow per-app scope. - 1. Let the values of the `$$attStmtType` "group socket" [[=CDDL=]] be the result of generating an [=attestation statement=] with the [=attestation statement format=], |attFormat|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value (in that order) for `clientDataHash` in the [=attestation statement format=]'s signing procedure (see [[#sctn-generating-an-attestation-object]]). If |nonce|'s value is a zero length bytestring, then there is no nonce value to concatenate. Attestation statement formats define the `fmt` and `attStmt` members of the `$$attStmtType` group socket. - - In summary, the `$$attStmtType` values generated by the foregoing procedure typically contain a signature value calculated over the bytes of (|aaguid| || |dpk| || |nonce|), the attestation certificate or public key, and supporting certificates, if any. + 1. Let the values of the `$$attStmtType` "group socket" [[=CDDL=]] be the result of generating an [=attestation statement=] with the [=attestation statement format=], |attFormat|. See [[#sctn-device-publickey-attestation-calculations]]. Note: The details of the `$$attStmtType` values are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust (although they do not have to be). This is in contrast to the associated [=user credential=]'s attestation. 1. If the `$$attStmtType` "group socket" contains uniquely identifying information then let `epAtt` be [TRUE]. Otherwise omit the `epAtt` field. (This field can only be [TRUE] when the {{AuthenticationExtensionsDevicePublicKeyInputs/attestation}} field of the extension input is "enterprise" and either the user-agent or the authenticator permits uniquely identifying attestation for the requested RP ID.) +#### Attestation calculations #### {#sctn-device-publickey-attestation-calculations} + +When computing attestations, the process in [[#sctn-generating-an-attestation-object]] takes two inputs: `authData` and `hash`. When calculating an attestation for a [=device-bound key=], the typical value for `hash` hashes over the attestation signature itself, which is impossible. Also the attestation of a [=device-bound key=] is potentially used repeatedly, thus may want to be cached. But signing over values that include [=[RP]=]-chosen nonces, like the [=hash of the serialized client data=], make that impossible. + +Therefore when calculating an attestation for a [=device-bound key=], the inputs are: + + * For `authData`, substitute the concatenation of the byte string h'64657669636520626f756e64206b6579206174746573746174696f6e2073696700ffffffff' and the value of |aaguid| from the extension output. + * For `hash`, substitute the concatenation of the |dpk| and |nonce| fields from the extension output. (The nonce may be empty.) + +The attestation signature is thus typically calculated over the bytes of (h'64657669636520626f756e64206b6579206174746573746174696f6e2073696700ffffffff' || |aaguid| || |dpk| || |nonce|). The 37-byte prefix ensures domain separation: it takes the place of the RP ID hash, flags, and signature counter fields in those messages and ensures that no attestation signature for a [=device-bound key=] can be confused with a signature for a [=user credential=]. + ### `devicePubKey` Extension Output Verification Procedures ### {#sctn-device-publickey-extension-verification} Verifying the [=devicePubKey=] extension output is performed by the [=[RP]=] whenever a new [=device public key=] is returned within the extension output. As explained in [[#sctn-device-publickey-extension-usage]], a new [=device public key=] is always returned as a result of a {{CredentialsContainer/create()|navigator.credentials.create()}} call (i.e., as part of a [=registration ceremony=]). In contrast, a new [=device public key=] may be returned as a result of a {{CredentialsContainer/get()|navigator.credentials.get()}} call (i.e., as part of an [=authentication ceremony=]). @@ -6295,7 +6306,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/create( 1. Verify that |sig| is a valid signature over the concatenation of |hash| and [=credentialId=] using the [=device public key=] |dpk| (the signature algorithm is indicated by |dpk|'s "alg" {{COSEAlgorithmIdentifier}} value). -1. Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. +1. Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. Note: If |fmt|'s value is "[=none=]" there is no attestation signature to verify. @@ -6305,7 +6316,6 @@ Issue(w3c/webauthn#1711): Storing this information ought to only occur if the ov See also [[#sctn-device-publickey-extension-usage]] for further details. - #### Authentication (`get()`) #### {#sctn-device-publickey-extension-verification-get} If the `devicePubKey` extension was included on 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|. @@ -6340,7 +6350,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n Note: This authenticator is not generating a fresh per-response random nonce. : unsuccessful - :: Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. + :: Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. If the result is: @@ -6365,7 +6375,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n :: Store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. Terminate these verification steps. : Otherwise: - :: Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. + :: Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. If the result is: @@ -6384,7 +6394,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n 1. If |fmt|'s value is "none" there is no attestation signature to verify. Store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the |credential|.{{Credential/id}} in the [=user account=]. Terminate these verification steps. - 1. Otherwise, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|, although substituting |aaguid|'s value for `authenticatorData`, and substituting the concatenation of |dpk|'s value and |nonce|'s value for `clientDataHash` in the [=attestation statement format=]'s [=verification procedure inputs=]. + 1. Otherwise, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. If the result is: From b7289e1686669ae00f65653ef608c594a504513f Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Thu, 19 May 2022 13:28:12 -0700 Subject: [PATCH 091/131] Reflow CDDL to avoid a scroll bar. --- index.bs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 1b6134a0b..8aa8796cd 100644 --- a/index.bs +++ b/index.bs @@ -6169,7 +6169,8 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo attObjForDevicePublicKey = { ; Note: This object conveys an attested ; device public key and is analogous to \`attObj\`. - sig: bstr, ; Result of sign((clientDataHash || userCredentialId), devicePrivateKey) + sig: bstr, ; Result of sign((clientDataHash || userCredentialId), + devicePrivateKey) ; Note that this signature value is unique per-response ; because the client data contains the per-request challenge. @@ -6192,7 +6193,8 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo scope: uint .size 1, ; a value of 0x00 means "entire device" ("all apps") scope. ; 0x01 means "per-app" scope. - ; Values other than 0x00 or 0x01 are reserved for future use. + ; Values other than 0x00 or 0x01 are reserved for future + ; use. ; An authenticator-generated random nonce for inclusion in the attestation ; signature. If the authenticator chooses to not generate a nonce, it sets this From dcfb39270f989b30dce6772d03e58c4549902afd Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Thu, 19 May 2022 15:23:22 -0700 Subject: [PATCH 092/131] Have the DPK sign over everything. Signing over too little is a common problem in protocols and the DPK wasn't signing over very much. The problem is that the signature was within the extension itself and thus couldn't easily cover the authenticator data. This change puts the DPK signature next to the normal signature in an assertion, transforming that field into a CBOR array. That's fine for assertions, but registration doesn't have such a signature output. Thus this change drops exercising the DPK during registration: Firstly, this is in line with the user credential which doesn't sign during registration unless "self" attestation is used. (And "self" attestation can be use with the DPK if desired.) Secondly, adding an extra signature output for registration is awkward. Putting it in the user credential attestation statement is awkward and conflicts when user-agents replace attestation statements. Adding a new CTAP field is possible but seems excessive since, as noted, there has never been a signature by the user credential. --- index.bs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/index.bs b/index.bs index 8aa8796cd..baeb4c275 100644 --- a/index.bs +++ b/index.bs @@ -4727,6 +4727,8 @@ In order to perform an [=authentication ceremony=], the [=[RP]=] MUST proceed as {{AuthenticatorResponse/clientDataJSON}}, {{AuthenticatorAssertionResponse/authenticatorData}}, and {{AuthenticatorAssertionResponse/signature}} respectively. + Note: if using the [devicePubKey](#sctn-device-publickey-extension) extension then {{AuthenticatorAssertionResponse/signature}} contains a CBOR array instead and |sig| should be the first element of that array. + 1. Let |JSONtext| be the result of running [=UTF-8 decode=] on the value of |cData|. Note: Using any implementation of [=UTF-8 decode=] is acceptable as long as it yields the same result as that yielded by @@ -6169,11 +6171,6 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo attObjForDevicePublicKey = { ; Note: This object conveys an attested ; device public key and is analogous to \`attObj\`. - sig: bstr, ; Result of sign((clientDataHash || userCredentialId), - devicePrivateKey) - ; Note that this signature value is unique per-response - ; because the client data contains the per-request challenge. - aaguid: bstr, ; Authenticator's AAGUID (16 bytes fixed-length) ; https://www.w3.org/TR/webauthn/#aaguid @@ -6242,7 +6239,7 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo
    : none - :: |attFormat| is "none" or "self", at the authenticator's discretion, and |aaguid| is 16 zero bytes. + :: |attFormat| is "none" or "self", at the authenticator's discretion, and |aaguid| is 16 zero bytes. (Note that, since the [=device-bound key=] is already exercised during {{CredentialsContainer/get()|navigator.credentials.get()}} calls, the proof-of-possession property provided by "self" attestation is superfluous in that context.) : indirect, direct :: |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] and |aaguid| is the [=authenticator's=] [=AAGUID=]. (Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust, although they do not have to be. This is in contrast to the associated [=user credential=]'s attestation.) @@ -6271,8 +6268,6 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo 1. Let the `nonce` key's value be |randomNonce|. - 1. Let the `sig` key's value be the result of signing the concatenation of |clientDataHash| and |userCredentialId| using the |devicePrivateKey| and the signature algorithm appropriate for the |devicePrivateKey|'s public key algorithm: sign((|clientDataHash| || |userCredentialId|), |devicePrivateKey|). - 1. Let the `scope` key have the value zero (0x00) if this is an "entire device" [=device public key=]. Otherwise, let `scope` have the value one (0x01), indicating a more narrow per-app scope. 1. Let the values of the `$$attStmtType` "group socket" [[=CDDL=]] be the result of generating an [=attestation statement=] with the [=attestation statement format=], |attFormat|. See [[#sctn-device-publickey-attestation-calculations]]. @@ -6281,6 +6276,12 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo 1. If the `$$attStmtType` "group socket" contains uniquely identifying information then let `epAtt` be [TRUE]. Otherwise omit the `epAtt` field. (This field can only be [TRUE] when the {{AuthenticationExtensionsDevicePublicKeyInputs/attestation}} field of the extension input is "enterprise" and either the user-agent or the authenticator permits uniquely identifying attestation for the requested RP ID.) + 1. If the extension was presented during a {{CredentialsContainer/get()|navigator.credentials.get()}} call: + + 1. Let |dpkSig| be the result of signing the [=assertion signature=] [input](#fig-signature) with the [=device private key=]. + + 1. Replace the usual [=assertion signature=] result with a two-element CBOR array containing the previous [=assertion signature=] and |dpkSig|, in that order. + #### Attestation calculations #### {#sctn-device-publickey-attestation-calculations} When computing attestations, the process in [[#sctn-generating-an-attestation-object]] takes two inputs: `authData` and `hash`. When calculating an attestation for a [=device-bound key=], the typical value for `hash` hashes over the attestation signature itself, which is impossible. Also the attestation of a [=device-bound key=] is potentially used repeatedly, thus may want to be cached. But signing over values that include [=[RP]=]-chosen nonces, like the [=hash of the serialized client data=], make that impossible. @@ -6302,12 +6303,10 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/create( 1. Let |attObjForDevicePublicKey| be the value of the `devicePubKey` member of |clientExtensionResults|. -1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |sig|, |aaguid|, |dpk|, |scope|, |nonce|, |fmt|, |attStmt|. +1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |aaguid|, |dpk|, |scope|, |nonce|, |fmt|, |attStmt|. Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of [=authenticator data=]. -1. Verify that |sig| is a valid signature over the concatenation of |hash| and [=credentialId=] using the [=device public key=] |dpk| (the signature algorithm is indicated by |dpk|'s "alg" {{COSEAlgorithmIdentifier}} value). - 1. Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. Note: If |fmt|'s value is "[=none=]" there is no attestation signature to verify. @@ -6324,11 +6323,13 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n 1. Let |attObjForDevicePublicKey| be the value of the `devicePubKey` member of |clientExtensionResults|. -1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |sig|, |aaguid|, |dpk|, |scope|, |nonce|, |fmt|, |attStmt|. +1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |aaguid|, |dpk|, |scope|, |nonce|, |fmt|, |attStmt|. Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of [=authenticator data=]. -1. Verify that |sig| is a valid signature over the concatenation of |hash| and [=credentialId=] using the [=device public key=] |dpk| (the signature algorithm is indicated by |dpk|'s "alg" {{COSEAlgorithmIdentifier}} value). +1. Verify that {{AuthenticatorAssertionResponse/signature}} contains a two-element CBOR array and let |sig| be the second element of the array. + +1. Verify that |sig| is a valid signature over the [=assertion signature=] [input](#fig-signature) by the [=device public key=] |dpk|. (The signature algorithm is indicated by |dpk|'s "alg" {{COSEAlgorithmIdentifier}} value.) 1. If the [=[RP]=]'s [=user account=] mapped to the |credential|.{{Credential/id}} in play (i.e., for the user being authenticated) holds `aaguid`, `dpk`, `scope`, `fmt`, and `attStmt` values corresponding to the extracted |attObjForDevicePublicKey| fields, then perform binary equality checks between the corresponding stored values and the extracted field values. The [=[RP]=] may have more than one set of {`aaguid`, `dpk`, `scope`, `fmt`, `attStmt`} values mapped to the [=user account=] and |credential|.{{Credential/id}} pair and each set must be checked. From cbb6b5d050a0c7647521da9ae9c6858c878d0f17 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Thu, 19 May 2022 15:57:07 -0700 Subject: [PATCH 093/131] Note that CTAP2 CBOR is required in DPK. --- index.bs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.bs b/index.bs index baeb4c275..302766173 100644 --- a/index.bs +++ b/index.bs @@ -6262,6 +6262,8 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo 1. Let the `devicePubKey` [=authenticator extension output=] value be a [=CBOR=] map as defined by `attObjForDevicePublicKey` above, with keys and values as follows: + Note: as with all CBOR structures used in this specification, the [=CTAP2 canonical CBOR encoding form=] MUST be used. + 1. Let the `aaguid` key's value be |aaguid|. 1. Let the `dpk` key's value be |dpk|. From ccfd0b4dfe95245d4771c290ed71c225142fd98d Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Thu, 19 May 2022 16:01:06 -0700 Subject: [PATCH 094/131] Resolve comment by jovasco --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 302766173..09fca733c 100644 --- a/index.bs +++ b/index.bs @@ -6084,7 +6084,7 @@ However, [=authenticators=] that do not utilize [[!FIDO-CTAP]] do not necessaril ## Device-bound public key extension (devicePubKey) ## {#sctn-device-publickey-extension} -This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal for backup eligible credentials. This is done by creating a [=[RP]=]-specific [=hardware-bound device key pair=] on the [=authenticator=], if such a key pair does not already exist for the [=user credential=] being created or exercised, and returning the attested [=device public key=] along with a signature by the [=device private key=] to the [=[RP]=]. This is done each time this [=devicePubKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/get()|navigator.credentials.get()}} call. +This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal for backup eligible credentials. This is done by creating a [=user credential=]-specific [=hardware-bound device key pair=] on the [=authenticator=], if such a key pair does not already exist for the [=user credential=] being created or exercised, and returning the attested [=device public key=] along with a signature by the [=device private key=] to the [=[RP]=]. This is done each time this [=devicePubKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/get()|navigator.credentials.get()}} call. If the [=authenticator=] is incapable of generating a [=hardware-bound device key pair=], or the registration or authentication operation fails for any reason, this extension is ignored and no [=hardware-bound device key pair=] is created. In this case, there is no [=devicePubKey=] extension output generated. From 27ef223f89d941e266cb125a37626702edfd6999 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Fri, 17 Jun 2022 12:04:19 -0700 Subject: [PATCH 095/131] Link definitions from PR 1695. --- index.bs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/index.bs b/index.bs index 6db193b71..6cfeaa1cb 100644 --- a/index.bs +++ b/index.bs @@ -6165,7 +6165,7 @@ However, [=authenticators=] that do not utilize [[!FIDO-CTAP]] do not necessaril ## Device-bound public key extension (devicePubKey) ## {#sctn-device-publickey-extension} -This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal for backup eligible credentials. This is done by creating a [=user credential=]-specific [=hardware-bound device key pair=] on the [=authenticator=], if such a key pair does not already exist for the [=user credential=] being created or exercised, and returning the attested [=device public key=] along with a signature by the [=device private key=] to the [=[RP]=]. This is done each time this [=devicePubKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/get()|navigator.credentials.get()}} call. +This [=authenticator extension|authenticator=] [=registration extension=] and [=authentication extension=] provides a [=[RP]=] with a "device continuity" signal for [=backup eligible=] credentials. This is done by creating a [=user credential=]-specific [=hardware-bound device key pair=] on the [=authenticator=], if such a key pair does not already exist for the [=user credential=] being created or exercised, and returning the [=device public key=] along with a signature by the [=device private key=] to the [=[RP]=]. This is done each time this [=devicePubKey=] extension is included with either a {{CredentialsContainer/create()|navigator.credentials.create()}} or {{CredentialsContainer/get()|navigator.credentials.get()}} call. If the [=authenticator=] is incapable of generating a [=hardware-bound device key pair=], or the registration or authentication operation fails for any reason, this extension is ignored and no [=hardware-bound device key pair=] is created. In this case, there is no [=devicePubKey=] extension output generated. @@ -6174,17 +6174,15 @@ The [=hardware-bound device key pair=] is not on its own a [=user credential=] a ### Relying Party Usage ### {#sctn-device-publickey-extension-usage} -Issue(w3c/webauthn#1665): TODO: explicitly add the notion of "Synced/multi-device User Credentials" to the spec. This will involve adding proper definitions for "single-device (user) credentials" and "multi-device (user) credentials" in the terminology section, updating the text of this "Device-bound public key extension" section, and perhaps other appropriate changes or additions to other portions of the spec. See also [issue #1692](https://github.com/w3c/webauthn/issues/1692) and [PR #1695](https://github.com/w3c/webauthn/pull/1695) regarding public key credential source "backup". - This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension provides a "device continuity" signal when used consistently with both {{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations: -When a [=[RP]=] uses the `devicePubKey` extension with a {{CredentialsContainer/create()|create()}} call to create a new [=user credential=], a signature by a new [=device-bound key=] ("dpk 1") is returned along with the new [=device public key=]. Even if the [=user credential=] is backed up, "dpk 1" never leaves the generating authenticator ("authenticator 1"). The [=[RP]=]'s subsequent {{CredentialsContainer/get()|get()}} operations using the same user credential with that same [=authenticator=] generate [=assertions=] including further signatures by the same [=device-bound key=] ("dpk 1"). This behavior on "authenticator  1" is independent of whether this user credential has been copied to any other [=authenticator=]. +When a [=[RP]=] uses the `devicePubKey` extension with a {{CredentialsContainer/create()|create()}} call to create a new [=user credential=], a signature by a new [=device-bound key=] ("dpk 1") is returned along with the new [=device public key=]. Even if the [=user credential=] is [=backed up=], "dpk 1" never leaves the [=generating authenticator=] ("authenticator 1"). The [=[RP]=]'s subsequent {{CredentialsContainer/get()|get()}} operations using the same user credential with that same [=authenticator=] generate [=assertions=] including further signatures by the same [=device-bound key=] ("dpk 1"). This behavior on "authenticator 1" is independent of whether this user credential has been copied to any other [=authenticator=]. -Then, if this same user credential is copied to a different [=authenticator=] ("authenticator 2"), the [=[RP]=]'s first {{CredentialsContainer/get()|get()}} call on "authenticator 2" (that includes the `devicePubKey` extension) will produce an [=assertion=] including a signature by a new [=device-bound key=] ("dpk 2"). Note that such a "multi-device user credential" can be exercised on "authenticator 2" without a {{CredentialsContainer/create()|create()}} having been performed on "authenticator 2". The [=[RP]=]'s subsequent {{CredentialsContainer/get()|get()}} calls on "authenticator 2", using the `devicePubKey` extension and the same user credential, yield further signatures by "dpk 2". +Then, if this same user credential is copied to a different [=authenticator=] ("authenticator 2"), the [=[RP]=]'s first {{CredentialsContainer/get()|get()}} call on "authenticator 2" (that includes the `devicePubKey` extension) will produce an [=assertion=] including a signature by a new [=device-bound key=] ("dpk 2"). Note that such a [=multi-device credential=] can be exercised on "authenticator 2" without a {{CredentialsContainer/create()|create()}} having been performed on "authenticator 2". The [=[RP]=]'s subsequent {{CredentialsContainer/get()|get()}} calls on "authenticator 2", using the `devicePubKey` extension and the same user credential, yield further signatures by "dpk 2". A usage example is thus: -> Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with an assertion by a multi-device user credential on its own. But if a signature by a [=device-bound key=] that is well established for this user can also be presented, then that may tip the balance. +> Say that a sign-in request appears at a website along with some geolocation signal that has not been seen for this [=user account=] before, and is outside of the typical usage hours observed for the account. The risk may be deemed high enough not to allow the request, even with an assertion by a [=multi-device credential=] on its own. But if a signature by a [=device-bound key=] that is well established for this user can also be presented, then that may tip the balance. Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures before associating and storing extension output value fields in conjunction with the [=user account=]. See [[#sctn-device-publickey-extension-verification]]. @@ -6198,8 +6196,7 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo :: [=registration extension|Registration=] and [=authentication extension|authentication=] : Client extension input -:: The Boolean value [TRUE] to indicate that this extension is requested by the [=[RP]=]. - +:: <xmp class="idl"> dictionary AuthenticationExtensionsDevicePublicKeyInputs { DOMString attestation = "none"; }; From bfce0cf27dd9dee6b7ec83a5a3d5f119ffe445fd Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Fri, 17 Jun 2022 13:14:03 -0700 Subject: [PATCH 096/131] Make the DPK signature a different output field. --- index.bs | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/index.bs b/index.bs index 6cfeaa1cb..7594d28de 100644 --- a/index.bs +++ b/index.bs @@ -4841,8 +4841,6 @@ In order to perform an [=authentication ceremony=], the [=[RP]=] MUST proceed as {{AuthenticatorResponse/clientDataJSON}}, {{AuthenticatorAssertionResponse/authenticatorData}}, and {{AuthenticatorAssertionResponse/signature}} respectively. - Note: if using the [devicePubKey](#sctn-device-publickey-extension) extension then {{AuthenticatorAssertionResponse/signature}} contains a CBOR array instead and |sig| should be the first element of that array. - 1. Let |JSONtext| be the result of running [=UTF-8 decode=] on the value of |cData|. Note: Using any implementation of [=UTF-8 decode=] is acceptable as long as it yields the same result as that yielded by @@ -6220,8 +6218,13 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo : Client extension output :: An ArrayBuffer containing the [=device public key attestation object=] that was returned in the authenticator extension output. <xmp class="idl"> + dictionary AuthenticationExtensionsDevicePublicKeyOutputs { + ArrayBuffer authenticatorOutput; + ArrayBuffer signature; + }; + partial dictionary AuthenticationExtensionsClientOutputs { - ArrayBuffer devicePubKey; + AuthenticationExtensionsDevicePublicKeyOutputs devicePubKey; }; @@ -6309,7 +6312,7 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo : 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), and let |userCredentialId| be its [=public key credential source/id|Credential ID=]. + 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 a [=hardware-bound device key pair=] does not already exist for this {[=public key credential source/id|Credential ID=], [=public key credential source/rpId|RP ID=], [=public key credential source/rpId|userHandle=]} tuple on the [=authenticator=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing [=device-bound key=]. @@ -6356,11 +6359,11 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo 1. If the `$$attStmtType` "group socket" contains uniquely identifying information then let `epAtt` be [TRUE]. Otherwise omit the `epAtt` field. (This field can only be [TRUE] when the {{AuthenticationExtensionsDevicePublicKeyInputs/attestation}} field of the extension input is "enterprise" and either the user-agent or the authenticator permits uniquely identifying attestation for the requested RP ID.) - 1. If the extension was presented during a {{CredentialsContainer/get()|navigator.credentials.get()}} call: + 1. Let |dpkSig| be the result of signing the [=assertion signature=] [input](#fig-signature) with |devicePrivateKey|. - 1. Let |dpkSig| be the result of signing the [=assertion signature=] [input](#fig-signature) with the [=device private key=]. + 1. Output |dpkSig| to the [=client platform=] so that it can be included in the [=client extension output=]. - 1. Replace the usual [=assertion signature=] result with a two-element CBOR array containing the previous [=assertion signature=] and |dpkSig|, in that order. + Note: |dpkSig| cannot be included in the extension output because the extension output is inside the [=authenticator data=] and that would imply that the signature signs over itself. Therefore the signature must be conveyed to the [=client platform=] via other means. #### Attestation calculations #### {#sctn-device-publickey-attestation-calculations} @@ -6384,9 +6387,10 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/create( 1. Let |attObjForDevicePublicKey| be the value of the `devicePubKey` member of |clientExtensionResults|. 1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |aaguid|, |dpk|, |scope|, |nonce|, |fmt|, |attStmt|. - Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of [=authenticator data=]. +1. Verify that {{AuthenticationExtensionsDevicePublicKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) by the [=device public key=] |dpk|. (The signature algorithm is the same as for the [=user credential=].) + 1. Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. Note: If |fmt|'s value is "[=none=]" there is no attestation signature to verify. @@ -6407,9 +6411,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of [=authenticator data=]. -1. Verify that {{AuthenticatorAssertionResponse/signature}} contains a two-element CBOR array and let |sig| be the second element of the array. - -1. Verify that |sig| is a valid signature over the [=assertion signature=] [input](#fig-signature) by the [=device public key=] |dpk|. (The signature algorithm is indicated by |dpk|'s "alg" {{COSEAlgorithmIdentifier}} value.) +1. Verify that {{AuthenticationExtensionsDevicePublicKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) by the [=device public key=] |dpk|. (The signature algorithm is the same as for the [=user credential=].) 1. If the [=[RP]=]'s [=user account=] mapped to the |credential|.{{Credential/id}} in play (i.e., for the user being authenticated) holds `aaguid`, `dpk`, `scope`, `fmt`, and `attStmt` values corresponding to the extracted |attObjForDevicePublicKey| fields, then perform binary equality checks between the corresponding stored values and the extracted field values. The [=[RP]=] may have more than one set of {`aaguid`, `dpk`, `scope`, `fmt`, `attStmt`} values mapped to the [=user account=] and |credential|.{{Credential/id}} pair and each set must be checked. @@ -6423,12 +6425,12 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n : successful :: This is likely a known device: - If |fmt|'s value is "none" then there is no attestation signature to verify and this is a known [=device public key=] with a valid |sig| and thus a known device. Terminate these verification steps. + If |fmt|'s value is "none" then there is no attestation signature to verify and this is a known [=device public key=] with a valid signature and thus a known device. Terminate these verification steps. Otherwise, check |attObjForDevicePublicKey|'s |attStmt| by performing a binary equality check between the corresponding stored and extracted |attStmt| values. If the result is:
    : successful - :: This is a known [=device public key=] with a valid |sig| and valid attestation and thus a known device. Terminate these verification steps. + :: This is a known [=device public key=] with a valid signature and valid attestation and thus a known device. Terminate these verification steps. Note: This authenticator is not generating a fresh per-response random nonce. @@ -6439,7 +6441,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n
    : successful - :: This is a known [=device public key=] with a valid |sig| and valid attestation and thus a known device. Terminate these verification steps. + :: This is a known [=device public key=] with a valid signature and valid attestation and thus a known device. Terminate these verification steps. Note: This authenticator is generating a fresh per-response random nonce. From 20dd35c00a4caf07b1514c5495afaaf1482e04ae Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Fri, 17 Jun 2022 15:24:59 -0700 Subject: [PATCH 097/131] Update attestation and add it for assertions --- index.bs | 101 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 91 insertions(+), 10 deletions(-) diff --git a/index.bs b/index.bs index 7594d28de..ad7fc71d8 100644 --- a/index.bs +++ b/index.bs @@ -1850,6 +1850,7 @@ a numbered step. If outdented, it (today) is rendered either as a bullet in the |credTypesAndPubKeyAlgs|, |excludeCredentialDescriptorList|, |enterpriseAttestationPossible|, + |options|.{{PublicKeyCredentialCreationOptions/attestationFormats}}, and |authenticatorExtensions| as parameters. @@ -2236,14 +2237,19 @@ When this method is invoked, the user agent MUST execute the following algorithm : [=list/is empty=] :: Using local configuration knowledge of the appropriate transport to use with |authenticator|, invoke the [=authenticatorGetAssertion=] operation on |authenticator| with |rpId|, - |clientDataHash|, |allowCredentialDescriptorList|, |userVerification|, and - |authenticatorExtensions| as parameters. + |clientDataHash|, |allowCredentialDescriptorList|, |userVerification|, + |options|.{{PublicKeyCredentialRequestOptions/attestation}}, + |options|.{{PublicKeyCredentialRequestOptions/attestationFormats}}, + and |authenticatorExtensions| as parameters.
    : [=list/is empty=] :: Using local configuration knowledge of the appropriate transport to use with |authenticator|, invoke the [=authenticatorGetAssertion=] operation on |authenticator| with |rpId|, |clientDataHash|, - |userVerification| and |authenticatorExtensions| as parameters. + |userVerification|, + |options|.{{PublicKeyCredentialRequestOptions/attestation}}, + |options|.{{PublicKeyCredentialRequestOptions/attestationFormats}}, + and |authenticatorExtensions| as parameters. Note: In this case, the [=[RP]=] did not supply a list of acceptable credential descriptors. Thus, the authenticator is being asked to exercise any credential it may possess that is [=scoped=] to @@ -2291,6 +2297,10 @@ When this method is invoked, the user agent MUST execute the following algorithm :: If the [=authenticator=] returned a [=user handle=], set the value of [=userHandleResult=] to be the bytes of the returned [=user handle=]. Otherwise, set the value of [=userHandleResult=] to null. + : assertionAttestation + :: If the [=authenticator=] returned an [=attestation=], set the value of [=assertionAttestation=] to be the bytes of + the [=attestation statement=]. Otherwise set it to null. + : clientExtensionResults :: whose value is an {{AuthenticationExtensionsClientOutputs}} object containing [=extension identifier=] → [=client extension output=] entries. The entries are created by running each extension's @@ -2330,6 +2340,12 @@ When this method is invoked, the user agent MUST execute the following algorithm [=%ArrayBuffer%=], containing the bytes of |assertionCreationData|.[=assertionCreationData/userHandleResult=]. + : {{AuthenticatorAssertionResponse/attestationObject}} + :: If |assertionCreationData|.[=assertionCreationData/assertionAttestation=] is null, set this + field to null. Otherwise, set this field to a new {{ArrayBuffer}}, created using |global|'s + [=%ArrayBuffer%=], containing the bytes of + |assertionCreationData|.[=assertionCreationData/assertionAttestation=]. + : {{PublicKeyCredential/[[clientExtensionsResults]]}} :: A new {{ArrayBuffer}}, created using |global|'s [=%ArrayBuffer%=], containing the bytes of |assertionCreationData|.[=assertionCreationData/clientExtensionResults=]. @@ -2512,6 +2528,7 @@ optionally evidence of [=user consent=] to a specific transaction. [SameObject] readonly attribute ArrayBuffer authenticatorData; [SameObject] readonly attribute ArrayBuffer signature; [SameObject] readonly attribute ArrayBuffer? userHandle; + [SameObject] readonly attribute ArrayBuffer? attestationObject; };
    @@ -2530,6 +2547,9 @@ optionally evidence of [=user consent=] to a specific transaction. : userHandle :: This attribute contains the [=user handle=] returned from the authenticator, or null if the authenticator did not return a [=user handle=]. See [[#sctn-op-get-assertion]]. + + : attestationObject + :: This OPTIONAL attribute contains an [=attestation object=]. The [=attestation object=], if present, includes an [=attestation statement=]. Unlike the {{AuthenticatorAttestationResponse/attestationObject}} in an {{AuthenticatorAssertionResponse}}, it does not contains an `authData` key because the [=authenticator data=] is provided directly in an {{AuthenticatorAssertionResponse}} structure. For more details on attestation, see [[#sctn-attestation]], [[#sctn-generating-an-attestation-object]], and [Figure 6](#fig-attStructs).
    ## Parameters for Credential Generation (dictionary PublicKeyCredentialParameters) ## {#dictionary-credential-params} @@ -2569,6 +2589,7 @@ optionally evidence of [=user consent=] to a specific transaction. sequence excludeCredentials = []; AuthenticatorSelectionCriteria authenticatorSelection; DOMString attestation = "none"; + sequence attestationFormats = []; AuthenticationExtensionsClientInputs extensions; }; @@ -2635,6 +2656,14 @@ optionally evidence of [=user consent=] to a specific transaction. The default value is {{AttestationConveyancePreference/none}}. + : attestationFormats + :: The [=[RP]=] MAY use this OPTIONAL member to specify a preference regarding the [=attestation=] statement format used by the [=authenticator=]. + Values SHOULD be taken from the IANA "WebAuthn Attestation Statement Format Identifiers" registry [[!IANA-WebAuthn-Registries]] established by [[!RFC8809]]. + Values are ordered from most preferable to least preferable. + This parameter is advisory and the [=authenticator=] MAY use an attestation statement not enumerated in this parameter. + + The default value is the empty list, which indicates no preference. + : extensions :: The [=[RP]=] MAY use this OPTIONAL member to provide [=client extension inputs=] requesting additional processing by the [=client=] and [=authenticator=]. @@ -2940,6 +2969,8 @@ an assertion. Its {{PublicKeyCredentialRequestOptions/challenge}} member MUST be USVString rpId; sequence allowCredentials = []; DOMString userVerification = "preferred"; + DOMString attestation = "none"; + sequence attestationFormats = []; AuthenticationExtensionsClientInputs extensions; }; @@ -3000,6 +3031,21 @@ an assertion. Its {{PublicKeyCredentialRequestOptions/challenge}} member MUST be See {{UserVerificationRequirement}} for the description of {{AuthenticatorSelectionCriteria/userVerification}}'s values and semantics. + : attestation + :: The [=[RP]=] MAY use this OPTIONAL member to specify a preference regarding [=attestation conveyance=]. + Its value SHOULD be a member of {{AttestationConveyancePreference}}. + [=Client platforms=] MUST ignore unknown values, treating an unknown value as if the [=map/exist|member does not exist=]. + + The default value is {{AttestationConveyancePreference/none}}. + + : attestationFormats + :: The [=[RP]=] MAY use this OPTIONAL member to specify a preference regarding the [=attestation=] statement format used by the [=authenticator=]. + Values SHOULD be taken from the IANA "WebAuthn Attestation Statement Format Identifiers" registry [[!IANA-WebAuthn-Registries]] established by [[!RFC8809]]. + Values are ordered from most preferable to least preferable. + This parameter is advisory and the [=authenticator=] MAY use an attestation statement not enumerated in this parameter. + + The default value is the empty list, which indicates no preference. + : extensions :: The [=[RP]=] MAY use this OPTIONAL member to provide [=client extension inputs=] requesting additional processing by the [=client=] and [=authenticator=]. @@ -4021,6 +4067,8 @@ It takes the following input parameters: list of known credentials. : |enterpriseAttestationPossible| :: A Boolean value that indicates that individually-identifying attestation MAY be returned by the authenticator. +: |attestationFormats| +:: A sequence of strings that expresses the [=RP=]'s preference for attestation statement formats, from most to least preferable. If it returns attestation then the [=authenticator=] makes a best-effort attempt to use the most preferable format that it supports. : |extensions| :: A [=CBOR=] [=map=] from [=extension identifiers=] to their [=authenticator extension inputs=], created by the [=client=] based on the extensions requested by the [=[RP]=], if any. @@ -4305,8 +4353,9 @@ produce, for each [=credential public key=], an [=attestation statement=] verifi a challenge, as well as a certificate or similar data providing provenance information for the [=attestation public key=], enabling the [=[RP]=] to make a trust decision. However, if an [=attestation key pair=] is not available, then the authenticator MAY either perform [=self attestation=] of the [=credential public key=] with the corresponding [=credential private key=], -or otherwise perform [=None|no attestation=]. All this -information is returned by [=authenticators=] any time a new [=public key credential=] is generated, in the overall form of an +or otherwise perform [=None|no attestation=]. + +All this information is returned by [=authenticators=] any time a new [=public key credential=] is generated, and optionally when exercised, in the overall form of an attestation object. The relationship of the [=attestation object=] with [=authenticator data=] (containing [=attested credential data=]) and the [=attestation statement=] is illustrated in figure , below. @@ -4316,7 +4365,7 @@ In these cases, the [=authenticator=] provides no guarantees about its operation
    -
    [=Attestation object=] layout illustrating the included [=authenticator data=] (containing [=attested credential +
    [=Attestation object=] layout illustrating the included [=authenticator data=] from a {{CredentialsContainer/create()|create()}} operation (containing [=attested credential data=]) and the [=attestation statement=].
    @@ -4353,17 +4402,23 @@ The privacy, security and operational characteristics of [=attestation=] depend operating environment, and so on. The [=attestation type=] and [=attestation statement format=] is chosen by the [=authenticator=]; -[=[RPS]=] can only signal limited [=attestation conveyance=] preferences during [=registration=]. +[=[RPS]=] can only signal their preferences by setting the {{PublicKeyCredentialCreationOptions/attestation}} and {{PublicKeyCredentialCreationOptions/attestationFormats}} parameters. (Or the parameters with the same names in {{PublicKeyCredentialRequestOptions}}.) + It is expected that most [=authenticators=] will support a small number of [=attestation types=] and [=attestation statement formats=], while [=[RPS]=] will decide what [=attestation types=] are acceptable to them by policy. [=[RPS]=] will also need to understand the characteristics of the [=authenticators=] that they trust, based on information they have about these [=authenticators=]. For example, the FIDO Metadata Service [[FIDOMetadataService]] provides one way to access such information. +### Attestation in assertions ### {#sctn-attestation-in-assertions} + +Attestation is most commonly provided during credential creation. However, [=multi-device credentials=] can move between [=authenticators=] during their lifetime and thus attestation MAY be provided during assertions if requested by the [=[RP]=] using the {{PublicKeyCredentialRequestOptions/attestation}} parameter. + +[=Attestation objects=] provided in an {{AuthenticatorAttestationResponse}} structure (i.e. as the result of a {{CredentialsContainer/create()|create()}} operation) contain at least the three keys shown in [the previous figure](#fig-attStructs): `fmt`, `attStmt`, and `authData`. The last of those keys is not included when an [=attestation object=] is provided in an {{AuthenticatorAssertionResponse}} (i.e. as the result of a {{CredentialsContainer/get()|get()}} operation). That is because the [=authenticator data=] is provided directly in the {{AuthenticatorAssertionResponse/authenticatorData}} member of the {{AuthenticatorAssertionResponse}}. Otherwise, processing of the [=attestation object=] is identical. ### Attested Credential Data ### {#sctn-attested-credential-data} Attested credential data is a variable-length byte array added to the [=authenticator data=] when generating an [=attestation -object=] for a given credential. Its format is shown in Table . +object=] for a credential. Its format is shown in Table .
    @@ -4407,6 +4462,8 @@ object=] for a given credential. Its format is shown in dictionary AuthenticationExtensionsDevicePublicKeyInputs { DOMString attestation = "none"; + sequence attestationFormats = []; }; partial dictionary AuthenticationExtensionsClientInputs { @@ -6210,6 +6282,14 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo [=Client platforms=] MUST ignore unknown values, treating an unknown value as if the [=map/exist|member does not exist=]. The default value is {{AttestationConveyancePreference/none}}. + + : attestationFormats + :: The [=[RP]=] MAY use this OPTIONAL member to specify a preference regarding the [=attestation=] statement format used by the [=authenticator=]. + Values SHOULD be taken from the IANA "WebAuthn Attestation Statement Format Identifiers" registry [[!IANA-WebAuthn-Registries]] established by [[!RFC8809]]. + Values are ordered from most preferable to least preferable. + This parameter is advisory and the [=authenticator=] MAY use an attestation statement not enumerated in this parameter. + + The default value is the empty list, which indicates no preference. : Client extension processing @@ -6235,6 +6315,7 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo ``` devicePublicKeyInputs = { attestation: "none" / "indirect" / "direct" / "enterprise", + attestationFormats: [tstr], } $$extensionInput //= ( devicePubKey: devicePublicKeyInputs, @@ -6323,10 +6404,10 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo :: |attFormat| is "none" or "self", at the authenticator's discretion, and |aaguid| is 16 zero bytes. (Note that, since the [=device-bound key=] is already exercised during {{CredentialsContainer/get()|navigator.credentials.get()}} calls, the proof-of-possession property provided by "self" attestation is superfluous in that context.) : indirect, direct - :: |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] and |aaguid| is the [=authenticator's=] [=AAGUID=]. (Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust, although they do not have to be. This is in contrast to the associated [=user credential=]'s attestation.) + :: |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsDevicePublicKeyInputs/attestationFormats}}, and |aaguid| is the [=authenticator's=] [=AAGUID=]. (Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust, although they do not have to be. This is in contrast to the associated [=user credential=]'s attestation.) : enterprise - :: The [=[RP]=] wants to receive an [=attestation statement=] that may include uniquely identifying information. This is intended for controlled deployments within an enterprise where the organization wishes to tie registrations to specific authenticators. [=Authenticators=] MUST NOT provide such an attestation unless the user agent or authenticator configuration expressly permits it for the requested [=RP ID=]. If not permitted, |attFormat| is "none" and |aaguid| is 16 zero bytes. Otherwise |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] and |aaguid| is the [=authenticator's=] [=AAGUID=]. (Again, since the [=hardware-bound device key pair=] is specific to a particular authenticator, the attestation may be tied to hardware roots of trust.) + :: The [=[RP]=] wants to receive an [=attestation statement=] that may include uniquely identifying information. This is intended for controlled deployments within an enterprise where the organization wishes to tie registrations to specific authenticators. [=Authenticators=] MUST NOT provide such an attestation unless the user agent or authenticator configuration expressly permits it for the requested [=RP ID=]. If not permitted, |attFormat| is "none", and |aaguid| is 16 zero bytes. Otherwise |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsDevicePublicKeyInputs/attestationFormats}}, and |aaguid| is the [=authenticator's=] [=AAGUID=]. (Again, since the [=hardware-bound device key pair=] is specific to a particular authenticator, the attestation may be tied to hardware roots of trust.) Note: CTAP2 does not currently provide for an enterpriseAttestation signal during an authenticatorGetAssertion call. Until that is changed, platform-managed enterprise attestation will not work in that context with CTAP2 [=authenticators=]. From 27d0895f6fa054bd9c5d253a56db1fe6087c30fc Mon Sep 17 00:00:00 2001 From: Arnar Birgisson Date: Wed, 22 Jun 2022 12:47:09 -0600 Subject: [PATCH 098/131] Introduce unsigned extension outputs and use it to return the dpk signature. --- index.bs | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/index.bs b/index.bs index ad7fc71d8..87419e5f0 100644 --- a/index.bs +++ b/index.bs @@ -5612,9 +5612,11 @@ and passes them to the authenticator in the {{CredentialsContainer/create()}} ca {{CredentialsContainer/get()}} call (for [=authentication extensions=]). These [=authenticator extension input=] values are represented in [=CBOR=] and passed as name-value pairs, with the [=extension identifier=] as the name, and the corresponding [=authenticator extension input=] as the value. The authenticator, in turn, performs additional processing for the extensions -that it supports, and returns the [=CBOR=] [=authenticator extension output=] for each as specified by the extension. Part of -the [=client extension processing=] for [=authenticator extensions=] is to use the [=authenticator extension output=] as an -input to creating the [=client extension output=]. +that it supports, and returns the [=CBOR=] [=authenticator extension output=] for each as specified by the extension. +Since [=authenticator extension output=] is returned as part of the signed [=Authenticator data=], authenticator extensions +may also specify an [=unsigned extension output=], e.g. for cases where an output itself depends on [=Authenticator data=]. +Part of the [=client extension processing=] for [=authenticator extensions=] is to use the [=authenticator extension output=] +and [=unsigned extension output=] as an input to creating the [=client extension output=]. All [=WebAuthn Extensions=] are OPTIONAL for both clients and authenticators. Thus, any extensions requested by a [=[RP]=] MAY be ignored by the client browser or OS and not passed to the authenticator at all, or they MAY be ignored by the authenticator. @@ -5662,6 +5664,7 @@ If the extension communicates with the authenticator (meaning it is an [=authent it MUST also specify the [=CBOR=] [=authenticator extension input=] argument sent via the [=authenticatorGetAssertion=] or [=authenticatorMakeCredential=] call, the [=authenticator extension processing=] rules, and the [=CBOR=] [=authenticator extension output=] value. +Extensions MAY specify [=unsigned extension outputs=]. Any [=client extension=] that is processed by the client MUST return a [=client extension output=] value so that the [=[WRP]=] knows that the extension was honored by the client. Similarly, any extension that requires authenticator processing MUST return @@ -5752,7 +5755,8 @@ There MUST NOT be any values returned for ignored extensions. Extensions that require authenticator processing MUST define the process by which the [=client extension input=] can be used to determine the [=CBOR=] [=authenticator extension input=] and -the process by which the [=CBOR=] [=authenticator extension output=] can be used to determine the [=client extension output=]. +the process by which the [=CBOR=] [=authenticator extension output=], and the [=unsigned extension output=] if used, can be +used to determine the [=client extension output=]. ## Authenticator Extension Processing ## {#sctn-authenticator-extension-processing} @@ -5765,8 +5769,16 @@ Likewise, the extension output is represented in the [=authdataextensions|extens [=authdataextensions|extensions=] part of the [=authenticator data=] is a CBOR map where each key is an [=extension identifier=] and the corresponding value is the authenticator extension output for that extension. +Unsigned extension outputs are represented independently from [=authenticator data=] and returned by authenticators +as a separate CBOR map, keyed with the same [=extension identifier=]. This map only contains entries for authenticator +extensions that make use of unsigned outputs. + +Note: In [[!FIDO-CTAP]] [=unsigned extension outputs=] are returned in a top-level field named `unsignedExtensionOutputs` +from both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=]. + For each supported extension, the [=authenticator extension processing=] rule for that extension is used create the -[=authenticator extension output=] from the [=authenticator extension input=] and possibly also other inputs. +[=authenticator extension output=], and [=unsigned extension output=] if used, from the [=authenticator extension input=] +and possibly also other inputs. There MUST NOT be any values returned for ignored extensions. @@ -6292,11 +6304,13 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo The default value is the empty list, which indicates no preference. + Note: To request the `devicePubKey` extension processing with default options, pass an empty dictionary as the input. + : Client extension processing :: If {{AuthenticationExtensionsClientInputs/devicePubKey}} is present, the client creates the authenticator extension input from the client extension input. : Client extension output -:: An ArrayBuffer containing the [=device public key attestation object=] that was returned in the authenticator extension output. +:: An ArrayBuffer containing the [=device public key attestation object=] that was returned in the authenticator extension output and the signature returned as the [=unsigned extension output=]. dictionary AuthenticationExtensionsDevicePublicKeyOutputs { ArrayBuffer authenticatorOutput; @@ -6391,6 +6405,9 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo ``` +: Unsigned extension output +:: A signature generated with the device public key as a CBOR bytestring + : 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). @@ -6442,9 +6459,9 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo 1. Let |dpkSig| be the result of signing the [=assertion signature=] [input](#fig-signature) with |devicePrivateKey|. - 1. Output |dpkSig| to the [=client platform=] so that it can be included in the [=client extension output=]. + 1. Output |dpkSig| as the extension's [=unsigned extension output=]. - Note: |dpkSig| cannot be included in the extension output because the extension output is inside the [=authenticator data=] and that would imply that the signature signs over itself. Therefore the signature must be conveyed to the [=client platform=] via other means. + Note: |dpkSig| cannot be included in the [=authenticator extension output=] because it is returned inside the [=authenticator data=] and that would imply that the signature signs over itself. #### Attestation calculations #### {#sctn-device-publickey-attestation-calculations} From e30cdb1212fc6d365e67e5b7a3de27d8a1f29ed2 Mon Sep 17 00:00:00 2001 From: Arnar Birgisson <arnarbi@gmail.com> Date: Wed, 22 Jun 2022 14:51:34 -0600 Subject: [PATCH 099/131] Fix build error --- index.bs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/index.bs b/index.bs index 87419e5f0..82b53c4d0 100644 --- a/index.bs +++ b/index.bs @@ -6295,13 +6295,13 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo The default value is {{AttestationConveyancePreference/none}}. - : <dfn>attestationFormats</dfn> - :: The [=[RP]=] MAY use this OPTIONAL member to specify a preference regarding the [=attestation=] statement format used by the [=authenticator=]. - Values SHOULD be taken from the IANA "WebAuthn Attestation Statement Format Identifiers" registry [[!IANA-WebAuthn-Registries]] established by [[!RFC8809]]. - Values are ordered from most preferable to least preferable. - This parameter is advisory and the [=authenticator=] MAY use an attestation statement not enumerated in this parameter. + : <dfn>attestationFormats</dfn> + :: The [=[RP]=] MAY use this OPTIONAL member to specify a preference regarding the [=attestation=] statement format used by the [=authenticator=]. + Values SHOULD be taken from the IANA "WebAuthn Attestation Statement Format Identifiers" registry [[!IANA-WebAuthn-Registries]] established by [[!RFC8809]]. + Values are ordered from most preferable to least preferable. + This parameter is advisory and the [=authenticator=] MAY use an attestation statement not enumerated in this parameter. - The default value is the empty list, which indicates no preference. + The default value is the empty list, which indicates no preference. </div> Note: To request the `devicePubKey` extension processing with default options, pass an empty dictionary as the input. From 38fb4e1d81118868a87fed92ef233666c023da8b Mon Sep 17 00:00:00 2001 From: Arnar Birgisson <arnarbi@gmail.com> Date: Wed, 22 Jun 2022 15:03:13 -0600 Subject: [PATCH 100/131] Review fixes and another indentation fix --- index.bs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/index.bs b/index.bs index 82b53c4d0..5759c218c 100644 --- a/index.bs +++ b/index.bs @@ -3089,7 +3089,7 @@ SHOULD be aborted. The subsections below define the data types used for conveying [=WebAuthn extension=] inputs and outputs. -Note: [=Authenticator extension outputs=] are conveyed as a part of [=Authenticator data=] (see [Table 1](#table-authData)). +Note: [=Authenticator extension outputs=] are conveyed as a part of [=authenticator data=] (see [Table 1](#table-authData)). Note: The types defined below &mdash; {{AuthenticationExtensionsClientInputs}} and {{AuthenticationExtensionsClientOutputs}} &mdash; are applicable to both [=registration extensions=] and [=authentication extensions=]. The "Authentication..." portion of their names should be regarded as meaning "WebAuthentication..." @@ -5613,8 +5613,8 @@ and passes them to the authenticator in the {{CredentialsContainer/create()}} ca represented in [=CBOR=] and passed as name-value pairs, with the [=extension identifier=] as the name, and the corresponding [=authenticator extension input=] as the value. The authenticator, in turn, performs additional processing for the extensions that it supports, and returns the [=CBOR=] [=authenticator extension output=] for each as specified by the extension. -Since [=authenticator extension output=] is returned as part of the signed [=Authenticator data=], authenticator extensions -may also specify an [=unsigned extension output=], e.g. for cases where an output itself depends on [=Authenticator data=]. +Since [=authenticator extension output=] is returned as part of the signed [=authenticator data=], authenticator extensions +may also specify an [=unsigned extension output=], e.g. for cases where an output itself depends on [=authenticator data=]. Part of the [=client extension processing=] for [=authenticator extensions=] is to use the [=authenticator extension output=] and [=unsigned extension output=] as an input to creating the [=client extension output=]. @@ -5770,11 +5770,11 @@ Likewise, the extension output is represented in the [=authdataextensions|extens and the corresponding value is the <dfn>authenticator extension output</dfn> for that extension. <dfn>Unsigned extension outputs</dfn> are represented independently from [=authenticator data=] and returned by authenticators -as a separate CBOR map, keyed with the same [=extension identifier=]. This map only contains entries for authenticator +as a separate map, keyed with the same [=extension identifier=]. This map only contains entries for authenticator extensions that make use of unsigned outputs. -Note: In [[!FIDO-CTAP]] [=unsigned extension outputs=] are returned in a top-level field named `unsignedExtensionOutputs` -from both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=]. +Note: In [[!FIDO-CTAP]] [=unsigned extension outputs=] are returned as a CBOR map in a top-level field named +`unsignedExtensionOutputs` from both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=]. For each supported extension, the [=authenticator extension processing=] rule for that extension is used create the [=authenticator extension output=], and [=unsigned extension output=] if used, from the [=authenticator extension input=] @@ -6301,10 +6301,10 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo Values are ordered from most preferable to least preferable. This parameter is advisory and the [=authenticator=] MAY use an attestation statement not enumerated in this parameter. - The default value is the empty list, which indicates no preference. + Note: The default value is the empty list, which indicates no preference. </div> - Note: To request the `devicePubKey` extension processing with default options, pass an empty dictionary as the input. + To request the `devicePubKey` extension processing with default options, pass an empty dictionary as the input. : Client extension processing :: If {{AuthenticationExtensionsClientInputs/devicePubKey}} is present, the client creates the authenticator extension input from the client extension input. @@ -6426,7 +6426,7 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo : enterprise :: The [=[RP]=] wants to receive an [=attestation statement=] that may include uniquely identifying information. This is intended for controlled deployments within an enterprise where the organization wishes to tie registrations to specific authenticators. [=Authenticators=] MUST NOT provide such an attestation unless the user agent or authenticator configuration expressly permits it for the requested [=RP ID=]. If <i>not</i> permitted, |attFormat| is "none", and |aaguid| is 16 zero bytes. Otherwise |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsDevicePublicKeyInputs/attestationFormats}}, and |aaguid| is the [=authenticator's=] [=AAGUID=]. (Again, since the [=hardware-bound device key pair=] is specific to a particular authenticator, the attestation may be tied to hardware roots of trust.) - Note: CTAP2 does not currently provide for an <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#makecred-enterpriseattestation">enterpriseAttestation</a> signal during an <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticatorGetAssertion">authenticatorGetAssertion</a> call. Until that is changed, <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#platform-managed-enterprise-attestation">platform-managed enterprise attestation</a> will not work in that context with CTAP2 [=authenticators=]. + Note: CTAP2 does not currently provide for an <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#makecred-enterpriseattestation">enterpriseAttestation</a> signal during an <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticatorGetAssertion">authenticatorGetAssertion</a> call. Until that is changed, <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#platform-managed-enterprise-attestation">platform-managed enterprise attestation</a> will not work in that context with CTAP2 [=authenticators=]. </dl> 1. Let |dpk| be the newly created or existing [=device public key=], in COSE_Key format [=credentialPublicKey|in the same fashion as for the user credential's credentialPublicKey=] when the latter is conveyed in [=attested credential data=]. From 0c7fad0231f7e30ac77bc5c49c335251d27d8373 Mon Sep 17 00:00:00 2001 From: Arnar Birgisson <arnarbi@gmail.com> Date: Wed, 22 Jun 2022 15:09:04 -0600 Subject: [PATCH 101/131] Fix misplaced Note annotation --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 5759c218c..7ddf819cf 100644 --- a/index.bs +++ b/index.bs @@ -6301,10 +6301,10 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo Values are ordered from most preferable to least preferable. This parameter is advisory and the [=authenticator=] MAY use an attestation statement not enumerated in this parameter. - Note: The default value is the empty list, which indicates no preference. + The default value is the empty list, which indicates no preference. </div> - To request the `devicePubKey` extension processing with default options, pass an empty dictionary as the input. + Note: To request the `devicePubKey` extension processing with default options, pass an empty dictionary as the input. : Client extension processing :: If {{AuthenticationExtensionsClientInputs/devicePubKey}} is present, the client creates the authenticator extension input from the client extension input. From 6fbfccf747a2af0e6893fa9abe7435310f2f2938 Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Fri, 17 Jun 2022 15:24:59 -0700 Subject: [PATCH 102/131] Update attestation and add it for assertions --- index.bs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 7ddf819cf..0b221f017 100644 --- a/index.bs +++ b/index.bs @@ -2232,7 +2232,10 @@ When this method is invoked, the user agent MUST execute the following algorithm Then, using |transport|, invoke the [=authenticatorGetAssertion=] operation on |authenticator|, with |rpId|, |clientDataHash|, |allowCredentialDescriptorList|, - |userVerification|, and |authenticatorExtensions| as parameters. + |userVerification|, + <code>|options|.{{PublicKeyCredentialRequestOptions/attestation}}</code>, + <code>|options|.{{PublicKeyCredentialRequestOptions/attestationFormats}}</code>, + and |authenticatorExtensions| as parameters. : [=list/is empty=] :: Using local configuration knowledge of the appropriate transport to use with |authenticator|, From 4e67faaa43c5e72eff518f8771df8245acc2aae0 Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Tue, 19 Jul 2022 14:49:18 -0700 Subject: [PATCH 103/131] Various fixes and updates in light of comments --- index.bs | 83 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 22 deletions(-) diff --git a/index.bs b/index.bs index 0b221f017..82ca58095 100644 --- a/index.bs +++ b/index.bs @@ -1821,6 +1821,17 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o </dl> + 1. Let |attestationFormats| be a list of strings, initialized to the value of <code>|options|.{{PublicKeyCredentialCreationOptions/attestationFormats}}</code>. + + 1. If <code>|options|.{{PublicKeyCredentialCreationOptions/attestation}}</code> + + <dl class="switch"> + + : is set to {{AttestationConveyancePreference/none}} + :: Set |attestationFormats| be the single-element list containing the string &ldquo;none&rdquo; + + </dl> + 1. Let |excludeCredentialDescriptorList| be a new [=list=]. 1. [=list/For each=] credential descriptor |C| in <code>|options|.{{PublicKeyCredentialCreationOptions/excludeCredentials}}</code>: @@ -1840,7 +1851,7 @@ a numbered step. If outdented, it (today) is rendered either as a bullet in the --> <li id='CreateCred-InvokeAuthnrMakeCred'> - <!-- @@EDITOR-ANCHOR-01A: KEEP THIS LIST SYNC'D WITH THE LIST UP AT @@EDITOR-ANCHOR-01B --> + <!-- @@EDITOR-ANCHOR-01: KEEP THIS LIST SYNC'D WITH OTHER LOCATIONS WITH THIS TAG --> Invoke the [=authenticatorMakeCredential=] operation on |authenticator| with |clientDataHash|, <code>|options|.{{PublicKeyCredentialCreationOptions/rp}}</code>, @@ -1850,7 +1861,7 @@ a numbered step. If outdented, it (today) is rendered either as a bullet in the |credTypesAndPubKeyAlgs|, |excludeCredentialDescriptorList|, |enterpriseAttestationPossible|, - <code>|options|.{{PublicKeyCredentialCreationOptions/attestationFormats}}</code>, + |attestationFormats|, and |authenticatorExtensions| as parameters. </li> @@ -2230,28 +2241,27 @@ When this method is invoked, the user agent MUST execute the following algorithm configuration knowledge of the appropriate transport to use with |authenticator| in making its selection. + <!-- @@EDITOR-ANCHOR-01: KEEP THIS LIST SYNC'D WITH OTHER LOCATIONS WITH THIS TAG --> Then, using |transport|, invoke the [=authenticatorGetAssertion=] operation on |authenticator|, with |rpId|, |clientDataHash|, |allowCredentialDescriptorList|, |userVerification|, - <code>|options|.{{PublicKeyCredentialRequestOptions/attestation}}</code>, - <code>|options|.{{PublicKeyCredentialRequestOptions/attestationFormats}}</code>, + |attestationFormats|, and |authenticatorExtensions| as parameters. + <!-- @@EDITOR-ANCHOR-01: KEEP THIS LIST SYNC'D WITH OTHER LOCATIONS WITH THIS TAG --> : [=list/is empty=] :: Using local configuration knowledge of the appropriate transport to use with |authenticator|, invoke the [=authenticatorGetAssertion=] operation on |authenticator| with |rpId|, |clientDataHash|, |allowCredentialDescriptorList|, |userVerification|, - <code>|options|.{{PublicKeyCredentialRequestOptions/attestation}}</code>, - <code>|options|.{{PublicKeyCredentialRequestOptions/attestationFormats}}</code>, - and |authenticatorExtensions| as parameters. + |attestationFormats|, and |authenticatorExtensions| as parameters. </dl> + <!-- @@EDITOR-ANCHOR-01: KEEP THIS LIST SYNC'D WITH OTHER LOCATIONS WITH THIS TAG --> : [=list/is empty=] :: Using local configuration knowledge of the appropriate transport to use with |authenticator|, invoke the [=authenticatorGetAssertion=] operation on |authenticator| with |rpId|, |clientDataHash|, |userVerification|, - <code>|options|.{{PublicKeyCredentialRequestOptions/attestation}}</code>, - <code>|options|.{{PublicKeyCredentialRequestOptions/attestationFormats}}</code>, + |attestationFormats|, and |authenticatorExtensions| as parameters. Note: In this case, the [=[RP]=] did not supply a list of acceptable credential descriptors. Thus, the @@ -4045,7 +4055,7 @@ The result of <dfn for="credential id">looking up</dfn> a [=credential id=] |cre It takes the following input parameters: -<!-- @@EDITOR-ANCHOR-01B: KEEP THIS LIST SYNC'D WITH THE LIST UP AT @@EDITOR-ANCHOR-01A --> +<!-- @@EDITOR-ANCHOR-01: KEEP THIS LIST SYNC'D WITH OTHER LOCATIONS WITH THIS TAG --> : |hash| :: The [=hash of the serialized client data=], provided by the client. : |rpEntity| @@ -4183,12 +4193,20 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o </dl> 1. Let |attestedCredentialData| be the [=attested credential data=] byte array including the |credentialId| and |publicKey|. +1. If |attestationFormats|: + <dl class="switch"> + : is [=list/is not empty|not empty=] + :: let |attestationFormat| be the first supported [=attestation statement format=] from |attestationFormats|, taking into account |enterpriseAttestationPossible|. If none are supported, fallthrough to: + + : is [=list/is empty|empty=] + :: let |attestationFormat| be the [=attestation statement format=] most preferred by this authenticator. + </dl> 1. Let |authenticatorData| [=perform the following steps to generate an authenticator data structure|be the byte array=] specified in [[#sctn-authenticator-data]], including |attestedCredentialData| as the <code>[=attestedCredentialData=]</code> and |processedExtensions|, if any, as the <code>[=authDataExtensions|extensions=]</code>. 1. Create an [=attestation object=] for the new credential using the procedure specified in - [[#sctn-generating-an-attestation-object]], using an authenticator-chosen [=attestation statement format=], |authenticatorData|, - and |hash|, as well as {{enterprise|taking into account}} the value of |enterpriseAttestationPossible|. For more details on attestation, see [[#sctn-attestation]]. + [[#sctn-generating-an-attestation-object]], the [=attestation statement format=] |attestationFormat|, and the values |authenticatorData| + and |hash|. For more details on attestation, see [[#sctn-attestation]]. On successful completion of this operation, the authenticator returns the [=attestation object=] to the client. @@ -4210,6 +4228,10 @@ It takes the following input parameters: wish to make a [=test of user presence=] optional although WebAuthn does not. : |requireUserVerification| :: The [=effective user verification requirement for assertion=], a Boolean value provided by the client. +: |enterpriseAttestationPossible| +:: A Boolean value that indicates that individually-identifying attestation MAY be returned by the authenticator. +: |attestationFormats| +:: A sequence of strings that expresses the [=RP=]'s preference for attestation statement formats, from most to least preferable. If it returns attestation then the [=authenticator=] makes a best-effort attempt to use the most preferable format that it supports. : |extensions| :: A [=CBOR=] [=map=] from [=extension identifiers=] to their [=authenticator extension inputs=], created by the client based on the extensions requested by the [=[RP]=], if any. @@ -4265,8 +4287,20 @@ When this method is invoked, the [=authenticator=] MUST perform the following pr <figcaption>Generating an [=assertion signature=].</figcaption> </figure> -1. If any error occurred while generating the [=assertion signature=], return an error code equivalent to "{{UnknownError}}" and - terminate the operation. +1. If |attestationFormats|: + <dl class="switch"> + : is [=list/is not empty|not empty=] + :: let |attestationFormat| be the first supported [=attestation statement format=] from |attestationFormats|, taking into account |enterpriseAttestationPossible|. If none are supported, fallthrough to: + + : is [=list/is empty|empty=] + :: let |attestationFormat| be the [=attestation statement format=] most preferred by this authenticator. + </dl> + +1. Create an [=attestation object=] for the new credential using the procedure specified in + [[#sctn-generating-an-attestation-object]], the [=attestation statement format=] |attestationFormat|, and the values |authenticatorData| + and |hash|. For more details on attestation, see [[#sctn-attestation]]. + +1. If any error occurred then return an error code equivalent to "{{UnknownError}}" and terminate the operation. <!-- Note: this next step is actually a top-level step, but bikeshed wanted it indented this much in order to render it as a numbered step. If outdented, it (today) is rendered as a bullet in the midst of a numbered list :-/ @@ -4283,6 +4317,7 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o - |authenticatorData| - |signature| + - The attestation object. - |selectedCredential|.[=public key credential source/userHandle=] Note: the returned [=public key credential source/userHandle=] value may be `null`, see: @@ -6270,6 +6305,7 @@ A usage example is thus: Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures before associating and storing extension output value fields in conjunction with the [=user account=]. See [[#sctn-device-publickey-extension-verification]]. +The weight that [=[RPS]=] give to the presence of a signature from a [=device-bound key=] may be based on information learned from its optional attestation. An attestation can indicate the level of protection that the hardware offers the private key, certifiations for the hardware, etc. ### Extension Definition ### {#sctn-device-publickey-extension-definition} @@ -6374,7 +6410,8 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo ; An authenticator-generated random nonce for inclusion in the attestation ; signature. If the authenticator chooses to not generate a nonce, it sets this - ; to a zero-length bytestring. + ; to a zero-length bytestring. See the note below about "randomNonce" for a + ; discussion on the nonce's purpose. nonce: bstr .size (0..32), @@ -6387,7 +6424,9 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo ; (1) a SIGNATURE value calculated (using the attestation private key) ; over (prefix || aaguid || dpk || nonce) where \`prefix\` is ; h'64657669636520626f756e64206b6579206174746573746174696f6e20736967 - ; 00ffffffff' + ; 00ffffffff'. + ; (See the attestation calculations section, below, for a discussion + ; about the purpose of this value.) ; (2) the attestation certificate or public key, and supporting certificates, ; if any. ; @@ -6400,7 +6439,7 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo ; An optional boolean that indicates whether the attestation statement ; contains uniquely identifying information. This can only be true ; when the \`attestation\` field of the extension input is "enterprise" - ; an either the user-agent or the authenticator permits uniquely + ; and either the user-agent or the authenticator permits uniquely ; identifying attestation for the requested RP ID. ? epAtt: bool .default false, @@ -6417,17 +6456,17 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo 1. If a [=hardware-bound device key pair=] does not already exist for this {[=public key credential source/id|Credential ID=], [=public key credential source/rpId|RP ID=], [=public key credential source/rpId|userHandle=]} tuple on the [=authenticator=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing [=device-bound key=]. - 1. Let |attFormat| be the chosen [=attestation statement format=], and |aaguid| be a 16-byte value, based on the value of {{AuthenticationExtensionsDevicePublicKeyInputs/attestation}} in the extension input: + 1. Let |attFormat| be the chosen [=attestation statement format=], and |attAaguid| be a 16-byte value, based on the value of {{AuthenticationExtensionsDevicePublicKeyInputs/attestation}} in the extension input: <dl class="switch"> : none - :: |attFormat| is "none" or "self", at the authenticator's discretion, and |aaguid| is 16 zero bytes. (Note that, since the [=device-bound key=] is already exercised during {{CredentialsContainer/get()|navigator.credentials.get()}} calls, the proof-of-possession property provided by "self" attestation is superfluous in that context.) + :: |attFormat| is "none" or "self", at the authenticator's discretion, and |attAaguid| is 16 zero bytes. (Note that, since the [=device-bound key=] is already exercised during {{CredentialsContainer/get()|navigator.credentials.get()}} calls, the proof-of-possession property provided by "self" attestation is superfluous in that context.) : indirect, direct - :: |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsDevicePublicKeyInputs/attestationFormats}}, and |aaguid| is the [=authenticator's=] [=AAGUID=]. (Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust, although they do not have to be. This is in contrast to the associated [=user credential=]'s attestation.) + :: |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsDevicePublicKeyInputs/attestationFormats}}, and |attAaguid| is the [=authenticator's=] [=AAGUID=]. (Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust, although they do not have to be. This is in contrast to the associated [=user credential=]'s attestation.) : enterprise - :: The [=[RP]=] wants to receive an [=attestation statement=] that may include uniquely identifying information. This is intended for controlled deployments within an enterprise where the organization wishes to tie registrations to specific authenticators. [=Authenticators=] MUST NOT provide such an attestation unless the user agent or authenticator configuration expressly permits it for the requested [=RP ID=]. If <i>not</i> permitted, |attFormat| is "none", and |aaguid| is 16 zero bytes. Otherwise |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsDevicePublicKeyInputs/attestationFormats}}, and |aaguid| is the [=authenticator's=] [=AAGUID=]. (Again, since the [=hardware-bound device key pair=] is specific to a particular authenticator, the attestation may be tied to hardware roots of trust.) + :: The [=[RP]=] wants to receive an [=attestation statement=] that may include uniquely identifying information. This is intended for controlled deployments within an enterprise where the organization wishes to tie registrations to specific authenticators. [=Authenticators=] MUST NOT provide such an attestation unless the user agent or authenticator configuration expressly permits it for the requested [=RP ID=]. If <i>not</i> permitted, |attFormat| is "none", and |attAaguid| is 16 zero bytes. Otherwise |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsDevicePublicKeyInputs/attestationFormats}}, and |attAaguid| is the [=authenticator's=] [=AAGUID=]. (Again, since the [=hardware-bound device key pair=] is specific to a particular authenticator, the attestation may be tied to hardware roots of trust.) Note: CTAP2 does not currently provide for an <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#makecred-enterpriseattestation">enterpriseAttestation</a> signal during an <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticatorGetAssertion">authenticatorGetAssertion</a> call. Until that is changed, <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#platform-managed-enterprise-attestation">platform-managed enterprise attestation</a> will not work in that context with CTAP2 [=authenticators=]. </dl> @@ -6446,7 +6485,7 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo Note: as with all CBOR structures used in this specification, the [=CTAP2 canonical CBOR encoding form=] MUST be used. - 1. Let the `aaguid` key's value be |aaguid|. + 1. Let the `aaguid` key's value be |attAaguid|. 1. Let the `dpk` key's value be |dpk|. From 7b531a866ab11715eee72667f7242c28acd08868 Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Sat, 20 Aug 2022 14:38:43 -0700 Subject: [PATCH 104/131] Apply more of emlun's suggestions from code review (GitHub is struggling with the number of them, thus I'm trying to do just the first half.) Co-authored-by: Emil Lundberg <emil@yubico.com> --- index.bs | 43 +++++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/index.bs b/index.bs index 4b2124aa3..7794c33b4 100644 --- a/index.bs +++ b/index.bs @@ -2871,7 +2871,7 @@ optionally evidence of [=user consent=] to a specific transaction. [=user handle=]. See [[#sctn-op-get-assertion]]. : <dfn>attestationObject</dfn> - :: This OPTIONAL attribute contains an [=attestation object=]. The [=attestation object=], if present, includes an [=attestation statement=]. Unlike the {{AuthenticatorAttestationResponse/attestationObject}} in an {{AuthenticatorAssertionResponse}}, it does not contains an `authData` key because the [=authenticator data=] is provided directly in an {{AuthenticatorAssertionResponse}} structure. For more details on attestation, see [[#sctn-attestation]], [[#sctn-generating-an-attestation-object]], and [Figure 6](#fig-attStructs). + :: This OPTIONAL attribute contains an [=attestation object=]. The [=attestation object=], if present, includes an [=attestation statement=]. Unlike the {{AuthenticatorAttestationResponse/attestationObject}} in an {{AuthenticatorAssertionResponse}}, it does not contain an `authData` key because the [=authenticator data=] is provided directly in an {{AuthenticatorAssertionResponse}} structure. For more details on attestation, see [[#sctn-attestation]], [[#sctn-generating-an-attestation-object]], and [Figure 6](#fig-attStructs). </div> ## Parameters for Credential Generation (dictionary <dfn dictionary>PublicKeyCredentialParameters</dfn>) ## {#dictionary-credential-params} @@ -4394,7 +4394,7 @@ It takes the following input parameters: : |enterpriseAttestationPossible| :: A Boolean value that indicates that individually-identifying attestation MAY be returned by the authenticator. : |attestationFormats| -:: A sequence of strings that expresses the [=RP=]'s preference for attestation statement formats, from most to least preferable. If it returns attestation then the [=authenticator=] makes a best-effort attempt to use the most preferable format that it supports. +:: A sequence of strings that expresses the [=RP=]'s preference for attestation statement formats, from most to least preferable. If the [=authenticator=] returns [=attestation=], then it makes a best-effort attempt to use the most preferable format that it supports. : |extensions| :: A [=CBOR=] [=map=] from [=extension identifiers=] to their [=authenticator extension inputs=], created by the [=client=] based on the extensions requested by the [=[RP]=], if any. @@ -4506,14 +4506,8 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o </dl> 1. Let |attestedCredentialData| be the [=attested credential data=] byte array including the |credentialId| and |publicKey|. -1. If |attestationFormats|: - <dl class="switch"> - : is [=list/is not empty|not empty=] - :: let |attestationFormat| be the first supported [=attestation statement format=] from |attestationFormats|, taking into account |enterpriseAttestationPossible|. If none are supported, fallthrough to: - - : is [=list/is empty|empty=] - :: let |attestationFormat| be the [=attestation statement format=] most preferred by this authenticator. - </dl> +1. Let |attestationFormat| be the first supported [=attestation statement format identifier=] from |attestationFormats|, taking into account |enterpriseAttestationPossible|. + If |attestationFormats| contains no supported value, then let |attestationFormat| be the [=attestation statement format identifier=] most preferred by this authenticator. 1. Let |authenticatorData| [=perform the following steps to generate an authenticator data structure|be the byte array=] specified in [[#sctn-authenticator-data]], including |attestedCredentialData| as the <code>[=attestedCredentialData=]</code> and |processedExtensions|, if any, as the <code>[=authDataExtensions|extensions=]</code>. @@ -4544,7 +4538,7 @@ It takes the following input parameters: : |enterpriseAttestationPossible| :: A Boolean value that indicates that individually-identifying attestation MAY be returned by the authenticator. : |attestationFormats| -:: A sequence of strings that expresses the [=RP=]'s preference for attestation statement formats, from most to least preferable. If it returns attestation then the [=authenticator=] makes a best-effort attempt to use the most preferable format that it supports. +:: A sequence of strings that expresses the [=[RP]=]'s preference for attestation statement formats, from most to least preferable. If the [=authenticator=] returns [=attestation=], then it makes a best-effort attempt to use the most preferable format that it supports. : |extensions| :: A [=CBOR=] [=map=] from [=extension identifiers=] to their [=authenticator extension inputs=], created by the client based on the extensions requested by the [=[RP]=], if any. @@ -4801,7 +4795,7 @@ The privacy, security and operational characteristics of [=attestation=] depend operating environment, and so on. The [=attestation type=] and [=attestation statement format=] is chosen by the [=authenticator=]; -[=[RPS]=] can only signal their preferences by setting the {{PublicKeyCredentialCreationOptions/attestation}} and {{PublicKeyCredentialCreationOptions/attestationFormats}} parameters. (Or the parameters with the same names in {{PublicKeyCredentialRequestOptions}}.) +[=[RPS]=] can only signal their preferences by setting the {{PublicKeyCredentialCreationOptions/attestation}} and {{PublicKeyCredentialCreationOptions/attestationFormats}} parameters, or those with the same names in {{PublicKeyCredentialRequestOptions}}. It is expected that most [=authenticators=] will support a small number of [=attestation types=] and [=attestation statement formats=], while [=[RPS]=] will decide what [=attestation types=] are acceptable to them by policy. [=[RPS]=] will also need to @@ -4810,9 +4804,9 @@ understand the characteristics of the [=authenticators=] that they trust, based ### Attestation in assertions ### {#sctn-attestation-in-assertions} -Attestation is most commonly provided during credential creation. However, [=multi-device credentials=] can move between [=authenticators=] during their lifetime and thus attestation MAY be provided during assertions if requested by the [=[RP]=] using the {{PublicKeyCredentialRequestOptions/attestation}} parameter. +Attestation is most commonly provided during credential creation. However, [=multi-device credentials=] can move between [=authenticators=] during their lifetime and thus attestation MAY be provided in [=assertions=] if requested by the [=[RP]=] using the {{PublicKeyCredentialRequestOptions/attestation}} parameter. -[=Attestation objects=] provided in an {{AuthenticatorAttestationResponse}} structure (i.e. as the result of a {{CredentialsContainer/create()|create()}} operation) contain at least the three keys shown in [the previous figure](#fig-attStructs): `fmt`, `attStmt`, and `authData`. The last of those keys is not included when an [=attestation object=] is provided in an {{AuthenticatorAssertionResponse}} (i.e. as the result of a {{CredentialsContainer/get()|get()}} operation). That is because the [=authenticator data=] is provided directly in the {{AuthenticatorAssertionResponse/authenticatorData}} member of the {{AuthenticatorAssertionResponse}}. Otherwise, processing of the [=attestation object=] is identical. +[=Attestation objects=] provided in an {{AuthenticatorAttestationResponse}} structure (i.e. as the result of a {{CredentialsContainer/create()|create()}} operation) contain at least the three keys shown in [the previous figure](#fig-attStructs): `fmt`, `attStmt`, and `authData`. The `authData` key is not included when an [=attestation object=] is provided in an {{AuthenticatorAssertionResponse}} (i.e. as the result of a {{CredentialsContainer/get()|get()}} operation). That is because the [=authenticator data=] is provided directly in the {{AuthenticatorAssertionResponse/authenticatorData}} member of the {{AuthenticatorAssertionResponse}}. Otherwise, processing of the [=attestation object=] is identical. ### Attested Credential Data ### {#sctn-attested-credential-data} @@ -4861,7 +4855,7 @@ object=] for a credential. Its format is shown in <a href="#table-attestedCreden </figcaption> </figure> -Attested credential data is always present in any [=authenticator data=] that results from a {{CredentialsContainer/create()|create()}} operation. It may only be present in an [=authenticator data=] resulting from a {{CredentialsContainer/get()|get()}} operation if attestation was requested using the {{PublicKeyCredentialRequestOptions/attestation}} parameter. +Attested credential data is always present in any [=authenticator data=] that results from a {{CredentialsContainer/create()|create()}} operation. It MAY be present in an [=authenticator data=] resulting from a {{CredentialsContainer/get()|get()}} operation only if attestation was requested using the {{PublicKeyCredentialRequestOptions/attestation}} parameter. #### Examples of `credentialPublicKey` Values Encoded in COSE_Key Format #### {#sctn-encoded-credPubKey-examples} @@ -5380,11 +5374,11 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o [=[RP]=]-specific. </dd> </dl> -1. If <code>|response|.{{AuthenticatorAssertionResponse/attestationObject}}</code> is present and the [=[RP]=] wishes to verify the attestation then perform CBOR decoding on {AuthenticatorAssertionResponse/attestationObject}} to obtain the attestation statement format |fmt|, and the attestation statement |attStmt|. +1. If <code>|response|.{{AuthenticatorAssertionResponse/attestationObject}}</code> is present and the [=[RP]=] wishes to verify the attestation then perform CBOR decoding on {{AuthenticatorAssertionResponse/attestationObject}} to obtain the attestation statement format |fmt|, and the attestation statement |attStmt|. 1. Verify that the `AT` bit in the [=flags=] field of |authData| is set, indicating that [=attested credential data=] is included. - 1. Verify that [=credentialId=] and [=credentialPublicKey=] fields of the [=attested credential data=] in |authData| match the stored values for the selected [=credential public key=]. + 1. Verify that the [=credentialPublicKey=] and [=credentialId=] fields of the [=attested credential data=] in |authData| match |credentialPublicKey| and the [=credential ID=] stored with it. 1. Determine the attestation statement format by performing a USASCII case-sensitive match on |fmt| against the set of supported WebAuthn Attestation Statement Format Identifier values. An up-to-date list of registered WebAuthn Attestation Statement Format Identifier values is maintained in the IANA "WebAuthn Attestation Statement Format Identifiers" registry [[!IANA-WebAuthn-Registries]] established by [[!RFC8809]]. @@ -6013,7 +6007,7 @@ represented in [=CBOR=] and passed as name-value pairs, with the [=extension ide [=authenticator extension input=] as the value. The authenticator, in turn, performs additional processing for the extensions that it supports, and returns the [=CBOR=] [=authenticator extension output=] for each as specified by the extension. Since [=authenticator extension output=] is returned as part of the signed [=authenticator data=], authenticator extensions -may also specify an [=unsigned extension output=], e.g. for cases where an output itself depends on [=authenticator data=]. +MAY also specify an [=unsigned extension output=], e.g. for cases where an output itself depends on [=authenticator data=]. Part of the [=client extension processing=] for [=authenticator extensions=] is to use the [=authenticator extension output=] and [=unsigned extension output=] as an input to creating the [=client extension output=]. @@ -6862,14 +6856,14 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo ; Whether device-scoped or not, keys are still device-bound. I.e. an ; app-scoped key does not enjoy lesser protection from extraction. - scope: uint .size 1, ; a value of 0x00 means "entire device" ("all apps") scope. - ; 0x01 means "per-app" scope. - ; Values other than 0x00 or 0x01 are reserved for future - ; use. + scope: uint .size 1, ; A value of 0x00 means "entire device" ("all apps") scope. + ; 0x01 means "per-app" scope. + ; Values other than 0x00 or 0x01 are reserved for future + ; use. ; An authenticator-generated random nonce for inclusion in the attestation ; signature. If the authenticator chooses to not generate a nonce, it sets this - ; to a zero-length bytestring. See the note below about "randomNonce" for a + ; to a zero-length byte string. See the note below about "randomNonce" for a ; discussion on the nonce's purpose. nonce: bstr .size (0..32), @@ -6878,6 +6872,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo ; ; Attestation statement formats define the \`fmt\` and \`attStmt\` members of ; $$attStmtType. + ; Note that `fmt` and `attStmt` are top-level members of `attObjForDevicePublicKey`. ; ; In summary, the \`attStmt\` will (typically) contain: ; (1) a SIGNATURE value calculated (using the attestation private key) @@ -6885,7 +6880,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo ; h'64657669636520626f756e64206b6579206174746573746174696f6e20736967 ; 00ffffffff'. ; (See the attestation calculations section, below, for a discussion - ; about the purpose of this value.) + ; about the purpose of this `prefix` value.) ; (2) the attestation certificate or public key, and supporting certificates, ; if any. ; From 04ddb485b6b9c4942a7b70d7f2076b801979924e Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Sat, 20 Aug 2022 14:43:35 -0700 Subject: [PATCH 105/131] Apply one of emlun's suggestions (This one seems to break GitHub's UI.) Co-authored-by: Emil Lundberg <emil@yubico.com> --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 7794c33b4..7231eb874 100644 --- a/index.bs +++ b/index.bs @@ -6902,7 +6902,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo ``` : Unsigned extension output -:: A signature generated with the device public key as a CBOR bytestring +:: A CBOR byte string containing a signature generated with the device private key. : Authenticator extension processing :: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: From 3cba94cf9532f9b28f1221dceb472d82494c9021 Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Sat, 20 Aug 2022 14:45:30 -0700 Subject: [PATCH 106/131] Apply one of emlun's suggestions Co-authored-by: Emil Lundberg <emil@yubico.com> --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 7231eb874..8955bdc2d 100644 --- a/index.bs +++ b/index.bs @@ -7050,8 +7050,8 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n 1. If |attObjForDevicePublicKey|.|dpk| did not match any of the [=[RP]=]'s stored |dpk| values for this [=user account=] and <code>|credential|.{{Credential/id}}</code> pair then: <dl class="switch"> - : If |fmt|'s value is "none" then there is no attestation signature to verify and this is a new device. - :: Store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. Terminate these verification steps. + : If |fmt|'s value is "none": + :: There is no attestation signature to verify and this is a new device. Store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. Terminate these verification steps.``` : Otherwise: :: Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. From 47017e4b81fe3084f2428c1f1f44b12f1f3dd33d Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Sat, 20 Aug 2022 14:45:48 -0700 Subject: [PATCH 107/131] Apply one of emlun's suggestions Co-authored-by: Emil Lundberg <emil@yubico.com> --- index.bs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/index.bs b/index.bs index 8955bdc2d..56d5db730 100644 --- a/index.bs +++ b/index.bs @@ -7011,13 +7011,17 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n Issue(w3c/webauthn#1711): In the below steps, where storing the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values is called for, it ought to only occur if the overall authentication operation is successful, i.e., crucially, the "encompassing signature" verified correctly. - 1. If more than one stored {`aaguid`, `dpk`, `scope`, `fmt`, `attStmt`} value set matched with the extracted |attObjForDevicePublicKey| fields, then some form of error has occurred. It is indeterminate whether this is a known device. Terminate these verification steps. - - 1. Otherwise, if the results are: - + If the above set of binary equality checks resulted in + <dl class="switch"> - : successful - :: This is likely a known device: + : more than one match + :: Some form of error has occurred. It is indeterminate whether this is a known device. Terminate these verification steps. + + : exactly one match + :: This is likely a known device: [...] + + : zero matches + :: This is possibly a new [=device public key=] signifying a new device: [...] If |fmt|'s value is "none" then there is no attestation signature to verify and this is a known [=device public key=] with a valid signature and thus a known device. Terminate these verification steps. From 16a846a3e1f696a42876aeaf6c6a9edee012a9ea Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Sat, 20 Aug 2022 14:46:14 -0700 Subject: [PATCH 108/131] Apply one of emlun's suggestions Co-authored-by: Emil Lundberg <emil@yubico.com> --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 56d5db730..a067bf0db 100644 --- a/index.bs +++ b/index.bs @@ -7007,7 +7007,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n 1. Verify that {{AuthenticationExtensionsDevicePublicKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) by the [=device public key=] |dpk|. (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 `aaguid`, `dpk`, `scope`, `fmt`, and `attStmt` values corresponding to the extracted |attObjForDevicePublicKey| fields, then perform binary equality checks between the corresponding stored values and the extracted field values. The [=[RP]=] may have more than one set of {`aaguid`, `dpk`, `scope`, `fmt`, `attStmt`} values mapped to the [=user account=] and <code>|credential|.{{Credential/id}}</code> pair and each set must be checked. +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 `aaguid`, `dpk`, `scope`, `fmt`, and `attStmt` values corresponding to the extracted |attObjForDevicePublicKey| fields, then perform binary equality checks between the corresponding stored values and the extracted field values. The [=[RP]=] MAY have more than one set of {`aaguid`, `dpk`, `scope`, `fmt`, `attStmt`} values mapped to the [=user account=] and <code>|credential|.{{Credential/id}}</code> pair and each set MUST be checked. Issue(w3c/webauthn#1711): In the below steps, where storing the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values is called for, it ought to only occur if the overall authentication operation is successful, i.e., crucially, the "encompassing signature" verified correctly. From 2ec8861a9d176992f3262989ea88222ee75d8a95 Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Sat, 20 Aug 2022 14:46:33 -0700 Subject: [PATCH 109/131] Apply one of emlun's suggestions Co-authored-by: Emil Lundberg <emil@yubico.com> --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index a067bf0db..c57fced16 100644 --- a/index.bs +++ b/index.bs @@ -6981,7 +6981,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/create( 1. Let |attObjForDevicePublicKey| be the value of the `devicePubKey` member of |clientExtensionResults|. 1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |aaguid|, |dpk|, |scope|, |nonce|, |fmt|, |attStmt|. - Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of [=authenticator data=]. + Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of the top-level [=attestation object=]. 1. Verify that {{AuthenticationExtensionsDevicePublicKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) by the [=device public key=] |dpk|. (The signature algorithm is the same as for the [=user credential=].) From 5c1cd985d599d95fafd708294ca93358c0896f44 Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Sat, 20 Aug 2022 14:46:59 -0700 Subject: [PATCH 110/131] Apply suggestions from code review Co-authored-by: Emil Lundberg <emil@yubico.com> --- index.bs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index c57fced16..5ec05f684 100644 --- a/index.bs +++ b/index.bs @@ -6947,11 +6947,11 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo 1. Let the `scope` key have the value zero (0x00) if this is an "entire device" [=device public key=]. Otherwise, let `scope` have the value one (0x01), indicating a more narrow per-app scope. - 1. Let the values of the `$$attStmtType` "group socket" [[=CDDL=]] be the result of generating an [=attestation statement=] with the [=attestation statement format=], |attFormat|. See [[#sctn-device-publickey-attestation-calculations]]. + 1. Let the values of the `$$attStmtType` [=group socket=] be the result of generating an [=attestation statement=] with the [=attestation statement format=], |attFormat|. See [[#sctn-device-publickey-attestation-calculations]]. Note: The details of the `$$attStmtType` values are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust (although they do not have to be). This is in contrast to the associated [=user credential=]'s attestation. - 1. If the `$$attStmtType` "group socket" contains uniquely identifying information then let `epAtt` be [TRUE]. Otherwise omit the `epAtt` field. (This field can only be [TRUE] when the {{AuthenticationExtensionsDevicePublicKeyInputs/attestation}} field of the extension input is "enterprise" and either the user-agent or the authenticator permits uniquely identifying attestation for the requested RP ID.) + 1. If the `$$attStmtType` [=group socket=] contains uniquely identifying information then let `epAtt` be [TRUE]. Otherwise omit the `epAtt` field. (This field can only be [TRUE] when the {{AuthenticationExtensionsDevicePublicKeyInputs/attestation}} field of the extension input is "enterprise" and either the user-agent or the authenticator permits uniquely identifying attestation for the requested RP ID.) 1. Let |dpkSig| be the result of signing the [=assertion signature=] [input](#fig-signature) with |devicePrivateKey|. @@ -6970,6 +6970,8 @@ Therefore when calculating an attestation for a [=device-bound key=], the inputs The attestation signature is thus typically calculated over the bytes of <code>(h'64657669636520626f756e64206b6579206174746573746174696f6e2073696700ffffffff' || |aaguid| || |dpk| || |nonce|)</code>. The 37-byte prefix ensures domain separation: it takes the place of the RP ID hash, flags, and signature counter fields in those messages and ensures that no attestation signature for a [=device-bound key=] can be confused with a signature for a [=user credential=]. +Note that when |nonce| is empty, then the (signed) authenticator extension output MAY be constant. However, the (unsigned) |dpkSig| output is always unique and prevents replay of the (signed) extension output without knowledge of the [=device private key=]. + ### `devicePubKey` Extension Output Verification Procedures ### {#sctn-device-publickey-extension-verification} Verifying the <code>[=devicePubKey=]</code> extension output is performed by the [=[RP]=] whenever a <i>new</i> [=device public key=] is returned within the extension output. As explained in [[#sctn-device-publickey-extension-usage]], a new [=device public key=] is always returned as a result of a {{CredentialsContainer/create()|navigator.credentials.create()}} call (i.e., as part of a [=registration ceremony=]). In contrast, a new [=device public key=] <i>may</i> be returned as a result of a {{CredentialsContainer/get()|navigator.credentials.get()}} call (i.e., as part of an [=authentication ceremony=]). @@ -6978,7 +6980,7 @@ Verifying the <code>[=devicePubKey=]</code> extension output is performed by the If the `devicePubKey` 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|. -1. Let |attObjForDevicePublicKey| be the value of the `devicePubKey` member of |clientExtensionResults|. +1. Let |attObjForDevicePublicKey| be the value of the {{AuthenticationExtensionsClientOutputs/devicePubKey}} member of |clientExtensionResults|. 1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |aaguid|, |dpk|, |scope|, |nonce|, |fmt|, |attStmt|. Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of the top-level [=attestation object=]. From 5c6c23daacd8e894f7c1c06ef8ee97d762938b37 Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Sat, 20 Aug 2022 14:47:23 -0700 Subject: [PATCH 111/131] Apply suggestions from code review Co-authored-by: Emil Lundberg <emil@yubico.com> --- index.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index 5ec05f684..7db132543 100644 --- a/index.bs +++ b/index.bs @@ -6920,7 +6920,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo :: |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsDevicePublicKeyInputs/attestationFormats}}, and |attAaguid| is the [=authenticator's=] [=AAGUID=]. (Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust, although they do not have to be. This is in contrast to the associated [=user credential=]'s attestation.) : enterprise - :: The [=[RP]=] wants to receive an [=attestation statement=] that may include uniquely identifying information. This is intended for controlled deployments within an enterprise where the organization wishes to tie registrations to specific authenticators. [=Authenticators=] MUST NOT provide such an attestation unless the user agent or authenticator configuration expressly permits it for the requested [=RP ID=]. If <i>not</i> permitted, |attFormat| is "none", and |attAaguid| is 16 zero bytes. Otherwise |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsDevicePublicKeyInputs/attestationFormats}}, and |attAaguid| is the [=authenticator's=] [=AAGUID=]. (Again, since the [=hardware-bound device key pair=] is specific to a particular authenticator, the attestation may be tied to hardware roots of trust.) + :: The [=[RP]=] wants to receive an [=attestation statement=] that may include uniquely identifying information. This is intended for controlled deployments within an enterprise where the organization wishes to tie registrations to specific authenticators. [=Authenticators=] MUST NOT provide such an attestation unless the user agent or authenticator configuration expressly permits it for the requested [=RP ID=]. If <i>not</i> permitted, then |attFormat| is "none" and |attAaguid| is 16 zero bytes. Otherwise |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsDevicePublicKeyInputs/attestationFormats}}, and |attAaguid| is the [=authenticator's=] [=AAGUID=]. (Again, since the [=hardware-bound device key pair=] is specific to a particular authenticator, the attestation may be tied to hardware roots of trust.) Note: CTAP2 does not currently provide for an <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#makecred-enterpriseattestation">enterpriseAttestation</a> signal during an <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticatorGetAssertion">authenticatorGetAssertion</a> call. Until that is changed, <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#platform-managed-enterprise-attestation">platform-managed enterprise attestation</a> will not work in that context with CTAP2 [=authenticators=]. </dl> @@ -6931,7 +6931,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo 1. Let |clientDataHash| be the [=hash of the serialized client data=]. - 1. Let |randomNonce| be a fresh randomly-generated bytestring of 32 bytes maximum length, or a zero length bytestring if the authenticator chooses to not generate a nonce. + 1. Let |randomNonce| be a fresh randomly-generated byte string of 32 bytes maximum length, or a zero length byte string if the authenticator chooses to not generate a nonce. Note: |randomNonce|'s purpose is to randomize the `devicePubKey` extension's [=attestation signature=] value. If this is not done, then the `devicePubKey` extension's [=attestation signature=] value remains constant for all such signatures issued on behalf of this [=user credential=], possibly exposing the [=authenticator=]'s [=attestation private key=] to side-channel attacks. The randomness-generation mechanism should be carefully chosen by the authenticator implementer. @@ -6949,7 +6949,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo 1. Let the values of the `$$attStmtType` [=group socket=] be the result of generating an [=attestation statement=] with the [=attestation statement format=], |attFormat|. See [[#sctn-device-publickey-attestation-calculations]]. - Note: The details of the `$$attStmtType` values are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust (although they do not have to be). This is in contrast to the associated [=user credential=]'s attestation. + Note: The details of the `$$attStmtType` values are dependent upon the particular [=attestation statement=] format. See [[#sctn-attestation-formats]]. Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust (although they do not have to be). This is in contrast to the associated [=user credential=]'s attestation, if it is a [=multi-device credential=]. 1. If the `$$attStmtType` [=group socket=] contains uniquely identifying information then let `epAtt` be [TRUE]. Otherwise omit the `epAtt` field. (This field can only be [TRUE] when the {{AuthenticationExtensionsDevicePublicKeyInputs/attestation}} field of the extension input is "enterprise" and either the user-agent or the authenticator permits uniquely identifying attestation for the requested RP ID.) From a026a5bebd56ccb8196c2688b57df16a1c4a66c4 Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Sat, 20 Aug 2022 14:48:08 -0700 Subject: [PATCH 112/131] Apply one of emlun's suggestions Co-authored-by: Emil Lundberg <emil@yubico.com> --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 7db132543..afd3603a2 100644 --- a/index.bs +++ b/index.bs @@ -6917,7 +6917,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo :: |attFormat| is "none" or "self", at the authenticator's discretion, and |attAaguid| is 16 zero bytes. (Note that, since the [=device-bound key=] is already exercised during {{CredentialsContainer/get()|navigator.credentials.get()}} calls, the proof-of-possession property provided by "self" attestation is superfluous in that context.) : indirect, direct - :: |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsDevicePublicKeyInputs/attestationFormats}}, and |attAaguid| is the [=authenticator's=] [=AAGUID=]. (Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust, although they do not have to be. This is in contrast to the associated [=user credential=]'s attestation.) + :: |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsDevicePublicKeyInputs/attestationFormats}}, and |attAaguid| is the [=authenticator's=] [=AAGUID=]. (Since the [=hardware-bound device key pair=] is specific to a particular authenticator, its attestation can be tied to hardware roots of trust, although they do not have to be. This is in contrast to the associated [=user credential=]'s attestation, if it is a [=multi-device credential=].) : enterprise :: The [=[RP]=] wants to receive an [=attestation statement=] that may include uniquely identifying information. This is intended for controlled deployments within an enterprise where the organization wishes to tie registrations to specific authenticators. [=Authenticators=] MUST NOT provide such an attestation unless the user agent or authenticator configuration expressly permits it for the requested [=RP ID=]. If <i>not</i> permitted, then |attFormat| is "none" and |attAaguid| is 16 zero bytes. Otherwise |attFormat| is an [=attestation statement format=] appropriate for this [=authenticator=] based on {{AuthenticationExtensionsDevicePublicKeyInputs/attestationFormats}}, and |attAaguid| is the [=authenticator's=] [=AAGUID=]. (Again, since the [=hardware-bound device key pair=] is specific to a particular authenticator, the attestation may be tied to hardware roots of trust.) From ec03d4da8d88cd797a3c003c0212cc1beb438f4e Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Sat, 20 Aug 2022 14:57:53 -0700 Subject: [PATCH 113/131] Have the authenticator output a bytestring, not a map. The platform generally echos the authenticator extension outputs in its own extension output and DPK does the same. In order to avoid either replicating the DPK output structure into IDL, or having the platforms re-encode it, have the authenticators produce a byte string containing the encoded map. The platform can then copy the byte string verbatim. --- index.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index afd3603a2..06be46747 100644 --- a/index.bs +++ b/index.bs @@ -6829,11 +6829,11 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo ``` : Authenticator extension output -:: The <dfn>device public key attestation object</dfn>, defined by the `attObjForDevicePublicKey` type: +:: A byte string containing the CBOR encoding of the <dfn>device public key attestation object</dfn>, defined by the `attObjForDevicePublicKey` type: ``` $$extensionOutput //= ( - devicePubKey: attObjForDevicePublicKey, + devicePubKey: bstr, ) attObjForDevicePublicKey = { ; Note: This object conveys an attested @@ -6935,7 +6935,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo Note: |randomNonce|'s purpose is to randomize the `devicePubKey` extension's [=attestation signature=] value. If this is not done, then the `devicePubKey` extension's [=attestation signature=] value remains constant for all such signatures issued on behalf of this [=user credential=], possibly exposing the [=authenticator=]'s [=attestation private key=] to side-channel attacks. The randomness-generation mechanism should be carefully chosen by the authenticator implementer. - 1. Let the `devicePubKey` [=authenticator extension output=] value be a [=CBOR=] map as defined by `attObjForDevicePublicKey` above, with keys and values as follows: + 1. Let the `devicePubKey` [=authenticator extension output=] value be a byte string that encodes a [=CBOR=] map as defined by `attObjForDevicePublicKey` above, with keys and values as follows: Note: as with all CBOR structures used in this specification, the [=CTAP2 canonical CBOR encoding form=] MUST be used. From 88be1a6dd6701059482c7bbbb1961ea08f84863d Mon Sep 17 00:00:00 2001 From: Emil Lundberg <emil@yubico.com> Date: Tue, 23 Aug 2022 15:24:05 +0200 Subject: [PATCH 114/131] Fix devicePubKey sub-heading levels --- index.bs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/index.bs b/index.bs index 06be46747..7730779d7 100644 --- a/index.bs +++ b/index.bs @@ -6744,7 +6744,7 @@ If the [=authenticator=] is incapable of generating a [=hardware-bound device ke The [=hardware-bound device key pair=] is not on its own a [=user credential=] and does not have its own [=credential ID=]. Instead, the returned [=device public key=] is a device-specific contextual attribute of its associated [=user credential=]. That is, when that [=user credential=] is used&mdash;along with the [=devicePubKey=] extension&mdash;on a particular [=authenticator=], a particular [=device public key=] is returned by the extension, along with a signature demonstrating proof-of-possession of the [=device private key=] by that device. -### Relying Party Usage ### {#sctn-device-publickey-extension-usage} +#### Relying Party Usage #### {#sctn-device-publickey-extension-usage} This extension is intended for use by those [=[RPS]=] employing risk-analysis systems informing their sign-in decisions. This extension provides a "device continuity" signal <i>when used consistently</i> with both {{CredentialsContainer/create()|navigator.credentials.create()}} and {{CredentialsContainer/get()|navigator.credentials.get()}} operations: @@ -6760,7 +6760,7 @@ Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures befo The weight that [=[RPS]=] give to the presence of a signature from a [=device-bound key=] may be based on information learned from its optional attestation. An attestation can indicate the level of protection that the hardware offers the private key, certifiations for the hardware, etc. -### Extension Definition ### {#sctn-device-publickey-extension-definition} +#### Extension Definition #### {#sctn-device-publickey-extension-definition} : Extension identifier :: `devicePubKey` @@ -6959,7 +6959,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo Note: |dpkSig| cannot be included in the [=authenticator extension output=] because it is returned inside the [=authenticator data=] and that would imply that the signature signs over itself. -#### Attestation calculations #### {#sctn-device-publickey-attestation-calculations} +##### Attestation calculations ##### {#sctn-device-publickey-attestation-calculations} When computing attestations, the process in [[#sctn-generating-an-attestation-object]] takes two inputs: `authData` and `hash`. When calculating an attestation for a [=device-bound key=], the typical value for `hash` hashes over the attestation signature itself, which is impossible. Also the attestation of a [=device-bound key=] is potentially used repeatedly, thus may want to be cached. But signing over values that include [=[RP]=]-chosen nonces, like the [=hash of the serialized client data=], make that impossible. @@ -6972,11 +6972,11 @@ The attestation signature is thus typically calculated over the bytes of <code>( Note that when |nonce| is empty, then the (signed) authenticator extension output MAY be constant. However, the (unsigned) |dpkSig| output is always unique and prevents replay of the (signed) extension output without knowledge of the [=device private key=]. -### `devicePubKey` Extension Output Verification Procedures ### {#sctn-device-publickey-extension-verification} +#### `devicePubKey` Extension Output Verification Procedures #### {#sctn-device-publickey-extension-verification} Verifying the <code>[=devicePubKey=]</code> extension output is performed by the [=[RP]=] whenever a <i>new</i> [=device public key=] is returned within the extension output. As explained in [[#sctn-device-publickey-extension-usage]], a new [=device public key=] is always returned as a result of a {{CredentialsContainer/create()|navigator.credentials.create()}} call (i.e., as part of a [=registration ceremony=]). In contrast, a new [=device public key=] <i>may</i> be returned as a result of a {{CredentialsContainer/get()|navigator.credentials.get()}} call (i.e., as part of an [=authentication ceremony=]). -#### Registration (`create()`) #### {#sctn-device-publickey-extension-verification-create} +##### Registration (`create()`) ##### {#sctn-device-publickey-extension-verification-create} If the `devicePubKey` 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|. @@ -6997,7 +6997,7 @@ Issue(w3c/webauthn#1711): Storing this information ought to only occur if the ov See also [[#sctn-device-publickey-extension-usage]] for further details. -#### Authentication (`get()`) #### {#sctn-device-publickey-extension-verification-get} +##### Authentication (`get()`) ##### {#sctn-device-publickey-extension-verification-get} If the `devicePubKey` 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|. From 3430c953e47ab35085e80ac0086623f443de2f8e Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Thu, 25 Aug 2022 15:26:09 -0700 Subject: [PATCH 115/131] Hopefully fix up my misinterpretation of emlun's comment. --- index.bs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/index.bs b/index.bs index 7730779d7..878ec2aaf 100644 --- a/index.bs +++ b/index.bs @@ -7013,17 +7013,14 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n Issue(w3c/webauthn#1711): In the below steps, where storing the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values is called for, it ought to only occur if the overall authentication operation is successful, i.e., crucially, the "encompassing signature" verified correctly. - If the above set of binary equality checks resulted in - + If the above set of binary equality checks resulted in: + <dl class="switch"> : more than one match :: Some form of error has occurred. It is indeterminate whether this is a known device. Terminate these verification steps. : exactly one match - :: This is likely a known device: [...] - - : zero matches - :: This is possibly a new [=device public key=] signifying a new device: [...] + :: This is likely a known device. If |fmt|'s value is "none" then there is no attestation signature to verify and this is a known [=device public key=] with a valid signature and thus a known device. Terminate these verification steps. @@ -7050,7 +7047,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n </dl> </dl> - : unsuccessful + : zero matches :: This is possibly a new [=device public key=] signifying a new device: 1. If |attObjForDevicePublicKey|.|dpk| did not match any of the [=[RP]=]'s stored |dpk| values for this [=user account=] and <code>|credential|.{{Credential/id}}</code> pair then: @@ -7072,6 +7069,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n :: Some form of error has occurred. It is indeterminate whether this is a valid new device. Terminate these verification steps. </dl> </dl> + 1. Otherwise there is some form of error: we recieved a known |dpk| value, but one or more of the accompanying |aaguid|, |scope|, or |fmt| values did not match what the [=[RP]=] has stored along with that |dpk| value. Terminate these verification steps. </dl> From 5af393d40ff4275a343cb7b7cec19ac6876045be Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Thu, 25 Aug 2022 15:32:56 -0700 Subject: [PATCH 116/131] RPs shouldn't check DPK attestation for equality. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise they'll consider fresh attestations to be different devices. The attestation for existing DPKs is already checked in the “exactly one match” case. --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 878ec2aaf..fb0007f1f 100644 --- a/index.bs +++ b/index.bs @@ -7009,7 +7009,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n 1. Verify that {{AuthenticationExtensionsDevicePublicKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) by the [=device public key=] |dpk|. (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 `aaguid`, `dpk`, `scope`, `fmt`, and `attStmt` values corresponding to the extracted |attObjForDevicePublicKey| fields, then perform binary equality checks between the corresponding stored values and the extracted field values. The [=[RP]=] MAY have more than one set of {`aaguid`, `dpk`, `scope`, `fmt`, `attStmt`} values mapped to the [=user account=] and <code>|credential|.{{Credential/id}}</code> pair and each set MUST be checked. +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 `aaguid`, `dpk` and `scope` values corresponding to the extracted |attObjForDevicePublicKey| fields, then perform binary equality checks between the corresponding stored values and the extracted field values. The [=[RP]=] MAY have more than one set of {`aaguid`, `dpk`, `scope`} values mapped to the [=user account=] and <code>|credential|.{{Credential/id}}</code> pair and each set MUST be checked. Issue(w3c/webauthn#1711): In the below steps, where storing the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values is called for, it ought to only occur if the overall authentication operation is successful, i.e., crucially, the "encompassing signature" verified correctly. From fe333fe13b5a6b16a8012a49b680adf4a30e056a Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Thu, 25 Aug 2022 15:34:29 -0700 Subject: [PATCH 117/131] Remove incorrect note about nonces. Just because the attestation is changing doesn't mean that the authenticator is using a nonce. It could just be signing the same message repeatedly. --- index.bs | 2 -- 1 file changed, 2 deletions(-) diff --git a/index.bs b/index.bs index fb0007f1f..26b40e899 100644 --- a/index.bs +++ b/index.bs @@ -7040,8 +7040,6 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n : successful :: This is a known [=device public key=] with a valid signature and valid attestation and thus a known device. Terminate these verification steps. - Note: This authenticator is generating a fresh per-response random nonce. - : unsuccessful :: Some form of error has occurred. It is indeterminate whether this is a known device. Terminate these verification steps. </dl> From ece61f0870936697d3b78427191e73598c147a3d Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Thu, 25 Aug 2022 15:39:46 -0700 Subject: [PATCH 118/131] Include enterpriseAttestationPossible when calling authenticatorGetAssertion --- index.bs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 26b40e899..3762baac1 100644 --- a/index.bs +++ b/index.bs @@ -2566,6 +2566,7 @@ The steps for [=issuing a credential request to an authenticator=] are as follow Then, using |transport|, invoke the [=authenticatorGetAssertion=] operation on |authenticator|, with |rpId|, |clientDataHash|, |allowCredentialDescriptorList|, |userVerification|, + |enterpriseAttestationPossible|, |attestationFormats|, and |authenticatorExtensions| as parameters. @@ -2573,13 +2574,14 @@ The steps for [=issuing a credential request to an authenticator=] are as follow :: Using local configuration knowledge of the appropriate transport to use with |authenticator|, invoke the [=authenticatorGetAssertion=] operation on |authenticator| with |rpId|, |clientDataHash|, |allowCredentialDescriptorList|, |userVerification|, - |attestationFormats|, and |authenticatorExtensions| as parameters. + |enterpriseAttestationPossible|, |attestationFormats|, and |authenticatorExtensions| as parameters. </dl> : [=list/is empty=] :: Using local configuration knowledge of the appropriate transport to use with |authenticator|, invoke the [=authenticatorGetAssertion=] operation on |authenticator| with |rpId|, |clientDataHash|, |userVerification|, + |enterpriseAttestationPossible|, |attestationFormats|, and |authenticatorExtensions| as parameters. From 4279e6ec00949bd0ec8c6d8f760c7a7605a704bd Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Thu, 25 Aug 2022 15:42:06 -0700 Subject: [PATCH 119/131] Apply one of emlun's suggestions Co-authored-by: Emil Lundberg <emil@yubico.com> --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 3762baac1..55fa89ce7 100644 --- a/index.bs +++ b/index.bs @@ -4396,7 +4396,7 @@ It takes the following input parameters: : |enterpriseAttestationPossible| :: A Boolean value that indicates that individually-identifying attestation MAY be returned by the authenticator. : |attestationFormats| -:: A sequence of strings that expresses the [=RP=]'s preference for attestation statement formats, from most to least preferable. If the [=authenticator=] returns [=attestation=], then it makes a best-effort attempt to use the most preferable format that it supports. +:: A sequence of strings that expresses the [=[RP]=]'s preference for attestation statement formats, from most to least preferable. If the [=authenticator=] returns [=attestation=], then it makes a best-effort attempt to use the most preferable format that it supports. : |extensions| :: A [=CBOR=] [=map=] from [=extension identifiers=] to their [=authenticator extension inputs=], created by the [=client=] based on the extensions requested by the [=[RP]=], if any. From d25fd531c2f5ffcc6ec9af8857b2007f74d1b34a Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Thu, 25 Aug 2022 15:49:03 -0700 Subject: [PATCH 120/131] Mention where authData and hash are used. --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 55fa89ce7..19d591fa0 100644 --- a/index.bs +++ b/index.bs @@ -6987,7 +6987,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/create( 1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |aaguid|, |dpk|, |scope|, |nonce|, |fmt|, |attStmt|. Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of the top-level [=attestation object=]. -1. Verify that {{AuthenticationExtensionsDevicePublicKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) by the [=device public key=] |dpk|. (The signature algorithm is the same as for the [=user credential=].) +1. Verify that {{AuthenticationExtensionsDevicePublicKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) (i.e. `authData` and `hash`) by the [=device public key=] |dpk|. (The signature algorithm is the same as for the [=user credential=].) 1. Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. From 8966fe69bda5ccee862ae9519cf1649b0356468a Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Fri, 9 Sep 2022 17:02:28 -0700 Subject: [PATCH 121/131] Apply emlun's suggestions from code review Co-authored-by: Emil Lundberg <emil@yubico.com> --- index.bs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/index.bs b/index.bs index 19d591fa0..c97c49b32 100644 --- a/index.bs +++ b/index.bs @@ -6874,7 +6874,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo ; ; Attestation statement formats define the \`fmt\` and \`attStmt\` members of ; $$attStmtType. - ; Note that `fmt` and `attStmt` are top-level members of `attObjForDevicePublicKey`. + ; Note that \`fmt\` and \`attStmt\` are top-level members of \`attObjForDevicePublicKey\`. ; ; In summary, the \`attStmt\` will (typically) contain: ; (1) a SIGNATURE value calculated (using the attestation private key) @@ -6882,7 +6882,7 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo ; h'64657669636520626f756e64206b6579206174746573746174696f6e20736967 ; 00ffffffff'. ; (See the attestation calculations section, below, for a discussion - ; about the purpose of this `prefix` value.) + ; about the purpose of this \`prefix\` value.) ; (2) the attestation certificate or public key, and supporting certificates, ; if any. ; @@ -6963,12 +6963,12 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo ##### Attestation calculations ##### {#sctn-device-publickey-attestation-calculations} -When computing attestations, the process in [[#sctn-generating-an-attestation-object]] takes two inputs: `authData` and `hash`. When calculating an attestation for a [=device-bound key=], the typical value for `hash` hashes over the attestation signature itself, which is impossible. Also the attestation of a [=device-bound key=] is potentially used repeatedly, thus may want to be cached. But signing over values that include [=[RP]=]-chosen nonces, like the [=hash of the serialized client data=], make that impossible. +When computing attestations, the process in [[#sctn-generating-an-attestation-object]] takes two inputs: `authData` and `hash`. When calculating an attestation for a [=device-bound key=], the typical value for `hash` hashes over the attestation signature itself, which is impossible. Also the attestation of a [=device-bound key=] is potentially used repeatedly, thus may want to be cached. But signing over values that include [=[RP]=]-chosen nonces, like the [=hash of the serialized client data=], makes that impossible. Therefore when calculating an attestation for a [=device-bound key=], the inputs are: * For `authData`, substitute the concatenation of the byte string h'64657669636520626f756e64206b6579206174746573746174696f6e2073696700ffffffff' and the value of |aaguid| from the extension output. - * For `hash`, substitute the concatenation of the |dpk| and |nonce| fields from the extension output. (The nonce may be empty.) + * For `hash`, substitute the concatenation of the |dpk| and |nonce| fields from the extension output. (The nonce MAY be empty.) The attestation signature is thus typically calculated over the bytes of <code>(h'64657669636520626f756e64206b6579206174746573746174696f6e2073696700ffffffff' || |aaguid| || |dpk| || |nonce|)</code>. The 37-byte prefix ensures domain separation: it takes the place of the RP ID hash, flags, and signature counter fields in those messages and ensures that no attestation signature for a [=device-bound key=] can be confused with a signature for a [=user credential=]. @@ -7054,7 +7054,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n <dl class="switch"> : If |fmt|'s value is "none": - :: There is no attestation signature to verify and this is a new device. Store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. Terminate these verification steps.``` + :: There is no attestation signature to verify and this is a new device. Store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. Terminate these verification steps. : Otherwise: :: Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. From d671894d3c68492b41d50787938da8c03aec6b5e Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Fri, 9 Sep 2022 17:17:56 -0700 Subject: [PATCH 122/131] Address emlun's comments. --- index.bs | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/index.bs b/index.bs index c97c49b32..1c08c2bc9 100644 --- a/index.bs +++ b/index.bs @@ -2873,7 +2873,7 @@ optionally evidence of [=user consent=] to a specific transaction. [=user handle=]. See [[#sctn-op-get-assertion]]. : <dfn>attestationObject</dfn> - :: This OPTIONAL attribute contains an [=attestation object=]. The [=attestation object=], if present, includes an [=attestation statement=]. Unlike the {{AuthenticatorAttestationResponse/attestationObject}} in an {{AuthenticatorAssertionResponse}}, it does not contain an `authData` key because the [=authenticator data=] is provided directly in an {{AuthenticatorAssertionResponse}} structure. For more details on attestation, see [[#sctn-attestation]], [[#sctn-generating-an-attestation-object]], and [Figure 6](#fig-attStructs). + :: This OPTIONAL attribute contains an [=attestation object=], if the [=authenticator=] supports attestation in assertions. The [=attestation object=], if present, includes an [=attestation statement=]. Unlike the {{AuthenticatorAttestationResponse/attestationObject}} in an {{AuthenticatorAssertionResponse}}, it does not contain an `authData` key because the [=authenticator data=] is provided directly in an {{AuthenticatorAssertionResponse}} structure. For more details on attestation, see [[#sctn-attestation]], [[#sctn-attestation-in-assertions]], [[#sctn-generating-an-attestation-object]], and [Figure 6](#fig-attStructs). </div> ## Parameters for Credential Generation (dictionary <dfn dictionary>PublicKeyCredentialParameters</dfn>) ## {#dictionary-credential-params} @@ -4515,7 +4515,7 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o <code>[=authDataExtensions|extensions=]</code>. 1. Create an [=attestation object=] for the new credential using the procedure specified in [[#sctn-generating-an-attestation-object]], the [=attestation statement format=] |attestationFormat|, and the values |authenticatorData| - and |hash|. For more details on attestation, see [[#sctn-attestation]]. + and |hash|, as well as {{enterprise|taking into account}} the value of |enterpriseAttestationPossible|. For more details on attestation, see [[#sctn-attestation]]. On successful completion of this operation, the authenticator returns the [=attestation object=] to the client. @@ -4582,9 +4582,17 @@ When this method is invoked, the [=authenticator=] MUST perform the following pr which approach is implemented by the [=authenticator=], by some positive value. If the [=authenticator=] does not implement a [=signature counter=], let the [=signature counter=] value remain constant at zero. +1. If |attestationFormats|: + <dl class="switch"> + : is [=list/is not empty|not empty=] + :: let |attestationFormat| be the first supported [=attestation statement format=] from |attestationFormats|, taking into account |enterpriseAttestationPossible|. If none are supported, fallthrough to: + + : is [=list/is empty|empty=] + :: let |attestationFormat| be the [=attestation statement format=] most preferred by this authenticator. If it does not support attestation during assertion then let this be `none`. + </dl> 1. Let |authenticatorData| [=perform the following steps to generate an authenticator data structure|be the byte array=] specified in [[#sctn-authenticator-data]] including |processedExtensions|, if any, as - the <code>[=authDataExtensions|extensions=]</code> and excluding <code>[=attestedCredentialData=]</code>. + the <code>[=authDataExtensions|extensions=]</code> and excluding <code>[=attestedCredentialData=]</code>. This |authenticatorData| MUST include [=attested credential data=] if, and only if, |attestationFormat| is not `none`. 1. Let |signature| be the [=assertion signature=] of the concatenation <code>|authenticatorData| || |hash|</code> using the [=public key credential source/privateKey=] of |selectedCredential| as shown in <a href="#fig-signature">Figure <span class="figure-num-following"/></a>, below. A simple, undelimited @@ -4596,18 +4604,9 @@ When this method is invoked, the [=authenticator=] MUST perform the following pr <figcaption>Generating an [=assertion signature=].</figcaption> </figure> -1. If |attestationFormats|: - <dl class="switch"> - : is [=list/is not empty|not empty=] - :: let |attestationFormat| be the first supported [=attestation statement format=] from |attestationFormats|, taking into account |enterpriseAttestationPossible|. If none are supported, fallthrough to: - - : is [=list/is empty|empty=] - :: let |attestationFormat| be the [=attestation statement format=] most preferred by this authenticator. - </dl> - -1. Create an [=attestation object=] for the new credential using the procedure specified in +1. The |attestationFormat| is not `none` then create an [=attestation object=] for the new credential using the procedure specified in [[#sctn-generating-an-attestation-object]], the [=attestation statement format=] |attestationFormat|, and the values |authenticatorData| - and |hash|. For more details on attestation, see [[#sctn-attestation]]. + and |hash|, as well as {{enterprise|taking into account}} the value of |enterpriseAttestationPossible|. For more details on attestation, see [[#sctn-attestation]]. 1. If any error occurred then return an error code equivalent to "{{UnknownError}}" and terminate the operation. @@ -4626,7 +4625,7 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o - |authenticatorData| - |signature| - - The attestation object. + - The attestation object, if an [=attestation object=] was created for this assertion. - |selectedCredential|.[=public key credential source/userHandle=] Note: the returned [=public key credential source/userHandle=] value may be `null`, see: @@ -4806,7 +4805,7 @@ understand the characteristics of the [=authenticators=] that they trust, based ### Attestation in assertions ### {#sctn-attestation-in-assertions} -Attestation is most commonly provided during credential creation. However, [=multi-device credentials=] can move between [=authenticators=] during their lifetime and thus attestation MAY be provided in [=assertions=] if requested by the [=[RP]=] using the {{PublicKeyCredentialRequestOptions/attestation}} parameter. +Attestation is most commonly provided during credential creation. However, if supported by the authenticator and requested by the [=[RP]=] using the {{PublicKeyCredentialRequestOptions/attestation}} parameter, attestation MAY be provided in [=assertions=]. [=Attestation objects=] provided in an {{AuthenticatorAttestationResponse}} structure (i.e. as the result of a {{CredentialsContainer/create()|create()}} operation) contain at least the three keys shown in [the previous figure](#fig-attStructs): `fmt`, `attStmt`, and `authData`. The `authData` key is not included when an [=attestation object=] is provided in an {{AuthenticatorAssertionResponse}} (i.e. as the result of a {{CredentialsContainer/get()|get()}} operation). That is because the [=authenticator data=] is provided directly in the {{AuthenticatorAssertionResponse/authenticatorData}} member of the {{AuthenticatorAssertionResponse}}. Otherwise, processing of the [=attestation object=] is identical. @@ -4857,7 +4856,7 @@ object=] for a credential. Its format is shown in <a href="#table-attestedCreden </figcaption> </figure> -Attested credential data is always present in any [=authenticator data=] that results from a {{CredentialsContainer/create()|create()}} operation. It MAY be present in an [=authenticator data=] resulting from a {{CredentialsContainer/get()|get()}} operation only if attestation was requested using the {{PublicKeyCredentialRequestOptions/attestation}} parameter. +Attested credential data is always present in any [=authenticator data=] that results from a {{CredentialsContainer/create()|create()}} operation. It MUST be present in an [=authenticator data=] resulting from a {{CredentialsContainer/get()|get()}} operation if, and only if, the {{AuthenticatorAssertionResponse/attestationObject}} attribute is present in the assertion result. #### Examples of `credentialPublicKey` Values Encoded in COSE_Key Format #### {#sctn-encoded-credPubKey-examples} @@ -6976,7 +6975,7 @@ Note that when |nonce| is empty, then the (signed) authenticator extension outpu #### `devicePubKey` Extension Output Verification Procedures #### {#sctn-device-publickey-extension-verification} -Verifying the <code>[=devicePubKey=]</code> extension output is performed by the [=[RP]=] whenever a <i>new</i> [=device public key=] is returned within the extension output. As explained in [[#sctn-device-publickey-extension-usage]], a new [=device public key=] is always returned as a result of a {{CredentialsContainer/create()|navigator.credentials.create()}} call (i.e., as part of a [=registration ceremony=]). In contrast, a new [=device public key=] <i>may</i> be returned as a result of a {{CredentialsContainer/get()|navigator.credentials.get()}} call (i.e., as part of an [=authentication ceremony=]). +Verifying the <code>[=devicePubKey=]</code> extension output is performed by the [=[RP]=] whenever a <i>new</i> [=device public key=] is returned within the extension output. As explained in [[#sctn-device-publickey-extension-usage]], a new [=device public key=] is always returned as a result of a {{CredentialsContainer/create()|navigator.credentials.create()}} call (i.e., as part of a [=registration ceremony=]). In contrast, a {{CredentialsContainer/get()|navigator.credentials.get()}} call (i.e., an authentication ceremony) MAY create and return new [=device public key=], or MAY reuse an existing one. ##### Registration (`create()`) ##### {#sctn-device-publickey-extension-verification-create} From ca1b0c69c2a2e930b25e1d2d7ef2849aef8b9a3a Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Tue, 20 Sep 2022 19:34:25 -0700 Subject: [PATCH 123/131] Add a note to explain how the RP's challenge is included in dpkSig. --- index.bs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.bs b/index.bs index 1c08c2bc9..ee8525a8d 100644 --- a/index.bs +++ b/index.bs @@ -6956,6 +6956,8 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo 1. Let |dpkSig| be the result of signing the [=assertion signature=] [input](#fig-signature) with |devicePrivateKey|. + Note: the [=assertion signature=] [input](#fig-signature), and thus |dpkSig|, covers the [=[RP]=]'s {{PublicKeyCredentialCreationOptions/challenge}} because it includes the [=hash of the serialized client data=]. Thus the [=[RP]=] knows that |dpkSig| is a fresh signature. + 1. Output |dpkSig| as the extension's [=unsigned extension output=]. Note: |dpkSig| cannot be included in the [=authenticator extension output=] because it is returned inside the [=authenticator data=] and that would imply that the signature signs over itself. From 6112877aa5f97c1555f8207ab597a6b67dcb6432 Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Tue, 20 Sep 2022 19:36:04 -0700 Subject: [PATCH 124/131] Remove a horizontal scrollbar on the DPK CDDL. --- index.bs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index ee8525a8d..72c40e288 100644 --- a/index.bs +++ b/index.bs @@ -6857,8 +6857,8 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo ; Whether device-scoped or not, keys are still device-bound. I.e. an ; app-scoped key does not enjoy lesser protection from extraction. - scope: uint .size 1, ; A value of 0x00 means "entire device" ("all apps") scope. - ; 0x01 means "per-app" scope. + scope: uint .size 1, ; A value of 0x00 means "entire device" ("all apps") + ; scope. 0x01 means "per-app" scope. ; Values other than 0x00 or 0x01 are reserved for future ; use. @@ -6873,7 +6873,8 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo ; ; Attestation statement formats define the \`fmt\` and \`attStmt\` members of ; $$attStmtType. - ; Note that \`fmt\` and \`attStmt\` are top-level members of \`attObjForDevicePublicKey\`. + ; Note that \`fmt\` and \`attStmt\` are top-level members of + ; \`attObjForDevicePublicKey\`. ; ; In summary, the \`attStmt\` will (typically) contain: ; (1) a SIGNATURE value calculated (using the attestation private key) From ed0b7797d317fca01c46ee80789df0d83c54d0c7 Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Tue, 20 Sep 2022 19:38:03 -0700 Subject: [PATCH 125/131] Remove now superfluous variable in DPK processing. --- index.bs | 2 -- 1 file changed, 2 deletions(-) diff --git a/index.bs b/index.bs index 72c40e288..feebddc48 100644 --- a/index.bs +++ b/index.bs @@ -6931,8 +6931,6 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo 1. Let |devicePrivateKey| be the newly created or existing [=device private key=]. - 1. Let |clientDataHash| be the [=hash of the serialized client data=]. - 1. Let |randomNonce| be a fresh randomly-generated byte string of 32 bytes maximum length, or a zero length byte string if the authenticator chooses to not generate a nonce. Note: |randomNonce|'s purpose is to randomize the `devicePubKey` extension's [=attestation signature=] value. If this is not done, then the `devicePubKey` extension's [=attestation signature=] value remains constant for all such signatures issued on behalf of this [=user credential=], possibly exposing the [=authenticator=]'s [=attestation private key=] to side-channel attacks. The randomness-generation mechanism should be carefully chosen by the authenticator implementer. From 9bd0e3d55e5f3990a9635320abd06f879f54299b Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Tue, 27 Sep 2022 13:15:16 -0700 Subject: [PATCH 126/131] Apply Shane's suggestions Co-authored-by: Shane Weeden <sbweeden@users.noreply.github.com> --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index feebddc48..0ccaa46b7 100644 --- a/index.bs +++ b/index.bs @@ -2873,7 +2873,7 @@ optionally evidence of [=user consent=] to a specific transaction. [=user handle=]. See [[#sctn-op-get-assertion]]. : <dfn>attestationObject</dfn> - :: This OPTIONAL attribute contains an [=attestation object=], if the [=authenticator=] supports attestation in assertions. The [=attestation object=], if present, includes an [=attestation statement=]. Unlike the {{AuthenticatorAttestationResponse/attestationObject}} in an {{AuthenticatorAssertionResponse}}, it does not contain an `authData` key because the [=authenticator data=] is provided directly in an {{AuthenticatorAssertionResponse}} structure. For more details on attestation, see [[#sctn-attestation]], [[#sctn-attestation-in-assertions]], [[#sctn-generating-an-attestation-object]], and [Figure 6](#fig-attStructs). + :: This OPTIONAL attribute contains an [=attestation object=], if the [=authenticator=] supports attestation in assertions. The [=attestation object=], if present, includes an [=attestation statement=]. Unlike the {{AuthenticatorAttestationResponse/attestationObject}} in an {{AuthenticatorAttestationResponse}}, it does not contain an `authData` key because the [=authenticator data=] is provided directly in an {{AuthenticatorAssertionResponse}} structure. For more details on attestation, see [[#sctn-attestation]], [[#sctn-attestation-in-assertions]], [[#sctn-generating-an-attestation-object]], and [Figure 6](#fig-attStructs). </div> ## Parameters for Credential Generation (dictionary <dfn dictionary>PublicKeyCredentialParameters</dfn>) ## {#dictionary-credential-params} @@ -6759,7 +6759,7 @@ A usage example is thus: Note: [=[RPS]=] need to take care to verify [=device-bound key=] signatures before associating and storing extension output value fields in conjunction with the [=user account=]. See [[#sctn-device-publickey-extension-verification]]. -The weight that [=[RPS]=] give to the presence of a signature from a [=device-bound key=] may be based on information learned from its optional attestation. An attestation can indicate the level of protection that the hardware offers the private key, certifiations for the hardware, etc. +The weight that [=[RPS]=] give to the presence of a signature from a [=device-bound key=] may be based on information learned from its optional attestation. An attestation can indicate the level of protection that the hardware offers the private key, certifications for the hardware, etc. #### Extension Definition #### {#sctn-device-publickey-extension-definition} From 759ce04e597537ca8a49c6b0a8bef71db7dc13d9 Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Tue, 27 Sep 2022 15:15:12 -0700 Subject: [PATCH 127/131] DPK is only valid for backup eligible credentials. (This was discussed at TPAC. --- index.bs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.bs b/index.bs index 0ccaa46b7..4ddac2c72 100644 --- a/index.bs +++ b/index.bs @@ -6910,6 +6910,8 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo :: For both [=authenticatorMakeCredential=] and [=authenticatorGetAssertion=] operations: 1. Create or select the [=public key credential source=] as usual (see [[#sctn-op-make-cred]], or [[#sctn-op-get-assertion]] as appropriate). + 1. If the [=public key credential source=] is not [=backup eligible=] then terminate these processing steps: this extension only applies to [=multi-device credentials=]. + 1. If a [=hardware-bound device key pair=] does not already exist for this {[=public key credential source/id|Credential ID=], [=public key credential source/rpId|RP ID=], [=public key credential source/rpId|userHandle=]} tuple on the [=authenticator=], create it using the same public key algorithm as that used by the [=user credential=]'s [=credential key pair=], otherwise locate the existing [=device-bound key=]. 1. Let |attFormat| be the chosen [=attestation statement format=], and |attAaguid| be a 16-byte value, based on the value of {{AuthenticationExtensionsDevicePublicKeyInputs/attestation}} in the extension input: From 8aa160ceaf8b411b1747889fffc40a0cdb6ef213 Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Tue, 27 Sep 2022 15:15:55 -0700 Subject: [PATCH 128/131] Address Shane's comments. --- index.bs | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/index.bs b/index.bs index 4ddac2c72..c03caefd3 100644 --- a/index.bs +++ b/index.bs @@ -4807,6 +4807,11 @@ understand the characteristics of the [=authenticators=] that they trust, based Attestation is most commonly provided during credential creation. However, if supported by the authenticator and requested by the [=[RP]=] using the {{PublicKeyCredentialRequestOptions/attestation}} parameter, attestation MAY be provided in [=assertions=]. +Attestations in [=assertions=] could be helpful in at least the following situations: + +1. For [=multi-device credentials=], the [=generating authenticator=] may have returned a meaningfully different attestation then the authenticator currently exercising the credential. Thus returning an attestation for each use of the credential allows the [=[RP]=] to observe these changes. +1. If the [=attestation statement format=] involves a 3rd-party attesting to the state of the authenticator, then returning an attestation with each use of the credential allows for the continued good health of the authenticator to be attested. + [=Attestation objects=] provided in an {{AuthenticatorAttestationResponse}} structure (i.e. as the result of a {{CredentialsContainer/create()|create()}} operation) contain at least the three keys shown in [the previous figure](#fig-attStructs): `fmt`, `attStmt`, and `authData`. The `authData` key is not included when an [=attestation object=] is provided in an {{AuthenticatorAssertionResponse}} (i.e. as the result of a {{CredentialsContainer/get()|get()}} operation). That is because the [=authenticator data=] is provided directly in the {{AuthenticatorAssertionResponse/authenticatorData}} member of the {{AuthenticatorAssertionResponse}}. Otherwise, processing of the [=attestation object=] is identical. ### Attested Credential Data ### {#sctn-attested-credential-data} @@ -6963,6 +6968,10 @@ The weight that [=[RPS]=] give to the presence of a signature from a [=device-bo Note: |dpkSig| cannot be included in the [=authenticator extension output=] because it is returned inside the [=authenticator data=] and that would imply that the signature signs over itself. +##### AAGUIDs ##### {#sctn-device-publickey-attestation-aaguid} + +The [=AAGUID=] included in the <code>[=devicePubKey=]</code> extension output, if non-zero, identifies the make or model of hardware that is storing the [=device-bound key=]. This is distinct from the [=AAGUID=] in the [=attested credential data=] of a [=multi-device credential=], which likely identifies something broader since such credentials are not bound to a single device. Thus the two AAGUIDs MAY be different in a single response and either, or both, may be zero depending on the options requested and authenticator behaviour. + ##### Attestation calculations ##### {#sctn-device-publickey-attestation-calculations} When computing attestations, the process in [[#sctn-generating-an-attestation-object]] takes two inputs: `authData` and `hash`. When calculating an attestation for a [=device-bound key=], the typical value for `hash` hashes over the attestation signature itself, which is impossible. Also the attestation of a [=device-bound key=] is potentially used repeatedly, thus may want to be cached. But signing over values that include [=[RP]=]-chosen nonces, like the [=hash of the serialized client data=], makes that impossible. @@ -6978,11 +6987,11 @@ Note that when |nonce| is empty, then the (signed) authenticator extension outpu #### `devicePubKey` Extension Output Verification Procedures #### {#sctn-device-publickey-extension-verification} -Verifying the <code>[=devicePubKey=]</code> extension output is performed by the [=[RP]=] whenever a <i>new</i> [=device public key=] is returned within the extension output. As explained in [[#sctn-device-publickey-extension-usage]], a new [=device public key=] is always returned as a result of a {{CredentialsContainer/create()|navigator.credentials.create()}} call (i.e., as part of a [=registration ceremony=]). In contrast, a {{CredentialsContainer/get()|navigator.credentials.get()}} call (i.e., an authentication ceremony) MAY create and return new [=device public key=], or MAY reuse an existing one. +Verifying the <code>[=devicePubKey=]</code> extension output is performed by the [=[RP]=] whenever a [=device public key=] is returned within the extension output. ##### Registration (`create()`) ##### {#sctn-device-publickey-extension-verification-create} -If the `devicePubKey` 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|. +If the `devicePubKey` 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 `devicePubKey` is acceptable. 1. Let |attObjForDevicePublicKey| be the value of the {{AuthenticationExtensionsClientOutputs/devicePubKey}} member of |clientExtensionResults|. @@ -6991,19 +7000,17 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/create( 1. Verify that {{AuthenticationExtensionsDevicePublicKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) (i.e. `authData` and `hash`) by the [=device public key=] |dpk|. (The signature algorithm is the same as for the [=user credential=].) -1. Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. +1. Optionally, if attestation was requested and the [=[RP]=] wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. [=[RP]=] policy may specifiy which attestations are acceptable. Note: If |fmt|'s value is "[=none=]" there is no attestation signature to verify. -1. Store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. - -Issue(w3c/webauthn#1711): Storing this information ought to only occur if the overall registration operation is successful, i.e., crucially, the "encompassing signature" verified correctly. +1. Complete the steps from [[#sctn-registering-a-new-credential]] and, if those steps are successful, store the |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. See also [[#sctn-device-publickey-extension-usage]] for further details. ##### Authentication (`get()`) ##### {#sctn-device-publickey-extension-verification-get} -If the `devicePubKey` 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|. +If the `devicePubKey` 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 `devicePubKey` is acceptable. 1. Let |attObjForDevicePublicKey| be the value of the `devicePubKey` member of |clientExtensionResults|. @@ -7011,12 +7018,10 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of [=authenticator data=]. -1. Verify that {{AuthenticationExtensionsDevicePublicKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) by the [=device public key=] |dpk|. (The signature algorithm is the same as for the [=user credential=].) +1. Verify that {{AuthenticationExtensionsDevicePublicKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) (i.e. `authData` and `hash`) by the [=device public key=] |dpk|. (The signature algorithm is the same as for the [=user credential=].) 1. If the [=[RP]=]'s [=user account=] mapped to the <code>|credential|.{{Credential/id}}</code> in play (i.e., for the user being authenticated) holds `aaguid`, `dpk` and `scope` values corresponding to the extracted |attObjForDevicePublicKey| fields, then perform binary equality checks between the corresponding stored values and the extracted field values. The [=[RP]=] MAY have more than one set of {`aaguid`, `dpk`, `scope`} values mapped to the [=user account=] and <code>|credential|.{{Credential/id}}</code> pair and each set MUST be checked. - Issue(w3c/webauthn#1711): In the below steps, where storing the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values is called for, it ought to only occur if the overall authentication operation is successful, i.e., crucially, the "encompassing signature" verified correctly. - If the above set of binary equality checks resulted in: <dl class="switch"> @@ -7036,7 +7041,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n Note: This authenticator is not generating a fresh per-response random nonce. : unsuccessful - :: Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. + :: Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. [=[RP]=] policy may specifiy which attestations are acceptable. If the result is: @@ -7056,16 +7061,16 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n <dl class="switch"> : If |fmt|'s value is "none": - :: There is no attestation signature to verify and this is a new device. Store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. Terminate these verification steps. + :: There is no attestation signature to verify and this is a new device. Unless [=[RP]=] policy specifies that this attestation is unacceptable, store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. Terminate these verification steps. : Otherwise: - :: Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. + :: Optionally, if attestation was requested and the RP wishes to verify it, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. [=[RP]=] policy may specifiy which attestations are acceptable. If the result is: <dl class="switch"> : successful - :: This is a new [=device public key=] signifying a new device. Store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. Terminate these verification steps. + :: This is a new [=device public key=] signifying a new device. Complete the steps in [[#sctn-verifying-assertion]] and, if those steps are successful, store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. Terminate these verification steps. : unsuccessful :: Some form of error has occurred. It is indeterminate whether this is a valid new device. Terminate these verification steps. @@ -7077,15 +7082,15 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/get()|n 1. Otherwise, the [=[RP]=] does not have |attObjForDevicePublicKey| fields presently mapped to this [=user account=] and <code>|credential|.{{Credential/id}}</code> pair: - 1. If |fmt|'s value is "none" there is no attestation signature to verify. Store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. Terminate these verification steps. + 1. If |fmt|'s value is "none" there is no attestation signature to verify. Complete the steps in [[#sctn-verifying-assertion]] and, if those steps are successful, store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. Terminate these verification steps. - 1. Otherwise, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. + 1. Otherwise, verify that |attStmt| is a correct [=attestation statement=], conveying a valid [=attestation signature=], by using the [=attestation statement format=] |fmt|'s [=verification procedure=] given |attStmt|. See [[#sctn-device-publickey-attestation-calculations]]. [=[RP]=] policy may specifiy which attestations are acceptable. If the result is: <dl class="switch"> : successful - :: This is the first [=device public key=]&mdash;and thus device&mdash;mapped to this [=user account=] and <code>|credential|.{{Credential/id}}</code> pair. Store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. Terminate these verification steps. + :: This is the first [=device public key=]&mdash;and thus device&mdash;mapped to this [=user account=] and <code>|credential|.{{Credential/id}}</code> pair. Complete the steps in [[#sctn-verifying-assertion]] and, if those steps are successful, store the extracted |aaguid|, |dpk|, |scope|, |fmt|, |attStmt| values indexed to the <code>|credential|.{{Credential/id}}</code> in the [=user account=]. Terminate these verification steps. : unsuccessful :: Some form of error has occurred. It is indeterminate whether this is a valid new device. Terminate these verification steps. From bff403d8cb36d97fdf19021ea5e979e407d59cb9 Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Fri, 30 Sep 2022 17:15:18 -0700 Subject: [PATCH 129/131] s/then/than, noticed by Shane. --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index c03caefd3..a59d6ee95 100644 --- a/index.bs +++ b/index.bs @@ -4809,7 +4809,7 @@ Attestation is most commonly provided during credential creation. However, if su Attestations in [=assertions=] could be helpful in at least the following situations: -1. For [=multi-device credentials=], the [=generating authenticator=] may have returned a meaningfully different attestation then the authenticator currently exercising the credential. Thus returning an attestation for each use of the credential allows the [=[RP]=] to observe these changes. +1. For [=multi-device credentials=], the [=generating authenticator=] may have returned a meaningfully different attestation than the authenticator currently exercising the credential. Thus returning an attestation for each use of the credential allows the [=[RP]=] to observe these changes. 1. If the [=attestation statement format=] involves a 3rd-party attesting to the state of the authenticator, then returning an attestation with each use of the credential allows for the continued good health of the authenticator to be attested. [=Attestation objects=] provided in an {{AuthenticatorAttestationResponse}} structure (i.e. as the result of a {{CredentialsContainer/create()|create()}} operation) contain at least the three keys shown in [the previous figure](#fig-attStructs): `fmt`, `attStmt`, and `authData`. The `authData` key is not included when an [=attestation object=] is provided in an {{AuthenticatorAssertionResponse}} (i.e. as the result of a {{CredentialsContainer/get()|get()}} operation). That is because the [=authenticator data=] is provided directly in the {{AuthenticatorAssertionResponse/authenticatorData}} member of the {{AuthenticatorAssertionResponse}}. Otherwise, processing of the [=attestation object=] is identical. From fba2725dd9c3e6c8b40e91eefd17a488af2f498e Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Tue, 4 Oct 2022 19:02:44 -0700 Subject: [PATCH 130/131] Add missing blank line. Thanks Emil. --- index.bs | 1 + 1 file changed, 1 insertion(+) diff --git a/index.bs b/index.bs index a59d6ee95..a52d4b122 100644 --- a/index.bs +++ b/index.bs @@ -6996,6 +6996,7 @@ If the `devicePubKey` extension was included on a {{CredentialsContainer/create( 1. Let |attObjForDevicePublicKey| be the value of the {{AuthenticationExtensionsClientOutputs/devicePubKey}} member of |clientExtensionResults|. 1. Verify that |attObjForDevicePublicKey| is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields: |aaguid|, |dpk|, |scope|, |nonce|, |fmt|, |attStmt|. + Note: The latter |attObjForDevicePublicKey| fields are referenced exclusively in the below steps and are not to be confused with other fields with the same names in other portions of the top-level [=attestation object=]. 1. Verify that {{AuthenticationExtensionsDevicePublicKeyOutputs/signature}} is a valid signature over the [=assertion signature=] [input](#fig-signature) (i.e. `authData` and `hash`) by the [=device public key=] |dpk|. (The signature algorithm is the same as for the [=user credential=].) From f7808700683c57196eb77f8342ac8413c7091259 Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Thu, 6 Oct 2022 17:18:07 -0700 Subject: [PATCH 131/131] Resolve last comment. This resolves https://github.com/w3c/webauthn/pull/1663/files#r790893167 but including the suggested wording. (Tweaked to make bikeshed happy.) --- index.bs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.bs b/index.bs index a52d4b122..59f447c21 100644 --- a/index.bs +++ b/index.bs @@ -1126,6 +1126,8 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S :: A [=hardware-bound device key pair=], also known as a [=device-bound key=], is an [=authenticator=]-, [=[RP]=]-, and [=user credential=]-specific public key pair created upon a [=[RP]=]'s request via the [=devicePubKey=] [=WebAuthn extension=]. The [=authenticator=] that a [=hardware-bound device key pair=] is created upon guarantees that the [=device private key=] is securely stored in hardware, i.e., it is unextractable. See also [[#sctn-device-publickey-extension]]. + Note: All guarantees about the operation of an [=authenticator=] operation rely on [=attestation=]. In particular, [=[RPS]=] MUST NOT rely on the above guarantee of unextractability unless supported by a valid, trusted [=attestation statement=]. + : <dfn>Generating Authenticator</dfn> :: The Generating Authenticator is the authenticator involved in the [=authenticatorMakeCredential=] operation resulting in the creation of a given [=public key credential source=]. The [=generating authenticator=] is the same as the [=managing authenticator=]