Skip to content

Commit

Permalink
Verify the crit header
Browse files Browse the repository at this point in the history
  • Loading branch information
anakinj committed Oct 10, 2024
1 parent e6fbe16 commit faa6f85
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 5 deletions.
39 changes: 35 additions & 4 deletions lib/jwt/encoded_token.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module JWT
# encoded_token = JWT::EncodedToken.new(token.jwt)
# encoded_token.verify_signature!algorithm: 'HS256', key: 'secret')
# encoded_token.payload # => {'pay' => 'load'}
#
class EncodedToken
include Claims::VerificationMethods

Expand All @@ -21,11 +22,17 @@ class EncodedToken
# Initializes a new EncodedToken instance.
#
# @param jwt [String] the encoded JWT token.
# @param enabled_crits [Array<String>] the list of enabled critical headers.
# @param allow_unverified [Boolean] whether to allow access to payload for unverified tokens.
# @raise [ArgumentError] if the provided JWT is not a String.
def initialize(jwt)
raise ArgumentError 'Provided JWT must be a String' unless jwt.is_a?(String)
# @raise [ArgumentError] if enabled_crits is not an Array.
def initialize(jwt, enabled_crits: [])
raise ArgumentError, 'Provided JWT must be a String' unless jwt.is_a?(String)
raise ArgumentError, 'enabled_crits must be an Array' unless enabled_crits.is_a?(Array)

@enabled_crits = enabled_crits
@jwt = jwt
@signature_verified = false
@encoded_header, @encoded_payload, @encoded_signature = jwt.split('.')
end

Expand Down Expand Up @@ -55,6 +62,7 @@ def header

# Returns the payload of the JWT token.
#
# @param allow_unverified [Boolean] whether to allow payloads to be accessed for unverified tokens.
# @return [Hash] the payload.
def payload
@payload ||= decode_payload
Expand Down Expand Up @@ -85,6 +93,7 @@ def verify_signature!(algorithm:, key: nil, key_finder: nil)
raise ArgumentError, 'Provide either key or key_finder, not both or neither' if key.nil? == key_finder.nil?

key ||= key_finder.call(self)

return if valid_signature?(algorithm: algorithm, key: key)

raise JWT::VerificationError, 'Signature verification failed'
Expand All @@ -103,15 +112,33 @@ def valid_signature?(algorithm:, key:)
end
end

# Verifies that a critical header is present and enabled.
#
# @param critical_header [String] the critical header to verify.
# @return [nil]
# @raise [InvalidCritError] if the critical header is missing or not enabled.
def verify_crit!(crit)
unless Array(header['crit']).include?(crit)
raise InvalidCritError, "'#{crit}' missing from crit header"
end

return if Array(enabled_crits).include?(crit)

raise InvalidCritError, "'#{crit}' not enabled for token instance"
end

alias to_s jwt

private

attr_reader :enabled_crits

def decode_payload
raise(JWT::DecodeError, 'Encoded payload is empty') if encoded_payload == ''
raise JWT::DecodeError, 'Encoded payload is empty' if encoded_payload == ''

if unecoded_payload?
return parse(encoded_payload)
verify_crit!('b64')
return parse_unencoded(encoded_payload)
end

parse_and_decode(encoded_payload)
Expand All @@ -125,6 +152,10 @@ def parse_and_decode(segment)
parse(::JWT::Base64.url_decode(segment))
end

def parse_unencoded(segment)
parse(segment)
end

def parse(segment)
JWT::JSON.parse(segment)
rescue ::JSON::ParserError
Expand Down
3 changes: 3 additions & 0 deletions lib/jwt/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,7 @@ class Base64DecodeError < DecodeError; end

# The JWKError class is raised when there is an error with the JSON Web Key (JWK).
class JWKError < DecodeError; end

# The InvalidCritError class is raised when the JWT crit header contains unexpected or invalid values.
class InvalidCritError < DecodeError; end
end
12 changes: 11 additions & 1 deletion spec/jwt/encoded_token_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,24 @@
end
end

context 'when payload is not encoded' do
context 'when payload is not encoded and the b64 crit is enabled' do
subject(:token) { described_class.new(encoded_token, enabled_crits: ['b64']) }
let(:encoded_token) { 'eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..signature' }
before { token.encoded_payload = '{"foo": "bar"}' }

it 'does not raise' do
expect(token.payload).to eq({ 'foo' => 'bar' })
end
end

context 'when payload is not encoded and the b64 crit is NOT enabled' do
let(:encoded_token) { 'eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..signature' }
before { token.encoded_payload = '{"foo": "bar"}' }

it 'raises an error' do
expect { token.payload }.to raise_error(JWT::InvalidCritError, "'b64' not enabled for token instance")
end
end
end

describe '#header' do
Expand Down

0 comments on commit faa6f85

Please sign in to comment.