Skip to content

feat: support SCRAM-SHA-256-PLUS for client connections - #1457

Merged
levkk merged 6 commits into
pgdogdev:mainfrom
abnegate:feat/scram-sha-256-plus
Sep 1, 2026
Merged

feat: support SCRAM-SHA-256-PLUS for client connections#1457
levkk merged 6 commits into
pgdogdev:mainfrom
abnegate:feat/scram-sha-256-plus

Conversation

@abnegate

@abnegate abnegate commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • On connections PgDog terminates TLS for, advertise SCRAM-SHA-256-PLUS first (then SCRAM-SHA-256), matching PostgreSQL.
  • Bind PLUS handshakes to tls-server-end-point from the leaf cert presented on that connection, snapshotted at accept so a later TLS reload cannot change the hash mid-login.
  • Clients that pick SCRAM-SHA-256 still work. PLUS is not advertised on plaintext, or when the cert's signature algorithm does not name a single hash (Ed25519 / RSASSA-PSS).

Fixes #1453

scram is pinned to merged pgdogdev/scram @ ee15a47 (pgdogdev/scram#6).

Test plan

  • AuthenticationSASL encodes SCRAM-SHA-256-PLUS\0SCRAM-SHA-256\0\0 and still encodes SHA-256-only for plaintext
  • tls-server-end-point for tests/tls/cert.pem matches SHA-256 of the DER
  • Every named RFC 5929 signature OID maps to the right digest length; Ed25519 / RSASSA-PSS / garbage DER return None
  • Hashed-password PLUS handshake succeeds / wrong password fails
  • handle() refuses PLUS with no cbind, unknown mechanisms, GS2 y, non-SASL initial, ErrorResponse, and a SASLInitial sent as client-final
  • Plain-password PLUS handle() accepts the correct password
  • Empty AuthenticationSASL mechanism list is UnexpectedPayload
  • scram pin points at pgdogdev/scram @ ee15a47
  • integration/rust tokio-postgres client: sslmode=require + channel_binding=require (plain + hashed users); wrong password fails; channel_binding=disable still works over TLS
  • channel_binding=require against this branch as a TLS PgDog (psql 18.6 / rust-postgres 0.19)

Made with Cursor

Clients that require channel binding were refused because we only
offered SCRAM-SHA-256. On connections we terminate TLS for, advertise
PLUS first and bind the handshake to tls-server-end-point.

Co-authored-by: Cursor <cursoragent@cursor.com>
@CLAassistant

CLAassistant commented Aug 31, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

A SHA-256 client that sends y is the RFC 5802 section 6 downgrade
(PLUS stripped from the mechanism list). n is still allowed.
scram_server(true, None) is now an error, and handle() is covered
over a TCP pair.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

The crate PR landed as ee15a47; keep the pin on that rev. Also exercise
the OID-to-hash table, empty SASL parse, and handle() refusals Codecov flagged.

Co-authored-by: Cursor <cursoragent@cursor.com>
@levkk

levkk commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Could you add a client integration test? We could use the Rust client in integration/rust.

Rewrite the auth message assertions so Codecov is not counting unused
panic arms, and drive handle() through the leftover refusal paths.
tokio-postgres with channel_binding=require is the client integration
test for the advertised PLUS mechanism.

Co-authored-by: Cursor <cursoragent@cursor.com>
@abnegate

Copy link
Copy Markdown
Contributor Author

Added a tokio-postgres client test in integration/rust/tests/integration/scram_plus.rs. It connects with sslmode=require and channel_binding=require (so the rust client will only succeed if we advertised PLUS and the tls-server-end-point binding matches), plus the hashed-password user, a wrong-password refusal, and channel_binding=disable over TLS so SHA-256 still works when the client opts out.

Clippy's err_expect lint fails the fmt job on `.err().expect()`.

Co-authored-by: Cursor <cursoragent@cursor.com>
@levkk levkk changed the title feat: advertise SCRAM-SHA-256-PLUS on TLS client connections feat: support SCRAM-SHA-256-PLUS on TLS client connections Aug 31, 2026
@levkk levkk changed the title feat: support SCRAM-SHA-256-PLUS on TLS client connections feat: support SCRAM-SHA-256-PLUS for client connections Aug 31, 2026
tokio-postgres Display is just "db error" even when PgDog sent 28P01.
CI already proved the connect fails; assert that and nothing else.

Co-authored-by: Cursor <cursoragent@cursor.com>
@abnegate

Copy link
Copy Markdown
Contributor Author

Ran the last test-plan item against this branch (feat/scram-sha-256-plus @ 0cca725e) as a TLS PgDog (auth_type=scram, integration/tls/cert.pem).

  • rust-postgres 0.19 with sslmode=require + channel_binding=require: plain user and hashed user both completed SELECT 1; wrong password failed; channel_binding=disable still worked over TLS
  • psql 18.6 (libpq): the same four cases

channel_binding=require no longer hits channel binding required but not supported by server's authentication request.

@levkk
levkk merged commit bfe686f into pgdogdev:main Sep 1, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support SCRAM-SHA-256-PLUS (channel binding) for client connections

3 participants