scram.bend relies on unsafe/foreign
raw source on the hub · import bend-kit-postgres@0.1.0.1/scram.bend as Scram
SCRAM-SHA-256 (RFC 5802, RFC 7677) client for PostgreSQL SASL authentication, without channel binding.
3 imports
import Base import bend-kit-bytes@0.3.1.0/bytes.bend as Bytes import bend-kit-crypto@0.1.1.0/crypto.bend as Crypto
Types
type First source · line 18 · raw
Data
bare: client-first-message-bare. nonce: the client nonce.
First@bare:String -> @nonce:String -> First
type Final source · line 22 · raw
Data
sig: the ServerSignature the server-final-message must carry.
Final@sig:String -> Final
type Parsed source · line 26 · raw
Data
nonce: the combined nonce. salt: decoded octets. iters: at least 1.
Parsed@nonce:String -> @salt:String -> @iters:U32 -> Parsed
Definitions
def mechanism source · line 14 · raw
String
def saslname.of source · line 30 · raw
@+c:U32 -> @+rest:String -> String
saslname (RFC 5802 §5.1): '=' as "=3D", ',' as "=2C".
def saslname source · line 33 · raw
@s:String -> String
def first.with source · line 41 · raw
@+user:String -> @+nonce:String -> Pair(First, String)
The client-first-message for a nonce: gs2 header "n,," (no channel binding), then the bare part.
def first.of source · line 45 · raw
@+user:String -> @r:Result<&1, &1, Pair(U32, String), Pair(U32, Array<U32>)> -> Result<&1, &1, Pair(U32, String), Pair(First, String)>
def first source · line 53 · raw
@+user:String -> IO(Result<&1, &1, Pair(U32, String), Pair(First, String)>)
A fresh 18-octet nonce from the OS secure random source, base64 (24 printable chars, no ',').
def attr source · line 61 · raw
@+name:U32 -> @s:String -> Maybe<&2, String>
The value of attribute name, when s is name=value.
def printable source · line 69 · raw
@s:String -> Bool
printable (RFC 5802 §7): %x21-2B / %x2D-7E.
def digits source · line 76 · raw
@s:String -> Bool
def nonce.ok source · line 84 · raw
@+client:String -> @+server:String -> Bool
The server nonce must be the client nonce plus at least one more printable char.
def parse.iters source · line 87 · raw
@ok:Bool -> @m:Maybe<&2, U32> -> @+nonce:String -> @+salt:String -> Result<&1, &1, Pair(U32, String), Parsed>
def parse.salt.of source · line 94 · raw
@empty:Bool -> @+nonce:String -> @+salt:String -> @+it:String -> Result<&1, &1, Pair(U32, String), Parsed>
def parse.salt source · line 101 · raw
@m:Maybe<&1, 0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes> -> @+nonce:String -> @+it:String -> Result<&1, &1, Pair(U32, String), Parsed>
def parse.nonce source · line 109 · raw
@ok:Bool -> @+nonce:String -> @+salt:String -> @+it:String -> Result<&1, &1, Pair(U32, String), Parsed>
def parse.fields source · line 116 · raw
@+client:String -> @r:Maybe<&2, String> -> @s:Maybe<&2, String> -> @i:Maybe<&2, String> -> Result<&1, &1, Pair(U32, String), Parsed>
def parse.parts source · line 123 · raw
@+client:String -> @parts:List<&2, String> -> Result<&1, &1, Pair(U32, String), Parsed>
def parse source · line 131 · raw
@+client:String -> @+msg:String -> Result<&1, &1, Pair(U32, String), Parsed>
The server-first-message checked against the client nonce.
def octets source · line 136 · raw
@r:Result<&1, &1, Pair(U32, String), Pair(U32, Array<U32>)> -> Result<&1, &1, Pair(U32, String), String>
def hmac.of source · line 143 · raw
@k:0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes -> @d:0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes -> IO(Result<&1, &1, Pair(U32, String), Pair(U32, Array<U32>)>)
def hmac source · line 148 · raw
@+key:String -> @+data:String -> IO(Result<&1, &1, Pair(U32, String), String>)
def sha.of source · line 153 · raw
@b:0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes -> IO(Result<&1, &1, Pair(U32, String), Pair(U32, Array<U32>)>)
def sha source · line 157 · raw
@+data:String -> IO(Result<&1, &1, Pair(U32, String), String>)
def pbkdf2.of source · line 162 · raw
@p:0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes -> @s:0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes -> @+iters:U32 -> IO(Result<&1, &1, Pair(U32, String), Pair(U32, Array<U32>)>)
def pbkdf2 source · line 168 · raw
@+pass:String -> @+salt:String -> @+iters:U32 -> IO(Result<&1, &1, Pair(U32, String), String>)
SaltedPassword = Hi(pass, salt, iters) (RFC 5802 §2.2), which is PBKDF2 with HMAC-SHA-256.
def eq.of source · line 173 · raw
@a:0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes -> @b:0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes -> IO(Bool)
def xor source · line 178 · raw
@a:String -> @b:String -> String
def step.of source · line 187 · raw
@r:Result<&1, &1, Pair(U32, String), String> -> @k:(@_:String -> IO(Result<&1, &1, Pair(U32, String), Pair(Final, String)>)) -> IO(Result<&1, &1, Pair(U32, String), Pair(Final, String)>)
def step source · line 194 · raw
@m:IO(Result<&1, &1, Pair(U32, String), String>) -> @k:(@_:String -> IO(Result<&1, &1, Pair(U32, String), Pair(Final, String)>)) -> IO(Result<&1, &1, Pair(U32, String), Pair(Final, String)>)
def respond.of source · line 200 · raw
@r:Result<&1, &1, Pair(U32, String), Parsed> -> @+bare:String -> @+sf:String -> @+pass:String -> IO(Result<&1, &1, Pair(U32, String), Pair(Final, String)>)
c=biws is base64 of the gs2 header "n,,". AuthMessage joins the three bare messages.
def respond source · line 216 · raw
@st:First -> @+sf:String -> @+pass:String -> IO(Result<&1, &1, Pair(U32, String), Pair(Final, String)>)
The client-final-message with ClientProof for the server-first-message sf and the password.
def final.sig source · line 222 · raw
@m:Maybe<&1, 0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes> -> Result<&1, &1, Pair(U32, String), String>
def final.attr source · line 229 · raw
@s:String -> Result<&1, &1, Pair(U32, String), String>
def final.parts source · line 238 · raw
@parts:List<&2, String> -> Result<&1, &1, Pair(U32, String), String>
def verify.ok source · line 245 · raw
@ok:Bool -> Result<&1, &1, Pair(U32, String), Unit>
def verify.of source · line 252 · raw
@+sig:String -> @r:Result<&1, &1, Pair(U32, String), String> -> IO(Result<&1, &1, Pair(U32, String), Unit>)
def verify source · line 262 · raw
@st:Final -> @+msg:String -> IO(Result<&1, &1, Pair(U32, String), Unit>)
Done when the server-final-message proves the server knows the password, compared in constant time.