Skip to content

Commit 014d4b3

Browse files
committed
Encode and decode the k value for hmac keys
1 parent c073c98 commit 014d4b3

File tree

5 files changed

+28
-14
lines changed

5 files changed

+28
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- Support only stricter base64 decoding (RFC 4648) [#658](https://github.com/jwt/ruby-jwt/pull/658) ([@anakinj](https://github.com/anakinj))
1313
- Custom algorithms are required to include `JWT::JWA::SigningAlgorithm` [#660](https://github.com/jwt/ruby-jwt/pull/660) ([@anakinj](https://github.com/anakinj))
1414
- Require RSA keys to be at least 2048 bits [#661](https://github.com/jwt/ruby-jwt/pull/661) ([@anakinj](https://github.com/anakinj))
15+
- Base64 encode and decode the k value for HMAC JWKs [#662](https://github.com/jwt/ruby-jwt/pull/662) ([@anakinj](https://github.com/anakinj))
1516

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

UPGRADING.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,8 @@ Claim verification has been [split into separate classes](https://github.com/jwt
3939

4040
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:
4141
- The `sign` and `verify` static methods on all the algorithms (`::JWT::JWA`) will be removed.
42-
- Custom algorithms are expected to include the `JWT::JWA::SigningAlgorithm` module.
42+
- Custom algorithms are expected to include the `JWT::JWA::SigningAlgorithm` module.
43+
44+
## Base64 the `k´ value for HMAC JWKs
45+
46+
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.

lib/jwt/jwk/hmac.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,15 @@ def []=(key, value)
7070
private
7171

7272
def secret
73-
self[:k]
73+
@secret ||= ::JWT::Base64.url_decode(self[:k])
7474
end
7575

7676
def extract_key_params(key)
7777
case key
7878
when JWT::JWK::HMAC
7979
key.export(include_private: true)
8080
when String # Accept String key as input
81-
{ kty: KTY, k: key }
81+
{ kty: KTY, k: ::JWT::Base64.url_encode(key) }
8282
when Hash
8383
key.transform_keys(&:to_sym)
8484
else

spec/jwt/jwk/hmac_spec.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,15 @@
7373
end
7474
end
7575
end
76+
77+
context 'when example from RFC' do
78+
let(:params) { { kty: 'oct', k: 'AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow' } }
79+
80+
it 'decodes the k' do
81+
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')
82+
expect(subject.verify_key).to eq(expected_key)
83+
end
84+
end
7685
end
7786

7887
describe '#[]=' do

spec/jwt/jwk/set_spec.rb

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@
1313
end
1414

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

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

@@ -30,7 +30,7 @@
3030
end
3131

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

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

5454
describe '.eql?' do
5555
it 'correctly classifies equal sets' do
56-
jwk = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
56+
jwk = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') })
5757
jwks1 = described_class.new(jwk)
5858
jwks2 = described_class.new(jwk)
5959
expect(jwks1).to eql(jwks2)
6060
end
6161

6262
it 'correctly classifies different sets' do
63-
jwk1 = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
64-
jwk2 = JWT::JWK.new({ kty: 'oct', k: 'testkex' })
63+
jwk1 = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') })
64+
jwk2 = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkex') })
6565
jwks1 = described_class.new(jwk1)
6666
jwks2 = described_class.new(jwk2)
6767
expect(jwks1).not_to eql(jwks2)
@@ -72,8 +72,8 @@
7272
# but Array#uniq! doesn't recognize this, despite the documentation saying otherwise
7373
describe '.uniq!' do
7474
it 'filters out equal keys' do
75-
jwk = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
76-
jwk2 = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
75+
jwk = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') })
76+
jwk2 = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') })
7777
jwks = described_class.new([jwk, jwk2])
7878
jwks.uniq!
7979
expect(jwks.keys.size).to eql(1)

0 commit comments

Comments
 (0)