From 24359a14f2098d260f7b8529d38fe6346fed2326 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Wed, 4 Jan 2023 13:17:04 -0800 Subject: [PATCH 1/7] Don't be so strict about uv with the PRF extension. Authenticators may have different PRFs for the UV and non-UV case. Thus setting uv=preferred during an assertion is fraught: it doesn't fully specify which PRF to use. However, while implementing this, I ended up feeling that the prohibition on using uv=preferred was too strong. Sites may reasonably want to use uv=preferred and to take advantage of available PRF results. If the evaluation points are global then this isn't so silly as to justify a prohibition, I suspect. --- index.bs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 6e445109e..307183f1a 100644 --- a/index.bs +++ b/index.bs @@ -6547,7 +6547,7 @@ At this time, one [=credential property=] is defined: the [=resident key credent This [=client extension|client=] [=registration extension=] and [=authentication extension=] allows a [=[RP]=] to evaluate outputs from a pseudo-random function (PRF) associated with a [=credential=]. The PRFs provided by this extension map from {{USVString}}s of any length to 32-byte {{ArrayBuffer}}s. -When requested, if a [=credential=] is created on an [=authenticator=] that supports this extension then either one or two freshly-seeded PRFs are associated with it. If two PRFs are associated then, when outputs are requested, one PRF is evaluated if [=user verification=] is performed and the other is evaluated if only a [=test of user presence|user presence test=] is performed. It is the responsibility of the [=[RP]=] to set the {{PublicKeyCredentialRequestOptions/userVerification}} parameter accordingly: [=[RPS]=] SHOULD *consistently* use either the {{UserVerificationRequirement/required}} or {{UserVerificationRequirement/discouraged}} values of {{UserVerificationRequirement}} when using this extension, otherwise the outputs may vary for a given input. To avoid mistakes, the default value of {{UserVerificationRequirement/preferred}} is prohibited when using this extension during [=assertion=] because that could cause the PRF used to vary between operations if [=user verification=] is later enabled on an [=authenticator=]. +When requested, if a [=credential=] is created on an [=authenticator=] that supports this extension then either one or two freshly-seeded PRFs are associated with it. If two PRFs are associated then, when outputs are requested, one PRF is evaluated if [=user verification=] is performed and the other is evaluated if only a [=test of user presence|user presence test=] is performed. It is the responsibility of the [=[RP]=] to set the {{PublicKeyCredentialRequestOptions/userVerification}} parameter accordingly: [=[RPS]=] SHOULD *consistently* use either the {{UserVerificationRequirement/required}} or {{UserVerificationRequirement/discouraged}} values of {{UserVerificationRequirement}} when using this extension, otherwise the outputs may vary for a given input. For example, if this extension is used with {{PublicKeyCredentialRequestOptions/userVerification}} set to {{UserVerificationRequirement/preferred}} and an authenticator that doesn't have [=user verification=] configured, then later configuring user verification on that authenticator may cause the PRF outputs to change. As a motivating example, PRF outputs could be used as symmetric keys to encrypt user data. Such encrypted data would be inaccessible without the ability to get assertions from the associated [=credential=]. By using the provision below to evaluate the PRF at two inputs in a single [=assertion=] operation, the encryption key could be periodically rotated during [=assertions=] by choosing a fresh, random input and reencrypting under the new output. If the evaluation inputs are unpredictable then even an attacker who could satisfy [=user verification=], and who had time-limited access to the authenticator, could not learn the encryption key without also knowing the correct PRF input. @@ -6600,7 +6600,6 @@ Note: If PRF results are obtained during [=registration=] then the [=[RP]=] MUST : Client extension processing ([=authentication extension|authentication=]) :: - 1. If {{PublicKeyCredentialRequestOptions/userVerification}} has the value {{UserVerificationRequirement/preferred}}, return a {{DOMException}} whose name is “{{NotSupportedError}}”. 1. If {{AuthenticationExtensionsPRFInputs/evalByCredential}} is not empty but {{PublicKeyCredentialRequestOptions/allowCredentials}} is empty, return a {{DOMException}} whose name is “{{NotSupportedError}}”. 1. Initialize the {{AuthenticationExtensionsClientOutputs/prf}} extension output to an empty dictionary. 1. Let |ev| be null, and try to find any applicable PRF input(s): From cac92424af0313f36b591089b331300850854e1d Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Fri, 13 Jan 2023 07:20:49 -0800 Subject: [PATCH 2/7] Update wording to reflect discussions. --- index.bs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 307183f1a..995fce00d 100644 --- a/index.bs +++ b/index.bs @@ -6547,7 +6547,7 @@ At this time, one [=credential property=] is defined: the [=resident key credent This [=client extension|client=] [=registration extension=] and [=authentication extension=] allows a [=[RP]=] to evaluate outputs from a pseudo-random function (PRF) associated with a [=credential=]. The PRFs provided by this extension map from {{USVString}}s of any length to 32-byte {{ArrayBuffer}}s. -When requested, if a [=credential=] is created on an [=authenticator=] that supports this extension then either one or two freshly-seeded PRFs are associated with it. If two PRFs are associated then, when outputs are requested, one PRF is evaluated if [=user verification=] is performed and the other is evaluated if only a [=test of user presence|user presence test=] is performed. It is the responsibility of the [=[RP]=] to set the {{PublicKeyCredentialRequestOptions/userVerification}} parameter accordingly: [=[RPS]=] SHOULD *consistently* use either the {{UserVerificationRequirement/required}} or {{UserVerificationRequirement/discouraged}} values of {{UserVerificationRequirement}} when using this extension, otherwise the outputs may vary for a given input. For example, if this extension is used with {{PublicKeyCredentialRequestOptions/userVerification}} set to {{UserVerificationRequirement/preferred}} and an authenticator that doesn't have [=user verification=] configured, then later configuring user verification on that authenticator may cause the PRF outputs to change. +When requested, if a [=credential=] is created on an [=authenticator=] that supports this extension then either one or two freshly-seeded PRFs are associated with it. If two PRFs are associated then, when outputs are requested, one PRF is evaluated if [=user verification=] is performed and the other is evaluated if only a [=test of user presence|user presence test=] is performed. It is the responsibility of the [=[RP]=] to set the {{PublicKeyCredentialRequestOptions/userVerification}} parameter accordingly: [=[RPS]=] SHOULD *consistently* use either the {{UserVerificationRequirement/required}} or {{UserVerificationRequirement/discouraged}} values of {{UserVerificationRequirement}} when using this extension, otherwise the outputs may vary for a given input. For example, if this extension is used with {{PublicKeyCredentialRequestOptions/userVerification}} set to {{UserVerificationRequirement/preferred}} and an authenticator that doesn't have [=user verification=] configured, then later configuring user verification on that authenticator may cause the PRF outputs to change. Because of this, when creating a credential with this extension where {{PublicKeyCredentialRequestOptions/userVerification}} set to {{UserVerificationRequirement/preferred}}, the [=client platform=] SHOULD configure the authenticator for [=user verification=] if it is capable of it as that avoids the PRF changing in the future. At assertion time, [=[RPS]=] SHOULD check the UV bit in the [=flags=] to confirm that the expected PRF was evaluated. As a motivating example, PRF outputs could be used as symmetric keys to encrypt user data. Such encrypted data would be inaccessible without the ability to get assertions from the associated [=credential=]. By using the provision below to evaluate the PRF at two inputs in a single [=assertion=] operation, the encryption key could be periodically rotated during [=assertions=] by choosing a fresh, random input and reencrypting under the new output. If the evaluation inputs are unpredictable then even an attacker who could satisfy [=user verification=], and who had time-limited access to the authenticator, could not learn the encryption key without also knowing the correct PRF input. @@ -6601,6 +6601,7 @@ Note: If PRF results are obtained during [=registration=] then the [=[RP]=] MUST : Client extension processing ([=authentication extension|authentication=]) :: 1. If {{AuthenticationExtensionsPRFInputs/evalByCredential}} is not empty but {{PublicKeyCredentialRequestOptions/allowCredentials}} is empty, return a {{DOMException}} whose name is “{{NotSupportedError}}”. + 1. If any [=map/key=] in {{AuthenticationExtensionsPRFInputs/evalByCredential}} is the empty string, or is not a valid [=base64url encoding=], return a {{DOMException}} whose name is “{{NotSupportedError}}”. 1. Initialize the {{AuthenticationExtensionsClientOutputs/prf}} extension output to an empty dictionary. 1. Let |ev| be null, and try to find any applicable PRF input(s): 1. If {{AuthenticationExtensionsPRFInputs/evalByCredential}} is present and [=map/exists|contains=] an [=map/entry=] whose [=map/key=] is the [=base64url encoding=] of the [=credential ID=] that will be returned, let |ev| be the [=map/value=] of that entry. From 8680f5861f410f9ba015cd8b4ba4b778071469a6 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Tue, 24 Jan 2023 15:41:32 -0800 Subject: [PATCH 3/7] Apply suggestion from emlun Co-authored-by: Emil Lundberg --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 995fce00d..b4007f8e2 100644 --- a/index.bs +++ b/index.bs @@ -6547,7 +6547,7 @@ At this time, one [=credential property=] is defined: the [=resident key credent This [=client extension|client=] [=registration extension=] and [=authentication extension=] allows a [=[RP]=] to evaluate outputs from a pseudo-random function (PRF) associated with a [=credential=]. The PRFs provided by this extension map from {{USVString}}s of any length to 32-byte {{ArrayBuffer}}s. -When requested, if a [=credential=] is created on an [=authenticator=] that supports this extension then either one or two freshly-seeded PRFs are associated with it. If two PRFs are associated then, when outputs are requested, one PRF is evaluated if [=user verification=] is performed and the other is evaluated if only a [=test of user presence|user presence test=] is performed. It is the responsibility of the [=[RP]=] to set the {{PublicKeyCredentialRequestOptions/userVerification}} parameter accordingly: [=[RPS]=] SHOULD *consistently* use either the {{UserVerificationRequirement/required}} or {{UserVerificationRequirement/discouraged}} values of {{UserVerificationRequirement}} when using this extension, otherwise the outputs may vary for a given input. For example, if this extension is used with {{PublicKeyCredentialRequestOptions/userVerification}} set to {{UserVerificationRequirement/preferred}} and an authenticator that doesn't have [=user verification=] configured, then later configuring user verification on that authenticator may cause the PRF outputs to change. Because of this, when creating a credential with this extension where {{PublicKeyCredentialRequestOptions/userVerification}} set to {{UserVerificationRequirement/preferred}}, the [=client platform=] SHOULD configure the authenticator for [=user verification=] if it is capable of it as that avoids the PRF changing in the future. At assertion time, [=[RPS]=] SHOULD check the UV bit in the [=flags=] to confirm that the expected PRF was evaluated. +When requested, if a [=credential=] is created on an [=authenticator=] that supports this extension then either one or two freshly-seeded PRFs are associated with it. If two PRFs are associated then, when outputs are requested, one PRF is evaluated if [=user verification=] is performed and the other is evaluated if only a [=test of user presence|user presence test=] is performed. It is the responsibility of the [=[RP]=] to set the {{PublicKeyCredentialRequestOptions/userVerification}} parameter accordingly: [=[RPS]=] SHOULD *consistently* use either the {{UserVerificationRequirement/required}} or {{UserVerificationRequirement/discouraged}} values of {{UserVerificationRequirement}} when using this extension, otherwise the outputs may vary for a given input. For example, if this extension is used with {{PublicKeyCredentialRequestOptions/userVerification}} set to {{UserVerificationRequirement/preferred}} and an authenticator that doesn't have [=user verification=] configured, then later configuring user verification on that authenticator may cause the PRF outputs to change. Because of this, when creating a credential with this extension where {{PublicKeyCredentialRequestOptions/userVerification}} is set to {{UserVerificationRequirement/preferred}}, the [=client platform=] SHOULD configure the authenticator for [=user verification=] if it is capable of it as that avoids the PRF changing in the future. At assertion time, [=[RPS]=] SHOULD check the [=authData/flags/UV=] [=flag=] to confirm that the expected PRF was evaluated. As a motivating example, PRF outputs could be used as symmetric keys to encrypt user data. Such encrypted data would be inaccessible without the ability to get assertions from the associated [=credential=]. By using the provision below to evaluate the PRF at two inputs in a single [=assertion=] operation, the encryption key could be periodically rotated during [=assertions=] by choosing a fresh, random input and reencrypting under the new output. If the evaluation inputs are unpredictable then even an attacker who could satisfy [=user verification=], and who had time-limited access to the authenticator, could not learn the encryption key without also knowing the correct PRF input. From 414de68c8d3f3d458c1c600dab4f450615c2dad4 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Tue, 24 Jan 2023 15:44:04 -0800 Subject: [PATCH 4/7] Switch to SyntaxError --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index b4007f8e2..901ec916e 100644 --- a/index.bs +++ b/index.bs @@ -6601,7 +6601,7 @@ Note: If PRF results are obtained during [=registration=] then the [=[RP]=] MUST : Client extension processing ([=authentication extension|authentication=]) :: 1. If {{AuthenticationExtensionsPRFInputs/evalByCredential}} is not empty but {{PublicKeyCredentialRequestOptions/allowCredentials}} is empty, return a {{DOMException}} whose name is “{{NotSupportedError}}”. - 1. If any [=map/key=] in {{AuthenticationExtensionsPRFInputs/evalByCredential}} is the empty string, or is not a valid [=base64url encoding=], return a {{DOMException}} whose name is “{{NotSupportedError}}”. + 1. If any [=map/key=] in {{AuthenticationExtensionsPRFInputs/evalByCredential}} is the empty string, or is not a valid [=base64url encoding=], return a {{DOMException}} whose name is “{{SyntaxError}}”. 1. Initialize the {{AuthenticationExtensionsClientOutputs/prf}} extension output to an empty dictionary. 1. Let |ev| be null, and try to find any applicable PRF input(s): 1. If {{AuthenticationExtensionsPRFInputs/evalByCredential}} is present and [=map/exists|contains=] an [=map/entry=] whose [=map/key=] is the [=base64url encoding=] of the [=credential ID=] that will be returned, let |ev| be the [=map/value=] of that entry. From 5ebc25721158cc45a985e171121911da87d64994 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Mon, 13 Feb 2023 15:17:58 -0800 Subject: [PATCH 5/7] Specify that there's only one PRF, and it's the UV one. Fixes #1851 --- index.bs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/index.bs b/index.bs index 901ec916e..095e98e46 100644 --- a/index.bs +++ b/index.bs @@ -6545,14 +6545,14 @@ At this time, one [=credential property=] is defined: the [=resident key credent ### Pseudo-random function extension (prf) ### {#prf-extension} -This [=client extension|client=] [=registration extension=] and [=authentication extension=] allows a [=[RP]=] to evaluate outputs from a pseudo-random function (PRF) associated with a [=credential=]. The PRFs provided by this extension map from {{USVString}}s of any length to 32-byte {{ArrayBuffer}}s. - -When requested, if a [=credential=] is created on an [=authenticator=] that supports this extension then either one or two freshly-seeded PRFs are associated with it. If two PRFs are associated then, when outputs are requested, one PRF is evaluated if [=user verification=] is performed and the other is evaluated if only a [=test of user presence|user presence test=] is performed. It is the responsibility of the [=[RP]=] to set the {{PublicKeyCredentialRequestOptions/userVerification}} parameter accordingly: [=[RPS]=] SHOULD *consistently* use either the {{UserVerificationRequirement/required}} or {{UserVerificationRequirement/discouraged}} values of {{UserVerificationRequirement}} when using this extension, otherwise the outputs may vary for a given input. For example, if this extension is used with {{PublicKeyCredentialRequestOptions/userVerification}} set to {{UserVerificationRequirement/preferred}} and an authenticator that doesn't have [=user verification=] configured, then later configuring user verification on that authenticator may cause the PRF outputs to change. Because of this, when creating a credential with this extension where {{PublicKeyCredentialRequestOptions/userVerification}} is set to {{UserVerificationRequirement/preferred}}, the [=client platform=] SHOULD configure the authenticator for [=user verification=] if it is capable of it as that avoids the PRF changing in the future. At assertion time, [=[RPS]=] SHOULD check the [=authData/flags/UV=] [=flag=] to confirm that the expected PRF was evaluated. +This [=client extension|client=] [=registration extension=] and [=authentication extension=] allows a [=[RP]=] to evaluate outputs from a pseudo-random function (PRF) associated with a [=credential=]. The PRFs provided by this extension map from {{BufferSource}}s of any length to 32-byte {{ArrayBuffer}}s. As a motivating example, PRF outputs could be used as symmetric keys to encrypt user data. Such encrypted data would be inaccessible without the ability to get assertions from the associated [=credential=]. By using the provision below to evaluate the PRF at two inputs in a single [=assertion=] operation, the encryption key could be periodically rotated during [=assertions=] by choosing a fresh, random input and reencrypting under the new output. If the evaluation inputs are unpredictable then even an attacker who could satisfy [=user verification=], and who had time-limited access to the authenticator, could not learn the encryption key without also knowing the correct PRF input. This extension is implemented on top of the [[FIDO-CTAP]] `hmac-secret` extension. It is a separate [=client extension=] because `hmac-secret` requires that inputs and outputs be encrypted in a manner that only the user agent can perform, and to provide separation between uses by WebAuthn and any uses by the underlying platform. This separation is achieved by hashing the provided PRF inputs with a context string to prevent evaluation of the PRFs for arbitrary inputs. +The `hmac-secret` extension provides two PRFs per credential: one which is used for requests where [=user verification=] is performed and another for all other requests. This extension only exposes a single PRF per credential and, when implementing on top of `hmac-secret`, that PRF MUST be the one used for when [=user verification=] is performed. This overrides the {{UserVerificationRequirement}} if neccessary. + Note: this extension may be implemented for [=authenticators=] that do not use [[FIDO-CTAP]] so long as the behavior observed by a [=[RP]=] is identical. : Extension identifier @@ -6564,8 +6564,8 @@ Note: this extension may be implemented for [=authenticators=] that do not use [ : Client extension input :: dictionary AuthenticationExtensionsPRFValues { - required ArrayBuffer first; - ArrayBuffer second; + required BufferSource first; + BufferSource second; }; dictionary AuthenticationExtensionsPRFInputs { From 3b83189b8f30f6fff36d0bd4b1ef2bcf53e148c6 Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@imperialviolet.org> Date: Thu, 23 Feb 2023 15:33:52 -0800 Subject: [PATCH 6/7] Require that evalByCredential keys match a credential from the allowList, if any. They are superfluous if they don't. --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 095e98e46..39998d8ab 100644 --- a/index.bs +++ b/index.bs @@ -6601,7 +6601,7 @@ Note: If PRF results are obtained during [=registration=] then the [=[RP]=] MUST : Client extension processing ([=authentication extension|authentication=]) :: 1. If {{AuthenticationExtensionsPRFInputs/evalByCredential}} is not empty but {{PublicKeyCredentialRequestOptions/allowCredentials}} is empty, return a {{DOMException}} whose name is “{{NotSupportedError}}”. - 1. If any [=map/key=] in {{AuthenticationExtensionsPRFInputs/evalByCredential}} is the empty string, or is not a valid [=base64url encoding=], return a {{DOMException}} whose name is “{{SyntaxError}}”. + 1. If any [=map/key=] in {{AuthenticationExtensionsPRFInputs/evalByCredential}} is the empty string, or is not a valid [=base64url encoding=], or does not match an element of {{PublicKeyCredentialRequestOptions/allowCredentials}} after performing [=base64url encoding|base64url decoding=], then return a {{DOMException}} whose name is “{{SyntaxError}}”. 1. Initialize the {{AuthenticationExtensionsClientOutputs/prf}} extension output to an empty dictionary. 1. Let |ev| be null, and try to find any applicable PRF input(s): 1. If {{AuthenticationExtensionsPRFInputs/evalByCredential}} is present and [=map/exists|contains=] an [=map/entry=] whose [=map/key=] is the [=base64url encoding=] of the [=credential ID=] that will be returned, let |ev| be the [=map/value=] of that entry. From 6478874bf740d1daf4f5fba5ddfab602ca5a8c2b Mon Sep 17 00:00:00 2001 From: Adam Langley <agl@google.com> Date: Thu, 16 Mar 2023 09:43:32 -0700 Subject: [PATCH 7/7] Apply emlun's suggestions from code review 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 39998d8ab..55e0b23e3 100644 --- a/index.bs +++ b/index.bs @@ -6545,7 +6545,7 @@ At this time, one [=credential property=] is defined: the [=resident key credent ### Pseudo-random function extension (<dfn>prf</dfn>) ### {#prf-extension} -This [=client extension|client=] [=registration extension=] and [=authentication extension=] allows a [=[RP]=] to evaluate outputs from a pseudo-random function (PRF) associated with a [=credential=]. The PRFs provided by this extension map from {{BufferSource}}s of any length to 32-byte {{ArrayBuffer}}s. +This [=client extension|client=] [=registration extension=] and [=authentication extension=] allows a [=[RP]=] to evaluate outputs from a pseudo-random function (PRF) associated with a [=credential=]. The PRFs provided by this extension map from {{BufferSource}}s of any length to 32-byte {{BufferSource}}s. As a motivating example, PRF outputs could be used as symmetric keys to encrypt user data. Such encrypted data would be inaccessible without the ability to get assertions from the associated [=credential=]. By using the provision below to evaluate the PRF at two inputs in a single [=assertion=] operation, the encryption key could be periodically rotated during [=assertions=] by choosing a fresh, random input and reencrypting under the new output. If the evaluation inputs are unpredictable then even an attacker who could satisfy [=user verification=], and who had time-limited access to the authenticator, could not learn the encryption key without also knowing the correct PRF input. @@ -6601,7 +6601,7 @@ Note: If PRF results are obtained during [=registration=] then the [=[RP]=] MUST : Client extension processing ([=authentication extension|authentication=]) :: 1. If {{AuthenticationExtensionsPRFInputs/evalByCredential}} is not empty but {{PublicKeyCredentialRequestOptions/allowCredentials}} is empty, return a {{DOMException}} whose name is “{{NotSupportedError}}”. - 1. If any [=map/key=] in {{AuthenticationExtensionsPRFInputs/evalByCredential}} is the empty string, or is not a valid [=base64url encoding=], or does not match an element of {{PublicKeyCredentialRequestOptions/allowCredentials}} after performing [=base64url encoding|base64url decoding=], then return a {{DOMException}} whose name is “{{SyntaxError}}”. + 1. If any [=map/key=] in {{AuthenticationExtensionsPRFInputs/evalByCredential}} is the empty string, or is not a valid [=base64url encoding=], or does not equal the {{PublicKeyCredentialDescriptor/id}} of some element of {{PublicKeyCredentialRequestOptions/allowCredentials}} after performing [=base64url encoding|base64url decoding=], then return a {{DOMException}} whose name is “{{SyntaxError}}”. 1. Initialize the {{AuthenticationExtensionsClientOutputs/prf}} extension output to an empty dictionary. 1. Let |ev| be null, and try to find any applicable PRF input(s): 1. If {{AuthenticationExtensionsPRFInputs/evalByCredential}} is present and [=map/exists|contains=] an [=map/entry=] whose [=map/key=] is the [=base64url encoding=] of the [=credential ID=] that will be returned, let |ev| be the [=map/value=] of that entry.