Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Promp for/send TLS client certificates for CORS preflights #1181

Closed
eyeinsky opened this issue Feb 27, 2021 · 2 comments
Closed

Promp for/send TLS client certificates for CORS preflights #1181

eyeinsky opened this issue Feb 27, 2021 · 2 comments

Comments

@eyeinsky
Copy link

(This is a follow-up for the StackOverflow question here[1])

Background

The issue:

  • fetch spec currently doesn't allow sending any credentials during preflights[2]
  • client certificates are considered credentials

In this context, web servers protected with a client-certificate: required (pseudocode) policy won't work in a cross-origin context because preflights will be rejected during the TLS handshake, thus no HTTP will ever happen and the application server won't be able to respond to the preflight.

I.e, the issue is circular (catch-22/chicken-and-egg): TLS lives at a lower lever from HTTP, but CORS is negotiated in HTTP headers. So if setting up TLS itself fails due to not sending client certificates then there is no way for the application to even respond with a policy where sending client certificates is allowed.

Browsers behave differently for preflighted cross-origin requests:

  • Firefox follows the spec and explicitly won't prompt for a certificate, the preflight will fail and the main request is not performed. There is a bug report for this[3], but as the current behavior is per spec, then the current situation won't be changed unless the spec changes.
  • Firefox has recently gotten a new flag, network.cors_preflight.allow_client_cert, which does send the client certificate to work around this issue[4];
  • Chrome (and other Chromium-based browsers like Brave) violate the spec and prompt for a certificate during preflights, thus allowing cross-orign requests on a client-certificates: required-servers. There is also a bug report to change this [5].

(Haven't gotten around to testing Safari)

Argument

Prompt for/send client certificates during preflights.

This should be safe because:

  • a certificate is essentially a public key plus metadata, signed by a certificate authority. This data is OK to "leak" because any TLS handshake with client certificates also requires the private key for the certificate to available at the client (not sent over the wire), thus client certificate authentication can't be performed with the sent (leaked) certificate alone.
  • an OPTIONS HTTP request (which a preflight is) doesn't cause a server to change state on the resource for which the request is for, so it should be safe to perform, even with credentials.

A security expert should verify the first point.

Workarounds, discussion

For context, there are workarounds for the current situation for developers/admins if they have control over the implementation of the front- and back-ends. They could:

  • change to using client-certificate: optional for TLS, so that preflights could get a Access-Control-Allow-Credentials: true header in the response.
    • Using this makes the whole setup less secure as now certificates aren't strictly required, and also gives more room to fail at setting it up correctly, as now some credential-less requests (= preflights) are allowed while others are not.
  • not set any non-safelisted CORS headers,
    • hackish, as you would really like to set whatever headers fit the request
  • use HTTP POST with one of the allowed content-types (application/x-www-form-urlencoded multipart/form-data, text/plain) and either encode their data accordingly, or actually send e.g json still, even though the content-type says something else.
    • hackish, as you should be able to use any content-type you need

References

[1] https://stackoverflow.com/questions/66007054/why-doesnt-cors-preflight-request-prompt-for-or-reuse-a-connection-where-clie
[2] https://fetch.spec.whatwg.org/#cors-protocol-and-credentials
[3] https://bugzilla.mozilla.org/show_bug.cgi?id=1019603
[4] https://bugzilla.mozilla.org/show_bug.cgi?id=1511151
[5] https://bugs.chromium.org/p/chromium/issues/detail?id=775438

@sleevi
Copy link

sleevi commented Feb 27, 2021

The Chrome side is most definitely a bug, and while organizational changes have inhibited us from fixing, we do see it as a bug that can and should be fixed.

Prompting for sub-resources is equally an anti-pattern, in that it's confusing to end users to understand what, why, and how. Browsers have been moving away, particularly for subresources, from prompting the end user for credentials.

The premise that this information is OK to leak is also flawed, because such information very much should be considered sensitive, both in terms of identifying the user to potential network adversaries, and to the end-server.

The problem stems from the fact that, at the core, client certificates are poorly designed and violate a number of good security practices. At a minimum, using optional is the expectation, but as you note, even then, it can be problematic. Proposals have been made (e.g. Secondary Certificates, CATCH) to move this up to the application layer, which is really where they belong.

I think there's a core takeaway here: mTLS is, and has always been, a giant hack. Literally, its introduction in SSL2 was merely a "we might want this", not with any fundamental design considerations, and its interactions with HTTP have never been well-considered. Just like stream-oriented authentication (e.g. NTLM, Kerberos) has been explicitly forbidden with HTTP/2, mTLS is, at least within the interaction of a broader HTTP stack, a problematic anti-pattern. If you are going to deploy it, as you note, the solution is that your application layer needs to be ready and aware that the client may decline to send credentials, and handle authentication at the request layer (as with every other HTTP authentication method), not at the transport layer.

@annevk
Copy link
Member

annevk commented Feb 27, 2021

I'd prefer to duplicate this into #869. I don't see the need to have two issues on this subject even if they are arguably slightly distinct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants