Skip to content

Internal API Reference

This document describes the exported public API of each luci-sso module. It is intended for developers extending the system, writing tests, or implementing a new crypto backend.

For the rationale behind the architectural boundaries described here, see About the Architecture.


The Result type

All fallible functions return a Result object rather than throwing. The shape is:

{
    ok:      bool,    // true = success, false = failure
    data:    any,     // present when ok == true
    error:   string,  // error code from luci_sso.errors; present when ok == false
    details: any      // optional diagnostic context; present when ok == false
}

Check result.ok before using result.data. Never access .data on a failed result.


luci_sso.result

Constructors for the Result type.

ok(data)Result

Creates a successful result.

Parameter Type Description
data any The success value.

err(error, details?)Result

Creates a failed result.

Parameter Type Description
error string An error code constant from luci_sso.errors.
details any Optional. Additional context (string, object, or {http_status: int}).

is(obj)bool

Returns true if obj is a Result instance. Use this before duck-typing .ok.


luci_sso.handshake

The OIDC state machine. This is the top-level entry point for both legs of the authorization code flow.

initiate(io, config)Result<{url, token}>

Starts the login flow. Generates PKCE, state, and nonce; writes a handshake file; and returns the IdP redirect URL.

Parameter Type Description
io io provider The I/O provider from luci_sso.io.
config object Loaded UCI config from luci_sso.config.load().

On success, result.data contains:

Field Type Description
url string Full redirect URL to the IdP's authorization_endpoint.
token string State token. Set this as the value of the __Host-luci_sso_state cookie.

authenticate(io, config, request, policy?)Result<{sid, email}>

Processes the IdP callback. Verifies state and nonce, exchanges the code for tokens, validates the ID Token, maps claims to a role, and injects a UBUS session.

Parameter Type Description
io io provider The I/O provider.
config object Loaded UCI config.
request object Parsed request from luci_sso.web.request(). Contains params and cookies.
policy object Optional. Security policy overrides. Defaults to {allowed_algs: ["RS256", "ES256"]}.

On success, result.data contains:

Field Type Description
sid string UBUS session ID. Set this as the sysauth_https and sysauth cookie values.
email string Authenticated user's email address.

luci_sso.crypto

High-level cryptographic operations. Wraps the native C bridge — all calls go through the loaded luci_sso.native backend.

constant_time_eq(a, b)bool

Compares two strings in constant time. Use this for all security-sensitive comparisons (nonces, states, tokens).

jws_sign(payload, secret)string

Creates a JWS compact serialization (HS256) of payload. Returns the signed token string.

Parameter Type Description
payload object JSON-serializable object to sign.
secret string HMAC key (raw bytes).

jws_verify(token, secret)Result<object>

Verifies a JWS token and returns the decoded payload.

Parameter Type Description
token string JWS compact serialization.
secret string HMAC key (raw bytes).

jwt_verify(token, pubkey, options)Result<object>

Verifies a JWT using an asymmetric public key and returns the decoded claims object.

Parameter Type Description
token string JWT compact serialization.
pubkey string PEM-encoded public key (RSA or EC).
options object Options object. options.alg constrains the accepted algorithm.

random(len)string

Returns len cryptographically random bytes (raw binary string) from the CSPRNG.

hash_sha256(str)string

Returns the SHA-256 digest of str as 32 raw bytes.

hash_sha256_hex(str)string

Returns the SHA-256 digest of str as a 64-character lowercase hex string.

pkce_pair(len){verifier, challenge}

Generates a PKCE verifier and its S256 code challenge.

Field Type Description
verifier string Base64URL-encoded random string of length len (43–128).
challenge string Base64URL-encoded SHA256 of the verifier. Send this to the IdP.

jwk_to_pem(jwk)Result<string>

Converts a JSON Web Key object (RSA or EC P-256) to a PEM-encoded public key string.

safe_id(token)string

Returns the first 8 characters of the hex SHA-256 of token. Safe to include in log messages as a non-reversible token identifier.

set_native(n)void

Replaces the active native backend. Testing use only — allows tests to substitute a mock crypto implementation.


luci_sso.oidc

Pure OIDC protocol logic. No I/O except where an io parameter is explicitly required. Also re-exports discover, fetch_jwks, and find_jwk from luci_sso.discovery.

get_auth_url(io, config, discovery_doc, params)Result<string>

Constructs the full authorization URL (including PKCE challenge, state, nonce, scope, and redirect URI).

Parameter Type Description
io io provider Used for logging only.
config object UCI config.
discovery_doc object Parsed OIDC discovery document.
params object Handshake state object from luci_sso.session.create_state().

exchange_code(io, config, discovery, code, verifier, session_id)Result<{id_token, access_token, ...}>

Exchanges an authorization code for tokens via the IdP's token endpoint (back-channel).

Parameter Type Description
io io provider Used for HTTP and logging.
config object UCI config.
discovery object Parsed OIDC discovery document.
code string Authorization code from the IdP callback.
verifier string PKCE code verifier generated at flow initiation.
session_id string Handshake session ID (for log correlation).

verify_id_token(io, tokens, keys, config, handshake, discovery, now, policy)Result<claims>

Validates an ID Token against all OIDC Core §3.1.3.7 requirements: algorithm, signature, iss, aud, exp, iat, nonce, and at_hash.

