jose

JOSE (RFC 7515-7519) implementation in pure Crystal — JWK, JWS, JWE. Built to support crystal-clevis-geli (Tang client).

= jose :toc: left :toclevels: 2 :source-highlighter: rouge

image:https://github.com/aloli-crystal/jose/actions/workflows/ci.yml/badge.svg[CI,link=https://github.com/aloli-crystal/jose/actions/workflows/ci.yml]

JOSE (RFC 7515-7519) implementation in pure Crystal: JSON Web Signature (JWS), JSON Web Encryption (JWE), JSON Web Key (JWK). Elliptic curve and RSA.

French version: link:README.fr.adoc[README.fr.adoc].

== Why

jose exists primarily to support https://github.com/aloli-crystal/crystal-clevis-geli[crystal-clevis-geli], the Tang client for FreeBSD GELI volumes. Initial scope is intentionally minimal — only what the Tang protocol needs:

  • JWK: thumbprints (RFC 7638), serialization, ECDH key types (P-256, P-384, P-521).
  • JWS: ES256, ES384, ES512 (signature & verification).
  • JWE: ECDH-ES key agreement, A256GCM content encryption.

Since v0.2, RSA is supported as well, for a second reason: OpenID Connect and WebAuthn both sign with RS256 by default, so an EC-only library cannot verify a token from a typical identity provider, nor an assertion from a Windows Hello authenticator.

  • JWK: RSAKey — generation, JWK parsing and serialization, thumbprints, PKCS#1 / PKCS#8 / SubjectPublicKeyInfo DER.
  • JWS: RS256, RS384, RS512 (signature & verification).

Still out of scope (may be added later if needed):

  • RSA-OAEP and RSA-PSS (PS256, …).
  • EdDSA (Ed25519).
  • Full JWT validation (exp, nbf, aud, …).
  • JWKS endpoints (key discovery over HTTP).

== Installation

Add to your shard.yml:

[source,yaml]

dependencies: jose: github: aloli-crystal/jose version: ~> 0.2

Then shards install.

== Usage

[source,crystal]

require "jose"

=== JWK, elliptic curve ============================================

key = Jose::JWK::ECKey.generate(Jose::JWK::Curve::P256) key.thumbprint_base64url # => "cn-I_WNMClehiVp51i_0VpOENW1upEerA8sEam5hn-s" key.public_key.to_json # => {"kty":"EC","crv":"P-256","x":"...","y":"..."}

Parse a JWK (e.g. from a Tang advertisement).

peer = Jose::JWK::ECKey.from_json(jwk_string)

=== JWS, elliptic curve ============================================

jws = Jose::JWS.sign("payload", Jose::JWS::Algorithm::ES256, key) payload = Jose::JWS.verify(jws, key.public_key)

=== JWE ============================================================

jwe = Jose::JWE.encrypt("secret", peer.public_key) plaintext = Jose::JWE.decrypt(jwe, peer)

=== RSA ============================================================

rsa = Jose::JWK::RSAKey.generate(2048) rsa.modulus_bits # => 2048 token = Jose::JWS.sign("payload", Jose::JWS::Algorithm::RS256, rsa) payload = Jose::JWS.verify(token, rsa.public_key)

Verifying a token from an identity provider: take the JWK it publishes

and check the token against it.

idp_key = Jose::JWK::RSAKey.from_json(published_jwk) claims = Jose::JWS.verify(id_token, idp_key)

An algorithm and a key type must agree: signing with RS256 and an ECKey, or ES256 and an RSAKey, raises rather than silently doing something surprising. Algorithm#family reports which key a given algorithm wants.

NOTE: A private RSAKey needs its CRT members (p, q, dp, dq, qi) to be usable for signing. RFC 7518 makes them optional in a JWK, but OpenSSL cannot build a key from n, e and d alone. #crt_complete? reports whether they are present; verification with a public key is unaffected.

== Development

[source,shell]

shards install crystal spec crystal tool format --check crystal run lib/ameba/bin/ameba.cr -- src spec

== Contributing

. Fork the repository. . Create a feature branch from production. . Run crystal tool format src/ spec/ before each commit. . Open a pull request against production.

== License

MIT — see link:LICENSE[LICENSE].

== References

Repository

jose

Owner
Statistic
  • 0
  • 0
  • 0
  • 3
  • 1
  • 4 days ago
  • April 27, 2026
License

MIT License

Links
Synced at

Sat, 26 Sep 2026 19:12:08 GMT

Languages