diff --git a/README.md b/README.md index 2d62b3f..05f9e75 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,11 @@ token = JWT.encode({pay: "load"}, private_key, "EdDSA") payload, header = JWT.decode(token, private_key.verify_key, true, algorithm: "EdDSA") ``` +OKP JWKs must specify `crv: "Ed25519"` to provide a signing or verification key. +Requesting a key with a missing or unsupported curve (including `X25519` and +`Ed448`) raises `JWT::JWKError`. Such JWKs can still be imported, so an unrelated +key in a mixed JWKS does not prevent verification with a selected Ed25519 key. + ## Development ``` diff --git a/lib/jwt/eddsa/jwk/okp.rb b/lib/jwt/eddsa/jwk/okp.rb index 35af1ae..22860a9 100644 --- a/lib/jwt/eddsa/jwk/okp.rb +++ b/lib/jwt/eddsa/jwk/okp.rb @@ -23,12 +23,14 @@ def initialize(key, params = nil, options = {}) end def verify_key + check_curve! return @verify_key if defined?(@verify_key) @verify_key = verify_key_from_parameters end def signing_key + check_curve! return @signing_key if defined?(@signing_key) @signing_key = signing_key_from_parameters @@ -93,6 +95,13 @@ def check_jwk_params!(key_params, _given_params) "Incorrect 'kty' value: #{key_params[:kty]}, expected #{KTY}" end + def check_curve! + return if self[:crv] == "Ed25519" + + raise JWT::JWKError, + "Incorrect 'crv' value: #{self[:crv]}, expected Ed25519" + end + def parse_okp_key_params(verify_key, signing_key = nil) params = { kty: KTY, diff --git a/spec/integration_spec.rb b/spec/integration_spec.rb index 53d47b2..75e2155 100644 --- a/spec/integration_spec.rb +++ b/spec/integration_spec.rb @@ -68,6 +68,38 @@ { algorithms: ["EDDSA"], jwks: key_loader }) expect(payload).to eq(token_payload) end + + it "rejects an Ed25519 key labelled as X25519" do + public_jwks[:keys].first[:crv] = "X25519" + + expect do + JWT.decode(signed_token, nil, true, algorithms: ["EdDSA"], jwks: public_jwks) + end.to raise_error(JWT::JWKError, /Incorrect 'crv' value/) + end + + context "with a mixed JWKS" do + let(:public_jwks) do + { keys: [ + { kty: "OKP", crv: "X25519", use: "enc", kid: "encryption", + x: Base64.urlsafe_encode64("x" * 32, padding: false) }, + { kty: "OKP", crv: "Ed448", kid: "ed448", x: Base64.urlsafe_encode64("x" * 57, padding: false) }, + jwk.export + ] } + end + + it "verifies with the selected Ed25519 key" do + payload, _header = JWT.decode(signed_token, nil, true, algorithms: ["EdDSA"], jwks: public_jwks) + expect(payload).to eq(token_payload) + end + + it "rejects selecting an unsupported curve for EdDSA" do + token = JWT.encode(token_payload, jwk.signing_key, "EdDSA", kid: "ed448") + + expect do + JWT.decode(token, nil, true, algorithms: ["EdDSA"], jwks: public_jwks) + end.to raise_error(JWT::JWKError, /Incorrect 'crv' value/) + end + end end describe "JWK as key" do diff --git a/spec/jwt/jwk/okp_spec.rb b/spec/jwt/jwk/okp_spec.rb index be2adb7..617b4f1 100644 --- a/spec/jwt/jwk/okp_spec.rb +++ b/spec/jwt/jwk/okp_spec.rb @@ -53,6 +53,34 @@ end end + describe "key access with an unsupported curve" do + ["X25519", "Ed448", "ed25519", nil].each do |curve| + context "when the curve is #{curve.inspect}" do + let(:key) { described_class.new(private_key).export(include_private: true).merge(crv: curve) } + + it "imports the JWK but rejects signing and verification key access" do + jwk = instance + expect { jwk.verify_key }.to raise_error(JWT::JWKError, /Incorrect 'crv' value/) + expect { jwk.signing_key }.to raise_error(JWT::JWKError, /Incorrect 'crv' value/) + end + + it "rejects returning a native key with a curve override" do + jwk = described_class.new(private_key, "crv" => curve) + expect { jwk.verify_key }.to raise_error(JWT::JWKError, /Incorrect 'crv' value/) + expect { jwk.signing_key }.to raise_error(JWT::JWKError, /Incorrect 'crv' value/) + end + end + end + + it "rejects key access without the required curve" do + parameters = described_class.new(private_key).export(include_private: true) + parameters.delete(:crv) + jwk = described_class.import(parameters) + expect { jwk.verify_key }.to raise_error(JWT::JWKError, /Incorrect 'crv' value/) + expect { jwk.signing_key }.to raise_error(JWT::JWKError, /Incorrect 'crv' value/) + end + end + describe "#verify_key" do let(:key) { private_key } subject { instance.verify_key }