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
7 changes: 1 addition & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
ruby-version: ruby
bundler-cache: true
- name: Run RuboCop
run: bundle exec rubocop
Expand All @@ -38,11 +38,6 @@
steps:
- uses: actions/checkout@v4

- name: Install libsodium
run: |
sudo apt-get update -q
sudo apt-get install libsodium-dev -y

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
Expand Down
2 changes: 1 addition & 1 deletion jwt-eddsa.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ Gem::Specification.new do |spec|
spec.require_paths = ["lib"]

spec.add_dependency "base64"
spec.add_dependency "ed25519"
spec.add_dependency "jwt", "> 2.8.2"
spec.add_dependency "rbnacl", "~> 6.0"

spec.metadata["rubygems_mfa_required"] = "true"
end
1 change: 1 addition & 0 deletions lib/jwt/eddsa.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require "jwt"
require "ed25519"

require_relative "eddsa/version"
require_relative "eddsa/jwk/okp"
Expand Down
12 changes: 5 additions & 7 deletions lib/jwt/eddsa/algo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,20 @@ module Algo

class << self
def sign(_algorithm, msg, key)
unless key.is_a?(RbNaCl::Signatures::Ed25519::SigningKey)
raise_sign_error!("Key given is a #{key.class} but needs to be a " \
"RbNaCl::Signatures::Ed25519::SigningKey")
unless key.is_a?(Ed25519::SigningKey)
raise_sign_error!("Key given is a #{key.class} but needs to be a Ed25519::SigningKey")
end

key.sign(msg)
end

def verify(_algorithm, public_key, signing_input, signature)
unless public_key.is_a?(RbNaCl::Signatures::Ed25519::VerifyKey)
raise_verify_error!("Key given is a #{public_key.class} but needs to be a " \
"RbNaCl::Signatures::Ed25519::VerifyKey")
unless public_key.is_a?(Ed25519::VerifyKey)
raise_verify_error!("Key given is a #{public_key.class} but needs to be a Ed25519::VerifyKey")
end

public_key.verify(signature, signing_input)
rescue RbNaCl::CryptoError
rescue Ed25519::VerifyError
false
end
end
Expand Down
15 changes: 7 additions & 8 deletions lib/jwt/eddsa/jwk/okp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ module JWK
# https://datatracker.ietf.org/doc/html/rfc8037
class OKP < ::JWT::JWK::KeyBase
KTY = "OKP"
KTYS = [KTY, JWT::EdDSA::JWK::OKP, RbNaCl::Signatures::Ed25519::SigningKey,
RbNaCl::Signatures::Ed25519::VerifyKey].freeze
KTYS = [KTY, JWT::EdDSA::JWK::OKP, Ed25519::SigningKey, Ed25519::VerifyKey].freeze
OKP_PUBLIC_KEY_ELEMENTS = %i[kty n x].freeze
OKP_PRIVATE_KEY_ELEMENTS = %i[d].freeze

Expand Down Expand Up @@ -63,20 +62,20 @@ def extract_key_params(key) # rubocop:disable Metrics/MethodLength
case key
when JWT::JWK::KeyBase
key.export(include_private: true)
when RbNaCl::Signatures::Ed25519::SigningKey
when Ed25519::SigningKey
@signing_key = key
@verify_key = key.verify_key
parse_okp_key_params(@verify_key, @signing_key)
when RbNaCl::Signatures::Ed25519::VerifyKey
when Ed25519::VerifyKey
@signing_key = nil
@verify_key = key
parse_okp_key_params(@verify_key)
when Hash
key.transform_keys(&:to_sym)
else
raise ArgumentError,
"key must be of type RbNaCl::Signatures::Ed25519::SigningKey, " \
"RbNaCl::Signatures::Ed25519::VerifyKey " \
"key must be of type Ed25519::SigningKey, " \
"Ed25519::VerifyKey " \
"or Hash with key parameters"
end
end
Expand All @@ -101,13 +100,13 @@ def parse_okp_key_params(verify_key, signing_key = nil)
end

def verify_key_from_parameters
RbNaCl::Signatures::Ed25519::VerifyKey.new(::Base64.urlsafe_decode64(self[:x]))
Ed25519::VerifyKey.new(::Base64.urlsafe_decode64(self[:x]))
end

def signing_key_from_parameters
return nil unless self[:d]

RbNaCl::Signatures::Ed25519::SigningKey.new(::Base64.urlsafe_decode64(self[:d]))
Ed25519::SigningKey.new(::Base64.urlsafe_decode64(self[:d]))
end

