Repository navigation
Add the OKP key type (RFC 8037) and the Ed25519 and Ed448 signing algorithms (RFC 9864) - #169
Conversation
27e705c to
be00cf9
Compare
TheStormN
left a comment
There was a problem hiding this comment.
Interestingly enough, the unit tests crash on my local machine. I had the AI do some analysis and this is what I got as output. Could you please check it out.
P1 — Reject public OKP keys before signing
_cjose_jws_validate_okp_key() validates only the key type and curve. A public-only OKP can therefore reach EVP_DigestSign(). With OpenSSL 3.0, this can segfault instead of returning an error.
Please explicitly verify private-key material with EVP_PKEY_get_raw_private_key() before signing and return CJOSE_ERR_INVALID_ARG or CJOSE_ERR_CRYPTO. The existing public-key regression test should complete without crashing.
P2 — Add Ed448 coverage for private-key validation
The public-key signing regression currently covers Ed25519 only. Please add the equivalent Ed448 case so both new algorithms verify that public-only keys are rejected safely.
P2 — Reject empty OKP fields
The generic _decode_json_object_base64url_attribute() helper treats empty strings as absent. As a result, an OKP JWK with "d":"" can be accepted as a public key, while one with a missing or empty "x" can be accepted as a private key.
RFC 8037 requires x for every OKP JWK and requires a correctly sized d for private keys. Please validate OKP field presence separately and add malformed-input tests.
|
I suspect an OpenSSL version compatibility issue; if so, I'll also provide a PR for CI to catch this type of thing |
Ok, just as a note then that my test Linux box is Ubuntu 22.04 and OpenSSL is 3.0.2. |
Add "OKP" JWKs (RFC 8037 section 2) holding a raw Ed25519 or Ed448 signature key (RFC 8032) or an X25519 or X448 key agreement key (RFC 7748): cjose_jwk_create_OKP_random, cjose_jwk_create_OKP_spec and cjose_jwk_OKP_get_curve, plus import through cjose_jwk_import and export through cjose_jwk_to_json. The public key "x" and the private key "d" must have the fixed size of the curve, when both are given "x" must be the public key that belongs to "d", and the decoded "d" is wiped after use like the other private key material. The key lives in an EVP_PKEY through the raw key API of OpenSSL 1.1.1; with an older OpenSSL the OKP functions fail with CJOSE_ERR_INVALID_ARG. Ported from the version-1.0.x branch of OpenIDC/cjose, rewritten from the OpenSSL 3 OSSL_PARAM API to the OpenSSL 1.1.1 raw key API. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Hans Zandbelt <hans.zandbelt@openidc.com>
be00cf9 to
974683a
Compare
|
Thanks, all three confirmed and addressed; the fixes are folded into the two commits of the force-pushed branch.
|
Add the fully-specified EdDSA (RFC 8032) JWS algorithms "Ed25519" and "Ed448" of RFC 9864 over OKP keys. Each identifier is bound to a key of its own curve, the X25519/X448 key agreement keys are refused (RFC 8037 section 3.1), and a signature must have the fixed size of the curve (64 or 114 octets) before it is handed to OpenSSL. PureEdDSA signs the JWS signing input itself, so the digest step keeps B64U(HEADER).B64U(DATA) and signing and verification use the one-shot EVP_DigestSign and EVP_DigestVerify of OpenSSL 1.1.1. The polymorphic "EdDSA" identifier of RFC 8037, which RFC 9864 deprecates in favour of Ed25519 and Ed448, is not supported: a JWS carrying it does not import and nothing can be signed with it. Ported from the version-1.0.x branch of OpenIDC/cjose. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Hans Zandbelt <hans.zandbelt@openidc.com>
974683a to
01cb5d2
Compare
|
Thanks, I will merge it as soon as the CI passes. Btw, I plan to start the work on the 1.0.x release with removing old OpenSSL 1 support, breaking some ABI stuff etc. Before that I will make one more 0.7.x release. Do you have any pending stuff which you would like to be merged as part of 0.7.x? |
|
the only thing I'd like to see back in 0.7.x is older libcheck support - back to 0.9.4 - as was documented before since that will make it possible to compile/test/package on RHEL 7 (and RHEL 6 for that matter) which is useful for mod_auth_openidc distributions; I'll submit another PR against current master and you can decide on whether to include it or not; no other bugfixes or functions reside in our forks so 0.7.1 (or 0.8.0) would be a complete replacement/upgrade for 0.6.2.x, thanks |
Summary
Two commits, ported from the
version-1.0.xbranch of OpenIDC/cjose and reworked for master:"kty":"OKP"JWKs holding a raw Ed25519 or Ed448 signature key (RFC 8032) or an X25519 or X448 key agreement key (RFC 7748). New public API:cjose_jwk_okp_curve,cjose_jwk_okp_keyspec,cjose_jwk_create_OKP_random(),cjose_jwk_create_OKP_spec()andcjose_jwk_OKP_get_curve(), added to the three export lists;cjose_jwk_import()andcjose_jwk_to_json()handle the new type.CJOSE_HDR_ALG_ED25519andCJOSE_HDR_ALG_ED448.Design notes
"EdDSA". RFC 9864 sets the JOSE implementation requirements of the polymorphic"EdDSA"identifier of RFC 8037 to Deprecated in favour ofEd25519andEd448, so it is deliberately not supported: a JWS carrying it does not import and nothing can be signed with it (test_cjose_jws_eddsa_deprecated). Accepting it anyway, for interoperability with existing deployments, would be a three-line change in_cjose_jws_validate_hdr()and_cjose_jws_validate_okp_key().2 * coordlencheck.B64U(HEADER).B64U(DATA)and signing/verification use the one-shotEVP_DigestSign()/EVP_DigestVerify(). The digest step frees a previous digest first socjose_jws_verify()can be called repeatedly on one JWS with different candidate keys.xis required (RFC 8037 section 2) andd, when present, must be a non-empty string; both must have the fixed size of the curve, and whendis givenxmust be the public key that belongs to it (the 1.0.x code accepted mismatched pairs). A lonedis only accepted bycjose_jwk_create_OKP_spec(), which derives the public key. The decodeddis wiped after use like the other private key material.cjose_jws_sign()refuses a public-only OKP key withCJOSE_ERR_INVALID_ARGbefore calling OpenSSL: OpenSSL 1.1.1 and 3.0.0 through 3.0.7 sign with the missing private key and crash instead of failing (3.0.8 added the guard), which is what the review found; Restore the OpenSSL 1.1.x build and test against every supported OpenSSL release series #170 adds those releases to CI.OSSL_PARAMAPI; this port uses the OpenSSL 1.1.1 raw key API (EVP_PKEY_new_raw_private_key()& co.) and gates everything on a newCJOSE_OPENSSL_111Xmacro next toCJOSE_OPENSSL_11X, since master still declares OpenSSL 1.0.1 as its minimum. With an older OpenSSL the library still builds, the OKP functions fail withCJOSE_ERR_INVALID_ARG, the two identifiers are unknown, and the new tests are compiled out; verified with the gate forced off.ES256Kprecedent in Add ES256K and secp256k1 support #167.Tests
check_jwk: create/spec/random for all four curves, private-only and public-only specs, mismatchedx/d, wrong sizes, JSON export, import/export round trips with the RFC 8037 appendix A.1/A.2/A.6 keys and the RFC 7748 X25519 vectors, invalid JWKs (includingdwithoutx, emptyx, empty or non-stringd).check_jws: self sign/verify forEd25519andEd448in the existing loops; fixed vectors (EdDSA signatures are deterministic; cross-checked withopenssl pkeyutl -sign -rawin); tampered signature and content; wrong key type or curve for sign and verify; signing with a public-only Ed25519 or Ed448 key;"EdDSA"refused; 63 and 65 octet signatures refused.Verified locally with
-Wall -Werror, the fullcheck_cjoserun, valgrind on thejwkandjwssuites, theclang-formattarget (no diff) and doxygen. Each commit builds and passes on its own.Rebased onto #168, merging the two "Unreleased" CHANGELOG sections.
🤖 Generated with Claude Code