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.
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.
[dependencies]
reallyme-jose = "0.2.0"reallyme-jose supports a deliberately small JOSE profile:
- compact JWS for
ES256andEdDSA; - signed and unsigned JWT parsing with algorithm/key binding, temporal policy, and
typpolicy; - JWT signing and verification for
ES256,ES256K, andEdDSA;ES256Kis JWT-only and uses the low-S secp256k1 policy enforced byreallyme-crypto; - compact JWE encryption and decryption for
dirandECDH-ES; - AES-GCM content encryption with
A128GCM,A192GCM, andA256GCM; - 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, embeddedjwk,x5u, andx5cprotected-header parameters.
With the wire feature enabled, the wire module provides a transport-neutral,
Codec-style process-proto lane. Its byte-level flow is:
- Encode one
JoseOperationRequest, whoseoneofselects the operation. - Pass those bytes to
process_protoor the equivalent adapter-specific entrypoint. - Decode the returned
JoseProtoResultEnvelope. Forresultstatus, its payload is the operation-specific result message; forjose_errorstatus, its payload is a structuredJoseError.
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-protoThe 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.
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.
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.
- RFC 7515: JSON Web Signature
- RFC 7516: JSON Web Encryption
- RFC 7518: JSON Web Algorithms
- RFC 7519: JSON Web Token
- RFC 8725: JSON Web Token Best Current Practices
- RFC 9864: Fully-Specified Algorithms for JOSE and COSE
Licensed under the Apache License, Version 2.0. See LICENSE and NOTICE.
Copyright © 2026 by ReallyMe LLC.
ReallyMe® is a registered trademark of ReallyMe LLC.