Skip to content

Improve specs and blind spots #665

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 29, 2024
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: 2 additions & 5 deletions lib/jwt/decode.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,8 @@ def allowed_and_valid_algorithms
:algorithms].freeze

def given_algorithms
ALGORITHM_KEYS.each do |alg_key|
alg = @options[alg_key]
return Array(alg) if alg
end
[]
alg_key = ALGORITHM_KEYS.find { |key| @options[key] }
Array(@options[alg_key])
end

def allowed_algorithms
Expand Down
4 changes: 0 additions & 4 deletions lib/jwt/jwa/ecdsa.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,6 @@ def verify(data:, signature:, verification_key:)
register_algorithm(new(v[:algorithm], v[:digest]))
end

def self.from_algorithm(algorithm)
new(algorithm, algorithm.downcase.gsub('es', 'sha'))
end

def self.curve_by_name(name)
NAMED_CURVES.fetch(name) do
raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
Expand Down
4 changes: 0 additions & 4 deletions lib/jwt/jwa/hmac.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ module JWA
class Hmac
include JWT::JWA::SigningAlgorithm

def self.from_algorithm(algorithm)
new(algorithm, OpenSSL::Digest.new(algorithm.downcase.gsub('hs', 'sha')))
end

def initialize(alg, digest)
@alg = alg
@digest = digest
Expand Down
4 changes: 0 additions & 4 deletions lib/jwt/jwk/ec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,6 @@ def encode_octets(octets)
::JWT::Base64.url_encode(octets)
end

def encode_open_ssl_bn(key_part)
::JWT::Base64.url_encode(key_part.to_s(BINARY))
end

def parse_ec_key(key)
crv, x_octets, y_octets = keypair_components(key)
octets = key.private_key&.to_bn&.to_s(BINARY)
Expand Down
3 changes: 3 additions & 0 deletions lib/jwt/jwk/rsa.rb
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ def create_rsa_key_using_sets(rsa_parameters)
end
end

# :nocov:
# Before openssl 2.0, we need to use the accessors to set the key
def create_rsa_key_using_accessors(rsa_parameters) # rubocop:disable Metrics/AbcSize
validate_rsa_parameters!(rsa_parameters)

Expand All @@ -179,6 +181,7 @@ def create_rsa_key_using_accessors(rsa_parameters) # rubocop:disable Metrics/Abc
rsa_key.iqmp = rsa_parameters[:qi] if rsa_parameters[:qi]
end
end
# :nocov:

def validate_rsa_parameters!(rsa_parameters)
return unless rsa_parameters.key?(:d)
Expand Down
27 changes: 27 additions & 0 deletions spec/jwt/claims/verifier_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

RSpec.describe JWT::Claims::Verifier do
describe '.verify!' do
context 'when all claims are given' do
let(:options) do
[
:exp,
:nbf,
{ iss: 'issuer' },
:iat,
:jti,
{ aud: 'aud' },
:sub,
:crit,
{ required: [] },
:numeric
]
end

it 'verifies all claims' do
token = SpecSupport::Token.new(payload: { 'iss' => 'issuer', 'jti' => 1, 'aud' => 'aud' }, header: { 'crit' => [] })
expect(described_class.verify!(token, *options)).to eq(nil)
end
end
end
end
17 changes: 17 additions & 0 deletions spec/jwt/jwa/none_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

RSpec.describe JWT::JWA::None do
subject { described_class.new }

describe '#sign' do
it 'returns an empty string' do
expect(subject.sign('data', 'key')).to eq('')
end
end

describe '#verify' do
it 'returns true' do
expect(subject.verify('data', 'signature', 'key')).to be true
end
end
end
4 changes: 2 additions & 2 deletions spec/jwt/jwa/ps_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

RSpec.describe JWT::JWA::Ps do
let(:rsa_key) { OpenSSL::PKey::RSA.generate(2048) }
let(:rsa_key) { test_pkey('rsa-2048-private.pem') }
let(:data) { 'test data' }
let(:ps256_instance) { described_class.new('PS256') }
let(:ps384_instance) { described_class.new('PS384') }
Expand Down Expand Up @@ -44,7 +44,7 @@
end

context 'with a key length less than 2048 bits' do
let(:rsa_key) { OpenSSL::PKey::RSA.generate(1024) }
let(:rsa_key) { OpenSSL::PKey::RSA.generate(2047) }

it 'raises an error' do
expect do
Expand Down
4 changes: 2 additions & 2 deletions spec/jwt/jwa/rsa_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

RSpec.describe JWT::JWA::Rsa do
let(:rsa_key) { OpenSSL::PKey::RSA.generate(2048) }
let(:rsa_key) { test_pkey('rsa-2048-private.pem') }
let(:data) { 'test data' }
let(:rsa_instance) { described_class.new('RS256') }

Expand All @@ -21,7 +21,7 @@
end

