fix: decline SSL negotiation in plaintext mode
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -11,7 +11,8 @@ import (
|
||||
|
||||
const (
|
||||
startupProtocolVersion uint32 = 196608
|
||||
maxStartupMessageSize = 64 << 10
|
||||
maxStartupMessageSize uint32 = 64 << 10
|
||||
gssEncryptionCode uint32 = 80877104
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
Reference in New Issue
Block a user