From 3fb719a432b390de487ba61fa30969aabc4c6764 Mon Sep 17 00:00:00 2001 From: Joakim Antman Date: Sun, 15 Sep 2024 14:19:59 +0300 Subject: [PATCH] Do not require EdDSA algo if rbnacl not available. Unit tests for some algos --- CHANGELOG.md | 1 + lib/jwt/jwa.rb | 6 ++-- lib/jwt/jwa/hmac.rb | 1 - spec/jwt/jwa/hmac_spec.rb | 22 ++++++++++++ spec/jwt/jwa/ps_spec.rb | 72 +++++++++++++++++++++++++++++++++++++++ spec/jwt/jwa/rsa_spec.rb | 53 ++++++++++++++++++++++++++++ 6 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 spec/jwt/jwa/ps_spec.rb create mode 100644 spec/jwt/jwa/rsa_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d443425..5a96bf79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Refactor claim validators into their own classes [#605](https://github.com/jwt/ruby-jwt/pull/605) ([@anakinj](https://github.com/anakinj), [@MatteoPierro](https://github.com/MatteoPierro)) - Allow extending available algorithms [#607](https://github.com/jwt/ruby-jwt/pull/607) ([@anakinj](https://github.com/anakinj)) +- Do not include the EdDSA algorithm if rbnacl not available [#613](https://github.com/jwt/ruby-jwt/pull/613) ([@anakinj](https://github.com/anakinj)) - Your contribution here ## [v2.8.2](https://github.com/jwt/ruby-jwt/tree/v2.8.2) (2024-06-18) diff --git a/lib/jwt/jwa.rb b/lib/jwt/jwa.rb index 8d515d26..8a975824 100644 --- a/lib/jwt/jwa.rb +++ b/lib/jwt/jwa.rb @@ -9,9 +9,7 @@ end require_relative 'jwa/signing_algorithm' - require_relative 'jwa/ecdsa' -require_relative 'jwa/eddsa' require_relative 'jwa/hmac' require_relative 'jwa/none' require_relative 'jwa/ps' @@ -19,6 +17,10 @@ require_relative 'jwa/unsupported' require_relative 'jwa/wrapper' +if JWT.rbnacl? + require_relative 'jwa/eddsa' +end + if JWT.rbnacl_6_or_greater? require_relative 'jwa/hmac_rbnacl' elsif JWT.rbnacl? diff --git a/lib/jwt/jwa/hmac.rb b/lib/jwt/jwa/hmac.rb index 1dac71b3..f2db8faa 100644 --- a/lib/jwt/jwa/hmac.rb +++ b/lib/jwt/jwa/hmac.rb @@ -12,7 +12,6 @@ def initialize(alg, digest) def sign(data:, signing_key:) signing_key ||= '' - raise_verify_error!('HMAC key expected to be a String') unless signing_key.is_a?(String) OpenSSL::HMAC.digest(digest.new, signing_key, data) diff --git a/spec/jwt/jwa/hmac_spec.rb b/spec/jwt/jwa/hmac_spec.rb index 736f1e56..20504cde 100644 --- a/spec/jwt/jwa/hmac_spec.rb +++ b/spec/jwt/jwa/hmac_spec.rb @@ -2,10 +2,16 @@ RSpec.describe JWT::JWA::Hmac do let(:instance) { described_class.new('HS256', OpenSSL::Digest::SHA256) } + let(:valid_signature) { [60, 56, 87, 72, 185, 194, 150, 13, 18, 148, 76, 245, 94, 91, 201, 64, 111, 91, 167, 156, 43, 148, 41, 113, 168, 156, 137, 12, 11, 31, 58, 97].pack('C*') } + let(:hmac_secret) { 'secret_key' } describe '#sign' do subject { instance.sign(data: 'test', signing_key: hmac_secret) } + context 'when signing with a key' do + it { is_expected.to eq(valid_signature) } + end + # Address OpenSSL 3.0 errors with empty hmac_secret - https://github.com/jwt/ruby-jwt/issues/526 context 'when nil hmac_secret is passed' do let(:hmac_secret) { nil } @@ -103,4 +109,20 @@ end end end + + describe '#verify' do + subject { instance.verify(data: 'test', signature: signature, verification_key: hmac_secret) } + + context 'when signature is valid' do + let(:signature) { valid_signature } + + it { is_expected.to be(true) } + end + + context 'when signature is invalid' do + let(:signature) { [60, 56, 87, 72, 185, 194].pack('C*') } + + it { is_expected.to be(false) } + end + end end diff --git a/spec/jwt/jwa/ps_spec.rb b/spec/jwt/jwa/ps_spec.rb new file mode 100644 index 00000000..53b57433 --- /dev/null +++ b/spec/jwt/jwa/ps_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +RSpec.describe JWT::JWA::Ps do + let(:rsa_key) { OpenSSL::PKey::RSA.generate(2048) } + let(:data) { 'test data' } + let(:ps256_instance) { described_class.new('PS256') } + let(:ps384_instance) { described_class.new('PS384') } + let(:ps512_instance) { described_class.new('PS512') } + + describe '#initialize' do + it 'initializes with the correct algorithm and digest' do + expect(ps256_instance.instance_variable_get(:@alg)).to eq('PS256') + expect(ps256_instance.send(:digest_algorithm)).to eq('sha256') + + expect(ps384_instance.instance_variable_get(:@alg)).to eq('PS384') + expect(ps384_instance.send(:digest_algorithm)).to eq('sha384') + + expect(ps512_instance.instance_variable_get(:@alg)).to eq('PS512') + expect(ps512_instance.send(:digest_algorithm)).to eq('sha512') + end + end + + describe '#sign' do + context 'with a valid RSA key' do + it 'signs the data with PS256' do + expect(ps256_instance.sign(data: data, signing_key: rsa_key)).not_to be_nil + end + + it 'signs the data with PS384' do + expect(ps384_instance.sign(data: data, signing_key: rsa_key)).not_to be_nil + end + + it 'signs the data with PS512' do + expect(ps512_instance.sign(data: data, signing_key: rsa_key)).not_to be_nil + end + end + + context 'with an invalid key' do + it 'raises an error' do + expect do + ps256_instance.sign(data: data, signing_key: 'invalid_key') + end.to raise_error(JWT::EncodeError, /The given key is a String. It has to be an OpenSSL::PKey::RSA instance./) + end + end + end + + describe '#verify' do + let(:ps256_signature) { ps256_instance.sign(data: data, signing_key: rsa_key) } + let(:ps384_signature) { ps384_instance.sign(data: data, signing_key: rsa_key) } + let(:ps512_signature) { ps512_instance.sign(data: data, signing_key: rsa_key) } + + context 'with a valid RSA key' do + it 'verifies the signature with PS256' do + expect(ps256_instance.verify(data: data, signature: ps256_signature, verification_key: rsa_key)).to be(true) + end + + it 'verifies the signature with PS384' do + expect(ps384_instance.verify(data: data, signature: ps384_signature, verification_key: rsa_key)).to be(true) + end + + it 'verifies the signature with PS512' do + expect(ps512_instance.verify(data: data, signature: ps512_signature, verification_key: rsa_key)).to be(true) + end + end + + context 'with an invalid signature' do + it 'raises a verification error' do + expect(ps256_instance.verify(data: data, signature: 'invalid_signature', verification_key: rsa_key)).to be(false) + end + end + end +end diff --git a/spec/jwt/jwa/rsa_spec.rb b/spec/jwt/jwa/rsa_spec.rb new file mode 100644 index 00000000..ea73c93d --- /dev/null +++ b/spec/jwt/jwa/rsa_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +RSpec.describe JWT::JWA::Rsa do + let(:rsa_key) { OpenSSL::PKey::RSA.generate(2048) } + let(:data) { 'test data' } + let(:rsa_instance) { described_class.new('RS256') } + + describe '#initialize' do + it 'initializes with the correct algorithm and digest' do + expect(rsa_instance.instance_variable_get(:@alg)).to eq('RS256') + expect(rsa_instance.send(:digest).name).to eq('SHA256') + end + end + + describe '#sign' do + context 'with a valid RSA key' do + it 'signs the data' do + signature = rsa_instance.sign(data: data, signing_key: rsa_key) + expect(signature).not_to be_nil + end + end + + context 'with an invalid key' do + it 'raises an error' do + expect do + rsa_instance.sign(data: data, signing_key: 'invalid_key') + end.to raise_error(JWT::EncodeError, /The given key is a String. It has to be an OpenSSL::PKey::RSA instance/) + end + end + end + + describe '#verify' do + let(:signature) { rsa_instance.sign(data: data, signing_key: rsa_key) } + + context 'with a valid RSA key' do + it 'returns true' do + expect(rsa_instance.verify(data: data, signature: signature, verification_key: rsa_key)).to be(true) + end + end + + context 'with an invalid signature' do + it 'returns false' do + expect(rsa_instance.verify(data: data, signature: 'invalid_signature', verification_key: rsa_key)).to be(false) + end + end + + context 'with an invalid key' do + it 'returns false' do + expect(rsa_instance.verify(data: data, signature: 'invalid_signature', verification_key: OpenSSL::PKey::RSA.generate(2048))).to be(false) + end + end + end +end