Review privacy concerns around error conditions
emlun opened this issue · 3 comments
The spec contains privacy concerns such as this in the final steps of §5.1.3. Create a New Credential and §5.1.4. Use an Existing Credential to Make an Assertion:
Throw a "NotAllowedError" DOMException. In order to prevent information leak that could identify the user without consent, this step MUST NOT be executed before lifetimeTimer has expired. See § 14.5.1 Registration Ceremony Privacy for details.
These privacy concerns were written for an architecture of these operations that is no longer relevant, and may in fact not have been relevant even at the time the privacy concerns were written (see: #2095 (comment)). We should review whether these privacy concerns are still valid, or if they can be shown to be redundant under the current specification of these operations and thus removed. This would simplify initiatives such as #2096 and #2095.
Proposed Change
Review the validity of these privacy concerns. If they can be shown redundant, delete the prohibition against returning certain errors due to these privacy concerns.
I aim to do a thorough review of this by the 2024-09-11 WG meeting.
Enumeration of errors
The following errors may be thrown during create()
as of commit a871f79 (2024-09-04):
During init:
- (If sameOriginWithAncestors is false) and (If options.mediation is present with the value conditional): Throw a "NotAllowedError" DOMException
- (If sameOriginWithAncestors is false) and (If the relevant global object, as determined by the calling create() implementation, does not have transient activation:): Throw a "NotAllowedError" DOMException
- (If the length of pkOptions.user.id is not between 1 and 64 bytes (inclusive)): throw a TypeError
- (If callerOrigin is an opaque origin): throw a "NotAllowedError" DOMException
- (If effective domain is not a valid domain): throw a "SecurityError" DOMException
- (If pkOptions.rp.id is present) and (If pkOptions.rp.id is not a registrable domain suffix of and is not equal to effectiveDomain) and (if the client supports related origin requests) and (Run the related origins validation procedure with arguments callerOrigin and rpIdRequested. If the result is false): throw a "SecurityError" DOMException
- (If pkOptions.rp.id is present) and (If pkOptions.rp.id is not a registrable domain suffix of and is not equal to effectiveDomain) and (if the client does not support related origin requests): throw a "SecurityError" DOMException
- (If pkOptions.pubKeyCredParams’s size is non-zero) and (after filtering pkOptions.pubKeyCredParams: If credTypesAndPubKeyAlgs is empty): throw a "NotSupportedError" DOMException
- (Extensions may throw unspecified errors)
- (If options.signal is present and aborted): throw the options.signal’s abort reason
- (If options.mediation is present with the value conditional) and (If the user agent has not recently mediated an authentication, the origin of said authentication is not callerOrigin, or the user does not consent to this type of credential creation): throw a "NotAllowedError" DOMException.
During lifetimeTimer wait loop:
- (If the user exercises a user agent user-interface option to cancel the process): throw a "NotAllowedError" DOMException
- (If options.signal is present and aborted): throw the options.signal’s abort reason
- (If pkOptions.authenticatorSelection.userVerification is set to required) and (If options.mediation is set to conditional and user verification cannot be collected during the ceremony): throw a ConstraintError DOMException
- (If any authenticator returns an error status equivalent to "InvalidStateError"): throw an "InvalidStateError" DOMException
- (If lifetimeTimer expires): throw a "NotAllowedError" DOMException
Rearranging this as a map of errors to causes of that error, and assigning numbers to each for easy reference:
- "ConstraintError" DOMException:
- C1: During lifetimeTimer wait loop: (If pkOptions.authenticatorSelection.userVerification is set to required) and (If options.mediation is set to conditional and user verification cannot be collected during the ceremony)
- "InvalidStateError" DOMException:
- I2: During lifetimeTimer wait loop: (If any authenticator returns an error status equivalent to "InvalidStateError")
- "NotAllowedError" DOMException:
- NA3: During init: (If options.mediation is present with the value conditional) and (If the user agent has not recently mediated an authentication, the origin of said authentication is not callerOrigin, or the user does not consent to this type of credential creation)
- NA4: During init: (If callerOrigin is an opaque origin)
- NA5: During init: (If sameOriginWithAncestors is false) and (If options.mediation is present with the value conditional)
- NA6: During init: (If sameOriginWithAncestors is false) and (If the relevant global object, as determined by the calling create() implementation, does not have transient activation:)
- NA7: During lifetimeTimer wait loop: (If lifetimeTimer expires)
- NA8: During lifetimeTimer wait loop: (If the user exercises a user agent user-interface option to cancel the process)
- "NotSupportedError" DOMException:
- NS9: During init: (If pkOptions.pubKeyCredParams’s size is non-zero) and (after filtering pkOptions.pubKeyCredParams: If credTypesAndPubKeyAlgs is empty)
- "SecurityError" DOMException:
- S10: During init: (If effective domain is not a valid domain)
- S11: During init: (If pkOptions.rp.id is present) and (If pkOptions.rp.id is not a registrable domain suffix of and is not equal to effectiveDomain) and (if the client does not support related origin requests)
- S12: During init: (If pkOptions.rp.id is present) and (If pkOptions.rp.id is not a registrable domain suffix of and is not equal to effectiveDomain) and (if the client supports related origin requests) and (Run the related origins validation procedure with arguments callerOrigin and rpIdRequested. If the result is false)
- TypeError:
- T13: During init: (If the length of pkOptions.user.id is not between 1 and 64 bytes (inclusive))
- (Unspecified error):
- X14: During init: (Extensions may throw unspecified errors)
- X15: During init: (If options.signal is present and aborted)
- X16: During lifetimeTimer wait loop: (If options.signal is present and aborted)
Analysis
-
Concern 1: (NA8) could cause an information leak that could identify the user without consent (§14.5.1. Registration Ceremony Privacy). The information leak occurs if the Relying Party can distinguish between these cases:
- No authenticators are present.
- At least one authenticator is present, and at least one present authenticator is excluded.
Preconditions:
Case (ii) requires that the client has first probed the authenticator for any credentials listed inexcludeCredentials
.Observations:
The only preconditions for (NA8) are that the client successfully runs through the initialization steps 1-22, then offers the user an option to cancel, and the user exercising that option. There is no precondition of probing for excluded credentials.Conclusion:
(NA8) cannot cause an information leak that could identify the user without consent. -
Concern 2: (NA7) and (NA8) should be indistinghishable in order to not facilitate the information leak in Concern 1.
Observations:
(NA7) and (NA8) are already distinguishable, since (NA7) likely occurs aroundoptions.publicKey.timeout
milliseconds aftercreate()
was invoked, especially ifoptions.publicKey.timeout
falls within the recommended reasonable range.Since in Concern 1 we already concluded that (NA8) cannot cause the relevant information leak, the information leak cannot be made worse by (NA7) and (NA8) being distinguishable.
Conclusion:
(NA7) and (NA8) do not need to be indistinguishable. (NA7) can be safely changed to a distinct TimeoutError. -
Remark 3:
(I2) does allow the Relying Party to detect that an excluded credential is available to the user. However, this error has an explicit consent precondition in the authenticator operation: (If looking up descriptor.id in this authenticator returns non-null, and the returned item's RP ID and type match rpEntity.id and excludeCredentialDescriptorList.type respectively) and (If the user confirms consent to create a new credential). If the user does not consent to this, then the authenticator instead returns an error code equivalent to "NotAllowedError", which does not immediately cause an error on the client layer but most likely eventually results in (NA7) or (NA8) instead.
Enumeration of errors
The following errors may be thrown during get()
as of commit a871f79 (2024-09-04):
During init:
- (If callerOrigin is an opaque origin): throw a "NotAllowedError" DOMException
- (If effective domain is not a valid domain): throw a "SecurityError" DOMException
- (If pkOptions.rpId is present) and (If pkOptions.rpId is not a registrable domain suffix of and is not equal to effectiveDomain) and (if the client supports related origin requests) and (Run the related origins validation procedure with arguments callerOrigin and rpIdRequested. If the result is false): throw a "SecurityError" DOMException
- (If pkOptions.rpId is present) and (If pkOptions.rpId is not a registrable domain suffix of and is not equal to effectiveDomain) and (if the client does not support related origin requests): throw a "SecurityError" DOMException
- (Extensions may throw unspecified errors): throw a (Unspecified error)
- (If options.signal is present and aborted): throw the options.signal’s abort reason
During lifetimeTimer wait loop:
- (If lifetimeTimer expires): throw a "NotAllowedError" DOMException
- (If the user exercises a user agent user-interface option to cancel the process): throw a "NotAllowedError" DOMException
- (If options.signal is present and aborted): throw the options.signal’s abort reason
- (If options.mediation is not conditional, issuedRequests is empty, pkOptions.allowCredentials is not empty, and no authenticator will become available for any public key credentials therein) and (Indicate to the user that no eligible credential could be found. When the user acknowledges the dialog): throw a "NotAllowedError" DOMException
Rearranging this as a map of errors to causes of that error, and assigning numbers to each for easy reference:
- "NotAllowedError" DOMException:
- (NA17): During init: (If callerOrigin is an opaque origin)
- (NA18): During lifetimeTimer wait loop: (If lifetimeTimer expires)
- (NA19): During lifetimeTimer wait loop: (If options.mediation is not conditional, issuedRequests is empty, pkOptions.allowCredentials is not empty, and no authenticator will become available for any public key credentials therein) and (When the user acknowledges the dialog)
- (NA20): During lifetimeTimer wait loop: (If the user exercises a user agent user-interface option to cancel the process)
- "SecurityError" DOMException:
- (S21): During init: (If effective domain is not a valid domain)
- (S22): During init: (If pkOptions.rpId is present) and (If pkOptions.rpId is not a registrable domain suffix of and is not equal to effectiveDomain) and (if the client does not support related origin requests)
- (S23): During init: (If pkOptions.rpId is present) and (If pkOptions.rpId is not a registrable domain suffix of and is not equal to effectiveDomain) and (if the client supports related origin requests) and (Run the related origins validation procedure with arguments callerOrigin and rpIdRequested. If the result is false)
- (Unspecified error):
- (X24): During init: (Extensions may throw unspecified errors)
- (X25): During init: (If options.signal is present and aborted)
- (X26): During lifetimeTimer wait loop: (If options.signal is present and aborted)
Analysis
-
Concern 4: (NA20) could cause an information leak that could identify the user without consent (§14.5.2. Authentication Ceremony Privacy). The information leak occurs if the Relying Party can distinguish between these cases:
- A named credential is not available.
- A named credential is available, but the user does not consent to use it.
Preconditions:
Both case (i) and case (ii) require that the client has first probed the authenticator for any credentials listed inallowCredentials
.Observations:
The only preconditions for (NA20) are that the client successfully runs through the initialization steps 1-20, then offers the user an option to cancel, and the user exercising that option. There is no precondition of probing for eligible credentials.Conclusion:
(NA20) cannot cause an information leak that could identify the user without consent. -
Concern 5: (NA18) and (NA20) should be indistinghishable in order to not facilitate the information leak in Concern 4.
Observations:
(NA18) and (NA20) are already distinguishable, since (NA18) likely occurs aroundoptions.publicKey.timeout
milliseconds afterget()
was invoked, especially ifoptions.publicKey.timeout
falls within the recommended reasonable range.Since in Concern 4 we already concluded that (NA20) cannot cause the relevant information leak, the information leak cannot be made worse by (NA18) and (NA20) being distinguishable.
Conclusion:
(NA18) and (NA20) do not need to be indistinguishable. (NA18) can be safely changed to a distinct TimeoutError.