gose

Package Version Hex Docs

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

JOSE:

COSE:

Project Goals

Should you use this?

My professional opinion as a long-time security engineering practitioner is that you should basically never use these algorithms in a greenfield system. This library was created for the purpose of integrating with existing systems that already use these standards (like ACME or Webauthn).

Installation

gleam add gose

Some examples below import kryptos directly for key generation; add it with gleam add kryptos if needed.

Platform support

Browser JavaScript is not supported.

Supported Algorithms

Signing (JWS, COSE_Sign1, and COSE_Sign)

FamilyAlgorithms
HMACHS256, HS384, HS512
RSA PKCS#1 v1.5RS256, RS384, RS512
RSA-PSSPS256, PS384, PS512
ECDSAES256 (P-256), ES384 (P-384), ES512 (P-521), ES256K (secp256k1)
EdDSAEd25519, Ed448

MAC (COSE_Mac0)

FamilyAlgorithms
HMACHS256, HS384, HS512

JWE Key Management

FamilyAlgorithms
Directdir
AES Key WrapA128KW, A192KW, A256KW
AES-GCM Key WrapA128GCMKW, A192GCMKW, A256GCMKW
ChaCha20 Key WrapC20PKW, XC20PKW
RSARSA1_5, RSA-OAEP, RSA-OAEP-256
ECDH-ESECDH-ES, ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW, ECDH-ES+C20PKW, ECDH-ES+XC20PKW
PBES2PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW

Content Encryption (JWE and COSE_Encrypt0)

FamilyAlgorithms
AES-GCMA128GCM, A192GCM, A256GCM
AES-CBC + HMACA128CBC-HS256, A192CBC-HS384, A256CBC-HS512 (JWE only)
ChaCha20C20P (ChaCha20-Poly1305), XC20P (XChaCha20-Poly1305)

Quick Start

JWT

import gleam/dynamic/decode
import gleam/time/duration
import gleam/time/timestamp
import gose/algorithm
import gose/jose/jwt
import gose/key

pub fn main() {
  let signing_key = key.generate_hmac_key(algorithm.HmacSha256)
  let now = timestamp.system_time()

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

  let assert Ok(signed) =
    jwt.sign(algorithm.Mac(algorithm.Hmac(algorithm.HmacSha256)), claims:, key: signing_key)
  let token = jwt.serialize(signed)

  let assert Ok(verifier) =
    jwt.verifier(algorithm.Mac(algorithm.Hmac(algorithm.HmacSha256)), keys: [signing_key], options: jwt.default_validation())
  let assert Ok(verified) = jwt.verify_and_validate(verifier, token, now)

  let decoder = decode.field("sub", decode.string, decode.success)
  let assert Ok("user123") = jwt.decode(verified, using: decoder)
}

JWE

import gose/algorithm
import gose/jose/jwe
import gose/key

pub fn main() {
  let encryption_key = key.generate_enc_key(algorithm.AesGcm(algorithm.Aes256))
  let plaintext = <<"sensitive data":utf8>>

  let assert Ok(encrypted) =
    jwe.new_direct(algorithm.AesGcm(algorithm.Aes256))
    |> jwe.encrypt(key: encryption_key, plaintext:)

  let assert Ok(token) = jwe.serialize_compact(encrypted)

  let assert Ok(parsed) = jwe.parse_compact(token)
  let assert Ok(decryptor) = jwe.key_decryptor(algorithm.Direct, algorithm.AesGcm(algorithm.Aes256), keys: [encryption_key])
  let assert Ok(decrypted) = jwe.decrypt(decryptor, parsed)
  assert decrypted == <<"sensitive data":utf8>>
}

COSE_Sign1

import gose/algorithm
import gose/cose/sign1
import gose/key
import kryptos/ec

pub fn main() {
  let signing_key = key.generate_ec(ec.P256)
  let payload = <<"hello COSE":utf8>>

  let assert Ok(signed) =
    sign1.new(algorithm.Ecdsa(algorithm.EcdsaP256))
    |> sign1.sign(signing_key, payload)
  let data = sign1.serialize(signed)

  let assert Ok(parsed) = sign1.parse(data)
  let assert Ok(verifier) =
    sign1.verifier(algorithm.Ecdsa(algorithm.EcdsaP256), keys: [signing_key])
  let assert Ok(Nil) = sign1.verify(verifier, parsed)
  assert sign1.payload(parsed) == Ok(payload)
}

CWT

import gleam/time/duration
import gleam/time/timestamp
import gose/algorithm
import gose/cose/cwt
import gose/key
import kryptos/ec

pub fn main() {
  let signing_key = key.generate_ec(ec.P256)
  let now = timestamp.system_time()

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

  let assert Ok(token) =
    cwt.sign(claims, alg: algorithm.Ecdsa(algorithm.EcdsaP256), key: signing_key)

  let assert Ok(verifier) =
    cwt.verifier(algorithm.Ecdsa(algorithm.EcdsaP256), keys: [signing_key])
  let assert Ok(verified) = cwt.verify_and_validate(verifier, token:, now:)
  let verified_claims = cwt.verified_claims(verified)
  let assert Ok(subject) = cwt.subject(verified_claims)
  assert subject == "user123"
}

Error Handling

The library uses a two-tier error design:

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

VariantWhen It Occurs
ParseErrorInvalid base64 encoding, malformed JSON, wrong token format
CryptoErrorDecryption failure, key derivation error
InvalidStateWrong key type for algorithm, missing required header, incompatible parameters
VerificationFailedSignature or MAC verification failed (intentionally opaque)

JwtError used by JWT and encrypted JWT modules:

VariantWhen It Occurs
TokenExpiredToken’s exp claim is in the past
TokenNotYetValidToken’s nbf claim is in the future
IssuerMismatchToken’s iss doesn’t match expected issuer
AudienceMismatchToken’s aud doesn’t match expected audience
InvalidSignatureJWS signature verification failed
DecryptionFailedJWE decryption failed
JoseError(GoseError)Underlying JOSE operation failed (key validation, signing, etc.)
See JwtError type for all variants

CwtError used by CWT and encrypted CWT modules:

VariantWhen It Occurs
TokenExpiredToken’s exp claim is in the past
TokenNotYetValidToken’s nbf claim is in the future
IssuerMismatchToken’s iss doesn’t match expected issuer
AudienceMismatchToken’s aud doesn’t match expected audience
MissingExpirationToken lacks a required exp claim
InvalidClaimClaim value is invalid (empty audience list, etc.)
InvalidSignatureCOSE_Sign1 signature verification failed
MalformedTokenCBOR structure or claim types are invalid
DecryptionFailedCOSE decryption failed
CoseError(GoseError)Underlying COSE operation failed (key validation, signing, etc.)

Limitations

Documentation

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

Search Document