jwt 2.7.1 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +84 -27
- data/README.md +37 -19
- data/lib/jwt/base64.rb +16 -2
- data/lib/jwt/claims/audience.rb +20 -0
- data/lib/jwt/claims/expiration.rb +22 -0
- data/lib/jwt/claims/issued_at.rb +15 -0
- data/lib/jwt/claims/issuer.rb +24 -0
- data/lib/jwt/claims/jwt_id.rb +25 -0
- data/lib/jwt/claims/not_before.rb +22 -0
- data/lib/jwt/claims/numeric.rb +43 -0
- data/lib/jwt/claims/required.rb +23 -0
- data/lib/jwt/claims/subject.rb +20 -0
- data/lib/jwt/claims.rb +38 -0
- data/lib/jwt/configuration/container.rb +14 -3
- data/lib/jwt/configuration/jwk_configuration.rb +1 -1
- data/lib/jwt/decode.rb +12 -21
- data/lib/jwt/deprecations.rb +48 -0
- data/lib/jwt/encode.rb +4 -14
- data/lib/jwt/error.rb +1 -0
- data/lib/jwt/{algos → jwa}/ecdsa.rb +39 -26
- data/lib/jwt/jwa/eddsa.rb +34 -0
- data/lib/jwt/{algos → jwa}/hmac.rb +25 -19
- data/lib/jwt/jwa/hmac_rbnacl.rb +45 -0
- data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +42 -0
- data/lib/jwt/jwa/none.rb +23 -0
- data/lib/jwt/jwa/ps.rb +36 -0
- data/lib/jwt/jwa/rsa.rb +36 -0
- data/lib/jwt/jwa/signing_algorithm.rb +59 -0
- data/lib/jwt/jwa/unsupported.rb +19 -0
- data/lib/jwt/jwa/wrapper.rb +43 -0
- data/lib/jwt/jwa.rb +45 -0
- data/lib/jwt/jwk/ec.rb +42 -27
- data/lib/jwt/jwk/key_base.rb +3 -1
- data/lib/jwt/jwk/key_finder.rb +4 -4
- data/lib/jwt/jwk/set.rb +1 -1
- data/lib/jwt/jwk.rb +1 -1
- data/lib/jwt/version.rb +4 -3
- data/lib/jwt/x5c_key_finder.rb +2 -5
- data/lib/jwt.rb +5 -1
- data/ruby-jwt.gemspec +3 -0
- metadata +58 -20
- data/lib/jwt/algos/algo_wrapper.rb +0 -26
- data/lib/jwt/algos/eddsa.rb +0 -33
- data/lib/jwt/algos/hmac_rbnacl.rb +0 -53
- data/lib/jwt/algos/hmac_rbnacl_fixed.rb +0 -52
- data/lib/jwt/algos/none.rb +0 -19
- data/lib/jwt/algos/ps.rb +0 -41
- data/lib/jwt/algos/rsa.rb +0 -23
- data/lib/jwt/algos/unsupported.rb +0 -19
- data/lib/jwt/algos.rb +0 -66
- data/lib/jwt/claims_validator.rb +0 -37
- data/lib/jwt/verify.rb +0 -113
data/lib/jwt/decode.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'json'
|
4
|
-
|
5
|
-
require 'jwt/verify'
|
6
4
|
require 'jwt/x5c_key_finder'
|
7
5
|
|
8
6
|
# JWT::Decode module
|
@@ -10,7 +8,7 @@ module JWT
|
|
10
8
|
# Decoding logic for JWT
|
11
9
|
class Decode
|
12
10
|
def initialize(jwt, key, verify, options, &keyfinder)
|
13
|
-
raise
|
11
|
+
raise JWT::DecodeError, 'Nil JSON web token' unless jwt
|
14
12
|
|
15
13
|
@jwt = jwt
|
16
14
|
@key = key
|
@@ -30,7 +28,7 @@ module JWT
|
|
30
28
|
verify_signature
|
31
29
|
verify_claims
|
32
30
|
end
|
33
|
-
raise
|
31
|
+
raise JWT::DecodeError, 'Not enough or too many segments' unless header && payload
|
34
32
|
|
35
33
|
[payload, header]
|
36
34
|
end
|
@@ -46,21 +44,21 @@ module JWT
|
|
46
44
|
|
47
45
|
return if Array(@key).any? { |key| verify_signature_for?(key) }
|
48
46
|
|
49
|
-
raise
|
47
|
+
raise JWT::VerificationError, 'Signature verification failed'
|
50
48
|
end
|
51
49
|
|
52
50
|
def verify_algo
|
53
|
-
raise
|
54
|
-
raise
|
55
|
-
raise
|
51
|
+
raise JWT::IncorrectAlgorithm, 'An algorithm must be specified' if allowed_algorithms.empty?
|
52
|
+
raise JWT::IncorrectAlgorithm, 'Token is missing alg header' unless alg_in_header
|
53
|
+
raise JWT::IncorrectAlgorithm, 'Expected a different algorithm' if allowed_and_valid_algorithms.empty?
|
56
54
|
end
|
57
55
|
|
58
56
|
def set_key
|
59
57
|
@key = find_key(&@keyfinder) if @keyfinder
|
60
58
|
@key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks], allow_nil_kid: @options[:allow_nil_kid]).key_for(header['kid']) if @options[:jwks]
|
61
|
-
|
62
|
-
|
63
|
-
|
59
|
+
return unless (x5c_options = @options[:x5c])
|
60
|
+
|
61
|
+
@key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(header['x5c'])
|
64
62
|
end
|
65
63
|
|
66
64
|
def verify_signature_for?(key)
|
@@ -92,13 +90,7 @@ module JWT
|
|
92
90
|
end
|
93
91
|
|
94
92
|
def resolve_allowed_algorithms
|
95
|
-
algs = given_algorithms.map
|
96
|
-
if Algos.implementation?(alg)
|
97
|
-
alg
|
98
|
-
else
|
99
|
-
Algos.create(alg)
|
100
|
-
end
|
101
|
-
end
|
93
|
+
algs = given_algorithms.map { |alg| JWA.resolve(alg) }
|
102
94
|
|
103
95
|
sort_by_alg_header(algs)
|
104
96
|
end
|
@@ -119,8 +111,7 @@ module JWT
|
|
119
111
|
end
|
120
112
|
|
121
113
|
def verify_claims
|
122
|
-
|
123
|
-
Verify.verify_required_claims(payload, @options)
|
114
|
+
Claims.verify!(payload, @options)
|
124
115
|
end
|
125
116
|
|
126
117
|
def validate_segment_count!
|
@@ -128,7 +119,7 @@ module JWT
|
|
128
119
|
return if !@verify && segment_length == 2 # If no verifying required, the signature is not needed
|
129
120
|
return if segment_length == 2 && none_algorithm?
|
130
121
|
|
131
|
-
raise
|
122
|
+
raise JWT::DecodeError, 'Not enough or too many segments'
|
132
123
|
end
|
133
124
|
|
134
125
|
def segment_length
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
# Deprecations module to handle deprecation warnings in the gem
|
5
|
+
module Deprecations
|
6
|
+
class << self
|
7
|
+
def context
|
8
|
+
yield.tap { emit_warnings }
|
9
|
+
ensure
|
10
|
+
Thread.current[:jwt_warning_store] = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def warning(message, only_if_valid: false)
|
14
|
+
method_name = only_if_valid ? :store : :warn
|
15
|
+
case JWT.configuration.deprecation_warnings
|
16
|
+
when :once
|
17
|
+
return if record_warned(message)
|
18
|
+
when :warn
|
19
|
+
# noop
|
20
|
+
else
|
21
|
+
return
|
22
|
+
end
|
23
|
+
|
24
|
+
send(method_name, "[DEPRECATION WARNING] #{message}")
|
25
|
+
end
|
26
|
+
|
27
|
+
def store(message)
|
28
|
+
(Thread.current[:jwt_warning_store] ||= []) << message
|
29
|
+
end
|
30
|
+
|
31
|
+
def emit_warnings
|
32
|
+
return if Thread.current[:jwt_warning_store].nil?
|
33
|
+
|
34
|
+
Thread.current[:jwt_warning_store].each { |warning| warn(warning) }
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def record_warned(message)
|
40
|
+
@warned ||= []
|
41
|
+
return true if @warned.include?(message)
|
42
|
+
|
43
|
+
@warned << message
|
44
|
+
false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/jwt/encode.rb
CHANGED
@@ -1,20 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
4
|
-
require_relative 'claims_validator'
|
3
|
+
require_relative 'jwa'
|
5
4
|
|
6
5
|
# JWT::Encode module
|
7
6
|
module JWT
|
8
7
|
# Encoding logic for JWT
|
9
8
|
class Encode
|
10
|
-
ALG_KEY = 'alg'
|
11
|
-
|
12
9
|
def initialize(options)
|
13
10
|
@payload = options[:payload]
|
14
11
|
@key = options[:key]
|
15
|
-
@algorithm =
|
12
|
+
@algorithm = JWA.resolve(options[:algorithm])
|
16
13
|
@headers = options[:headers].transform_keys(&:to_s)
|
17
|
-
@headers[ALG_KEY] = @algorithm.alg
|
18
14
|
end
|
19
15
|
|
20
16
|
def segments
|
@@ -24,12 +20,6 @@ module JWT
|
|
24
20
|
|
25
21
|
private
|
26
22
|
|
27
|
-
def resolve_algorithm(algorithm)
|
28
|
-
return algorithm if Algos.implementation?(algorithm)
|
29
|
-
|
30
|
-
Algos.create(algorithm)
|
31
|
-
end
|
32
|
-
|
33
23
|
def encoded_header
|
34
24
|
@encoded_header ||= encode_header
|
35
25
|
end
|
@@ -47,7 +37,7 @@ module JWT
|
|
47
37
|
end
|
48
38
|
|
49
39
|
def encode_header
|
50
|
-
encode_data(@headers)
|
40
|
+
encode_data(@headers.merge(@algorithm.header(signing_key: @key)))
|
51
41
|
end
|
52
42
|
|
53
43
|
def encode_payload
|
@@ -61,7 +51,7 @@ module JWT
|
|
61
51
|
def validate_claims!
|
62
52
|
return unless @payload.is_a?(Hash)
|
63
53
|
|
64
|
-
|
54
|
+
Claims::Numeric.new(@payload).verify!
|
65
55
|
end
|
66
56
|
|
67
57
|
def encode_signature
|
data/lib/jwt/error.rb
CHANGED
@@ -1,9 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module JWT
|
4
|
-
module
|
5
|
-
|
6
|
-
|
4
|
+
module JWA
|
5
|
+
class Ecdsa
|
6
|
+
include JWT::JWA::SigningAlgorithm
|
7
|
+
|
8
|
+
def initialize(alg, digest)
|
9
|
+
@alg = alg
|
10
|
+
@digest = OpenSSL::Digest.new(digest)
|
11
|
+
end
|
12
|
+
|
13
|
+
def sign(data:, signing_key:)
|
14
|
+
curve_definition = curve_by_name(signing_key.group.curve_name)
|
15
|
+
key_algorithm = curve_definition[:algorithm]
|
16
|
+
if alg != key_algorithm
|
17
|
+
raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} signing key was provided"
|
18
|
+
end
|
19
|
+
|
20
|
+
asn1_to_raw(signing_key.dsa_sign_asn1(digest.digest(data)), signing_key)
|
21
|
+
end
|
22
|
+
|
23
|
+
def verify(data:, signature:, verification_key:)
|
24
|
+
curve_definition = curve_by_name(verification_key.group.curve_name)
|
25
|
+
key_algorithm = curve_definition[:algorithm]
|
26
|
+
if alg != key_algorithm
|
27
|
+
raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} verification key was provided"
|
28
|
+
end
|
29
|
+
|
30
|
+
verification_key.dsa_verify_asn1(digest.digest(data), raw_to_asn1(signature, verification_key))
|
31
|
+
rescue OpenSSL::PKey::PKeyError
|
32
|
+
raise JWT::VerificationError, 'Signature verification raised'
|
33
|
+
end
|
7
34
|
|
8
35
|
NAMED_CURVES = {
|
9
36
|
'prime256v1' => {
|
@@ -28,36 +55,22 @@ module JWT
|
|
28
55
|
}
|
29
56
|
}.freeze
|
30
57
|
|
31
|
-
|
58
|
+
NAMED_CURVES.each_value do |v|
|
59
|
+
register_algorithm(new(v[:algorithm], v[:digest]))
|
60
|
+
end
|
32
61
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
if algorithm != key_algorithm
|
37
|
-
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided"
|
62
|
+
def self.curve_by_name(name)
|
63
|
+
NAMED_CURVES.fetch(name) do
|
64
|
+
raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
|
38
65
|
end
|
39
|
-
|
40
|
-
digest = OpenSSL::Digest.new(curve_definition[:digest])
|
41
|
-
asn1_to_raw(key.dsa_sign_asn1(digest.digest(msg)), key)
|
42
66
|
end
|
43
67
|
|
44
|
-
|
45
|
-
curve_definition = curve_by_name(public_key.group.curve_name)
|
46
|
-
key_algorithm = curve_definition[:algorithm]
|
47
|
-
if algorithm != key_algorithm
|
48
|
-
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided"
|
49
|
-
end
|
68
|
+
private
|
50
69
|
|
51
|
-
|
52
|
-
public_key.dsa_verify_asn1(digest.digest(signing_input), raw_to_asn1(signature, public_key))
|
53
|
-
rescue OpenSSL::PKey::PKeyError
|
54
|
-
raise JWT::VerificationError, 'Signature verification raised'
|
55
|
-
end
|
70
|
+
attr_reader :digest
|
56
71
|
|
57
72
|
def curve_by_name(name)
|
58
|
-
|
59
|
-
raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
|
60
|
-
end
|
73
|
+
self.class.curve_by_name(name)
|
61
74
|
end
|
62
75
|
|
63
76
|
def raw_to_asn1(signature, private_key)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
class Eddsa
|
6
|
+
include JWT::JWA::SigningAlgorithm
|
7
|
+
|
8
|
+
def initialize(alg)
|
9
|
+
@alg = alg
|
10
|
+
end
|
11
|
+
|
12
|
+
def sign(data:, signing_key:)
|
13
|
+
unless signing_key.is_a?(RbNaCl::Signatures::Ed25519::SigningKey)
|
14
|
+
raise_encode_error!("Key given is a #{signing_key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey")
|
15
|
+
end
|
16
|
+
|
17
|
+
signing_key.sign(data)
|
18
|
+
end
|
19
|
+
|
20
|
+
def verify(data:, signature:, verification_key:)
|
21
|
+
unless verification_key.is_a?(RbNaCl::Signatures::Ed25519::VerifyKey)
|
22
|
+
raise_decode_error!("key given is a #{verification_key.class} but has to be a RbNaCl::Signatures::Ed25519::VerifyKey")
|
23
|
+
end
|
24
|
+
|
25
|
+
verification_key.verify(signature, data)
|
26
|
+
rescue RbNaCl::CryptoError
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
register_algorithm(new('ED25519'))
|
31
|
+
register_algorithm(new('EdDSA'))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,36 +1,40 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module JWT
|
4
|
-
module
|
5
|
-
|
6
|
-
|
4
|
+
module JWA
|
5
|
+
class Hmac
|
6
|
+
include JWT::JWA::SigningAlgorithm
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
}.freeze
|
13
|
-
|
14
|
-
SUPPORTED = MAPPING.keys
|
15
|
-
|
16
|
-
def sign(algorithm, msg, key)
|
17
|
-
key ||= ''
|
8
|
+
def initialize(alg, digest)
|
9
|
+
@alg = alg
|
10
|
+
@digest = digest
|
11
|
+
end
|
18
12
|
|
19
|
-
|
13
|
+
def sign(data:, signing_key:)
|
14
|
+
signing_key ||= ''
|
15
|
+
raise_verify_error!('HMAC key expected to be a String') unless signing_key.is_a?(String)
|
20
16
|
|
21
|
-
OpenSSL::HMAC.digest(
|
17
|
+
OpenSSL::HMAC.digest(digest.new, signing_key, data)
|
22
18
|
rescue OpenSSL::HMACError => e
|
23
|
-
if
|
24
|
-
|
19
|
+
if signing_key == '' && e.message == 'EVP_PKEY_new_mac_key: malloc failure'
|
20
|
+
raise_verify_error!('OpenSSL 3.0 does not support nil or empty hmac_secret')
|
25
21
|
end
|
26
22
|
|
27
23
|
raise e
|
28
24
|
end
|
29
25
|
|
30
|
-
def verify(
|
31
|
-
SecurityUtils.secure_compare(signature, sign(
|
26
|
+
def verify(data:, signature:, verification_key:)
|
27
|
+
SecurityUtils.secure_compare(signature, sign(data: data, signing_key: verification_key))
|
32
28
|
end
|
33
29
|
|
30
|
+
register_algorithm(new('HS256', OpenSSL::Digest::SHA256))
|
31
|
+
register_algorithm(new('HS384', OpenSSL::Digest::SHA384))
|
32
|
+
register_algorithm(new('HS512', OpenSSL::Digest::SHA512))
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :digest
|
37
|
+
|
34
38
|
# Copy of https://github.com/rails/rails/blob/v7.0.3.1/activesupport/lib/active_support/security_utils.rb
|
35
39
|
# rubocop:disable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate
|
36
40
|
module SecurityUtils
|
@@ -44,6 +48,7 @@ module JWT
|
|
44
48
|
OpenSSL.fixed_length_secure_compare(a, b)
|
45
49
|
end
|
46
50
|
else
|
51
|
+
# :nocov:
|
47
52
|
def fixed_length_secure_compare(a, b)
|
48
53
|
raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
|
49
54
|
|
@@ -53,6 +58,7 @@ module JWT
|
|
53
58
|
b.each_byte { |byte| res |= byte ^ l.shift }
|
54
59
|
res == 0
|
55
60
|
end
|
61
|
+
# :nocov:
|
56
62
|
end
|
57
63
|
module_function :fixed_length_secure_compare
|
58
64
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
class HmacRbNaCl
|
6
|
+
include JWT::JWA::SigningAlgorithm
|
7
|
+
|
8
|
+
def initialize(alg, hmac)
|
9
|
+
@alg = alg
|
10
|
+
@hmac = hmac
|
11
|
+
end
|
12
|
+
|
13
|
+
def sign(data:, signing_key:)
|
14
|
+
Deprecations.warning("The use of the algorithm #{alg} is deprecated and will be removed in the next major version of ruby-jwt")
|
15
|
+
hmac.auth(key_for_rbnacl(hmac, signing_key).encode('binary'), data.encode('binary'))
|
16
|
+
end
|
17
|
+
|
18
|
+
def verify(data:, signature:, verification_key:)
|
19
|
+
Deprecations.warning("The use of the algorithm #{alg} is deprecated and will be removed in the next major version of ruby-jwt")
|
20
|
+
hmac.verify(key_for_rbnacl(hmac, verification_key).encode('binary'), signature.encode('binary'), data.encode('binary'))
|
21
|
+
rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
register_algorithm(new('HS512256', ::RbNaCl::HMAC::SHA512256))
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :hmac
|
30
|
+
|
31
|
+
def key_for_rbnacl(hmac, key)
|
32
|
+
key ||= ''
|
33
|
+
raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
|
34
|
+
|
35
|
+
return padded_empty_key(hmac.key_bytes) if key == ''
|
36
|
+
|
37
|
+
key
|
38
|
+
end
|
39
|
+
|
40
|
+
def padded_empty_key(length)
|
41
|
+
Array.new(length, 0x0).pack('C*').encode('binary')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
class HmacRbNaClFixed
|
6
|
+
include JWT::JWA::SigningAlgorithm
|
7
|
+
|
8
|
+
def initialize(alg, hmac)
|
9
|
+
@alg = alg
|
10
|
+
@hmac = hmac
|
11
|
+
end
|
12
|
+
|
13
|
+
def sign(data:, signing_key:)
|
14
|
+
signing_key ||= ''
|
15
|
+
Deprecations.warning("The use of the algorithm #{alg} is deprecated and will be removed in the next major version of ruby-jwt")
|
16
|
+
raise JWT::DecodeError, 'HMAC key expected to be a String' unless signing_key.is_a?(String)
|
17
|
+
|
18
|
+
hmac.auth(padded_key_bytes(signing_key, hmac.key_bytes), data.encode('binary'))
|
19
|
+
end
|
20
|
+
|
21
|
+
def verify(data:, signature:, verification_key:)
|
22
|
+
verification_key ||= ''
|
23
|
+
Deprecations.warning("The use of the algorithm #{alg} is deprecated and will be removed in the next major version of ruby-jwt")
|
24
|
+
raise JWT::DecodeError, 'HMAC key expected to be a String' unless verification_key.is_a?(String)
|
25
|
+
|
26
|
+
hmac.verify(padded_key_bytes(verification_key, hmac.key_bytes), signature.encode('binary'), data.encode('binary'))
|
27
|
+
rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
register_algorithm(new('HS512256', ::RbNaCl::HMAC::SHA512256))
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :hmac
|
36
|
+
|
37
|
+
def padded_key_bytes(key, bytesize)
|
38
|
+
key.bytes.fill(0, key.bytesize...bytesize).pack('C*')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/jwt/jwa/none.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
class None
|
6
|
+
include JWT::JWA::SigningAlgorithm
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@alg = 'none'
|
10
|
+
end
|
11
|
+
|
12
|
+
def sign(*)
|
13
|
+
''
|
14
|
+
end
|
15
|
+
|
16
|
+
def verify(*)
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
register_algorithm(new)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/jwt/jwa/ps.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
class Ps
|
6
|
+
include JWT::JWA::SigningAlgorithm
|
7
|
+
|
8
|
+
def initialize(alg)
|
9
|
+
@alg = alg
|
10
|
+
@digest_algorithm = alg.sub('PS', 'sha')
|
11
|
+
end
|
12
|
+
|
13
|
+
def sign(data:, signing_key:)
|
14
|
+
unless signing_key.is_a?(::OpenSSL::PKey::RSA)
|
15
|
+
raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance.")
|
16
|
+
end
|
17
|
+
|
18
|
+
signing_key.sign_pss(digest_algorithm, data, salt_length: :digest, mgf1_hash: digest_algorithm)
|
19
|
+
end
|
20
|
+
|
21
|
+
def verify(data:, signature:, verification_key:)
|
22
|
+
verification_key.verify_pss(digest_algorithm, signature, data, salt_length: :auto, mgf1_hash: digest_algorithm)
|
23
|
+
rescue OpenSSL::PKey::PKeyError
|
24
|
+
raise JWT::VerificationError, 'Signature verification raised'
|
25
|
+
end
|
26
|
+
|
27
|
+
register_algorithm(new('PS256'))
|
28
|
+
register_algorithm(new('PS384'))
|
29
|
+
register_algorithm(new('PS512'))
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :digest_algorithm
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/jwt/jwa/rsa.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
class Rsa
|
6
|
+
include JWT::JWA::SigningAlgorithm
|
7
|
+
|
8
|
+
def initialize(alg)
|
9
|
+
@alg = alg
|
10
|
+
@digest = OpenSSL::Digest.new(alg.sub('RS', 'SHA'))
|
11
|
+
end
|
12
|
+
|
13
|
+
def sign(data:, signing_key:)
|
14
|
+
unless signing_key.is_a?(OpenSSL::PKey::RSA)
|
15
|
+
raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance")
|
16
|
+
end
|
17
|
+
|
18
|
+
signing_key.sign(digest, data)
|
19
|
+
end
|
20
|
+
|
21
|
+
def verify(data:, signature:, verification_key:)
|
22
|
+
verification_key.verify(digest, signature, data)
|
23
|
+
rescue OpenSSL::PKey::PKeyError
|
24
|
+
raise JWT::VerificationError, 'Signature verification raised'
|
25
|
+
end
|
26
|
+
|
27
|
+
register_algorithm(new('RS256'))
|
28
|
+
register_algorithm(new('RS384'))
|
29
|
+
register_algorithm(new('RS512'))
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :digest
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
module SigningAlgorithm
|
6
|
+
module ClassMethods
|
7
|
+
def register_algorithm(algo)
|
8
|
+
::JWT::JWA.register_algorithm(algo)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.included(klass)
|
13
|
+
klass.extend(ClassMethods)
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :alg
|
17
|
+
|
18
|
+
def valid_alg?(alg_to_check)
|
19
|
+
alg&.casecmp(alg_to_check)&.zero? == true
|
20
|
+
end
|
21
|
+
|
22
|
+
def header(*)
|
23
|
+
{ 'alg' => alg }
|
24
|
+
end
|
25
|
+
|
26
|
+
def sign(*)
|
27
|
+
raise_sign_error!('Algorithm implementation is missing the sign method')
|
28
|
+
end
|
29
|
+
|
30
|
+
def verify(*)
|
31
|
+
raise_verify_error!('Algorithm implementation is missing the verify method')
|
32
|
+
end
|
33
|
+
|
34
|
+
def raise_verify_error!(message)
|
35
|
+
raise(DecodeError.new(message).tap { |e| e.set_backtrace(caller(1)) })
|
36
|
+
end
|
37
|
+
|
38
|
+
def raise_sign_error!(message)
|
39
|
+
raise(EncodeError.new(message).tap { |e| e.set_backtrace(caller(1)) })
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class << self
|
44
|
+
def register_algorithm(algo)
|
45
|
+
algorithms[algo.alg.to_s.downcase] = algo
|
46
|
+
end
|
47
|
+
|
48
|
+
def find(algo)
|
49
|
+
algorithms.fetch(algo.to_s.downcase, Unsupported)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def algorithms
|
55
|
+
@algorithms ||= {}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
module Unsupported
|
6
|
+
class << self
|
7
|
+
include JWT::JWA::SigningAlgorithm
|
8
|
+
|
9
|
+
def sign(*)
|
10
|
+
raise_sign_error!('Unsupported signing method')
|
11
|
+
end
|
12
|
+
|
13
|
+
def verify(*)
|
14
|
+
raise JWT::VerificationError, 'Algorithm not supported'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|