Skip to content
/ gose Public

Gleam implementation of JOSE (JSON Object Signing and Encryption) standards (JWS, JWE, JWK, and JWT)

License

Notifications You must be signed in to change notification settings

jtdowney/gose

Repository files navigation

gose

Package Version Hex Docs

A Gleam implementation of JOSE (JSON Object Signing and Encryption) standards:

Project Goals

  • Type-Safe by Design - types enforce correct API usage at compile time. Unsigned JWS can't be serialized, unverified JWT claims can't be trusted.
  • Algorithm Pinning - JWT/JWS/JWE require explicit algorithm declaration, preventing algorithm confusion attacks common in other JOSE libraries. It trades off verbosity for security.
  • Invalid States Are Unconstructable - Keys and tokens are validated at construction time. If you have a Jwk, it's valid.

Installation

gleam add gose

Platform support

  • Erlang/OTP 27+
  • Node.js 22+

Browser JavaScript is not supported.

Supported Algorithms

All algorithms below apply to both raw JWS/JWE operations and JWT signing and encryption.

JWS Signing

Family Algorithms
HMAC HS256, HS384, HS512
RSA PKCS#1 v1.5 RS256, RS384, RS512
RSA-PSS PS256, PS384, PS512
ECDSA ES256 (P-256), ES384 (P-384), ES512 (P-521), ES256K (secp256k1)
EdDSA Ed25519, Ed448

JWE Key Management

Family Algorithms
Direct dir
AES Key Wrap A128KW, A192KW, A256KW
AES-GCM Key Wrap A128GCMKW, A192GCMKW, A256GCMKW
ChaCha20 Key Wrap C20PKW, XC20PKW
RSA RSA1_5, RSA-OAEP, RSA-OAEP-256
ECDH-ES ECDH-ES, ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW, ECDH-ES+C20PKW, ECDH-ES+XC20PKW
PBES2 PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW

JWE Content Encryption

Family Algorithms
AES-GCM A128GCM, A192GCM, A256GCM
AES-CBC + HMAC A128CBC-HS256, A192CBC-HS384, A256CBC-HS512
ChaCha20 C20P (ChaCha20-Poly1305), XC20P (XChaCha20-Poly1305)

Quick Start

JWT - Creating and Verifying Tokens

import gleam/dynamic/decode
import gleam/option.{None}
import gleam/time/duration
import gleam/time/timestamp
import gose/jwa
import gose/jwk
import gose/jwt

pub fn main() {
  // Generate a symmetric key for HS256
  let key = jwk.generate_hmac_key(jwa.HmacSha256)
  let now = timestamp.system_time()

  // Create claims
  let claims =
    jwt.claims()
    |> jwt.with_subject("user123")
    |> jwt.with_issuer("my-app")
    |> jwt.with_expiration(timestamp.add(now, duration.hours(1)))

  // Sign the JWT
  let assert Ok(signed) = jwt.sign(jwa.JwsHmac(jwa.HmacSha256), claims, key)

  // Serialize to compact format
  let token = jwt.serialize(signed)

  // Verify and validate
  let assert Ok(verifier) =
    jwt.verifier(jwa.JwsHmac(jwa.HmacSha256), [key], jwt.default_validation())
  let assert Ok(verified) = jwt.verify_and_validate(verifier, token, now)

  // Decode verified claims
  let decoder = decode.field("sub", decode.string, decode.success)
  let assert Ok("user123") = jwt.decode(verified, decoder)
}

JWS - Signing Data

import gose/jwa
import gose/jwk
import gose/jws
import kryptos/eddsa

pub fn main() {
  // Generate an Ed25519 key
  let key = jwk.generate_eddsa(eddsa.Ed25519)
  let payload = <<"hello world":utf8>>

  // Create and sign
  let assert Ok(signed) =
    jws.new(jwa.JwsEddsa)
    |> jws.sign(key, payload)

  // Serialize to compact format
  let assert Ok(token) = jws.serialize_compact(signed)

  // Parse and verify using a Verifier
  let assert Ok(parsed) = jws.parse_compact(token)
  let assert Ok(verifier) = jws.verifier(jwa.JwsEddsa, [key])
  let assert Ok(True) = jws.verify(verifier, parsed)
}

JWE - Encrypting Data

import gose/jwa
import gose/jwe
import gose/jwk

pub fn main() {
  // Generate a 256-bit key for direct encryption with AES-256-GCM
  let key = jwk.generate_enc_key(jwa.AesGcm(jwa.Aes256))
  let plaintext = <<"sensitive data":utf8>>

  // Encrypt using direct key encryption
  let assert Ok(encrypted) =
    jwe.new_direct(jwa.AesGcm(jwa.Aes256))
    |> jwe.encrypt(key, plaintext)

  // Serialize to compact format
  let assert Ok(token) = jwe.serialize_compact(encrypted)

  // Parse and decrypt with algorithm pinning
  let assert Ok(parsed) = jwe.parse_compact(token)
  let assert Ok(decryptor) = jwe.key_decryptor(jwa.JweDirect, jwa.AesGcm(jwa.Aes256), [key])
  let assert Ok(decrypted) = jwe.decrypt(decryptor, parsed)
  // decrypted == <<"sensitive data":utf8>>
}

Error Handling

The library uses a two-tier error design:

GoseError — used by JOSE primitives (JWS, JWE, JWK):

Variant When It Occurs
ParseError Invalid base64 encoding, malformed JSON, wrong token format
CryptoError Signature verification failure, decryption failure, key derivation error
InvalidState Wrong key type for algorithm, missing required header, incompatible parameters

JwtError — used by JWT and encrypted JWT modules:

Variant When It Occurs
TokenExpired Token's exp claim is in the past
TokenNotYetValid Token's nbf claim is in the future
IssuerMismatch Token's iss doesn't match expected issuer
AudienceMismatch Token's aud doesn't match expected audience
InvalidSignature JWS signature verification failed
DecryptionFailed JWE decryption failed
JoseError(GoseError) Underlying JOSE operation failed (key validation, signing, etc.)
... See JwtError type for all variants

Limitations

  • X.509 certificate parameters not supported - JWKs containing X.509 certificate chain parameters (x5u, x5c, x5t, x5t#S256) are rejected with a parse error. Certificate-based key validation must be performed outside this library.

Documentation

Full API documentation is available at hexdocs.pm/gose.

About

Gleam implementation of JOSE (JSON Object Signing and Encryption) standards (JWS, JWE, JWK, and JWT)

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Languages