Repository navigation
Releases
polyjose 0.2.0
RSA private keys: RSA signing, RSA-OAEP-256 decryption and RSA private-key import, in the Rust crate and in every binding. This is a minor release because, on 0.x, behaviour changes to the public API bump the minor version: inputs that 0.1 refused now work, some error codes changed, and the Rust RSA key type has a new name. No binding function, type or signature changed (bindings/api-inventory.txt is the same as in 0.1.2).
Added
- RSA signing:
RS256,RS384,RS512,PS256,PS384andPS512sign with an RSA private key, in compact and JSON JWS and when issuing a JWT (RFC 7518 §3.3, §3.5).RS*signatures are deterministic and match RFC 7515 Appendix A.2 exactly. - RSA-OAEP-256 decryption (RFC 7518 §4.3), in compact and JSON JWE, with one or more recipients. A bad Encrypted Key is never reported as one: a random content key is used instead and the result is
DecryptionFailed, like any other failure (RFC 7516 §11.5). - RSA private keys: import from a JWK (all of
d,p,q,dp,dqandqi; RFC 7518 §6.3.2), from PKCS#8 (PRIVATE KEY) and from PKCS#1 (RSA PRIVATE KEYandKeyFormat::Pkcs1DER); export as a JWK and as PKCS#8. The private members must belong to the key ofnande, the modulus must be 2048 to 8192 bits, and the key is wiped on drop and redacted inDebug.RsaKey::is_privatetells a private RSA key from a public one, andJwk::to_publicdrops the private members. - JWK Sets keep an RSA private member as imported, as they already did for EC and OKP private keys, so a set can hold RSA-OAEP-256 decryption keys. Call
to_publicon each key before you publish a set.
Changed
- Behaviour change: an RSA private JWK, PEM or DER key used to fail with
UnsupportedAlgorithm; it now imports. A JWK Set with an RSA private member used to fail as a whole; it now keeps the member. Signing with RS*/PS* or decrypting RSA-OAEP-256 used to fail withUnsupportedAlgorithm; with a public key, both now fail withKeyAlgorithmMismatch. - Behaviour change: a PKCS#1 RSAPrivateKey passed as PKCS#8 or SEC1 (by
KeyFormator by PEM label) is nowInvalidKey, because the structure is wrong for the format. It used to beUnsupportedAlgorithm. - Behaviour change: PKCS#1 export of an RSA private key fails with
InvalidKey, in both DER and PEM, with the message "pkcs1 export writes the public key; call to_public first". PKCS#1 export always writes the RSAPublicKey (RFC 8017 Appendix A.1.1), so polyjose refuses rather than silently dropping the private part. A public key still exports as before. To export a private key, use PKCS#8 or a JWK. - Rust:
jwk::RsaPublicKeyis renamedjwk::RsaKey, because it now also holds private keys.RsaPublicKeystays as a deprecated alias ofRsaKey(#[deprecated(since = "0.2.0")]), so 0.1 code still compiles, with a warning.- Migration: replace
RsaPublicKeywithRsaKey.
- Migration: replace
- Rust: the
rsadependency is now=0.10.0, with only itspkcs1v15feature.crypto-bigintis now0.7.11. - Release builds: every published artifact is the exact build that the release candidate run built and smoke-tested. The tag run no longer rebuilds anything: it signs, attests and drafts those bytes (ADR 0068).
Security
-
Marvin (RUSTSEC-2023-0071) is patched.
rsa0.10.0 is the first release the advisory lists as patched, and thecargo denyignore for it is gone. RSA private-key operations go only throughrsa's blinded entry points, with randomness from the OS CSPRNG. Build checks (check-deps.shand a symbol check on the compiled library) keep key generation,RSA1_5, unblinded paths andrsa'shazmatAPI out, and they requirersa's blinded core to be present wherever private-key code is. -
Constant-time evidence:
- upstream's BINSEC verification (
cargo checkct) ofrsa's constant-time code; - polyjose's own dudect-style Welch t-test, run natively and on wasm32. It compares valid with invalid RSA-OAEP-256 ciphertexts, and signing different messages, and has a positive control that must detect a known public-input difference.
The signing test shows only that timing does not depend on the message. Both of its classes use the same key, so independence from the private key rests on
rsa's blinding. There is no Marvin-toolkit run, and thersa0.10 rewrite has no published independent audit. - upstream's BINSEC verification (
-
Residual risk: zeroization inside
rsa0.10.0. polyjose wipes its own copies of RSA private members, andrsa's private key wipes itself on drop. Butrsa's big integers (BoxedUint) areZeroize, notZeroizeOnDrop. On the error paths ofPrivateKey::from_components(a key that fails validation), and in some of its intermediate values, secret limbs may be freed without being wiped. This touches only rejected keys and transient values insidersa. The fix belongs upstream. -
Known deviations of
rsa0.10.0 (see SECURITY.md):- RSA-OAEP decryption does not check that the first octet of the encoded message is zero (RFC 8017 §7.1.2 step 3.g). This cannot help an attacker in JWE.
- A private key whose two primes differ in size by more than the half-modulus width is refused on import with
InvalidKey.
-
Bounded decryption cost: each candidate RSA-OAEP-256 recipient costs one private-key build (validation and CRT precomputation) and one blinded private-key operation. A decrypt call tries at most
DecryptPolicy::max_recipientsof them, and there is no separate RSA budget. With keys of 4096 bits or more, give the key and the recipients akid, which narrows the candidates (RFC 7516 §4.1.6), or lowermax_recipients. -
Multi-prime RSA keys (
oth, PKCS#1 versionmulti) areUnsupportedAlgorithm, and an RSA private JWK without its CRT members isInvalidKey. RSA key generation,RSA-OAEP(SHA-1) andRSA1_5are still not implemented, and no feature flag turns them on.
API by platform
No binding adds, removes or changes a function, type or signature. In every binding, these existing entry points now accept RSA private keys, and their error codes change as described under Changed: key import and export, JWS and JWT signing, JWE decryption, and JWK Sets.
- Rust (
polyjose):jwk::RsaKeyis the new name ofjwk::RsaPublicKey, which stays as a deprecated alias.RsaKey::is_privateis new. RS*/PS* signing, RSA-OAEP-256 decryption and RSA private import work through the existingjws,jwt,jweandjwkfunctions. - Node.js (
@polyjose/node): no change toindex.d.tsor theexportsmap. Behaviour changes as above. - Web (
@polyjose/wasm): no change to the TypeScript declarations. Behaviour changes as above. - Android and the JVM (
dev.polyjose:polyjose,polyjose-jvm): no change to the Kotlin API. Behaviour changes as above. - Swift (
Polyjose): no change to the Swift API; only doc comments are regenerated. Behaviour changes as above.Package.swiftpoints at the 0.2.0 XCFramework.
Every asset is listed in SHA256SUMS, signed in SHA256SUMS.asc by the release key
(https://github.com/bharathkontham/polyjose/blob/v0.2.0/.github/keys/polyjose-release.asc), and has a
build-provenance attestation (https://github.com/bharathkontham/polyjose/attestations):
gh attestation verify <asset> --repo bharathkontham/polyjose.
polyjose 0.1.2
A packaging and documentation release. The public API of every binding is unchanged from 0.1.1.
Added
- Rust: the crate has its own crates.io README, with install, import and a quick start. Its examples are compiled and run as doctests, so they stay in step with the API.
Changed
- Node.js:
@polyjose/nodeis now one package that contains the prebuilt addons for all 8 targets (macOS arm64 and x64, Linux glibc and musl arm64 and x64, Windows arm64 and x64). It no longer hasoptionalDependencies, and the 8@polyjose/node-<platform>packages are no longer published; they are deprecated on npm. The install now works with--omit=optionaland in bundles built on one platform for another. The download is larger, about 23 MB unpacked instead of about 3 MB. - Node.js: the addon is loaded only from the package's own directory. The loader resolves no module name and reads no environment variable. It loads
polyjose.<platform>.nodefrom its own package, checks the addon's SHA-256 against the pinned value, and refuses anything else withERR_POLYJOSE_ADDON_REFUSED.NODE_PATH, ancestornode_modulesdirectories and the@polyjose/node-<platform>packages no longer affect what loads. - Node.js, behaviour change: the
NAPI_RS_*environment variables are ignored.NAPI_RS_NATIVE_LIBRARY_PATHused to be accepted when it named the pinned addon, andNAPI_RS_FORCE_WASIandNAPI_RS_WASI_FLAVORused to make the import throw. Now none of them has any effect, and the import never throws because of one.- Migration: remove
NAPI_RS_NATIVE_LIBRARY_PATHfrom your environment; the bundled addon for your platform is always used. If you depended on@polyjose/node-<platform>directly, depend on@polyjose/node0.1.2 instead. If you setNAPI_RS_FORCE_WASIto get a WebAssembly build, use@polyjose/wasm, which has the same API. Platforms outside the 8 targets are still refused, with a message that points at@polyjose/wasm.
- Migration: remove
- Node.js on Windows embedders: a host that napi-rs mapped to
win32-x64-gnu(for examplenode_target_type: shared_library), which polyjose never shipped, now gets thewin32-x64-msvcaddon. - Docs: every package README (crates.io, npm, Maven, Swift) is cut to install, import and a quick start, and links the full guide for its platform on polyjose.dev. The repository README links each platform's docs.
- Swift:
Package.swiftpoints at the 0.1.2 XCFramework, and its header no longer calls the binary target a placeholder.
Security
- Smaller Node.js loader surface. The add-on loader no longer probes other packages,
NODE_PATHor environment overrides, so nothing outside the installed@polyjose/nodedirectory can influence which native code is loaded. - Publishing: crates.io and npm packages are published by trusted publishing (GitHub OIDC) with provenance. No long-lived registry token is used.
API by platform
- Rust (
polyjose): no change. - Node.js (
@polyjose/node): no change toindex.d.tsor theexportsmap. The internalbinding.cjsbackend, which was never a supported API, is no longer shipped. - Web (
@polyjose/wasm): no change. - Android and the JVM (
dev.polyjose:polyjose,polyjose-jvm): no change. - Swift (
Polyjose): no change.
Every asset is listed in SHA256SUMS, signed in SHA256SUMS.asc by the release key
(https://github.com/bharathkontham/polyjose/blob/v0.1.2/.github/keys/polyjose-release.asc), and has a
build-provenance attestation (https://github.com/bharathkontham/polyjose/attestations):
gh attestation verify <asset> --repo bharathkontham/polyjose.
polyjose 0.1.1
This is the first published release of polyjose; 0.1.0 was tagged but never published, because its release workflow did not create the draft release.
polyjose is a pure-Rust implementation of JOSE (JWS, JWE, JWK, JWT and JWA) for Rust, Node.js, the web (WebAssembly), Android and the JVM (Kotlin/Java), and Swift. All cryptography comes from RustCrypto and dalek crates, with no OpenSSL, ring or aws-lc. Every binding is built from the same Rust core and runs the same conformance vectors.
Added
Algorithms (RFC 7518, RFC 8037, RFC 8812, RFC 9864)
- JWS signing and verification:
HS256,HS384,HS512;ES256(P-256),ES384(P-384),ES512(P-521),ES256K(secp256k1);Ed25519, andEdDSAwith Ed25519 keys. - JWS verification only:
RS256,RS384,RS512,PS256,PS384,PS512with RSA public keys of 2048 to 8192 bits. - JWE key management:
dir;A128KW,A192KW,A256KW;A128GCMKW,A192GCMKW,A256GCMKW;ECDH-ES,ECDH-ES+A128KW,ECDH-ES+A192KW,ECDH-ES+A256KWon P-256, P-384, P-521 and X25519;PBES2-HS256+A128KW,PBES2-HS384+A192KW,PBES2-HS512+A256KW;RSA-OAEP-256for encryption only. - JWE content encryption:
A128CBC-HS256,A192CBC-HS384,A256CBC-HS512,A128GCM,A192GCM,A256GCM.
JOSE features
- JWS (RFC 7515): compact, flattened JSON and general JSON serializations, including several signatures; detached payloads; unencoded payloads (
b64: false, RFC 7797). - JWE (RFC 7516): compact, flattened JSON and general JSON serializations, including several recipients; additional authenticated data; protected, shared unprotected and per-recipient headers.
- JWT (RFC 7519): signed tokens and nested (signed, then encrypted) tokens. Validation covers
exp,nbf,iatwith leeway and maximum age, issuer, audience, subject, required claims andtyp, plus the RFC 9068 access-token profile (at+jwt). Verification can select its key from a JWK Set. - JWK (RFC 7517):
oct,EC(P-256, P-384, P-521, secp256k1),OKP(Ed25519, X25519) andRSApublic keys; JWK Sets with key selection bykid,alganduse; public-key extraction; RFC 7638 thumbprints (SHA-256, SHA-384, SHA-512) and RFC 9278 thumbprint URIs. - Key generation of
oct, EC and OKP keys from the operating system's random number generator, by key type or by algorithm. - PEM and DER import and export: PKCS#8 and SEC1 private keys and SubjectPublicKeyInfo public keys for EC and OKP; SubjectPublicKeyInfo and PKCS#1 public keys for RSA.
- base64url encoding and decoding (RFC 4648 §5).
- Rust only: JWE compression (
zip: DEF) behind the opt-indeflatecargo feature, off by default. The bindings are built without it.
Platforms and API
- Rust: the
polyjosecrate on crates.io. MSRV 1.89, edition 2024, nounsafecode. - Node.js:
@polyjose/nodeon npm, a native addon with prebuilt binaries for macOS (arm64, x64), Linux glibc 2.28+ and musl (arm64, x64), and Windows (arm64, x64), published as eight@polyjose/node-*platform packages. ESM only (CommonJS can load it throughrequire(esm)), Node.js 22.12 or later, TypeScript types included. Every operation returns a Promise and runs off the event loop. The package loads only the addon whose SHA-256 it was built with. - Web (WebAssembly):
@polyjose/wasmon npm, with the same API and TypeScript types as@polyjose/node. Entries for browsers (including Web Workers), Node.js 22.12 or later and Cloudflare Workers. It is tested on Node.js, headless Chrome and Cloudflare Workers (workerd). The module is integrity-checked before it is instantiated. - Android and the JVM (Kotlin and Java): on Maven Central under the group
dev.polyjose.polyjoseis the Android AAR (minSdk 23; arm64-v8a, armeabi-v7a, x86_64, x86).polyjose-jvmis the desktop JVM jar (Java 17 bytecode) with native libraries for macOS (arm64, x64), Linux (arm64, x64) and Windows (x64). Consuming the Kotlin API needs a Kotlin 2.4 compiler. - Swift: the Swift package
Polyjose(SwiftPM, from this repository'sPackage.swift) with a prebuilt XCFramework, for iOS 16, macOS 13 and watchOS 9. Tests run on macOS and the iOS Simulator; for watchOS the package is build- and link-checked only. - One API on every binding: utilities (
version,errorCodes,base64urlEncode,base64urlDecode); JWK (jwkParse,jwkToPublic,jwkThumbprint,jwkThumbprintUri,jwksSelect,jwkGenerateOct,jwkGenerateEc,jwkGenerateOkp,jwkGenerateForAlg,jwkFromPem,jwkFromDer,jwkToPem,jwkToDer); JWS (jwsSignCompact,jwsSignFlattened,jwsSignGeneral,jwsVerifyCompact,jwsVerifyJson); JWE (jweEncryptCompact,jweEncryptJson,jweDecryptCompact,jweDecryptJson); JWT (jwtSign,jwtVerify,jwtVerifyWithJwks,jwtEncryptNested,jwtDecryptNested,jwtDecryptNestedWithJwks). Names are camelCase in JavaScript, Kotlin and Swift, and snake_case in Rust. Errors carry one of 17 stable codes, such asInvalidSignature,AlgorithmNotAllowedandTokenExpired.
Fixed
- Release workflow: the
attestanddraftjobs ofrelease.yml, and thespmcheck ofpublish.yml, now run when a job further up their chain was skipped by design (the Apple build is skipped in tag mode). For 0.1.0 both jobs were skipped, so the tag run created no build attestations and no draft release.
Security
- Explicit algorithm allow-lists. Verify and decrypt always take the allowed algorithms (and, for JWE, the allowed
encvalues); the header'salgis never trusted on its own.alg: noneis never accepted. Keys are bound to their algorithm family (RFC 8725). - Bounded inputs. Limits on input length, signature and recipient count, JWK Set key candidates and, by default, the PBES2 iteration count (
p2cat most 10,000 per token on decrypt). Unknowncritheader parameters are rejected. - ECDH-ES: the ephemeral public key must be on the recipient's curve and is validated before any key agreement.
- Secrets: key material is zeroized when dropped and is never put into
Debugoutput or error messages. Kotlin and Swift records print their secret fields redacted. - Randomness comes only from the operating system's random number generator.
- Testing: the RFCs' own test vectors (RFC 7515, 7516, 7517, 7518, 7519, the RFC 7520 cookbook, 7638, 7797, 8037, 9278), Wycheproof where it applies, one shared conformance suite run by every binding, two-way interop tests against panva/jose, and fuzzing of the parsers.
- Audits: polyjose itself has not been independently audited. AUDITS.md lists which of the primitive crates have a published third-party audit, and which version line it covered.
- Release integrity: every release asset is listed in
SHA256SUMS, signed with the release key in.github/keys/, and has a GitHub build attestation. The Maven artifacts are GPG-signed.
Not supported in 0.1.1
- RSA private-key operations. There is no RS*/PS* signing, no RSA-OAEP-256 decryption and no RSA key generation, and RSA private keys are rejected on import (JWK, PEM and DER). The
rsacrate's private-key operations are affected by RUSTSEC-2023-0071 (the Marvin timing attack), which has no patched release. RSA private-key support is planned for a later release, once a stablersa0.10 is marked patched. RSA1_5andRSA-OAEP(SHA-1) key management, and SHA-1 thumbprints.- Ed448 and X448 keys.
- JWE compression (
zip: DEF) in the bindings. It is a Rust-only, opt-in feature. - Encrypt-only JWTs. A nested JWT must be signed, then encrypted.
- Encrypted PKCS#8 keys and BER encodings in PEM/DER import.
- Deno and Bun are not tested for
@polyjose/wasm.
Every asset is listed in SHA256SUMS, signed in SHA256SUMS.asc by the release key
(https://github.com/bharathkontham/polyjose/blob/v0.1.1/.github/keys/polyjose-release.asc), and has a
build-provenance attestation (https://github.com/bharathkontham/polyjose/attestations):
gh attestation verify <asset> --repo bharathkontham/polyjose.