jwt 2.7.1 → 2.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|