context 'with a key length less than 2048 bits' do
let(:rsa_key) { OpenSSL::PKey::RSA.generate(1024) }
let(:rsa_key) { OpenSSL::PKey::RSA.generate(2047) }

it 'raises an error' do
expect do
Expand Down
15 changes: 15 additions & 0 deletions spec/jwt/jwa/unsupported_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

RSpec.describe JWT::JWA::Unsupported do
describe '.sign' do
it 'raises an error for unsupported signing method' do
expect { described_class.sign('data', 'key') }.to raise_error(JWT::EncodeError, 'Unsupported signing method')
end
end

describe '.verify' do
it 'raises an error for unsupported algorithm' do
expect { described_class.verify('data', 'signature', 'key') }.to raise_error(JWT::VerificationError, 'Algorithm not supported')
end
end
end
8 changes: 4 additions & 4 deletions spec/jwt/jwk/decode_with_jwk_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

RSpec.describe JWT do
describe '.decode for JWK usecase' do
let(:keypair) { OpenSSL::PKey::RSA.new(2048) }
let(:keypair) { test_pkey('rsa-2048-private.pem') }
let(:jwk) { JWT::JWK.new(keypair) }
let(:public_jwks) { { keys: [jwk.export, { kid: 'not_the_correct_one', kty: 'oct', k: 'secret' }] } }
let(:token_payload) { { 'data' => 'something' } }
Expand Down Expand Up @@ -102,9 +102,9 @@

context 'mixing algorithms using kid header' do
let(:hmac_jwk) { JWT::JWK.new('secret') }
let(:rsa_jwk) { JWT::JWK.new(OpenSSL::PKey::RSA.new(2048)) }
let(:ec_jwk_secp384r1) { JWT::JWK.new(OpenSSL::PKey::EC.generate('secp384r1')) }
let(:ec_jwk_secp521r1) { JWT::JWK.new(OpenSSL::PKey::EC.generate('secp521r1')) }
let(:rsa_jwk) { JWT::JWK.new(test_pkey('rsa-2048-private.pem')) }
let(:ec_jwk_secp384r1) { JWT::JWK.new(test_pkey('ec384-private.pem')) }
let(:ec_jwk_secp521r1) { JWT::JWK.new(test_pkey('ec384-private.pem')) }
let(:jwks) { { keys: [hmac_jwk.export(include_private: true), rsa_jwk.export, ec_jwk_secp384r1.export, ec_jwk_secp521r1.export] } }

context 'when RSA key is pointed to as HMAC secret' do
Expand Down
26 changes: 24 additions & 2 deletions spec/jwt/jwk/ec_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

RSpec.describe JWT::JWK::EC do
let(:ec_key) { OpenSSL::PKey::EC.generate('secp384r1') }
let(:ec_key) { test_pkey('ec384-private.pem') }

describe '.new' do
subject { described_class.new(keypair) }
Expand All @@ -21,16 +21,38 @@
expect(subject.private?).to eq false
end
end

context 'when a number is given' do
let(:keypair) { 1234 }
it 'raises an argument error' do
expect { subject }.to raise_error(ArgumentError, 'key must be of type OpenSSL::PKey::EC or Hash with key parameters')
end
end

context 'when EC with unsupported curve is given' do
let(:keypair) { OpenSSL::PKey::EC.generate('prime239v2') }
it 'raises an error' do
expect { subject }.to raise_error(JWT::JWKError, "Unsupported curve 'prime239v2'")
end
end
end

describe '#keypair' do
subject(:jwk) { described_class.new(ec_key) }

it 'warns to stderr' do
it 'returns the key' do
expect(jwk.keypair).to eq(ec_key)
end
end

describe '#public_key' do
subject(:jwk) { described_class.new(ec_key) }

it 'returns the key' do
expect(jwk.public_key).to eq(ec_key)
end
end

describe '#export' do
let(:kid) { nil }
subject { described_class.new(keypair, kid).export }
Expand Down
7 changes: 7 additions & 0 deletions spec/jwt/jwk/hmac_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
expect(jwk.private?).to eq true
end
end

context 'when key is a number' do
let(:key) { 123 }
it 'raises an ArgumentError' do
expect { jwk }.to raise_error(ArgumentError, 'key must be of type String or Hash with key parameters')
end
end
end

describe '#keypair' do
Expand Down
2 changes: 1 addition & 1 deletion spec/jwt/jwk/rsa_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@

describe '.create_rsa_key_using_accessors' do
before do
skip 'OpenSSL if RSA#set_key is available there is no accessors anymore' if OpenSSL::PKey::RSA.new.respond_to?(:set_key)
skip 'OpenSSL if RSA#d= is not available there is no accessors anymore' unless OpenSSL::PKey::RSA.new.respond_to?(:d=)
end