Parameter Type Description
io io provider Used for JWKS refresh on signature failure and logging.
tokens object Token response object containing id_token and access_token.
keys array Array of JWK objects from fetch_jwks().
config object UCI config. Provides issuer_url, client_id, clock_tolerance.
handshake object Handshake state. Provides the expected nonce.
discovery object Discovery document. Provides jwks_uri for refresh.
now int Current Unix timestamp (from io.time()).
policy object Security policy. policy.allowed_algs lists accepted algorithms.

On success, result.data is the decoded JWT claims object.

fetch_userinfo(io, endpoint, access_token)Result<object>

Fetches user profile claims from the IdP's UserInfo endpoint. Called when the ID Token does not contain an email claim.


luci_sso.config

UCI configuration loader and role mapper.

is_enabled(io)Result<bool>

Returns Result.ok(true) if the enabled option is '1' in UCI.

load(io)Result<config_object>

Reads and validates the full UCI configuration. Returns a config object suitable for passing to handshake.initiate() and handshake.authenticate(). Fails with CONFIG_ERROR if any required option is missing or invalid.

The returned config object shape:

Field Type Source
issuer_url string luci-sso.default.issuer_url
internal_issuer_url string | null luci-sso.default.internal_issuer_url
client_id string luci-sso.default.client_id
client_secret string luci-sso.default.client_secret
redirect_uri string luci-sso.default.redirect_uri
scope string luci-sso.default.scope
clock_tolerance int luci-sso.default.clock_tolerance
roles array All config role sections

find_roles_for_user(config, claims)Result<{role_name, read, write}>

Matches a user's OIDC claims against the configured roles. Returns the merged permissions of all matching roles.

Parameter Type Description
config object Loaded config from load().
claims object JWT claims object. Uses claims.email and claims.groups.

On success, result.data contains:

Field Type Description
role_name string Name of the first matched role. Used as the UBUS session label.
read array Merged list of LuCI access groups granted read access.
write array Merged list of LuCI access groups granted write access.

luci_sso.discovery

OIDC metadata fetching and caching. Also exported from luci_sso.oidc.

discover(io, issuer, options)Result<discovery_doc>

Fetches and validates the OIDC discovery document from <issuer>/.well-known/openid-configuration. Caches the result in /var/run/luci-sso/ for 24 hours.

Parameter Type Description
io io provider Used for HTTP and filesystem.
issuer string The public OIDC issuer URL.
options object Optional. options.internal_issuer_url overrides the fetch origin for split-horizon setups.

fetch_jwks(io, jwks_uri, options)Result<{keys}>

Fetches the JWK Set from the IdP. Caches in /var/run/luci-sso/ for 24 hours.

Parameter Type Description
io io provider Used for HTTP and filesystem.
jwks_uri string The jwks_uri from the discovery document.
options object Optional. options.force bypasses the cache.

On success, result.data.keys is an array of JWK objects.

find_jwk(keys, kid)Result<jwk>

Finds a JWK by kid (key ID). If kid is absent, returns the first key.


Native C bridge (src/native.h)

The C interface that all crypto backends must implement. See How to Add a New Crypto Backend for the full walkthrough.

All input buffers are subject to the NATIVE_MAX_INPUT_SIZE (16 384 bytes) limit enforced by web.uc before data reaches the C layer.

native_crypto_init()int

Initializes the crypto backend (e.g., PSA Crypto for mbedTLS). Called once at startup. Returns 0 on success.

native_crypto_deinit()void

Releases backend resources. Called during test teardown and fuzzing cleanup.

native_verify_rs256(msg, msg_len, sig, sig_len, key_pem, key_len)bool

Verifies an RS256 signature. Rejects RSA keys shorter than NATIVE_RSA_MIN_BITS (2048 bits).

native_verify_es256(msg, msg_len, sig, sig_len, key_pem, key_len)bool

Verifies an ES256 (ECDSA P-256) signature using constant-time comparison.

native_sha256(input, input_len, output)int

Computes SHA-256. output must be at least NATIVE_SHA256_SIZE (32) bytes. Returns 0 on success.

native_hmac_sha256(key, key_len, msg, msg_len, output)int

Computes HMAC-SHA256. output must be at least 32 bytes. Returns 0 on success.

native_random(buf, len)int

Fills buf with len cryptographically random bytes from a CSPRNG. Returns 0 on success. Must not use a predictable source.

native_memzero(p, len)void

Zeroizes len bytes at p using a compiler-safe method (e.g., explicit_bzero) that cannot be optimized away.

native_jwk_rsa_to_pem(n, n_len, e, e_len, out, out_len)int

Converts RSA JWK modulus (n) and exponent (e) to a PEM-encoded public key. out must be at least NATIVE_RSA_PEM_MAX (4096) bytes.

native_jwk_ec_p256_to_pem(x, x_len, y, y_len, out, out_len)int

Converts EC P-256 JWK coordinates (x, y) to a PEM-encoded public key. Validates coordinate lengths against NATIVE_EC_COORD_SIZE (32 bytes) before use. out must be at least NATIVE_EC_PEM_MAX (2048) bytes.


Testing utilities (test/)

The test framework lives in test/ and is not part of the installed package.

Module Purpose
test/testing/ Test runner, test(), assert(), assert_eq()
test/mock.uc Mock I/O provider factory — mock.create()
test/lib/ Shared fixtures (valid JWTs, JWKS, discovery docs)

See Testing Architecture for the tier structure and How to Run Tests for usage.