-
-
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
packages/pg/package.json
Outdated
@@ -20,6 +20,7 @@ | |||
"author": "Brian Carlson <[email protected]>", | |||
"main": "./lib", | |||
"dependencies": { | |||
"@peculiar/x509": "^1.12.3", |
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.
should absolutely pin this dependency - i only allow packages within this repo to float patch versions w/ semver. Anything external needs to be vetted & checked on exact versions, and using exact versions in package.json please!
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.
Sure.
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.
See also comments below.
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
throw new Error('SASL: Only mechanisms ' + candidates.join(' and ') + ' are supported') | ||
} | ||
|
||
if (mechanism === 'SCRAM-SHA-256-PLUS' && !(stream instanceof tls.TLSSocket)) { |
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.
Is this instanceof
check important for correctness? (It doesn’t look like it is, but I’m not overly familiar with the flow.) It’s generally nice for users to be able to provide stream objects that satisfy the API pg uses without strictly needing them to inherit from a particular type (or pretend to).
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.
Probably not, so let's change to this, which only checks that the stream
object has the method we need:
if (mechanism === 'SCRAM-SHA-256-PLUS' && typeof stream.getPeerCertificate !== 'function') {
// this should never happen if we are really talking to a Postgres server
throw new Error('SASL: Mechanism SCRAM-SHA-256-PLUS requires a certificate')
}
// 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?
@@ -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.
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 |
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.