subject(:rsa) { described_class.create_rsa_key_using_accessors(rsa_parameters) }
Expand Down
20 changes: 10 additions & 10 deletions spec/jwt/jwk/set_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@
describe '.select!' do
it 'filters the keyset' do
jwks = described_class.new([])
jwks << JWT::JWK.new(OpenSSL::PKey::RSA.new(2048))
jwks << JWT::JWK.new(OpenSSL::PKey::EC.generate('secp384r1'))
jwks << JWT::JWK.new(test_pkey('rsa-2048-private.pem'))
jwks << JWT::JWK.new(test_pkey('ec384-private.pem'))
jwks.select! { |k| k[:kty] == 'RSA' }
expect(jwks.size).to eql(1)
expect(jwks.keys[0][:kty]).to eql('RSA')
Expand All @@ -94,8 +94,8 @@
describe '.reject!' do
it 'filters the keyset' do
jwks = described_class.new([])
jwks << JWT::JWK.new(OpenSSL::PKey::RSA.new(2048))
jwks << JWT::JWK.new(OpenSSL::PKey::EC.generate('secp384r1'))
jwks << JWT::JWK.new(test_pkey('rsa-2048-private.pem'))
jwks << JWT::JWK.new(test_pkey('ec384-private.pem'))
jwks.reject! { |k| k[:kty] == 'RSA' }
expect(jwks.size).to eql(1)
expect(jwks.keys[0][:kty]).to eql('EC')
Expand All @@ -105,8 +105,8 @@
describe '.merge' do
context 'merges two JWKSs' do
it 'when called via .union' do
jwks1 = described_class.new(JWT::JWK.new(OpenSSL::PKey::RSA.new(2048)))
jwks2 = described_class.new(JWT::JWK.new(OpenSSL::PKey::EC.generate('secp384r1')))
jwks1 = described_class.new(JWT::JWK.new(test_pkey('rsa-2048-private.pem')))
jwks2 = described_class.new(JWT::JWK.new(test_pkey('ec384-private.pem')))
jwks3 = jwks1.union(jwks2)
expect(jwks1.size).to eql(1)
expect(jwks2.size).to eql(1)
Expand All @@ -116,8 +116,8 @@
end

it 'when called via "|" operator' do
jwks1 = described_class.new(JWT::JWK.new(OpenSSL::PKey::RSA.new(2048)))
jwks2 = described_class.new(JWT::JWK.new(OpenSSL::PKey::EC.generate('secp384r1')))
jwks1 = described_class.new(JWT::JWK.new(test_pkey('rsa-2048-private.pem')))
jwks2 = described_class.new(JWT::JWK.new(test_pkey('ec384-private.pem')))
jwks3 = jwks1 | jwks2
expect(jwks1.size).to eql(1)
expect(jwks2.size).to eql(1)
Expand All @@ -127,8 +127,8 @@
end

it 'when called directly' do
jwks1 = described_class.new(JWT::JWK.new(OpenSSL::PKey::RSA.new(2048)))
jwks2 = described_class.new(JWT::JWK.new(OpenSSL::PKey::EC.generate('secp384r1')))
jwks1 = described_class.new(JWT::JWK.new(test_pkey('rsa-2048-private.pem')))
jwks2 = described_class.new(JWT::JWK.new(test_pkey('ec384-private.pem')))
jwks3 = jwks1.merge(jwks2)
expect(jwks1.size).to eql(2)
expect(jwks2.size).to eql(1)
Expand Down
11 changes: 9 additions & 2 deletions spec/jwt/jwk_spec.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# frozen_string_literal: true

RSpec.describe JWT::JWK do
let(:rsa_key) { OpenSSL::PKey::RSA.new(2048) }
let(:ec_key) { OpenSSL::PKey::EC.generate('secp384r1') }
let(:rsa_key) { test_pkey('rsa-2048-private.pem') }
let(:ec_key) { test_pkey('ec256k-private.pem') }

describe '.import' do
let(:keypair) { rsa_key.public_key }
Expand All @@ -16,6 +16,13 @@
expect(subject.export).to eq(exported_key)
end

context 'when number is given' do
let(:params) { 1234 }
it 'raises an error' do
expect { subject }.to raise_error(JWT::JWKError, 'Cannot create JWK from a Integer')
end
end

context 'parsed from JSON' do
let(:params) { exported_key }
it 'creates a ::JWT::JWK::RSA instance from JSON parsed JWK' do
Expand Down
10 changes: 10 additions & 0 deletions spec/jwt/jwt_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,16 @@
end.to raise_error JWT::IncorrectAlgorithm
end

it 'raises error when keyfinder does not find anything' do
token = JWT.encode(payload, 'secret', 'HS256')

expect do
JWT.decode(token, nil, true, algorithm: 'HS256') do
nil
end
end.to raise_error JWT::DecodeError, 'No verification key available'
end

it 'should raise JWT::IncorrectAlgorithm when algorithms array does not contain algorithm' do
token = JWT.encode payload, data[:secret], 'HS512'

Expand Down
Loading
Loading