Types
ScramState = object clientNonce*: string clientFirstBare*: string 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.
Procs
func channelBound(state: ScramState): bool {....raises: [], tags: [], forbids: [].}
- The client-first carried a channel binding (SCRAM-SHA-256-PLUS).
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: [PgProtocolError, PgSecurityError, PgConnectionError, PgProtocolError], 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; 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: var ScramState): bool {....raises: [], tags: [], forbids: [].}
- Verify the server's final SCRAM-SHA-256 signature matches the expected value, then wipe it, so each computed signature is checked at most once. Fails if scramClientFinalMessage has not computed it or it was wiped.
proc wipeServerSignature(state: var ScramState) {....raises: [], tags: [], forbids: [].}
- Wipe the expected server signature; it would aid an attacker impersonating the server. Later verification against it fails.