SASLAuth.jl is a pure Julia implementation of the Simple Authentication and Security Layer (SASL) framework. It provides both client and server support for multiple authentication mechanisms, suitable for implementing protocol layers such as IMAP, LDAP, SMTP, XMPP, or custom client-server auth.
Supported mechanisms:
- ✅
SCRAM-SHA-256— secure, salted password-based challenge-response - ✅
PLAIN— simple username/password (must be used over TLS) - ✅
EXTERNAL— identity established by external means (e.g. TLS client cert)
Also included: SASLAuth.GSSAPI, thin bindings to the operating system's GSSAPI/Kerberos library (MIT libgssapi_krb5 on Linux, the GSS.framework on macOS, MIT Kerberos for Windows) for protocols that carry raw GSS tokens, such as PostgreSQL's GSSAPI authentication and encryption. No Kerberos is bundled; the system krb5.conf, ticket cache, and keytabs are used.
Install from the Julia registry:
using Pkg
Pkg.add("SASLAuth")For the development version:
Pkg.add(url="https://github.com/JuliaServices/SASLAuth.jl")Each mechanism provides:
Client <: SASLClientServer <: SASLServer
With the shared interface:
step!(client::SASLClient, input) → (message, done::Bool)step!(server::SASLServer, input) → (reply, done::Bool, success::Bool)
This local exchange shows both peers. In a networked application, send each
message to the other peer before calling its next step!.
Use one client instance per exchange and advance it through SASLAuth.step!.
The client retains the expected server verifier for that exchange until it
completes. Do not change its password or transcript fields between steps.
The server authenticates the exact username supplied to its constructor.
Usernames containing commas or equals signs are escaped on the wire and decoded
before comparison. This implementation supports the n,, GS2 header, without
channel binding or a separate authorization identity. SASLprep normalization is
not implemented; callers remain responsible for any required normalization.
Malformed SCRAM messages raise SASLAuth.SASLAuthError; an invalid client-final
message instead completes the server exchange with success == false. The
client option verify_server_signature=false allows an omitted server-final
message, but still rejects an explicit server error or malformed message.
using SASLAuth
password = "correcthorsebatterystaple"
salt = rand(UInt8, 16)
iterations = 4096
salted_password = SASLAuth.pbkdf2(Vector{UInt8}(password), salt, iterations)
server = SASLAuth.SCRAMSHA256Server("alice", salted_password, salt, iterations)
client = SASLAuth.SCRAMSHA256Client("alice", password)
first_message, _ = SASLAuth.step!(client, nothing)
challenge, _, _ = SASLAuth.step!(server, first_message)
proof, _ = SASLAuth.step!(client, challenge)
verifier, server_done, success = SASLAuth.step!(server, proof)
_, client_done = SASLAuth.step!(client, verifier)
@assert server_done && success && client_doneclient = PLAINClient("alice", "hunter2")
msg, _ = step!(client, nothing)
server = PLAINServer(username -> username == "alice" ? "hunter2" : nothing)
_, _, ok = step!(server, msg)client = EXTERNALClient("alice")
msg, _ = step!(client, nothing)
server = EXTERNALServer(authzid -> authzid == "alice")
_, _, ok = step!(server, msg)G = SASLAuth.GSSAPI
G.available() # a GSSAPI library could be loaded
G.has_credentials() # a ticket is available (kinit)
ctx = G.Context("postgres@db.example.com"; delegate=false, encrypt=true)
token, done = G.step!(ctx, nothing) # first token to send
token, done = G.step!(ctx, server_token) # repeat until done
sealed = G.wrap(ctx, plaintext) # confidentiality required
plaintext = G.unwrap(ctx, sealed)
G.wrap_size_limit(ctx, 16380) # largest plaintext per packet
close(ctx)Failures throw SASLAuth.GSSAPI.GSSError with both decoded status strings.
using Pkg
Pkg.test("SASLAuth")MIT © 2024 JuliaServices