Documentation
¶
Overview ¶
Package scram implements SCRAM-SHA-256 authentication for PostgreSQL protocol connections.
Overview ¶
This package provides SCRAM-SHA-256 authentication for both server and client roles, enabling Multigres to verify client credentials and extract SCRAM keys for passthrough authentication to backend PostgreSQL servers. This eliminates the need to store plaintext passwords while maintaining compatibility with PostgreSQL's native authentication.
SCRAM-SHA-256 Protocol ¶
SCRAM (Salted Challenge Response Authentication Mechanism) is defined in RFC 5802: https://datatracker.ietf.org/doc/html/rfc5802
PostgreSQL's SCRAM-SHA-256 implementation is documented at: https://www.postgresql.org/docs/current/sasl-authentication.html
The protocol involves a three-message exchange:
- Client → Server: client-first-message (username, nonce)
- Server → Client: server-first-message (combined nonce, salt, iterations)
- Client → Server: client-final-message (proof)
- Server → Client: server-final-message (server signature for mutual auth)
Why Not Use an Existing Library? ¶
Several Go SCRAM libraries exist (xdg-go/scram, lib/pq, jackc/pgx), but none support our critical requirement: ClientKey extraction for passthrough authentication.
Existing libraries:
- xdg-go/scram: Most comprehensive, but lacks ClientKey extraction and context.Context support
- lib/pq: Maintenance mode, client-side only
- jackc/pgx: Client library, no server-side SCRAM
Our implementation adds:
- ExtractAndVerifyClientProof: Recovers ClientKey from client's proof for passthrough auth
- context.Context support: Allows timeout/cancellation during credential lookup
- Minimum security thresholds: Enforces 4096+ iterations and 8+ byte salts
These features enable Multigres to verify clients and reuse extracted keys to authenticate to backend PostgreSQL servers without storing plaintext passwords.
Architecture ¶
The package is organized into several components:
- ScramAuthenticator: Stateful server-side authenticator handling the protocol exchange
- SCRAMClient: Client-side authenticator supporting password and passthrough modes
- ScramHash: PostgreSQL pg_authid SCRAM-SHA-256 hash (salt, iterations, StoredKey, ServerKey)
- Cryptographic functions: RFC 5802 compliant key derivation and verification
- Protocol parsers/generators: Message construction and parsing (unexported)
Usage Example ¶
// Server-side authentication. The caller looks up the user's
// SCRAM hash up front (e.g. via a credential provider) and passes
// it to NewScramAuthenticator.
hash := fetchScramHashForUser(user, db)
auth := scram.NewScramAuthenticator(hash, "mydb")
// Optional: enable SCRAM-SHA-256-PLUS (channel binding) over TLS.
auth.SetOverTLS(true)
auth.SetChannelBinding(&scram.ChannelBinding{
TLSServerEndPointHash: certHash, // see ComputeTLSServerEndPointHash
})
// Start SASL negotiation
mechanisms := auth.StartAuthentication()
// Send AuthenticationSASL with mechanisms...
// Handle client-first-message. `mechanism` is the SASL mechanism the
// client picked in SASLInitialResponse; `startupUser` is the username
// from the StartupMessage (used when the SCRAM message itself has none).
serverFirst, err := auth.HandleClientFirst(mechanism, clientFirstMsg, startupUser)
// Send AuthenticationSASLContinue with serverFirst...
// Handle client-final-message
serverFinal, err := auth.HandleClientFinal(clientFinalMsg)
if auth.IsAuthenticated() {
// Authentication successful
clientKey, serverKey := auth.ExtractedKeys()
// Use keys for passthrough authentication...
}
Key Passthrough Authentication ¶
A critical feature of this implementation is extracting the ClientKey from the client's proof during authentication. This enables SCRAM passthrough:
- Client authenticates to Multigres multigateway
- Multigateway extracts ClientKey from the authentication proof
- Multigateway uses ClientKey to authenticate to PostgreSQL as that user
- No plaintext password needed at any stage
This is possible because SCRAM proofs reveal the ClientKey through XOR:
ClientKey = ClientProof XOR ClientSignature
The extracted ClientKey can then be used with the stored ServerKey to perform subsequent SCRAM authentications to backend PostgreSQL servers without knowing the original password.
Password Hash Storage ¶
This package expects password hashes in PostgreSQL's SCRAM-SHA-256 format:
SCRAM-SHA-256$<iterations>:<salt>$<StoredKey>:<ServerKey>
The caller looks up the user's SCRAM hash (typically via a credential provider that wraps the storage backend) and passes the parsed *ScramHash to NewScramAuthenticator. Storage backends can:
- Query PostgreSQL's pg_authid directly
- Use a credential cache with TTL and invalidation
- Fetch from a centralized credential service
- Combine multiple sources with fallback logic
Future Directions ¶
Current implementation:
- Core SCRAM protocol and cryptography
- Server-side authentication (ScramAuthenticator)
- Client-side authentication with passthrough support (SCRAMClient)
- No credential caching
- No integration with multigateway/multipooler
Planned enhancements:
- Caching password hashes in multigateway to save a round-trip to multipooler on connect
- Integration with multigateway/multipooler for end-to-end passthrough
Credential Cache Design Considerations ¶
When implementing credential caching:
TTL: Balance security (shorter) vs performance (longer)
PgBouncer: No cache, runs auth_query on every connection See src/client.c start_auth_query() https://github.com/pgbouncer/pgbouncer/tree/master/src
Supavisor: 24-hour cache with 15-second background refresh + refresh on auth failure See lib/supavisor/secret_cache.ex @default_secrets_ttl, fetch_validation_secrets/3 See lib/supavisor/secret_checker.ex @interval, check_secrets/2 (background polling) See lib/supavisor/client_handler/auth.ex check_and_update_secrets/7 (refresh on auth failure) https://github.com/supabase/supavisor/tree/main/lib/supavisor
Invalidation: Consider cache busting on password changes Option 1: PostgreSQL triggers + notification channel Option 2: Periodic refresh on access Option 3: External invalidation API
Security Considerations ¶
- All password hash comparisons use constant-time algorithms (crypto/subtle)
- Nonce validation prevents replay attacks
- State machine prevents protocol violations
- No plaintext passwords stored or logged
- ClientKey extraction requires successful authentication
Password Normalization (SASLprep) ¶
This implementation includes SASLprep password normalization (RFC 4013) for full PostgreSQL compatibility. SASLprep applies NFKC Unicode normalization and character mapping to passwords before hashing.
Key behaviors:
- Non-ASCII spaces normalized to ASCII space (U+0020)
- Soft hyphens and zero-width characters removed
- Unicode combining characters normalized
- Fallback to raw password on normalization failure (prohibited chars, bidi violations)
This matches PostgreSQL's lenient approach: passwords that fail SASLprep validation (invalid UTF-8, prohibited characters, bidirectional check failures) are accepted using their raw byte representation.
Compatibility ¶
This implementation is compatible with:
- PostgreSQL 10+ SCRAM-SHA-256 authentication
- PostgreSQL 11+ SCRAM-SHA-256-PLUS channel binding (tls-server-end-point, RFC 5929). Advertised alongside SCRAM-SHA-256 on TLS connections.
- Standard PostgreSQL client libraries (psql, libpq, pgx, etc.)
- PostgreSQL's pg_authid password hash format
- PostgreSQL's SASLprep implementation (RFC 4013)
Not currently supported:
- SCRAM-SHA-1 (deprecated, not used by PostgreSQL)
- tls-unique channel binding (deprecated; TLS 1.3 removed it)
- Channel binding with TLS configs that use GetCertificate/SNI dynamic certs without populating tlsConfig.Certificates[0]; PLUS falls back to SCRAM-SHA-256 in that case
- Custom iteration counts (uses hash's iteration count)
References ¶
- RFC 5802 (SCRAM): https://datatracker.ietf.org/doc/html/rfc5802
- PostgreSQL SASL: https://www.postgresql.org/docs/current/sasl-authentication.html
- PgBouncer auth: https://www.pgbouncer.org/config.html#authentication-settings
- Supavisor: https://github.com/supabase/supavisor
Index ¶
- Constants
- Variables
- func ComputeClientKey(saltedPassword []byte) []byte
- func ComputeClientSignature(storedKey []byte, authMessage string) []byte
- func ComputeSaltedPassword(password string, salt []byte, iterations int) []byte
- func ComputeServerKey(saltedPassword []byte) []byte
- func ComputeServerSignature(serverKey []byte, authMessage string) []byte
- func ComputeStoredKey(clientKey []byte) []byte
- func ComputeTLSServerEndPointHash(cert *x509.Certificate) ([]byte, error)
- func ExtractAndVerifyClientProof(storedKey []byte, authMessage string, clientProof []byte) ([]byte, error)
- func IsScramSHA256Hash(hash string) bool
- type ChannelBinding
- type SASLProtocolError
- type SCRAMClient
- func (c *SCRAMClient) ClientFirstMessage() (string, error)
- func (c *SCRAMClient) EnableChannelBinding(tlsServerEndPointHash []byte)
- func (c *SCRAMClient) Mechanism() string
- func (c *SCRAMClient) ProcessServerFirst(serverFirst string) (string, error)
- func (c *SCRAMClient) VerifyServerFinal(serverFinal string) error
- type ScramAuthenticator
- func (a *ScramAuthenticator) AuthenticatedUser() string
- func (a *ScramAuthenticator) ExtractedKeys() (clientKey, serverKey []byte)
- func (a *ScramAuthenticator) HandleClientFinal(clientFinalMessage string) (string, error)
- func (a *ScramAuthenticator) HandleClientFirst(mechanism, clientFirstMessage, startupMessageUsername string) (string, error)
- func (a *ScramAuthenticator) IsAuthenticated() bool
- func (a *ScramAuthenticator) Reset()
- func (a *ScramAuthenticator) SetChannelBinding(cb *ChannelBinding)
- func (a *ScramAuthenticator) SetOverTLS(overTLS bool)
- func (a *ScramAuthenticator) StartAuthentication() []string
- type ScramHash
Constants ¶
const ( // ScramSHA256Prefix is the prefix for SCRAM-SHA-256 password hashes in PostgreSQL. ScramSHA256Prefix = "SCRAM-SHA-256" // MinIterationCount is the minimum PBKDF2 iteration count accepted for security. // RFC 5802 recommends a minimum of 4096 iterations to make brute-force attacks harder. MinIterationCount = 4096 // MinSaltLength is the minimum salt length in bytes accepted for security. MinSaltLength = 8 )
const ( // ScramSHA256Mechanism is the SASL mechanism name for SCRAM-SHA-256. ScramSHA256Mechanism = "SCRAM-SHA-256" // ScramSHA256PlusMechanism is the SASL mechanism name for SCRAM-SHA-256 // with channel binding (RFC 5802 §6 + PostgreSQL convention). Advertised // only over TLS, alongside ScramSHA256Mechanism. ScramSHA256PlusMechanism = "SCRAM-SHA-256-PLUS" )
const ChannelBindingTypeTLSServerEndPoint = "tls-server-end-point"
ChannelBindingTypeTLSServerEndPoint is the channel binding type defined in RFC 5929 §4: a hash of the TLS server's certificate. This is the type PostgreSQL advertises for SCRAM-SHA-256-PLUS.
Variables ¶
var ( // ErrUserNotFound indicates the user does not exist. Callers fetching // credentials before constructing a ScramAuthenticator should return // this so the gateway can emit PG's "password authentication failed" // (SQLSTATE 28P01) without exposing user existence. ErrUserNotFound = errors.New("user not found") // ErrAuthenticationFailed indicates the password proof was invalid. ErrAuthenticationFailed = errors.New("authentication failed") // ErrLoginDisabled indicates the role has rolcanlogin=false in pg_authid. // Caller should emit PG's "role \"X\" is not permitted to log in" with // SQLSTATE 28000, matching native PostgreSQL. ErrLoginDisabled = errors.New("role not permitted to log in") // ErrPasswordExpired indicates the role's rolvaliduntil is in the past. // Caller should emit the opaque "password authentication failed" message // with SQLSTATE 28P01, matching native PostgreSQL's handling of expired // passwords. ErrPasswordExpired = errors.New("password expired") // ErrChannelBindingNegotiation indicates the client signaled GS2 flag // "y" while the server actually advertised SCRAM-SHA-256-PLUS — a // classic downgrade attempt. Per PostgreSQL, caller emits SQLSTATE // 28000 with "SCRAM channel binding negotiation error". ErrChannelBindingNegotiation = errors.New("scram: channel binding negotiation error") // ErrChannelBindingCheck indicates the cbind data the client echoed // in client-final-message does not match the value the server expects // (gs2-header + cbind-data for PLUS, or "biws"/"eSws" for base). Per // PostgreSQL, caller emits SQLSTATE 28000 with "SCRAM channel binding // check failed". ErrChannelBindingCheck = errors.New("scram: channel binding check failed") // ErrSASLProtocol indicates a SCRAM-level protocol violation tied to // channel binding: PLUS mechanism with non-"p=" flag, base mechanism // with "p=" flag, unsupported binding type, malformed messages. Per // PostgreSQL, caller emits SQLSTATE 08P01 with "malformed SCRAM // message" (or the more specific "unsupported SCRAM channel-binding // type" variant) plus an error detail. ErrSASLProtocol = errors.New("scram: protocol violation") // ErrAuthzidNotSupported indicates the client included a SASL // authorization identity ("a=...") in its gs2-header. PostgreSQL // rejects this with SQLSTATE 0A000 (feature_not_supported). ErrAuthzidNotSupported = errors.New("scram: authzid not supported") )
Sentinel errors for SCRAM authentication.
Functions ¶
func ComputeClientKey ¶
ComputeClientKey computes ClientKey = HMAC(SaltedPassword, "Client Key").
func ComputeClientSignature ¶
ComputeClientSignature computes ClientSignature = HMAC(StoredKey, AuthMessage).
func ComputeSaltedPassword ¶
ComputeSaltedPassword computes the SCRAM SaltedPassword using PBKDF2. SaltedPassword = Hi(Normalize(password), salt, iterations) Where Hi is PBKDF2 with HMAC-SHA-256.
Passwords are normalized using SASLprep (RFC 4013), which applies stringprep with NFKC normalization and character mapping. If normalization fails (invalid UTF-8, prohibited characters, bidirectional check failures), the raw password is used unchanged, matching PostgreSQL's lenient behavior.
func ComputeServerKey ¶
ComputeServerKey computes ServerKey = HMAC(SaltedPassword, "Server Key").
func ComputeServerSignature ¶
ComputeServerSignature computes ServerSignature = HMAC(ServerKey, AuthMessage).
func ComputeStoredKey ¶
ComputeStoredKey computes StoredKey = H(ClientKey) where H is SHA-256.
func ComputeTLSServerEndPointHash ¶
func ComputeTLSServerEndPointHash(cert *x509.Certificate) ([]byte, error)
ComputeTLSServerEndPointHash computes the tls-server-end-point channel binding data for a TLS server certificate per RFC 5929 §4: the hash of the DER-encoded certificate, using the certificate signature algorithm's hash function — with one PostgreSQL-compatible quirk: certificates signed with MD5 or SHA-1 are hashed with SHA-256 instead (mirroring PG's be_tls_get_certificate_hash). This is what libpq expects, so any client computing the binding the same way will interoperate.
func ExtractAndVerifyClientProof ¶
func ExtractAndVerifyClientProof(storedKey []byte, authMessage string, clientProof []byte) ([]byte, error)
ExtractAndVerifyClientProof verifies the client's proof and extracts the ClientKey. This is used for SCRAM key passthrough: after verifying a client, we can use the extracted ClientKey to authenticate as that user to PostgreSQL.
The verification process: 1. Compute ClientSignature = HMAC(StoredKey, AuthMessage) 2. Recover ClientKey = ClientProof XOR ClientSignature 3. Compute RecoveredStoredKey = H(ClientKey) 4. Verify RecoveredStoredKey == StoredKey
Returns the extracted ClientKey on successful verification (error == nil). Returns ErrAuthenticationFailed if the proof is invalid (wrong password). Returns other errors for unexpected conditions (malformed proof, length mismatches). Uses constant-time comparison to prevent timing attacks.
func IsScramSHA256Hash ¶
IsScramSHA256Hash returns true if the hash string appears to be a SCRAM-SHA-256 hash. This is a quick check based on the prefix; it does not validate the entire format.
Types ¶
type ChannelBinding ¶
type ChannelBinding struct {
// TLSServerEndPointHash is the hash of the server's TLS certificate, as
// defined for the tls-server-end-point channel binding type (RFC 5929).
// Compute it with ComputeTLSServerEndPointHash.
TLSServerEndPointHash []byte
}
ChannelBinding carries TLS channel binding context into the SCRAM authenticator. When non-nil and TLSServerEndPointHash is populated, the authenticator advertises SCRAM-SHA-256-PLUS in addition to SCRAM-SHA-256.
type SASLProtocolError ¶
type SASLProtocolError struct {
// Detail is the PostgreSQL errdetail() string — keep it byte-identical
// to the PG source so client-side log parsers (and humans) can't tell
// they're talking to multigres.
Detail string
// Msg overrides the primary errmsg(); empty means "malformed SCRAM
// message" (PG's default for cbind protocol errors).
Msg string
}
SASLProtocolError wraps ErrSASLProtocol with a PG-style detail message so the listener can emit "malformed SCRAM message" with errdetail("...") verbatim — matching libpq-readable output. Use as the wire-facing detail only; the sentinel chain still exposes ErrSASLProtocol via errors.Is.
func (*SASLProtocolError) Error ¶
func (e *SASLProtocolError) Error() string
func (*SASLProtocolError) Unwrap ¶
func (e *SASLProtocolError) Unwrap() error
Unwrap lets errors.Is(err, ErrSASLProtocol) succeed.
type SCRAMClient ¶
type SCRAMClient struct {
// contains filtered or unexported fields
}
SCRAMClient implements client-side SCRAM-SHA-256 authentication. It supports two modes: 1. Password mode: authenticate using a plaintext password 2. Passthrough mode: authenticate using pre-extracted ClientKey/ServerKey
Passthrough mode enables SCRAM key passthrough: after a proxy verifies a client, it can extract the ClientKey and use it to authenticate to the backend database without knowing the plaintext password.
When constructed with EnableChannelBinding, the client speaks SCRAM-SHA-256-PLUS instead, embedding the supplied tls-server-end-point hash into its messages.
func NewSCRAMClientWithKeys ¶
func NewSCRAMClientWithKeys(username string, clientKey, serverKey []byte) *SCRAMClient
NewSCRAMClientWithKeys creates a SCRAM client that authenticates with extracted SCRAM keys. This enables SCRAM passthrough: a proxy can verify a client, extract the ClientKey, and use it to authenticate to PostgreSQL without the plaintext password.
The clientKey and serverKey should be extracted from a previous SCRAM authentication using ExtractAndVerifyClientProof and the hash's ServerKey.
func NewSCRAMClientWithPassword ¶
func NewSCRAMClientWithPassword(username, password string) *SCRAMClient
NewSCRAMClientWithPassword creates a SCRAM client that authenticates with a password. This is the standard mode where the password is used to derive SCRAM keys.
func (*SCRAMClient) ClientFirstMessage ¶
func (c *SCRAMClient) ClientFirstMessage() (string, error)
ClientFirstMessage generates the client-first-message to send to the server. This starts the SCRAM authentication handshake. Returns the full message including the GS2 header.
func (*SCRAMClient) EnableChannelBinding ¶
func (c *SCRAMClient) EnableChannelBinding(tlsServerEndPointHash []byte)
EnableChannelBinding configures the client to negotiate SCRAM-SHA-256-PLUS using the tls-server-end-point binding type, with the supplied hash as the cbind-data. Call before ClientFirstMessage. Pass nil to clear.
func (*SCRAMClient) Mechanism ¶
func (c *SCRAMClient) Mechanism() string
Mechanism returns the SASL mechanism name the client will use, based on whether channel binding has been enabled.
func (*SCRAMClient) ProcessServerFirst ¶
func (c *SCRAMClient) ProcessServerFirst(serverFirst string) (string, error)
ProcessServerFirst processes the server-first-message and generates the client-final-message. The serverFirst parameter is the server's response to the client-first-message. Returns the client-final-message to send to the server.
func (*SCRAMClient) VerifyServerFinal ¶
func (c *SCRAMClient) VerifyServerFinal(serverFinal string) error
VerifyServerFinal verifies the server-final-message for mutual authentication. The serverFinal parameter is the server's response to the client-final-message. Returns nil if the server signature is valid, or an error if verification fails.
type ScramAuthenticator ¶
type ScramAuthenticator struct {
// contains filtered or unexported fields
}
ScramAuthenticator handles SCRAM-SHA-256 authentication. It implements the server side of the SCRAM protocol as defined in RFC 5802.
Usage:
- Call StartAuthentication() to get the list of supported mechanisms.
- After receiving client-first-message, call HandleClientFirst().
- After receiving client-final-message, call HandleClientFinal().
- Check IsAuthenticated() to see if auth succeeded.
- Call ExtractedKeys() to get SCRAM keys for passthrough authentication.
The authenticator maintains state between calls and enforces valid state transitions to prevent protocol errors.
Thread Safety: ScramAuthenticator is NOT thread-safe. Each connection must use its own authenticator instance. Do not share authenticators across goroutines or reuse them for multiple concurrent authentication attempts. The Reset() method allows reusing an authenticator sequentially, but only after the previous authentication has completed.
func NewScramAuthenticator ¶
func NewScramAuthenticator(hash *ScramHash, database string) *ScramAuthenticator
NewScramAuthenticator creates a new SCRAM authenticator for the given pre-fetched password hash and database name. The caller must look up the hash (e.g. via a credential provider) before constructing the authenticator so a single credential fetch can satisfy both SCRAM and any subsequent role-attribute checks the caller wants to perform.
Panics if hash is nil. Unknown-user / login-disabled / password-expired cases must be handled by the caller before reaching this constructor.
func (*ScramAuthenticator) AuthenticatedUser ¶
func (a *ScramAuthenticator) AuthenticatedUser() string
AuthenticatedUser returns the username that was successfully authenticated. Returns an empty string if authentication has not completed successfully.
func (*ScramAuthenticator) ExtractedKeys ¶
func (a *ScramAuthenticator) ExtractedKeys() (clientKey, serverKey []byte)
ExtractedKeys returns the SCRAM keys extracted during authentication. These can be used for passthrough authentication to backend PostgreSQL servers.
ClientKey is recovered from the client's proof (ClientKey = ClientProof XOR ClientSignature). ServerKey comes from the stored password hash.
Returns nil, nil if authentication has not completed successfully.
func (*ScramAuthenticator) HandleClientFinal ¶
func (a *ScramAuthenticator) HandleClientFinal(clientFinalMessage string) (string, error)
HandleClientFinal processes the client-final-message from the client. Returns the server-final-message to send back on success.
The client-final-message contains the channel binding, combined nonce, and client proof. This method verifies the proof and, if valid, returns the server signature for mutual authentication.
Returns ErrAuthenticationFailed if the proof is invalid. Returns ErrChannelBindingCheck (PLUS) or ErrSASLProtocol (base) when the cbind data the client echoes back does not match the gs2-header (+ TLS cert hash for PLUS) the server expects.
func (*ScramAuthenticator) HandleClientFirst ¶
func (a *ScramAuthenticator) HandleClientFirst(mechanism, clientFirstMessage, startupMessageUsername string) (string, error)
HandleClientFirst processes the client-first-message from the client. Returns the server-first-message to send back.
The mechanism is the SASL mechanism the client selected in the SASLInitialResponse (SCRAM-SHA-256 or SCRAM-SHA-256-PLUS); the authenticator validates it against what StartAuthentication advertised and against the GS2 channel-binding flag the client carries in the client-first-message.
The client-first-message comes from SASLInitialResponse and contains the GS2 header and client-first-message-bare (username, client nonce).
The startupMessageUsername is used when the client sends an empty username in the client-first-message. PostgreSQL allows this and uses the username from the startup message. This parameter should be the username from the startup message.
The user's password hash was supplied at construction time; this method generates the server-first-message containing the combined nonce, salt, and iteration count from that hash.
func (*ScramAuthenticator) IsAuthenticated ¶
func (a *ScramAuthenticator) IsAuthenticated() bool
IsAuthenticated returns true if the authentication completed successfully.
func (*ScramAuthenticator) Reset ¶
func (a *ScramAuthenticator) Reset()
Reset clears the authenticator state, allowing it to be reused for another authentication attempt with the same hash. The channel binding context and overTLS flag are preserved — they're tied to the TLS session, not to a particular handshake attempt. The extracted ClientKey is zeroized before being nilled so a memory dump after Reset cannot recover the previous session's secret.
func (*ScramAuthenticator) SetChannelBinding ¶
func (a *ScramAuthenticator) SetChannelBinding(cb *ChannelBinding)
SetChannelBinding attaches TLS channel binding context to the authenticator. Must be called before StartAuthentication; calling it afterwards panics, as the advertised mechanism list would already be locked in. When the binding hash is non-empty the authenticator advertises SCRAM-SHA-256-PLUS in addition to SCRAM-SHA-256 and enforces the corresponding GS2 flag rules (RFC 5802 §6).
Pass nil to clear any previously set binding.
func (*ScramAuthenticator) SetOverTLS ¶
func (a *ScramAuthenticator) SetOverTLS(overTLS bool)
SetOverTLS records that the underlying connection is TLS-encrypted. Must be called before StartAuthentication. This is independent of whether SetChannelBinding succeeded — downgrade detection still needs to fire over TLS even when cbind material couldn't be derived.
func (*ScramAuthenticator) StartAuthentication ¶
func (a *ScramAuthenticator) StartAuthentication() []string
StartAuthentication begins the SCRAM authentication process. Returns the list of supported SASL mechanisms. When channel binding is available, SCRAM-SHA-256-PLUS is listed first so libpq-style clients pick the stronger mechanism by default.
This corresponds to sending AuthenticationSASL (auth type 10) to the client.
type ScramHash ¶
type ScramHash struct {
// Iterations is the PBKDF2 iteration count used to derive the salted password.
Iterations int
// Salt is the random salt used in PBKDF2 key derivation.
Salt []byte
// StoredKey is H(ClientKey) where H is SHA-256 and ClientKey = HMAC(SaltedPassword, "Client Key").
// Used to verify the client's proof.
StoredKey []byte
// ServerKey is HMAC(SaltedPassword, "Server Key").
// Used to generate the server's signature for mutual authentication.
ServerKey []byte
}
ScramHash contains the parsed components of a PostgreSQL SCRAM-SHA-256 password hash. The hash format is: SCRAM-SHA-256$<iterations>:<salt>$<StoredKey>:<ServerKey> where salt, StoredKey, and ServerKey are base64-encoded.
func ParseScramSHA256Hash ¶
ParseScramSHA256Hash parses a PostgreSQL SCRAM-SHA-256 password hash string. The expected format is: SCRAM-SHA-256$<iterations>:<salt>$<StoredKey>:<ServerKey>
Example: SCRAM-SHA-256$4096:W22ZaJ0SNY7soEsUEjb6gQ==$WG5d8oPm3OtcPnkdi4Oln6rNiYzlYY42lUpMtdJ7U90=:HKZfkuYXDxJboM9DFNR0yFNHpRx/rbdVdNOTk/V0v0Q=