Skip to content

Commit

Permalink
Improve specs and blind spots
Browse files Browse the repository at this point in the history
  • Loading branch information
anakinj committed Dec 29, 2024
1 parent 9e6c4d8 commit 826dfe8
Show file tree
Hide file tree
Showing 18 changed files with 177 additions and 40 deletions.
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

0 comments on commit 826dfe8

Please sign in to comment.