fix: decline SSL negotiation in plaintext mode
All checks were successful
Build and Push Image / docker-build-and-push (push) Successful in 5m7s
Test and Release PawSQL / test (push) Successful in 44s
Test and Release PawSQL / release (push) Successful in 10s

This commit is contained in:
2026-09-15 22:28:16 -04:00
parent e67698f3c2
commit cae9419cd5
4 changed files with 125 additions and 2 deletions

View File

@@ -6,6 +6,8 @@ import (
"errors"
"fmt"
"io"
"net"
"time"
)
const (
@@ -13,7 +15,10 @@ const (
sslRequestCode uint32 = 80877103
)
var ErrNotSSLRequest = errors.New("expected PostgreSQL SSLRequest")
var (
ErrNotSSLRequest = errors.New("expected PostgreSQL SSLRequest")
ErrNotNegotiation = errors.New("not a PostgreSQL SSL or GSSENC negotiation request")
)
// ReadSSLRequest validates the eight-byte PostgreSQL SSL negotiation request.
func ReadSSLRequest(reader io.Reader) error {
@@ -30,6 +35,42 @@ func ReadSSLRequest(reader io.Reader) error {
return nil
}
// NegotiatePlainPostgreSQL handles a plaintext server's first protocol exchange
// and returns the connection positioned at the StartupMessage. When the client
// opens with an SSLRequest or GSSENCRequest it answers 'N', the standard
// PostgreSQL decline, so the client retries without encryption.
func NegotiatePlainPostgreSQL(connection net.Conn) (net.Conn, error) {
var preamble [8]byte
if err := connection.SetReadDeadline(time.Now().Add(15 * time.Second)); err != nil {
return connection, fmt.Errorf("set negotiation deadline: %w", err)
}
if _, err := io.ReadFull(connection, preamble[:]); err != nil {
return connection, fmt.Errorf("read PostgreSQL negotiation preamble: %w", err)
}
if err := connection.SetReadDeadline(time.Time{}); err != nil {
return connection, fmt.Errorf("clear negotiation deadline: %w", err)
}
length := binary.BigEndian.Uint32(preamble[0:4])
code := binary.BigEndian.Uint32(preamble[4:8])
if length == sslRequestLength && (code == sslRequestCode || code == gssEncryptionCode) {
if err := declineSSL(connection); err != nil {
return connection, err
}
return connection, nil
}
if length < 8 || length > maxStartupMessageSize {
return connection, fmt.Errorf("%w: invalid length %d", ErrInvalidStartupMessage, length)
}
return Replay(connection, preamble[:]), nil
}
func declineSSL(connection net.Conn) error {
if _, err := connection.Write([]byte{'N'}); err != nil {
return fmt.Errorf("write PostgreSQL SSL decline: %w", err)
}
return nil
}
// SSLRequest returns the exact client preamble used to request TLS from PostgreSQL.
// It exists to make protocol-level tests and clients unambiguous.
func SSLRequest() [8]byte {

View File

@@ -11,7 +11,8 @@ import (
const (
startupProtocolVersion uint32 = 196608
maxStartupMessageSize = 64 << 10
maxStartupMessageSize uint32 = 64 << 10
gssEncryptionCode uint32 = 80877104
)
var (