Skip to content

reallyme/jose

Repository files navigation

reallyme-jose

Rust CI reallyme-jose Security Policy License

reallyme-jose is a focused JOSE layer for identity systems that need compact JWS, JWT, and JWE handling without broad algorithm negotiation. It builds on reallyme-codec and reallyme-crypto to provide strict protected-header validation, explicit algorithm/key binding, typed non-PII errors, and portable conformance vectors for SDK and protocol implementations.

Crates

This repository publishes two Rust crates:

  • reallyme-jose: the ergonomic Rust SDK crate for compact JWS, JWT, and JWE;
  • reallyme-jose-proto: checked-in Buffa protobuf bindings for the versioned JOSE wire boundary.

The Rust SDK remains the primary application-facing API. The protobuf crate is published separately because Cargo requires optional normal dependencies to exist in the registry, but the wire feature is opt-in so native SDK users do not compile Buffa or generated protobuf code by default.

The protobuf boundary exists so FFI, WASM, Swift, Kotlin, TypeScript, and out-of-process adapters can share one generated byte contract around the same Rust dispatch and error semantics. It is not a second JOSE implementation and does not define an RPC service, transport, endpoint, framing protocol, or service-discovery model. Those concerns belong to the embedding application.

Install

[dependencies]
reallyme-jose = "0.2.0"

Supported JOSE Surface

reallyme-jose supports a deliberately small JOSE profile:

  • compact JWS for ES256 and EdDSA;
  • signed and unsigned JWT parsing with algorithm/key binding, temporal policy, and typ policy;
  • JWT signing and verification for ES256, ES256K, and EdDSA; ES256K is JWT-only and uses the low-S secp256k1 policy enforced by reallyme-crypto;
  • compact JWE encryption and decryption for dir and ECDH-ES;
  • AES-GCM content encryption with A128GCM, A192GCM, and A256GCM;
  • ECDH-ES over P-256, P-384, and P-521 in the native lane;

The profile follows RFC 7515, RFC 7516, RFC 7518, RFC 7519, RFC 8725, and RFC 9864. Algorithm identifiers map explicitly to ReallyMe crypto primitives; caller-supplied JOSE headers never select arbitrary algorithms or keys. EdDSA is accepted only with an Ed25519 JWK binding until the product adopts a fully specified JOSE identifier from RFC 9864.

Unsigned JWT decoding is parsing only. It enforces the alg = "none" compact shape but does not authenticate the sender, so verifier-grade paths must use the signed JWT verification helpers.

JWK parsing, serialization, validation, and thumbprints live in reallyme-crypto. This crate consumes that JWK representation for algorithm, curve, and kid binding, and rejects untrusted key-indirection headers in compact JWS/JWT/JWE inputs.

ES256 verification accepts both low-S and high-S ECDSA signatures that are otherwise valid JOSE signatures. That is RFC 7515-compatible; applications that deduplicate by token hash should canonicalize at a higher layer rather than assuming every issuer emits one unique compact serialization. Face ID and Secure Enclave protected P-256 keys may emit canonical signatures in practice, but this verifier does not depend on that signer behavior: replay protection, audit identity, and cache keys should be bound to the challenge, nonce, jti, payload hash, and key identity rather than raw compact-token or signature bytes.

Decoded claims can be returned as serde_json::Value or caller-owned types for normal Rust SDK ergonomics. Those deserialized values are not zeroizing owners, so privacy-sensitive callers should keep claim lifetimes short or use the claims-JSON byte helpers when they need explicit zeroization at the JOSE boundary.

The wasm feature lane delegates P-256 ECDH point decompression and shared-secret calculation to the host provider. That provider must reject malformed, off-curve, or wrong-length SEC1 public keys before returning key material; the native lane enforces the same contract inside reallyme-crypto.

The following JOSE features are not part of this profile and fail closed:

  • RSA JWS and RSA JWE algorithms;
  • AES Key Wrap and PBES2 key management;
  • JWE JSON serialization;
  • b64, crit, zip, jku, embedded jwk, x5u, and x5c protected-header parameters.

Wire Boundary

With the wire feature enabled, the wire module provides a transport-neutral, Codec-style process-proto lane. Its byte-level flow is:

  1. Encode one JoseOperationRequest, whose oneof selects the operation.
  2. Pass those bytes to process_proto or the equivalent adapter-specific entrypoint.
  3. Decode the returned JoseProtoResultEnvelope. For result status, its payload is the operation-specific result message; for jose_error status, its payload is a structured JoseError.

