async_postgres/pg_auth

Types

ScramState = object
  clientNonce*: string
  clientFirstBare*: string
  serverSignature*: array[32, byte]
  gs2Header*: string ## GS2 header: "n,," (no channel binding), "y,," (channel binding supported
                     ## but not negotiated โ€” downgrade-detection signal), or
                     ## "p=tls-server-end-point,," (channel binding in use).
  channelBindingData*: seq[byte] ## Channel binding data (empty for non-PLUS)
Intermediate state for SCRAM-SHA-256 authentication handshake.

Consts

DefaultMaxScramIterations = 10000000
Default cap on the server-requested SCRAM iteration count. PBKDF2 runs synchronously on the event loop, so an unbounded count would let a malicious server pin the process; 10M is far above realistic settings.
oidRsaPss = [42'u, 0x00000086, 0x00000048, 0x00000086, 0x000000F7, 0x0000000D,
             0x00000001, 0x00000001, 0x0000000A]

Procs

proc computeTlsServerEndpoint(certDer: openArray[byte]): seq[byte] {....raises: [],
    tags: [RootEffect], forbids: [].}
RFC 5929 ยง4 tls-server-end-point: hash follows the cert's signatureAlgorithm. SHA-384/SHA-512 preserved; MD5/SHA-1/unknown/parse-failure โ†’ SHA-256 (libpq parity). RSA-PSS names its hash in the AlgorithmIdentifier parameters, not the OID.
proc md5AuthHash(user, password: string; salt: array[4, byte]): string {.
    ...raises: [], tags: [], forbids: [].}
Compute MD5 authentication hash for PostgreSQL. Returns "md5" followed by hex of MD5(MD5(password+user) + salt).
proc scramClientFinalMessage(password: string; serverFirstData: openArray[byte];
                             state: var ScramState;
                             maxIterations: int = DefaultMaxScramIterations): seq[
    byte] {....raises: [PgConnectionError, PgConnectionError], tags: [RootEffect],
            forbids: [].}
Generate the SCRAM-SHA-256 client-final message from the server's first response. Computes the client proof and stores the expected server signature in state.
proc scramClientFirstMessage(user: string; nonce: string; state: var ScramState;
                             cbType: string = ""; cbData: seq[byte] = @[];
                             cbSupportedButUnused: bool = false): seq[byte] {.
    ...raises: [], tags: [], forbids: [].}
Overload with explicit nonce for testing.
proc scramClientFirstMessage(user: string; state: var ScramState;
                             cbType: string = ""; cbData: seq[byte] = @[];
                             cbSupportedButUnused: bool = false): seq[byte] {.
    ...raises: [PgConnectionError], tags: [], forbids: [].}
Generate the SCRAM-SHA-256 client-first message with a random nonce. When cbType is non-empty, use channel binding (SCRAM-SHA-256-PLUS) and emit a "p=<type>,," gs2 header. When cbSupportedButUnused is set (TLS is in use but channel binding was not negotiated), emit "y,," so the server can detect a man-in-the-middle that stripped SCRAM-SHA-256-PLUS from the offered mechanisms. Otherwise emit "n,," (channel binding not supported).
proc scramEscapeUsername(user: string): string {....raises: [], tags: [],
    forbids: [].}
Escape username for SCRAM per RFC 5802 Section 5.1. '=' is encoded as '=3D' and ',' is encoded as '=2C'.
proc scramVerifyServerFinal(serverFinalData: openArray[byte]; state: ScramState): bool {.
    ...raises: [], tags: [], forbids: [].}
Verify the server's final SCRAM-SHA-256 signature matches the expected value. The caller is expected to wipe state.serverSignature after verification since it is no longer needed and would aid an attacker impersonating the server.

Templates

template burnStr(s: var string)
Wipe a string's heap buffer. Compiler is prevented from eliding the write because nimcrypto's burnMem uses a volatile memset.