-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Add support for SCRAM-SHA-256-PLUS i.e. channel binding #3356
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for the PR! Put a few comments on here. My main concern is changing the default behavior - as much as its a pain on users to enable the feature, I'd rather it be an opt-in thing vs a default...otherwise it's a slight breaking change, which requires a new semver major. Would be nice to add a callout in the docs about this as well...though I can handle that if you don't want to
Thanks @brianc. I've made those changes and added a short section to the docs. However, while writing the docs it struck me that I have only provided the opt-in option directly on |
packages/pg/lib/crypto/sasl.js
Outdated
// override if channel binding is in use: | ||
if (session.mechanism === 'SCRAM-SHA-256-PLUS') { | ||
const peerCert = stream.getPeerCertificate().raw | ||
const parsedCert = new x509.X509Certificate(peerCert) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of a separate package, could stream.getPeerCertificate().fingerprint256
be used with a fixed selection of SHA-256? Or is that not the same hash?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never mind – that wouldn’t be spec-compliant and I missed that the hash
wasn’t being used for anything more than its name anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do appreciate that bringing in a whole X509 parsing library (and then parsing the whole certificate, when all we actually need is the signature algorithm) feels like overkill.
I did actually have a go at doing the minimum necessary parsing manually: see https://gist.github.com/jawj/04a90e51196ac054d6741c8d079d9cff
The reason I didn't go with that in the PR is that I haven't been able to find a list of either (a) what signature algorithms could theoretically be used or even (b) what signature algorithms would cover 99% of cases.
But I strongly suspect that the cases covered by this code would be almost all of them, and any missing ones might be plugged if people filed issues about them. So this could be another option?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I strongly suspect that the cases covered by this code would be almost all of them, and any missing ones might be plugged if people filed issues about them. So this could be another option?
I am absolutely totally down w/ the "make it mostly work and patch if different algorithms show up later" mode if it removes the requirement to pull in an entire dependency! I wouldn't say it's a mandatory change but certainly would be welcome. 😄 I have tried very hard over the years to keep as many 3rd party dependencies out of the code as possible just because....well...left-pad and all that stuff, ya know?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@brianc Good to hear: I'm all for that approach. I've done a bit more work on this code (including putting a base64 hash of the public cert in the error message, to make it easy to report failures in a way we can investigate) and removed the dependency.
@@ -45,12 +45,25 @@ if (!config.user || !config.password) { | |||
return | |||
} | |||
|
|||
suite.testAsync('can connect using sasl/scram', async () => { | |||
suite.testAsync('can connect using sasl/scram (channel binding enabled)', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These tests would work just as well if there were no implementation of channel binding at all, so this PR probably needs a targeted test from someone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in an ideal world we would test using SSL certs with a variety of different signature algorithms. But I'm not sure how easily that fits with the current test setup.
Locally, I have been using:
-
The
server-*-cert.pem
files from the OpenSSL tests: https://github.com/openssl/openssl/tree/master/test/certs -
This Docker file:
#!/bin/zsh
docker run --rm --name postgres-tls-cert \
-p 5432:5432 \
-e POSTGRES_USER=frodo \
-e POSTGRES_PASSWORD=friend \
-v ./certs:/etc/pgssl \
postgres:17.2-bookworm \
-c ssl=on \
-c ssl_cert_file=/etc/pgssl/server-$1-cert.pem \
-c ssl_key_file=/etc/pgssl/server-$1-key.pem
Which is run like: ./docker-pg.sh ecdsa
- And this test:
SCRAM_TEST_PGDATABASE=frodo \
SCRAM_TEST_PGUSER=frodo \
SCRAM_TEST_PGPASSWORD=friend \
PGSSLMODE=no-verify \
node --tls-keylog=/path/to/keylog.txt packages/pg/test/integration/client/sasl-scram-tests.js
(The --tls-keylog
enables the use of Wireshark if needed).
Co-authored-by: Charmander <[email protected]>
Co-authored-by: Charmander <[email protected]>
Co-authored-by: Charmander <[email protected]>
…-postgres into add-scram-sha-256-plus
hmm good question - would need to make it something that's thread through the pool to its create method as well. The pool passes its own constructor options to the client which probably if i were designing the API today from scratch I wouldn't do but..that ship sailed like 10 years ago 🙃 - so it should just be able to be set on the pool as well? |
@jawj ruh roh! looks like a test is failing (should be an easy fix). There are a lot of tests - some are duplicates, etc. If you just wanna run a subset locally for faster turn around you can run |
I think the tests should be passing again now. |
BTW, you may find this interesting: https://bytebybyte.dev/?postgres (Writing this was how I figured out how channel binding works). |
I just noticed that legacy crypto expects But a potential annoyance is that SubtleCrypto supports a very limited range of hash functions compared to Node's legacy crypto. If people run into this as a problem with real certificates, it could be worth considering using legacy crypto, when available, more widely. |
OK, this seems to have stalled a little in relation to testing. To summarise the previous position:
I've just added some more unit tests:
As part of the testing, I realised I have not provided a way to enforce (rather than just enable) channel binding. But since the client's response is different when it does and doesn't think the server supports channel binding, I don't think this makes a big difference to the risk of MITM attacks. @brianc and @charmander, how do you think we can move this forward? |
Hi all. I hope you'll consider this patch, which adds support for SCRAM-SHA-256-PLUS.
SCRAM-SHA-256-PLUS on Postgres enables
tls-server-end-point
channel binding, where the client sends the server a hash of the certificate it received as part of the TLS handshake. This prevents some kinds of MITM attacks where the attacker obtains a certificate that appears valid for the server, but is not actually the server's.So far I've tested it working against Neon (who support SCRAM-SHA-256-PLUS) and Supabase (who don't), and with the new
client.enableChannelBinding
flag bothtrue
andfalse
.Feel free to make any changes you think appropriate.