Malformed requests also return a result-envelope byte sequence with a typed backend error. This keeps expected validation and dispatch failures in the result model instead of relying on exceptions or transport-specific status codes. process_json accepts the proto3-compatible JSON representation of the same request but returns the same binary JoseProtoResultEnvelope bytes as process_proto; it is not a separate semantic API. Adapters that require a JSON response can convert the structured JoseProtoOutput with jose_proto_output_to_json.

Wire errors preserve both the branch (primitive, provider, or backend) and the exact JoseErrorReason. Malformed protobuf, malformed JSON, unsupported algorithms, provider failures, invalid keys, invalid lengths, authentication failures, and invalid signatures are kept distinct at this boundary.

JWT wire header policy is presence-sensitive: an omitted policy uses the standard lenient typ behavior, while an explicitly default-constructed policy is strict because allow_missing_typ defaults to false in protobuf.

JWT wire temporal validation is explicit: adapters must either set signature_only or provide temporal_policy with a nonzero temporal_policy.now_unix. This keeps an omitted proto3 timestamp from silently disabling expiration checks.

JWE decrypt requests can carry a presence-sensitive protected-header policy. Adapters can require kid and bind exact kid, typ, cty, apu, and apv values. The wrapper messages distinguish an omitted expectation from an explicitly expected empty string or byte sequence, which is required for protocol context binding.

Owned wire output buffers use Zeroizing<Vec<u8>>, and JSON envelope output uses Zeroizing<String>. Result envelopes can carry claims JSON or decrypted plaintext, so adapters must treat returned bytes as sensitive until they are handed to the next owner or destroyed.

The JSON adapter has a representation-specific text limit and, after decoding, also rejects any message whose protobuf encoding would exceed the binary message limit. This prevents JSON expansion from bypassing the binary resource budget; the binary lane avoids JSON escaping and base64 expansion for large byte fields.

The canonical schema is shipped by reallyme-jose-proto under crates/proto/jose/proto; generated bindings are checked in under crates/proto/jose/src/generated. Refresh them with:

buf generate
cargo fmt --package reallyme-jose-proto

The opt-in wire lane intentionally exposes generated protobuf types and Buffa enum wrappers as part of the public Rust boundary. That coupling is the release contract for host-language and process-boundary adapters; normal SDK callers should prefer the native jws, jwt, and jwe modules and avoid enabling the wire feature unless they are implementing a process-proto adapter.

The protobuf and Rust crates are released together. While the project remains pre-1.0, a new minor release may intentionally change the schema; consumers should keep both crates and generated adapters on the same release line. The repository enforces schema linting and generated-code freshness, but does not promise wire compatibility with earlier pre-1.0 releases.

Release Checks

The release path is Rust-first. CI runs formatting, linting, tests, native and wasm feature-lane checks, dependency policy, advisory audit, release-readiness checks, protobuf lint and generated-code freshness, package inspection, and fuzz target builds. A separate release-readiness workflow runs for documentation-only changes because README and policy text are part of the release contract.

Fuzz harnesses live in fuzz and cover compact JWS, JWT, and JWE parser boundaries plus the protobuf/JSON process boundary.

Independent Vector Audit

Committed conformance vectors are checked by tools/vector-audit, a standalone Rust binary that does not depend on reallyme-jose, reallyme-crypto, or reallyme-codec. It validates the vector manifest, compact JOSE structure, JWS/JWT signatures, unsigned JWT claims, and direct JWE AES-GCM fixtures with independent crates.

The panva-jose vectors add a small native Rust interop anchor for ES256, EdDSA, ES256 JWT, and ECDH-ES P-256/A128GCM. They do not yet execute through the wasm provider and are not intended to replace the broader local negative and round-trip corpus.

References

License

Licensed under the Apache License, Version 2.0. See LICENSE and NOTICE.

Copyright And Trademarks

Copyright © 2026 by ReallyMe LLC.

ReallyMe® is a registered trademark of ReallyMe LLC.

About

JOSE infrastructure for identity systems: JWT, JWS, and JWE with strict algorithm policy, typed errors, native/Wasm support, and transport-neutral protobuf boundaries.

Topics

Resources

License

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors