Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

```
Expand Down
9 changes: 9 additions & 0 deletions lib/jwt/eddsa/jwk/okp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
32 changes: 32 additions & 0 deletions spec/integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 28 additions & 0 deletions spec/jwt/jwk/okp_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
Loading