jwt 2.3.0 → 2.10.1
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/AUTHORS +60 -53
- data/CHANGELOG.md +194 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/CONTRIBUTING.md +99 -0
- data/README.md +360 -106
- data/lib/jwt/base64.rb +19 -2
- data/lib/jwt/claims/audience.rb +30 -0
- data/lib/jwt/claims/crit.rb +35 -0
- data/lib/jwt/claims/decode_verifier.rb +40 -0
- data/lib/jwt/claims/expiration.rb +32 -0
- data/lib/jwt/claims/issued_at.rb +22 -0
- data/lib/jwt/claims/issuer.rb +34 -0
- data/lib/jwt/claims/jwt_id.rb +35 -0
- data/lib/jwt/claims/not_before.rb +32 -0
- data/lib/jwt/claims/numeric.rb +77 -0
- data/lib/jwt/claims/required.rb +33 -0
- data/lib/jwt/claims/subject.rb +30 -0
- data/lib/jwt/claims/verification_methods.rb +20 -0
- data/lib/jwt/claims/verifier.rb +61 -0
- data/lib/jwt/claims.rb +74 -0
- data/lib/jwt/claims_validator.rb +7 -24
- data/lib/jwt/configuration/container.rb +52 -0
- data/lib/jwt/configuration/decode_configuration.rb +70 -0
- data/lib/jwt/configuration/jwk_configuration.rb +28 -0
- data/lib/jwt/configuration.rb +23 -0
- data/lib/jwt/decode.rb +70 -61
- data/lib/jwt/deprecations.rb +49 -0
- data/lib/jwt/encode.rb +18 -57
- data/lib/jwt/encoded_token.rb +139 -0
- data/lib/jwt/error.rb +36 -0
- data/lib/jwt/json.rb +1 -1
- data/lib/jwt/jwa/compat.rb +32 -0
- data/lib/jwt/jwa/ecdsa.rb +90 -0
- data/lib/jwt/jwa/eddsa.rb +35 -0
- data/lib/jwt/jwa/hmac.rb +82 -0
- data/lib/jwt/jwa/hmac_rbnacl.rb +50 -0
- data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +47 -0
- data/lib/jwt/jwa/none.rb +24 -0
- data/lib/jwt/jwa/ps.rb +35 -0
- data/lib/jwt/jwa/rsa.rb +35 -0
- data/lib/jwt/jwa/signing_algorithm.rb +63 -0
- data/lib/jwt/jwa/unsupported.rb +20 -0
- data/lib/jwt/jwa/wrapper.rb +44 -0
- data/lib/jwt/jwa.rb +58 -0
- data/lib/jwt/jwk/ec.rb +163 -63
- data/lib/jwt/jwk/hmac.rb +68 -24
- data/lib/jwt/jwk/key_base.rb +46 -6
- data/lib/jwt/jwk/key_finder.rb +20 -35
- data/lib/jwt/jwk/kid_as_key_digest.rb +16 -0
- data/lib/jwt/jwk/okp_rbnacl.rb +109 -0
- data/lib/jwt/jwk/rsa.rb +141 -54
- data/lib/jwt/jwk/set.rb +82 -0
- data/lib/jwt/jwk/thumbprint.rb +26 -0
- data/lib/jwt/jwk.rb +16 -11
- data/lib/jwt/token.rb +112 -0
- data/lib/jwt/verify.rb +16 -81
- data/lib/jwt/version.rb +53 -11
- data/lib/jwt/x5c_key_finder.rb +52 -0
- data/lib/jwt.rb +28 -4
- data/ruby-jwt.gemspec +15 -5
- metadata +75 -28
- data/.github/workflows/test.yml +0 -74
- data/.gitignore +0 -11
- data/.rspec +0 -2
- data/.rubocop.yml +0 -97
- data/.rubocop_todo.yml +0 -185
- data/.sourcelevel.yml +0 -18
- data/Appraisals +0 -10
- data/Gemfile +0 -5
- data/Rakefile +0 -14
- data/lib/jwt/algos/ecdsa.rb +0 -35
- data/lib/jwt/algos/eddsa.rb +0 -30
- data/lib/jwt/algos/hmac.rb +0 -34
- data/lib/jwt/algos/none.rb +0 -15
- data/lib/jwt/algos/ps.rb +0 -43
- data/lib/jwt/algos/rsa.rb +0 -19
- data/lib/jwt/algos/unsupported.rb +0 -17
- data/lib/jwt/algos.rb +0 -44
- data/lib/jwt/default_options.rb +0 -16
- data/lib/jwt/security_utils.rb +0 -57
- data/lib/jwt/signature.rb +0 -39
data/lib/jwt/error.rb
CHANGED
@@ -1,21 +1,57 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module JWT
|
4
|
+
# The EncodeError class is raised when there is an error encoding a JWT.
|
4
5
|
class EncodeError < StandardError; end
|
6
|
+
|
7
|
+
# The DecodeError class is raised when there is an error decoding a JWT.
|
5
8
|
class DecodeError < StandardError; end
|
9
|
+
|
10
|
+
# The RequiredDependencyError class is raised when a required dependency is missing.
|
6
11
|
class RequiredDependencyError < StandardError; end
|
7
12
|
|
13
|
+
# The VerificationError class is raised when there is an error verifying a JWT.
|
8
14
|
class VerificationError < DecodeError; end
|
15
|
+
|
16
|
+
# The ExpiredSignature class is raised when the JWT signature has expired.
|
9
17
|
class ExpiredSignature < DecodeError; end
|
18
|
+
|
19
|
+
# The IncorrectAlgorithm class is raised when the JWT algorithm is incorrect.
|
10
20
|
class IncorrectAlgorithm < DecodeError; end
|
21
|
+
|
22
|
+
# The ImmatureSignature class is raised when the JWT signature is immature.
|
11
23
|
class ImmatureSignature < DecodeError; end
|
24
|
+
|
25
|
+
# The InvalidIssuerError class is raised when the JWT issuer is invalid.
|
12
26
|
class InvalidIssuerError < DecodeError; end
|
27
|
+
|
28
|
+
# The UnsupportedEcdsaCurve class is raised when the ECDSA curve is unsupported.
|
29
|
+
class UnsupportedEcdsaCurve < IncorrectAlgorithm; end
|
30
|
+
|
31
|
+
# The InvalidIatError class is raised when the JWT issued at (iat) claim is invalid.
|
13
32
|
class InvalidIatError < DecodeError; end
|
33
|
+
|
34
|
+
# The InvalidAudError class is raised when the JWT audience (aud) claim is invalid.
|
14
35
|
class InvalidAudError < DecodeError; end
|
36
|
+
|
37
|
+
# The InvalidSubError class is raised when the JWT subject (sub) claim is invalid.
|
15
38
|
class InvalidSubError < DecodeError; end
|
39
|
+
|
40
|
+
# The InvalidCritError class is raised when the JWT crit header is invalid.
|
41
|
+
class InvalidCritError < DecodeError; end
|
42
|
+
|
43
|
+
# The InvalidJtiError class is raised when the JWT ID (jti) claim is invalid.
|
16
44
|
class InvalidJtiError < DecodeError; end
|
45
|
+
|
46
|
+
# The InvalidPayload class is raised when the JWT payload is invalid.
|
17
47
|
class InvalidPayload < DecodeError; end
|
48
|
+
|
49
|
+
# The MissingRequiredClaim class is raised when a required claim is missing from the JWT.
|
18
50
|
class MissingRequiredClaim < DecodeError; end
|
19
51
|
|
52
|
+
# The Base64DecodeError class is raised when there is an error decoding a Base64-encoded string.
|
53
|
+
class Base64DecodeError < DecodeError; end
|
54
|
+
|
55
|
+
# The JWKError class is raised when there is an error with the JSON Web Key (JWK).
|
20
56
|
class JWKError < DecodeError; end
|
21
57
|
end
|
data/lib/jwt/json.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
# Provides backwards compatibility for algorithms
|
6
|
+
# @api private
|
7
|
+
module Compat
|
8
|
+
# @api private
|
9
|
+
module ClassMethods
|
10
|
+
def from_algorithm(algorithm)
|
11
|
+
new(algorithm)
|
12
|
+
end
|
13
|
+
|
14
|
+
def sign(algorithm, msg, key)
|
15
|
+
Deprecations.warning('Support for calling sign with positional arguments will be removed in future ruby-jwt versions')
|
16
|
+
|
17
|
+
from_algorithm(algorithm).sign(data: msg, signing_key: key)
|
18
|
+
end
|
19
|
+
|
20
|
+
def verify(algorithm, key, signing_input, signature)
|
21
|
+
Deprecations.warning('Support for calling verify with positional arguments will be removed in future ruby-jwt versions')
|
22
|
+
|
23
|
+
from_algorithm(algorithm).verify(data: signing_input, signature: signature, verification_key: key)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.included(klass)
|
28
|
+
klass.extend(ClassMethods)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
# ECDSA signing algorithm
|
6
|
+
class Ecdsa
|
7
|
+
include JWT::JWA::SigningAlgorithm
|
8
|
+
|
9
|
+
def initialize(alg, digest)
|
10
|
+
@alg = alg
|
11
|
+
@digest = OpenSSL::Digest.new(digest)
|
12
|
+
end
|
13
|
+
|
14
|
+
def sign(data:, signing_key:)
|
15
|
+
curve_definition = curve_by_name(signing_key.group.curve_name)
|
16
|
+
key_algorithm = curve_definition[:algorithm]
|
17
|
+
raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} signing key was provided" if alg != key_algorithm
|
18
|
+
|
19
|
+
asn1_to_raw(signing_key.dsa_sign_asn1(digest.digest(data)), signing_key)
|
20
|
+
end
|
21
|
+
|
22
|
+
def verify(data:, signature:, verification_key:)
|
23
|
+
curve_definition = curve_by_name(verification_key.group.curve_name)
|
24
|
+
key_algorithm = curve_definition[:algorithm]
|
25
|
+
raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} verification key was provided" if alg != key_algorithm
|
26
|
+
|
27
|
+
verification_key.dsa_verify_asn1(digest.digest(data), raw_to_asn1(signature, verification_key))
|
28
|
+
rescue OpenSSL::PKey::PKeyError
|
29
|
+
raise JWT::VerificationError, 'Signature verification raised'
|
30
|
+
end
|
31
|
+
|
32
|
+
NAMED_CURVES = {
|
33
|
+
'prime256v1' => {
|
34
|
+
algorithm: 'ES256',
|
35
|
+
digest: 'sha256'
|
36
|
+
},
|
37
|
+
'secp256r1' => { # alias for prime256v1
|
38
|
+
algorithm: 'ES256',
|
39
|
+
digest: 'sha256'
|
40
|
+
},
|
41
|
+
'secp384r1' => {
|
42
|
+
algorithm: 'ES384',
|
43
|
+
digest: 'sha384'
|
44
|
+
},
|
45
|
+
'secp521r1' => {
|
46
|
+
algorithm: 'ES512',
|
47
|
+
digest: 'sha512'
|
48
|
+
},
|
49
|
+
'secp256k1' => {
|
50
|
+
algorithm: 'ES256K',
|
51
|
+
digest: 'sha256'
|
52
|
+
}
|
53
|
+
}.freeze
|
54
|
+
|
55
|
+
NAMED_CURVES.each_value do |v|
|
56
|
+
register_algorithm(new(v[:algorithm], v[:digest]))
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.from_algorithm(algorithm)
|
60
|
+
new(algorithm, algorithm.downcase.gsub('es', 'sha'))
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.curve_by_name(name)
|
64
|
+
NAMED_CURVES.fetch(name) do
|
65
|
+
raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
attr_reader :digest
|
72
|
+
|
73
|
+
def curve_by_name(name)
|
74
|
+
self.class.curve_by_name(name)
|
75
|
+
end
|
76
|
+
|
77
|
+
def raw_to_asn1(signature, private_key)
|
78
|
+
byte_size = (private_key.group.degree + 7) / 8
|
79
|
+
sig_bytes = signature[0..(byte_size - 1)]
|
80
|
+
sig_char = signature[byte_size..-1] || ''
|
81
|
+
OpenSSL::ASN1::Sequence.new([sig_bytes, sig_char].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
|
82
|
+
end
|
83
|
+
|
84
|
+
def asn1_to_raw(signature, public_key)
|
85
|
+
byte_size = (public_key.group.degree + 7) / 8
|
86
|
+
OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
# Implementation of the EdDSA family of algorithms
|
6
|
+
class Eddsa
|
7
|
+
include JWT::JWA::SigningAlgorithm
|
8
|
+
|
9
|
+
def initialize(alg)
|
10
|
+
@alg = alg
|
11
|
+
end
|
12
|
+
|
13
|
+
def sign(data:, signing_key:)
|
14
|
+
raise_sign_error!("Key given is a #{signing_key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey") unless signing_key.is_a?(RbNaCl::Signatures::Ed25519::SigningKey)
|
15
|
+
|
16
|
+
Deprecations.warning('Using Ed25519 keys is deprecated and will be removed in a future version of ruby-jwt. Please use the ruby-eddsa gem instead.')
|
17
|
+
|
18
|
+
signing_key.sign(data)
|
19
|
+
end
|
20
|
+
|
21
|
+
def verify(data:, signature:, verification_key:)
|
22
|
+
raise_verify_error!("key given is a #{verification_key.class} but has to be a RbNaCl::Signatures::Ed25519::VerifyKey") unless verification_key.is_a?(RbNaCl::Signatures::Ed25519::VerifyKey)
|
23
|
+
|
24
|
+
Deprecations.warning('Using Ed25519 keys is deprecated and will be removed in a future version of ruby-jwt. Please use the ruby-eddsa gem instead.')
|
25
|
+
|
26
|
+
verification_key.verify(signature, data)
|
27
|
+
rescue RbNaCl::CryptoError
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
register_algorithm(new('ED25519'))
|
32
|
+
register_algorithm(new('EdDSA'))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/jwt/jwa/hmac.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
# Implementation of the HMAC family of algorithms
|
6
|
+
class Hmac
|
7
|
+
include JWT::JWA::SigningAlgorithm
|
8
|
+
|
9
|
+
def self.from_algorithm(algorithm)
|
10
|
+
new(algorithm, OpenSSL::Digest.new(algorithm.downcase.gsub('hs', 'sha')))
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(alg, digest)
|
14
|
+
@alg = alg
|
15
|
+
@digest = digest
|
16
|
+
end
|
17
|
+
|
18
|
+
def sign(data:, signing_key:)
|
19
|
+
signing_key ||= ''
|
20
|
+
raise_verify_error!('HMAC key expected to be a String') unless signing_key.is_a?(String)
|
21
|
+
|
22
|
+
OpenSSL::HMAC.digest(digest.new, signing_key, data)
|
23
|
+
rescue OpenSSL::HMACError => e
|
24
|
+
raise_verify_error!('OpenSSL 3.0 does not support nil or empty hmac_secret') if signing_key == '' && e.message == 'EVP_PKEY_new_mac_key: malloc failure'
|
25
|
+
|
26
|
+
raise e
|
27
|
+
end
|
28
|
+
|
29
|
+
def verify(data:, signature:, verification_key:)
|
30
|
+
SecurityUtils.secure_compare(signature, sign(data: data, signing_key: verification_key))
|
31
|
+
end
|
32
|
+
|
33
|
+
register_algorithm(new('HS256', OpenSSL::Digest::SHA256))
|
34
|
+
register_algorithm(new('HS384', OpenSSL::Digest::SHA384))
|
35
|
+
register_algorithm(new('HS512', OpenSSL::Digest::SHA512))
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :digest
|
40
|
+
|
41
|
+
# Copy of https://github.com/rails/rails/blob/v7.0.3.1/activesupport/lib/active_support/security_utils.rb
|
42
|
+
# rubocop:disable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate
|
43
|
+
module SecurityUtils
|
44
|
+
# Constant time string comparison, for fixed length strings.
|
45
|
+
#
|
46
|
+
# The values compared should be of fixed length, such as strings
|
47
|
+
# that have already been processed by HMAC. Raises in case of length mismatch.
|
48
|
+
|
49
|
+
if defined?(OpenSSL.fixed_length_secure_compare)
|
50
|
+
def fixed_length_secure_compare(a, b)
|
51
|
+
OpenSSL.fixed_length_secure_compare(a, b)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
# :nocov:
|
55
|
+
def fixed_length_secure_compare(a, b)
|
56
|
+
raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
|
57
|
+
|
58
|
+
l = a.unpack "C#{a.bytesize}"
|
59
|
+
|
60
|
+
res = 0
|
61
|
+
b.each_byte { |byte| res |= byte ^ l.shift }
|
62
|
+
res == 0
|
63
|
+
end
|
64
|
+
# :nocov:
|
65
|
+
end
|
66
|
+
module_function :fixed_length_secure_compare
|
67
|
+
|
68
|
+
# Secure string comparison for strings of variable length.
|
69
|
+
#
|
70
|
+
# While a timing attack would not be able to discern the content of
|
71
|
+
# a secret compared via secure_compare, it is possible to determine
|
72
|
+
# the secret length. This should be considered when using secure_compare
|
73
|
+
# to compare weak, short secrets to user input.
|
74
|
+
def secure_compare(a, b)
|
75
|
+
a.bytesize == b.bytesize && fixed_length_secure_compare(a, b)
|
76
|
+
end
|
77
|
+
module_function :secure_compare
|
78
|
+
end
|
79
|
+
# rubocop:enable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
# Implementation of the HMAC family of algorithms (using RbNaCl)
|
6
|
+
class HmacRbNaCl
|
7
|
+
include JWT::JWA::SigningAlgorithm
|
8
|
+
|
9
|
+
def self.from_algorithm(algorithm)
|
10
|
+
new(algorithm, ::RbNaCl::HMAC.const_get(algorithm.upcase.gsub('HS', 'SHA')))
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(alg, hmac)
|
14
|
+
@alg = alg
|
15
|
+
@hmac = hmac
|
16
|
+
end
|
17
|
+
|
18
|
+
def sign(data:, signing_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.auth(key_for_rbnacl(hmac, signing_key).encode('binary'), data.encode('binary'))
|
21
|
+
end
|
22
|
+
|
23
|
+
def verify(data:, signature:, verification_key:)
|
24
|
+
Deprecations.warning("The use of the algorithm #{alg} is deprecated and will be removed in the next major version of ruby-jwt")
|
25
|
+
hmac.verify(key_for_rbnacl(hmac, verification_key).encode('binary'), signature.encode('binary'), data.encode('binary'))
|
26
|
+
rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
register_algorithm(new('HS512256', ::RbNaCl::HMAC::SHA512256))
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :hmac
|
35
|
+
|
36
|
+
def key_for_rbnacl(hmac, key)
|
37
|
+
key ||= ''
|
38
|
+
raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
|
39
|
+
|
40
|
+
return padded_empty_key(hmac.key_bytes) if key == ''
|
41
|
+
|
42
|
+
key
|
43
|
+
end
|
44
|
+
|
45
|
+
def padded_empty_key(length)
|
46
|
+
Array.new(length, 0x0).pack('C*').encode('binary')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
# Implementation of the HMAC family of algorithms (using RbNaCl prior to a certain version)
|
6
|
+
class HmacRbNaClFixed
|
7
|
+
include JWT::JWA::SigningAlgorithm
|
8
|
+
|
9
|
+
def self.from_algorithm(algorithm)
|
10
|
+
new(algorithm, ::RbNaCl::HMAC.const_get(algorithm.upcase.gsub('HS', 'SHA')))
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(alg, hmac)
|
14
|
+
@alg = alg
|
15
|
+
@hmac = hmac
|
16
|
+
end
|
17
|
+
|
18
|
+
def sign(data:, signing_key:)
|
19
|
+
signing_key ||= ''
|
20
|
+
Deprecations.warning("The use of the algorithm #{alg} is deprecated and will be removed in the next major version of ruby-jwt")
|
21
|
+
raise JWT::DecodeError, 'HMAC key expected to be a String' unless signing_key.is_a?(String)
|
22
|
+
|
23
|
+
hmac.auth(padded_key_bytes(signing_key, hmac.key_bytes), data.encode('binary'))
|
24
|
+
end
|
25
|
+
|
26
|
+
def verify(data:, signature:, verification_key:)
|
27
|
+
verification_key ||= ''
|
28
|
+
Deprecations.warning("The use of the algorithm #{alg} is deprecated and will be removed in the next major version of ruby-jwt")
|
29
|
+
raise JWT::DecodeError, 'HMAC key expected to be a String' unless verification_key.is_a?(String)
|
30
|
+
|
31
|
+
hmac.verify(padded_key_bytes(verification_key, hmac.key_bytes), signature.encode('binary'), data.encode('binary'))
|
32
|
+
rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
register_algorithm(new('HS512256', ::RbNaCl::HMAC::SHA512256))
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :hmac
|
41
|
+
|
42
|
+
def padded_key_bytes(key, bytesize)
|
43
|
+
key.bytes.fill(0, key.bytesize...bytesize).pack('C*')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/jwt/jwa/none.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
# Implementation of the none algorithm
|
6
|
+
class None
|
7
|
+
include JWT::JWA::SigningAlgorithm
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@alg = 'none'
|
11
|
+
end
|
12
|
+
|
13
|
+
def sign(*)
|
14
|
+
''
|
15
|
+
end
|
16
|
+
|
17
|
+
def verify(*)
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
register_algorithm(new)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/jwt/jwa/ps.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
# Implementation of the RSASSA-PSS family of algorithms
|
6
|
+
class Ps
|
7
|
+
include JWT::JWA::SigningAlgorithm
|
8
|
+
|
9
|
+
def initialize(alg)
|
10
|
+
@alg = alg
|
11
|
+
@digest_algorithm = alg.sub('PS', 'sha')
|
12
|
+
end
|
13
|
+
|
14
|
+
def sign(data:, signing_key:)
|
15
|
+
raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance.") unless signing_key.is_a?(::OpenSSL::PKey::RSA)
|
16
|
+
|
17
|
+
signing_key.sign_pss(digest_algorithm, data, salt_length: :digest, mgf1_hash: digest_algorithm)
|
18
|
+
end
|
19
|
+
|
20
|
+
def verify(data:, signature:, verification_key:)
|
21
|
+
verification_key.verify_pss(digest_algorithm, signature, data, salt_length: :auto, mgf1_hash: digest_algorithm)
|
22
|
+
rescue OpenSSL::PKey::PKeyError
|
23
|
+
raise JWT::VerificationError, 'Signature verification raised'
|
24
|
+
end
|
25
|
+
|
26
|
+
register_algorithm(new('PS256'))
|
27
|
+
register_algorithm(new('PS384'))
|
28
|
+
register_algorithm(new('PS512'))
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :digest_algorithm
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/jwt/jwa/rsa.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
# Implementation of the RSA family of algorithms
|
6
|
+
class Rsa
|
7
|
+
include JWT::JWA::SigningAlgorithm
|
8
|
+
|
9
|
+
def initialize(alg)
|
10
|
+
@alg = alg
|
11
|
+
@digest = OpenSSL::Digest.new(alg.sub('RS', 'SHA'))
|
12
|
+
end
|
13
|
+
|
14
|
+
def sign(data:, signing_key:)
|
15
|
+
raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance") unless signing_key.is_a?(OpenSSL::PKey::RSA)
|
16
|
+
|
17
|
+
signing_key.sign(digest, data)
|
18
|
+
end
|
19
|
+
|
20
|
+
def verify(data:, signature:, verification_key:)
|
21
|
+
verification_key.verify(digest, signature, data)
|
22
|
+
rescue OpenSSL::PKey::PKeyError
|
23
|
+
raise JWT::VerificationError, 'Signature verification raised'
|
24
|
+
end
|
25
|
+
|
26
|
+
register_algorithm(new('RS256'))
|
27
|
+
register_algorithm(new('RS384'))
|
28
|
+
register_algorithm(new('RS512'))
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :digest
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
# JSON Web Algorithms
|
5
|
+
module JWA
|
6
|
+
# Base functionality for signing algorithms
|
7
|
+
module SigningAlgorithm
|
8
|
+
# Class methods for the SigningAlgorithm module
|
9
|
+
module ClassMethods
|
10
|
+
def register_algorithm(algo)
|
11
|
+
::JWT::JWA.register_algorithm(algo)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.included(klass)
|
16
|
+
klass.extend(ClassMethods)
|
17
|
+
klass.include(JWT::JWA::Compat)
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :alg
|
21
|
+
|
22
|
+
def valid_alg?(alg_to_check)
|
23
|
+
alg&.casecmp(alg_to_check)&.zero? == true
|
24
|
+
end
|
25
|
+
|
26
|
+
def header(*)
|
27
|
+
{ 'alg' => alg }
|
28
|
+
end
|
29
|
+
|
30
|
+
def sign(*)
|
31
|
+
raise_sign_error!('Algorithm implementation is missing the sign method')
|
32
|
+
end
|
33
|
+
|
34
|
+
def verify(*)
|
35
|
+
raise_verify_error!('Algorithm implementation is missing the verify method')
|
36
|
+
end
|
37
|
+
|
38
|
+
def raise_verify_error!(message)
|
39
|
+
raise(DecodeError.new(message).tap { |e| e.set_backtrace(caller(1)) })
|
40
|
+
end
|
41
|
+
|
42
|
+
def raise_sign_error!(message)
|
43
|
+
raise(EncodeError.new(message).tap { |e| e.set_backtrace(caller(1)) })
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class << self
|
48
|
+
def register_algorithm(algo)
|
49
|
+
algorithms[algo.alg.to_s.downcase] = algo
|
50
|
+
end
|
51
|
+
|
52
|
+
def find(algo)
|
53
|
+
algorithms.fetch(algo.to_s.downcase, Unsupported)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def algorithms
|
59
|
+
@algorithms ||= {}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
# Represents an unsupported algorithm
|
6
|
+
module Unsupported
|
7
|
+
class << self
|
8
|
+
include JWT::JWA::SigningAlgorithm
|
9
|
+
|
10
|
+
def sign(*)
|
11
|
+
raise_sign_error!('Unsupported signing method')
|
12
|
+
end
|
13
|
+
|
14
|
+
def verify(*)
|
15
|
+
raise JWT::VerificationError, 'Algorithm not supported'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWA
|
5
|
+
# @api private
|
6
|
+
class Wrapper
|
7
|
+
include SigningAlgorithm
|
8
|
+
|
9
|
+
def initialize(algorithm)
|
10
|
+
@algorithm = algorithm
|
11
|
+
end
|
12
|
+
|
13
|
+
def alg
|
14
|
+
return @algorithm.alg if @algorithm.respond_to?(:alg)
|
15
|
+
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
def valid_alg?(alg_to_check)
|
20
|
+
return @algorithm.valid_alg?(alg_to_check) if @algorithm.respond_to?(:valid_alg?)
|
21
|
+
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
def header(*args, **kwargs)
|
26
|
+
return @algorithm.header(*args, **kwargs) if @algorithm.respond_to?(:header)
|
27
|
+
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
def sign(*args, **kwargs)
|
32
|
+
return @algorithm.sign(*args, **kwargs) if @algorithm.respond_to?(:sign)
|
33
|
+
|
34
|
+
super
|
35
|
+
end
|
36
|
+
|
37
|
+
def verify(*args, **kwargs)
|
38
|
+
return @algorithm.verify(*args, **kwargs) if @algorithm.respond_to?(:verify)
|
39
|
+
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/jwt/jwa.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'rbnacl'
|
7
|
+
rescue LoadError
|
8
|
+
raise if defined?(RbNaCl)
|
9
|
+
end
|
10
|
+
|
11
|
+
require_relative 'jwa/compat'
|
12
|
+
require_relative 'jwa/signing_algorithm'
|
13
|
+
require_relative 'jwa/ecdsa'
|
14
|
+
require_relative 'jwa/hmac'
|
15
|
+
require_relative 'jwa/none'
|
16
|
+
require_relative 'jwa/ps'
|
17
|
+
require_relative 'jwa/rsa'
|
18
|
+
require_relative 'jwa/unsupported'
|
19
|
+
require_relative 'jwa/wrapper'
|
20
|
+
|
21
|
+
require_relative 'jwa/eddsa' if JWT.rbnacl?
|
22
|
+
|
23
|
+
if JWT.rbnacl_6_or_greater?
|
24
|
+
require_relative 'jwa/hmac_rbnacl'
|
25
|
+
elsif JWT.rbnacl?
|
26
|
+
require_relative 'jwa/hmac_rbnacl_fixed'
|
27
|
+
end
|
28
|
+
|
29
|
+
module JWT
|
30
|
+
# The JWA module contains all supported algorithms.
|
31
|
+
module JWA
|
32
|
+
class << self
|
33
|
+
# @api private
|
34
|
+
def resolve(algorithm)
|
35
|
+
return find(algorithm) if algorithm.is_a?(String) || algorithm.is_a?(Symbol)
|
36
|
+
|
37
|
+
unless algorithm.is_a?(SigningAlgorithm)
|
38
|
+
Deprecations.warning('Custom algorithms are required to include JWT::JWA::SigningAlgorithm. Custom algorithms that do not include this module may stop working in the next major version of ruby-jwt.')
|
39
|
+
return Wrapper.new(algorithm)
|
40
|
+
end
|
41
|
+
|
42
|
+
algorithm
|
43
|
+
end
|
44
|
+
|
45
|
+
# @api private
|
46
|
+
def resolve_and_sort(algorithms:, preferred_algorithm:)
|
47
|
+
algs = Array(algorithms).map { |alg| JWA.resolve(alg) }
|
48
|
+
algs.partition { |alg| alg.valid_alg?(preferred_algorithm) }.flatten
|
49
|
+
end
|
50
|
+
|
51
|
+
# @deprecated The `::JWT::JWA.create` method is deprecated and will be removed in the next major version of ruby-jwt.
|
52
|
+
def create(algorithm)
|
53
|
+
Deprecations.warning('The ::JWT::JWA.create method is deprecated and will be removed in the next major version of ruby-jwt.')
|
54
|
+
resolve(algorithm)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|