Skip to content
Jennifer Programming Language

sasl API reference

The SASL authentication mechanisms shared by the mail clients (smtp / pop / imap). These format the client tokens; the protocol clients run the mechanism-specific wire dialogue (SMTP AUTH, IMAP AUTHENTICATE, POP3 AUTH) around them. No networking; TinyGo-clean, so it runs on both binaries.

  • Simple encoders (plain, loginUser / loginPass, bearer) - one base64 token per step. bearer builds the SASL XOAUTH2 response from an OAuth2 bearer token (named bearer, not xoauth2, because a Jennifer method name is letters-only; "XOAUTH2" is the string the client sends).
  • Challenge-response (cram, and the SCRAM family) - keyed by the hash / crypto libraries. cram (CRAM-MD5, RFC 2195) answers a server challenge in one step. SCRAM is a multi-step exchange over a Scram handle: scramStart -> scramClientFirst (send), receive server-first -> scramClientFinal + scramFinalToken (send), receive server-final -> scramVerify (checks the server signature, so a MITM without the password is caught). Mechanism "sha1" is SCRAM-SHA-1, "sha256" is SCRAM-SHA-256.

Import with import "sasl.j" as sasl;. See the sasl guide for prose and examples.

Functions

sasl.bearer(user as string, token as string)

Build the SASL XOAUTH2 response for an OAuth2 bearer token: base64("user=" user 0x01 "auth=Bearer " token 0x01 0x01). This is how Google and Microsoft 365 authenticate mail (both have retired password auth).

Parameters

  • user {string} - the account username
  • token {string} - the OAuth2 bearer access token

Returns {string} - the base64-encoded XOAUTH2 response

sasl.cram(user as string, pass as string, challenge as string)

Build the CRAM-MD5 response (RFC 2195): base64 of "user <hex>", where <hex> is the lowercase HMAC-MD5 of the server's (base64-encoded) challenge keyed by the password. A one-step challenge-response mechanism. Named cram (not cramMd5) because a Jennifer method name is letters-only; the wire mechanism is "CRAM-MD5".

Parameters

  • user {string} - the login username
  • pass {string} - the login password
  • challenge {string} - the server's base64-encoded challenge

Returns {string} - the base64-encoded CRAM-MD5 response

sasl.loginPass(pass as string)

Build the password step of SASL LOGIN (a server sends the "Password:" prompt).

Parameters

  • pass {string} - the login password

Returns {string} - the base64-encoded password

sasl.loginUser(user as string)

Build the username step of SASL LOGIN (a server sends the "Username:" prompt).

Parameters

  • user {string} - the login username

Returns {string} - the base64-encoded username

sasl.negotiate(advertised as list of string)

Choose the strongest password mechanism this module implements from the list a server advertises, returned as the auth token a mail client uses: "scram-sha-256" > "scram-sha-1" > "cram", or "" when the server offers none (the caller then falls back to its protocol default - PLAIN / LOGIN / USER-PASS). XOAUTH2 is never auto-selected: it needs a bearer token, not a password.

Parameters

  • advertised {list of string} - the mechanism names the server advertises (any case)

Returns {string} - the chosen auth token, or "" for the protocol default

sasl.plain(user as string, pass as string)

Build the SASL PLAIN response: base64 of "\0user\0pass".

Parameters

  • user {string} - the login username
  • pass {string} - the login password

Returns {string} - the base64-encoded PLAIN token

sasl.scramClientFinal(s as Scram, serverFirst as string, password as string)

Process the server-first message and compute the client-final message. Derives the salted password (PBKDF2 in the mechanism's hash), the client proof, and the expected server signature; the returned state carries both. Throws Error{kind: "sasl"} if the server's nonce does not extend the client nonce.

Parameters

  • s {Scram} - the exchange state
  • serverFirst {string} - the base64 server-first message
  • password {string} - the login password

Returns {Scram} - the updated state; pass it to scramFinalToken and scramVerify

sasl.scramClientFirst(s as Scram)

The base64 client-first message to send to the server (SASL initial response).

Parameters

  • s {Scram} - the exchange state from scramStart

Returns {string} - the base64-encoded client-first message

sasl.scramFinalToken(s as Scram)

The base64 client-final message to send (after scramClientFinal).

Parameters

  • s {Scram} - the state returned by scramClientFinal

Returns {string} - the base64-encoded client-final message

sasl.scramStart(user as string, algo as string)

Begin a SCRAM exchange: generate a crypto-grade client nonce and build the initial state. algo selects the mechanism ("sha1" or "sha256").

Parameters

  • user {string} - the login username
  • algo {string} - "sha1" (SCRAM-SHA-1) or "sha256" (SCRAM-SHA-256)

Returns {Scram} - the exchange state; pass it to scramClientFirst

sasl.scramVerify(s as Scram, serverFinal as string)

Verify the server-final message: the server's signature must match the one derived in scramClientFinal (constant-time), proving the server also knows the password. Reject the session on false.

Parameters

  • s {Scram} - the state returned by scramClientFinal
  • serverFinal {string} - the base64 server-final message

Returns {bool} - true if the server signature verifies

Structs

sasl.Scram

The state of an in-progress SCRAM exchange, threaded through the four calls. Value-semantic: each step returns an updated copy.

FieldTypeDescription
algostringthe hash mechanism: "sha1" (SCRAM-SHA-1) or "sha256" (SCRAM-SHA-256)
clientNoncestringthe client-generated nonce
clientFirstBarestringthe client-first message without the gs2 header
clientFinalstringthe client-final message (set by scramClientFinal)
serverSigstringthe base64 ServerSignature to expect (set by scramClientFinal)