class << self
Expand Down
6 changes: 3 additions & 3 deletions spec/integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
require "securerandom"

RSpec.describe "Usage via ruby-jwt" do
let(:private_key) { RbNaCl::Signatures::Ed25519::SigningKey.new("b" * 32) }
let(:private_key) { Ed25519::SigningKey.new("b" * 32) }
let(:public_key) { private_key.verify_key }

let(:payload) { { "pay" => "load" } }
Expand All @@ -18,7 +18,7 @@
end

context "when decoding key is wrong" do
let(:public_key) { RbNaCl::Signatures::Ed25519::SigningKey.new("a" * 32).verify_key }
let(:public_key) { Ed25519::SigningKey.new("a" * 32).verify_key }
it "raises decoding error" do
token = JWT.encode(payload, private_key, "EdDSA")

Expand Down Expand Up @@ -48,7 +48,7 @@
end

describe "OKP JWK usage" do
let(:jwk) { JWT::JWK.new(RbNaCl::Signatures::Ed25519::SigningKey.new(SecureRandom.hex)) }
let(:jwk) { JWT::JWK.new(Ed25519::SigningKey.new(SecureRandom.hex)) }
let(:public_jwks) { { keys: [jwk.export, { kid: "not_the_correct_one", kty: "oct", k: "secret" }] } }
let(:signed_token) { JWT.encode(token_payload, jwk.signing_key, "EdDSA", token_headers) }
let(:token_payload) { { "data" => "something" } }
Expand Down
28 changes: 21 additions & 7 deletions spec/jwt/jwk/okp_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
require "securerandom"

RSpec.describe JWT::EdDSA::JWK::OKP do
let(:private_key) { RbNaCl::Signatures::Ed25519::SigningKey.new(SecureRandom.hex) }
let(:private_key) { Ed25519::SigningKey.new(SecureRandom.hex) }
let(:public_key) { private_key.verify_key }
let(:key) { nil }

Expand Down Expand Up @@ -37,13 +37,27 @@
end
it { is_expected.to be_a(described_class) }
end

context "when a random key found from the Internet is given" do
let(:key) do
{
"kty" => "OKP",
"kid" => "-1909572257",
"alg" => "EdDSA",
"crv" => "Ed25519",
"x" => "XWxGtApfcqmKI7p0OKnF5JSEWMVoLsytFXLEP7xZ_l8"
}
end

it { is_expected.to be_a(described_class) }
end
end

describe "#verify_key" do
let(:key) { private_key }
subject { instance.verify_key }
it "is the verify key" do
expect(subject).to be_a(RbNaCl::Signatures::Ed25519::VerifyKey)
expect(subject).to be_a(Ed25519::VerifyKey)
end
end

Expand Down Expand Up @@ -91,7 +105,7 @@
let(:import_data) { described_class.new(public_key).export }
it "creates a new instance of the class" do
expect(subject.private?).to eq(false)
expect(subject.verify_key).to be_a(RbNaCl::Signatures::Ed25519::VerifyKey)
expect(subject.verify_key).to be_a(Ed25519::VerifyKey)
expect(subject.signing_key).to eq(nil)
expect(subject.verify_key.to_bytes).to eq(public_key.to_bytes)
expect(subject.kid).to eq(import_data[:kid])
Expand All @@ -102,8 +116,8 @@
let(:import_data) { described_class.new(private_key).export(include_private: true) }
it "creates a new instance of the class" do
expect(subject.private?).to eq(true)
expect(subject.verify_key).to be_a(RbNaCl::Signatures::Ed25519::VerifyKey)
expect(subject.signing_key).to be_a(RbNaCl::Signatures::Ed25519::SigningKey)
expect(subject.verify_key).to be_a(Ed25519::VerifyKey)
expect(subject.signing_key).to be_a(Ed25519::SigningKey)
expect(subject.verify_key.to_bytes).to eq(public_key.to_bytes)
expect(subject.kid).to eq(import_data[:kid])
end
Expand All @@ -113,8 +127,8 @@
let(:import_data) { described_class.new(private_key) }
it "creates a new instance of the class" do
expect(subject.private?).to eq(true)
expect(subject.verify_key).to be_a(RbNaCl::Signatures::Ed25519::VerifyKey)
expect(subject.signing_key).to be_a(RbNaCl::Signatures::Ed25519::SigningKey)
expect(subject.verify_key).to be_a(Ed25519::VerifyKey)
expect(subject.signing_key).to be_a(Ed25519::SigningKey)
expect(subject.verify_key.to_bytes).to eq(public_key.to_bytes)
expect(subject.kid).to eq(import_data[:kid])
end
Expand Down