chore: ⬆️ updated deps

This commit is contained in:
2026-05-20 22:52:20 +02:00
parent d9f27c1775
commit 43f4680176
374 changed files with 295527 additions and 301467 deletions
+148 -21
View File
@@ -1,7 +1,8 @@
// SCRAM-SHA-256 authentication
// SCRAM-SHA-256 and SCRAM-SHA-256-PLUS authentication
//
// Resources:
// https://tools.ietf.org/html/rfc5802
// https://tools.ietf.org/html/rfc5929
// https://tools.ietf.org/html/rfc8265
// https://www.postgresql.org/docs/current/sasl-authentication.html
//
@@ -15,19 +16,28 @@ package pgconn
import (
"bytes"
"crypto/hmac"
"crypto/pbkdf2"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"hash"
"slices"
"strconv"
"github.com/jackc/pgx/v5/pgproto3"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/text/secure/precis"
)
const clientNonceLen = 18
const (
clientNonceLen = 18
scramSHA256Name = "SCRAM-SHA-256"
scramSHA256PlusName = "SCRAM-SHA-256-PLUS"
)
// Perform SCRAM authentication.
func (c *PgConn) scramAuth(serverAuthMechanisms []string) error {
@@ -36,9 +46,35 @@ func (c *PgConn) scramAuth(serverAuthMechanisms []string) error {
return err
}
serverHasPlus := slices.Contains(sc.serverAuthMechanisms, scramSHA256PlusName)
if c.config.ChannelBinding == "require" && !serverHasPlus {
return errors.New("channel binding required but server does not support SCRAM-SHA-256-PLUS")
}
// If we have a TLS connection and channel binding is not disabled, attempt to
// extract the server certificate hash for tls-server-end-point channel binding.
if tlsConn, ok := c.conn.(*tls.Conn); ok && c.config.ChannelBinding != "disable" {
certHash, err := getTLSCertificateHash(tlsConn)
if err != nil && c.config.ChannelBinding == "require" {
return fmt.Errorf("channel binding required but failed to get server certificate hash: %w", err)
}
// Upgrade to SCRAM-SHA-256-PLUS if we have binding data and the server supports it.
if certHash != nil && serverHasPlus {
sc.authMechanism = scramSHA256PlusName
}
sc.channelBindingData = certHash
sc.hasTLS = true
}
if c.config.ChannelBinding == "require" && sc.channelBindingData == nil {
return errors.New("channel binding required but channel binding data is not available")
}
// Send client-first-message in a SASLInitialResponse
saslInitialResponse := &pgproto3.SASLInitialResponse{
AuthMechanism: "SCRAM-SHA-256",
AuthMechanism: sc.authMechanism,
Data: sc.clientFirstMessage(),
}
c.frontend.Send(saslInitialResponse)
@@ -107,10 +143,31 @@ func (c *PgConn) rxSASLFinal() (*pgproto3.AuthenticationSASLFinal, error) {
type scramClient struct {
serverAuthMechanisms []string
password []byte
password string
clientNonce []byte
// authMechanism is the selected SASL mechanism for the client. Must be
// either SCRAM-SHA-256 (default) or SCRAM-SHA-256-PLUS.
//
// Upgraded to SCRAM-SHA-256-PLUS during authentication when channel binding
// is not disabled, channel binding data is available (TLS connection with
// an obtainable server certificate hash) and the server advertises
// SCRAM-SHA-256-PLUS.
authMechanism string
// hasTLS indicates whether the connection is using TLS. This is
// needed because the GS2 header must distinguish between a client that
// supports channel binding but the server does not ("y,,") versus one
// that does not support it at all ("n,,").
hasTLS bool
// channelBindingData is the hash of the server's TLS certificate, computed
// per the tls-server-end-point channel binding type (RFC 5929). Used as
// the binding input in SCRAM-SHA-256-PLUS. nil when not in use.
channelBindingData []byte
clientFirstMessageBare []byte
clientGS2Header []byte
serverFirstMessage []byte
clientAndServerNonce []byte
@@ -124,26 +181,23 @@ type scramClient struct {
func newScramClient(serverAuthMechanisms []string, password string) (*scramClient, error) {
sc := &scramClient{
serverAuthMechanisms: serverAuthMechanisms,
authMechanism: scramSHA256Name,
}
// Ensure server supports SCRAM-SHA-256
hasScramSHA256 := false
for _, mech := range sc.serverAuthMechanisms {
if mech == "SCRAM-SHA-256" {
hasScramSHA256 = true
break
}
}
if !hasScramSHA256 {
// Ensure the server supports SCRAM-SHA-256. SCRAM-SHA-256-PLUS is the
// channel binding variant and is only advertised when the server supports
// SSL. PostgreSQL always advertises the base SCRAM-SHA-256 mechanism
// regardless of SSL.
if !slices.Contains(sc.serverAuthMechanisms, scramSHA256Name) {
return nil, errors.New("server does not support SCRAM-SHA-256")
}
// precis.OpaqueString is equivalent to SASLprep for password.
var err error
sc.password, err = precis.OpaqueString.Bytes([]byte(password))
sc.password, err = precis.OpaqueString.String(password)
if err != nil {
// PostgreSQL allows passwords invalid according to SCRAM / SASLprep.
sc.password = []byte(password)
sc.password = password
}
buf := make([]byte, clientNonceLen)
@@ -158,8 +212,32 @@ func newScramClient(serverAuthMechanisms []string, password string) (*scramClien
}
func (sc *scramClient) clientFirstMessage() []byte {
sc.clientFirstMessageBare = []byte(fmt.Sprintf("n=,r=%s", sc.clientNonce))
return []byte(fmt.Sprintf("n,,%s", sc.clientFirstMessageBare))
// The client-first-message is the GS2 header concatenated with the bare
// message (username + client nonce). The GS2 header communicates the
// client's channel binding capability to the server:
//
// "n,," - client is not using TLS (channel binding not possible)
// "y,," - client is using TLS but channel binding is not
// in use (e.g., server did not advertise SCRAM-SHA-256-PLUS
// or the server certificate hash was not obtainable)
// "p=tls-server-end-point,," - channel binding is active via SCRAM-SHA-256-PLUS
//
// See:
// https://www.rfc-editor.org/rfc/rfc5802#section-6
// https://www.rfc-editor.org/rfc/rfc5929#section-4
// https://www.postgresql.org/docs/current/sasl-authentication.html#SASL-SCRAM-SHA-256
sc.clientFirstMessageBare = fmt.Appendf(nil, "n=,r=%s", sc.clientNonce)
if sc.authMechanism == scramSHA256PlusName {
sc.clientGS2Header = []byte("p=tls-server-end-point,,")
} else if sc.hasTLS {
sc.clientGS2Header = []byte("y,,")
} else {
sc.clientGS2Header = []byte("n,,")
}
return append(sc.clientGS2Header, sc.clientFirstMessageBare...)
}
func (sc *scramClient) recvServerFirstMessage(serverFirstMessage []byte) error {
@@ -218,9 +296,25 @@ func (sc *scramClient) recvServerFirstMessage(serverFirstMessage []byte) error {
}
func (sc *scramClient) clientFinalMessage() string {
clientFinalMessageWithoutProof := []byte(fmt.Sprintf("c=biws,r=%s", sc.clientAndServerNonce))
// The c= attribute carries the base64-encoded channel binding input.
//
// Without channel binding this is just the GS2 header alone ("biws" for
// "n,," or "eSws" for "y,,").
//
// With channel binding, this is the GS2 header with the channel binding data
// (certificate hash) appended.
channelBindInput := sc.clientGS2Header
if sc.authMechanism == scramSHA256PlusName {
channelBindInput = slices.Concat(sc.clientGS2Header, sc.channelBindingData)
}
channelBindingEncoded := base64.StdEncoding.EncodeToString(channelBindInput)
clientFinalMessageWithoutProof := fmt.Appendf(nil, "c=%s,r=%s", channelBindingEncoded, sc.clientAndServerNonce)
sc.saltedPassword = pbkdf2.Key([]byte(sc.password), sc.salt, sc.iterations, 32, sha256.New)
var err error
sc.saltedPassword, err = pbkdf2.Key(sha256.New, sc.password, sc.salt, sc.iterations, 32)
if err != nil {
panic(err) // This should never happen.
}
sc.authMessage = bytes.Join([][]byte{sc.clientFirstMessageBare, sc.serverFirstMessage, clientFinalMessageWithoutProof}, []byte(","))
clientProof := computeClientProof(sc.saltedPassword, sc.authMessage)
@@ -254,7 +348,7 @@ func computeClientProof(saltedPassword, authMessage []byte) []byte {
clientSignature := computeHMAC(storedKey[:], authMessage)
clientProof := make([]byte, len(clientSignature))
for i := 0; i < len(clientSignature); i++ {
for i := range clientSignature {
clientProof[i] = clientKey[i] ^ clientSignature[i]
}
@@ -270,3 +364,36 @@ func computeServerSignature(saltedPassword, authMessage []byte) []byte {
base64.StdEncoding.Encode(buf, serverSignature)
return buf
}
// Get the server certificate hash for SCRAM channel binding type
// tls-server-end-point.
func getTLSCertificateHash(conn *tls.Conn) ([]byte, error) {
state := conn.ConnectionState()
if len(state.PeerCertificates) == 0 {
return nil, errors.New("no peer certificates for channel binding")
}
cert := state.PeerCertificates[0]
// Per RFC 5929 section 4.1: If the certificate's signatureAlgorithm uses
// MD5 or SHA-1, use SHA-256. Otherwise use the hash from the signature
// algorithm.
//
// See: https://www.rfc-editor.org/rfc/rfc5929.html#section-4.1
var h hash.Hash
switch cert.SignatureAlgorithm {
case x509.MD5WithRSA, x509.SHA1WithRSA, x509.ECDSAWithSHA1:
h = sha256.New()
case x509.SHA256WithRSA, x509.SHA256WithRSAPSS, x509.ECDSAWithSHA256:
h = sha256.New()
case x509.SHA384WithRSA, x509.SHA384WithRSAPSS, x509.ECDSAWithSHA384:
h = sha512.New384()
case x509.SHA512WithRSA, x509.SHA512WithRSAPSS, x509.ECDSAWithSHA512:
h = sha512.New()
default:
return nil, fmt.Errorf("tls-server-end-point channel binding is undefined for certificate signature algorithm %v", cert.SignatureAlgorithm)
}
h.Write(cert.Raw)
return h.Sum(nil), nil
}