Skip to content

Commit

Permalink
Encode and decode the k value for hmac keys
Browse files Browse the repository at this point in the history
  • Loading branch information
anakinj committed Dec 28, 2024
1 parent c073c98 commit fa9b705
Show file tree
Hide file tree
Showing 5 changed files with 28 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Support only stricter base64 decoding (RFC 4648) [#658](https://github.com/jwt/ruby-jwt/pull/658) ([@anakinj](https://github.com/anakinj))
- Custom algorithms are required to include `JWT::JWA::SigningAlgorithm` [#660](https://github.com/jwt/ruby-jwt/pull/660) ([@anakinj](https://github.com/anakinj))
- Require RSA keys to be at least 2048 bits [#661](https://github.com/jwt/ruby-jwt/pull/661) ([@anakinj](https://github.com/anakinj))
- Base64 encode and decode the k value for HMAC JWKs [#662](https://github.com/jwt/ruby-jwt/pull/662) ([@anakinj](https://github.com/anakinj))

Take a look at the [upgrade guide](UPGRADING.md) for more details.

Expand Down
6 changes: 5 additions & 1 deletion UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@ Claim verification has been [split into separate classes](https://github.com/jwt

The internal algorithms were [restructured](https://github.com/jwt/ruby-jwt/pull/607) to support extensions from separate libraries. The changes led to a few deprecations and new requirements:
- The `sign` and `verify` static methods on all the algorithms (`::JWT::JWA`) will be removed.
- Custom algorithms are expected to include the `JWT::JWA::SigningAlgorithm` module.
- Custom algorithms are expected to include the `JWT::JWA::SigningAlgorithm` module.

## Base64 the `k´ value for HMAC JWKs

The gem was missing the Base64 encoding and decoding when representing and parsing a HMAC key as a JWK. This issue is now addressed. The added encoding will break compatibility with JWKs produced by older versions of the gem.
4 changes: 2 additions & 2 deletions lib/jwt/jwk/hmac.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ def []=(key, value)
private

def secret
self[:k]
@secret ||= ::JWT::Base64.url_decode(self[:k])
end

def extract_key_params(key)
case key
when JWT::JWK::HMAC
key.export(include_private: true)
when String # Accept String key as input
{ kty: KTY, k: key }
{ kty: KTY, k: ::JWT::Base64.url_encode(key) }
when Hash
key.transform_keys(&:to_sym)
else
Expand Down
9 changes: 9 additions & 0 deletions spec/jwt/jwk/hmac_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@
end
end
end

context 'when example from RFC' do
let(:params) { { kty: 'oct', k: 'AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow' } }

it 'decodes the k' do
expected_key = "\x03#5K+\x0F\xA5\xBC\x83~\x06ew{\xA6\x8FZ\xB3(\xE6\xF0T\xC9(\xA9\x0F\x84\xB2\xD2P.\xBF\xD3\xFBZ\x92\xD2\x06G\xEF\x96\x8A\xB4\xC3wb=\"=.!r\x05.O\b\xC0\xCD\x9A\xF5g\xD0\x80\xA3".dup.force_encoding('ASCII-8BIT')
expect(subject.verify_key).to eq(expected_key)
end
end
end

describe '#[]=' do
Expand Down
22 changes: 11 additions & 11 deletions spec/jwt/jwk/set_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
end

it 'from a JWKS hash with symbol keys' do
jwks = { keys: [{ kty: 'oct', k: 'testkey' }] }
jwk = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
jwks = { keys: [{ kty: 'oct', k: Base64.strict_encode64('testkey') }] }
jwk = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') })
expect(described_class.new(jwks).keys).to eql([jwk])
end

it 'from a JWKS hash with string keys' do
jwks = { 'keys' => [{ 'kty' => 'oct', 'k' => 'testkey' }] }
jwk = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
jwks = { 'keys' => [{ 'kty' => 'oct', 'k' => Base64.strict_encode64('testkey') }] }
jwk = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') })
expect(described_class.new(jwks).keys).to eql([jwk])
end

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

it 'from an existing JWT::JWK::Set' do
jwk = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
jwk = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') })
jwks = described_class.new(jwk)
expect(described_class.new(jwks)).to eql(jwks)
end
Expand All @@ -43,7 +43,7 @@

describe '.export' do
it 'exports the JWKS to Hash' do
jwk = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
jwk = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') })
jwks = described_class.new(jwk)
exported = jwks.export
expect(exported[:keys].size).to eql(1)
Expand All @@ -53,15 +53,15 @@

describe '.eql?' do
it 'correctly classifies equal sets' do
jwk = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
jwk = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') })
jwks1 = described_class.new(jwk)
jwks2 = described_class.new(jwk)
expect(jwks1).to eql(jwks2)
end

it 'correctly classifies different sets' do
jwk1 = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
jwk2 = JWT::JWK.new({ kty: 'oct', k: 'testkex' })
jwk1 = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') })
jwk2 = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkex') })
jwks1 = described_class.new(jwk1)
jwks2 = described_class.new(jwk2)
expect(jwks1).not_to eql(jwks2)
Expand All @@ -72,8 +72,8 @@
# but Array#uniq! doesn't recognize this, despite the documentation saying otherwise
describe '.uniq!' do
it 'filters out equal keys' do
jwk = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
jwk2 = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
jwk = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') })
jwk2 = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') })
jwks = described_class.new([jwk, jwk2])
jwks.uniq!
expect(jwks.keys.size).to eql(1)
Expand Down

0 comments on commit fa9b705

Please sign in to comment.