jwt 1.5.0 → 2.5.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.
Files changed (56) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +8 -0
  3. data/.github/workflows/coverage.yml +27 -0
  4. data/.github/workflows/test.yml +67 -0
  5. data/.gitignore +13 -0
  6. data/.reek.yml +22 -0
  7. data/.rspec +2 -0
  8. data/.rubocop.yml +67 -0
  9. data/.sourcelevel.yml +17 -0
  10. data/AUTHORS +119 -0
  11. data/Appraisals +13 -0
  12. data/CHANGELOG.md +786 -0
  13. data/CODE_OF_CONDUCT.md +84 -0
  14. data/CONTRIBUTING.md +99 -0
  15. data/Gemfile +7 -0
  16. data/LICENSE +7 -0
  17. data/README.md +639 -0
  18. data/Rakefile +13 -14
  19. data/lib/jwt/algos/ecdsa.rb +64 -0
  20. data/lib/jwt/algos/eddsa.rb +35 -0
  21. data/lib/jwt/algos/hmac.rb +36 -0
  22. data/lib/jwt/algos/none.rb +17 -0
  23. data/lib/jwt/algos/ps.rb +43 -0
  24. data/lib/jwt/algos/rsa.rb +22 -0
  25. data/lib/jwt/algos/unsupported.rb +19 -0
  26. data/lib/jwt/algos.rb +44 -0
  27. data/lib/jwt/base64.rb +19 -0
  28. data/lib/jwt/claims_validator.rb +37 -0
  29. data/lib/jwt/configuration/container.rb +21 -0
  30. data/lib/jwt/configuration/decode_configuration.rb +46 -0
  31. data/lib/jwt/configuration/jwk_configuration.rb +27 -0
  32. data/lib/jwt/configuration.rb +15 -0
  33. data/lib/jwt/decode.rb +145 -0
  34. data/lib/jwt/encode.rb +69 -0
  35. data/lib/jwt/error.rb +22 -0
  36. data/lib/jwt/json.rb +10 -22
  37. data/lib/jwt/jwk/ec.rb +199 -0
  38. data/lib/jwt/jwk/hmac.rb +67 -0
  39. data/lib/jwt/jwk/key_base.rb +35 -0
  40. data/lib/jwt/jwk/key_finder.rb +62 -0
  41. data/lib/jwt/jwk/kid_as_key_digest.rb +15 -0
  42. data/lib/jwt/jwk/rsa.rb +138 -0
  43. data/lib/jwt/jwk/thumbprint.rb +26 -0
  44. data/lib/jwt/jwk.rb +52 -0
  45. data/lib/jwt/security_utils.rb +59 -0
  46. data/lib/jwt/signature.rb +35 -0
  47. data/lib/jwt/verify.rb +113 -0
  48. data/lib/jwt/version.rb +28 -0
  49. data/lib/jwt/x5c_key_finder.rb +55 -0
  50. data/lib/jwt.rb +20 -215
  51. data/ruby-jwt.gemspec +35 -0
  52. metadata +138 -30
  53. data/Manifest +0 -6
  54. data/jwt.gemspec +0 -34
  55. data/spec/helper.rb +0 -2
  56. data/spec/jwt_spec.rb +0 -434
data/lib/jwt/json.rb CHANGED
@@ -1,29 +1,17 @@
1
- module JWT
2
- module Json
3
- if RUBY_VERSION >= '1.9' && !defined?(MultiJson)
4
- require 'json'
5
-
6
- def decode_json(encoded)
7
- JSON.parse(encoded)
8
- rescue JSON::ParserError
9
- raise JWT::DecodeError.new('Invalid segment encoding')
10
- end
1
+ # frozen_string_literal: true
11
2
 
12
- def encode_json(raw)
13
- JSON.generate(raw)
14
- end
15
-
16
- else
17
- require 'multi_json'
3
+ require 'json'
18
4
 
19
- def decode_json(encoded)
20
- MultiJson.decode(encoded)
21
- rescue MultiJson::LoadError
22
- raise JWT::DecodeError.new('Invalid segment encoding')
5
+ module JWT
6
+ # JSON wrapper
7
+ class JSON
8
+ class << self
9
+ def generate(data)
10
+ ::JSON.generate(data)
23
11
  end
24
12
 
25
- def encode_json(raw)
26
- MultiJson.encode(raw)
13
+ def parse(data)
14
+ ::JSON.parse(data)
27
15
  end
28
16
  end
29
17
  end
data/lib/jwt/jwk/ec.rb ADDED
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module JWT
6
+ module JWK
7
+ class EC < KeyBase # rubocop:disable Metrics/ClassLength
8
+ extend Forwardable
9
+ def_delegators :keypair, :public_key
10
+
11
+ KTY = 'EC'
12
+ KTYS = [KTY, OpenSSL::PKey::EC].freeze
13
+ BINARY = 2
14
+
15
+ attr_reader :keypair
16
+
17
+ def initialize(keypair, options = {})
18
+ raise ArgumentError, 'keypair must be of type OpenSSL::PKey::EC' unless keypair.is_a?(OpenSSL::PKey::EC)
19
+
20
+ @keypair = keypair
21
+
22
+ super(options)
23
+ end
24
+
25
+ def private?
26
+ @keypair.private_key?
27
+ end
28
+
29
+ def members
30
+ crv, x_octets, y_octets = keypair_components(keypair)
31
+ {
32
+ kty: KTY,
33
+ crv: crv,
34
+ x: encode_octets(x_octets),
35
+ y: encode_octets(y_octets)
36
+ }
37
+ end
38
+
39
+ def export(options = {})
40
+ exported_hash = members.merge(kid: kid)
41
+
42
+ return exported_hash unless private? && options[:include_private] == true
43
+
44
+ append_private_parts(exported_hash)
45
+ end
46
+
47
+ def key_digest
48
+ _crv, x_octets, y_octets = keypair_components(keypair)
49
+ sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(x_octets, BINARY)),
50
+ OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(y_octets, BINARY))])
51
+ OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
52
+ end
53
+
54
+ private
55
+
56
+ def append_private_parts(the_hash)
57
+ octets = keypair.private_key.to_bn.to_s(BINARY)
58
+ the_hash.merge(
59
+ d: encode_octets(octets)
60
+ )
61
+ end
62
+
63
+ def keypair_components(ec_keypair)
64
+ encoded_point = ec_keypair.public_key.to_bn.to_s(BINARY)
65
+ case ec_keypair.group.curve_name
66
+ when 'prime256v1'
67
+ crv = 'P-256'
68
+ x_octets, y_octets = encoded_point.unpack('xa32a32')
69
+ when 'secp256k1'
70
+ crv = 'P-256K'
71
+ x_octets, y_octets = encoded_point.unpack('xa32a32')
72
+ when 'secp384r1'
73
+ crv = 'P-384'
74
+ x_octets, y_octets = encoded_point.unpack('xa48a48')
75
+ when 'secp521r1'
76
+ crv = 'P-521'
77
+ x_octets, y_octets = encoded_point.unpack('xa66a66')
78
+ else
79
+ raise JWT::JWKError, "Unsupported curve '#{ec_keypair.group.curve_name}'"
80
+ end
81
+ [crv, x_octets, y_octets]
82
+ end
83
+
84
+ def encode_octets(octets)
85
+ ::JWT::Base64.url_encode(octets)
86
+ end
87
+
88
+ def encode_open_ssl_bn(key_part)
89
+ ::JWT::Base64.url_encode(key_part.to_s(BINARY))
90
+ end
91
+
92
+ class << self
93
+ def import(jwk_data)
94
+ # See https://tools.ietf.org/html/rfc7518#section-6.2.1 for an
95
+ # explanation of the relevant parameters.
96
+
97
+ jwk_crv, jwk_x, jwk_y, jwk_d, jwk_kid = jwk_attrs(jwk_data, %i[crv x y d kid])
98
+ raise JWT::JWKError, 'Key format is invalid for EC' unless jwk_crv && jwk_x && jwk_y
99
+
100
+ new(ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d), kid: jwk_kid)
101
+ end
102
+
103
+ def to_openssl_curve(crv)
104
+ # The JWK specs and OpenSSL use different names for the same curves.
105
+ # See https://tools.ietf.org/html/rfc5480#section-2.1.1.1 for some
106
+ # pointers on different names for common curves.
107
+ case crv
108
+ when 'P-256' then 'prime256v1'
109
+ when 'P-384' then 'secp384r1'
110
+ when 'P-521' then 'secp521r1'
111
+ when 'P-256K' then 'secp256k1'
112
+ else raise JWT::JWKError, 'Invalid curve provided'
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ def jwk_attrs(jwk_data, attrs)
119
+ attrs.map do |attr|
120
+ jwk_data[attr] || jwk_data[attr.to_s]
121
+ end
122
+ end
123
+
124
+ if ::JWT.openssl_3?
125
+ def ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d) # rubocop:disable Metrics/MethodLength
126
+ curve = to_openssl_curve(jwk_crv)
127
+
128
+ x_octets = decode_octets(jwk_x)
129
+ y_octets = decode_octets(jwk_y)
130
+
131
+ point = OpenSSL::PKey::EC::Point.new(
132
+ OpenSSL::PKey::EC::Group.new(curve),
133
+ OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
134
+ )
135
+
136
+ sequence = if jwk_d
137
+ # https://datatracker.ietf.org/doc/html/rfc5915.html
138
+ # ECPrivateKey ::= SEQUENCE {
139
+ # version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
140
+ # privateKey OCTET STRING,
141
+ # parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
142
+ # publicKey [1] BIT STRING OPTIONAL
143
+ # }
144
+
145
+ OpenSSL::ASN1::Sequence([
146
+ OpenSSL::ASN1::Integer(1),
147
+ OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
148
+ OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
149
+ OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
150
+ ])
151
+ else
152
+ OpenSSL::ASN1::Sequence([
153
+ OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]),
154
+ OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
155
+ ])
156
+ end
157
+
158
+ OpenSSL::PKey::EC.new(sequence.to_der)
159
+ end
160
+ else
161
+ def ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d)
162
+ curve = to_openssl_curve(jwk_crv)
163
+
164
+ x_octets = decode_octets(jwk_x)
165
+ y_octets = decode_octets(jwk_y)
166
+
167
+ key = OpenSSL::PKey::EC.new(curve)
168
+
169
+ # The details of the `Point` instantiation are covered in:
170
+ # - https://docs.ruby-lang.org/en/2.4.0/OpenSSL/PKey/EC.html
171
+ # - https://www.openssl.org/docs/manmaster/man3/EC_POINT_new.html
172
+ # - https://tools.ietf.org/html/rfc5480#section-2.2
173
+ # - https://www.secg.org/SEC1-Ver-1.0.pdf
174
+ # Section 2.3.3 of the last of these references specifies that the
175
+ # encoding of an uncompressed point consists of the byte `0x04` followed
176
+ # by the x value then the y value.
177
+ point = OpenSSL::PKey::EC::Point.new(
178
+ OpenSSL::PKey::EC::Group.new(curve),
179
+ OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
180
+ )
181
+
182
+ key.public_key = point
183
+ key.private_key = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d
184
+
185
+ key
186
+ end
187
+ end
188
+
189
+ def decode_octets(jwk_data)
190
+ ::JWT::Base64.url_decode(jwk_data)
191
+ end
192
+
193
+ def decode_open_ssl_bn(jwk_data)
194
+ OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWK
5
+ class HMAC < KeyBase
6
+ KTY = 'oct'
7
+ KTYS = [KTY, String].freeze
8
+
9
+ attr_reader :signing_key
10
+
11
+ def initialize(signing_key, options = {})
12
+ raise ArgumentError, 'signing_key must be of type String' unless signing_key.is_a?(String)
13
+
14
+ @signing_key = signing_key
15
+ super(options)
16
+ end
17
+
18
+ def private?
19
+ true
20
+ end
21
+
22
+ def public_key
23
+ nil
24
+ end
25
+
26
+ # See https://tools.ietf.org/html/rfc7517#appendix-A.3
27
+ def export(options = {})
28
+ exported_hash = {
29
+ kty: KTY,
30
+ kid: kid
31
+ }
32
+
33
+ return exported_hash unless private? && options[:include_private] == true
34
+
35
+ exported_hash.merge(
36
+ k: signing_key
37
+ )
38
+ end
39
+
40
+ def members
41
+ {
42
+ kty: KTY,
43
+ k: signing_key
44
+ }
45
+ end
46
+
47
+ alias keypair signing_key # for backwards compatibility
48
+
49
+ def key_digest
50
+ sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::UTF8String.new(signing_key),
51
+ OpenSSL::ASN1::UTF8String.new(KTY)])
52
+ OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
53
+ end
54
+
55
+ class << self
56
+ def import(jwk_data)
57
+ jwk_k = jwk_data[:k] || jwk_data['k']
58
+ jwk_kid = jwk_data[:kid] || jwk_data['kid']
59
+
60
+ raise JWT::JWKError, 'Key format is invalid for HMAC' unless jwk_k
61
+
62
+ new(jwk_k, kid: jwk_kid)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWK
5
+ class KeyBase
6
+ def self.inherited(klass)
7
+ super
8
+ ::JWT::JWK.classes << klass
9
+ end
10
+
11
+ def initialize(options)
12
+ options ||= {}
13
+
14
+ if options.is_a?(String) # For backwards compatibility when kid was a String
15
+ options = { kid: options }
16
+ end
17
+
18
+ @kid = options[:kid]
19
+ @kid_generator = options[:kid_generator] || ::JWT.configuration.jwk.kid_generator
20
+ end
21
+
22
+ def kid
23
+ @kid ||= generate_kid
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :kid_generator
29
+
30
+ def generate_kid
31
+ kid_generator.new(self).generate
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWK
5
+ class KeyFinder
6
+ def initialize(options)
7
+ jwks_or_loader = options[:jwks]
8
+ @jwks = jwks_or_loader if jwks_or_loader.is_a?(Hash)
9
+ @jwk_loader = jwks_or_loader if jwks_or_loader.respond_to?(:call)
10
+ end
11
+
12
+ def key_for(kid)
13
+ raise ::JWT::DecodeError, 'No key id (kid) found from token headers' unless kid
14
+
15
+ jwk = resolve_key(kid)
16
+
17
+ raise ::JWT::DecodeError, 'No keys found in jwks' if jwks_keys.empty?
18
+ raise ::JWT::DecodeError, "Could not find public key for kid #{kid}" unless jwk
19
+
20
+ ::JWT::JWK.import(jwk).keypair
21
+ end
22
+
23
+ private
24
+
25
+ def resolve_key(kid)
26
+ jwk = find_key(kid)
27
+
28
+ return jwk if jwk
29
+
30
+ if reloadable?
31
+ load_keys(invalidate: true, kid_not_found: true, kid: kid) # invalidate for backwards compatibility
32
+ return find_key(kid)
33
+ end
34
+
35
+ nil
36
+ end
37
+
38
+ def jwks
39
+ return @jwks if @jwks
40
+
41
+ load_keys
42
+ @jwks
43
+ end
44
+
45
+ def load_keys(opts = {})
46
+ @jwks = @jwk_loader.call(opts)
47
+ end
48
+
49
+ def jwks_keys
50
+ Array(jwks[:keys] || jwks['keys'])
51
+ end
52
+
53
+ def find_key(kid)
54
+ jwks_keys.find { |key| (key[:kid] || key['kid']) == kid }
55
+ end
56
+
57
+ def reloadable?
58
+ @jwk_loader
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWK
5
+ class KidAsKeyDigest
6
+ def initialize(jwk)
7
+ @jwk = jwk
8
+ end
9
+
10
+ def generate
11
+ @jwk.key_digest
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWK
5
+ class RSA < KeyBase
6
+ BINARY = 2
7
+ KTY = 'RSA'
8
+ KTYS = [KTY, OpenSSL::PKey::RSA].freeze
9
+ RSA_KEY_ELEMENTS = %i[n e d p q dp dq qi].freeze
10
+
11
+ attr_reader :keypair
12
+
13
+ def initialize(keypair, options = {})
14
+ raise ArgumentError, 'keypair must be of type OpenSSL::PKey::RSA' unless keypair.is_a?(OpenSSL::PKey::RSA)
15
+
16
+ @keypair = keypair
17
+
18
+ super(options)
19
+ end
20
+
21
+ def private?
22
+ keypair.private?
23
+ end
24
+
25
+ def public_key
26
+ keypair.public_key
27
+ end
28
+
29
+ def export(options = {})
30
+ exported_hash = members.merge(kid: kid)
31
+
32
+ return exported_hash unless private? && options[:include_private] == true
33
+
34
+ append_private_parts(exported_hash)
35
+ end
36
+
37
+ def members
38
+ {
39
+ kty: KTY,
40
+ n: encode_open_ssl_bn(public_key.n),
41
+ e: encode_open_ssl_bn(public_key.e)
42
+ }
43
+ end
44
+
45
+ def key_digest
46
+ sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(public_key.n),
47
+ OpenSSL::ASN1::Integer.new(public_key.e)])
48
+ OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
49
+ end
50
+
51
+ private
52
+
53
+ def append_private_parts(the_hash)
54
+ the_hash.merge(
55
+ d: encode_open_ssl_bn(keypair.d),
56
+ p: encode_open_ssl_bn(keypair.p),
57
+ q: encode_open_ssl_bn(keypair.q),
58
+ dp: encode_open_ssl_bn(keypair.dmp1),
59
+ dq: encode_open_ssl_bn(keypair.dmq1),
60
+ qi: encode_open_ssl_bn(keypair.iqmp)
61
+ )
62
+ end
63
+
64
+ def encode_open_ssl_bn(key_part)
65
+ ::JWT::Base64.url_encode(key_part.to_s(BINARY))
66
+ end
67
+
68
+ class << self
69
+ def import(jwk_data)
70
+ pkey_params = jwk_attributes(jwk_data, *RSA_KEY_ELEMENTS) do |value|
71
+ decode_open_ssl_bn(value)
72
+ end
73
+ new(rsa_pkey(pkey_params), kid: jwk_attributes(jwk_data, :kid)[:kid])
74
+ end
75
+
76
+ private
77
+
78
+ def jwk_attributes(jwk_data, *attributes)
79
+ attributes.each_with_object({}) do |attribute, hash|
80
+ value = jwk_data[attribute] || jwk_data[attribute.to_s]
81
+ value = yield(value) if block_given?
82
+ hash[attribute] = value
83
+ end
84
+ end
85
+
86
+ def rsa_pkey(rsa_parameters)
87
+ raise JWT::JWKError, 'Key format is invalid for RSA' unless rsa_parameters[:n] && rsa_parameters[:e]
88
+
89
+ create_rsa_key(rsa_parameters)
90
+ end
91
+
92
+ if ::JWT.openssl_3?
93
+ ASN1_SEQUENCE = %i[n e d p q dp dq qi].freeze
94
+ def create_rsa_key(rsa_parameters)
95
+ sequence = ASN1_SEQUENCE.each_with_object([]) do |key, arr|
96
+ next if rsa_parameters[key].nil?
97
+
98
+ arr << OpenSSL::ASN1::Integer.new(rsa_parameters[key])
99
+ end
100
+
101
+ if sequence.size > 2 # For a private key
102
+ sequence.unshift(OpenSSL::ASN1::Integer.new(0))
103
+ end
104
+
105
+ OpenSSL::PKey::RSA.new(OpenSSL::ASN1::Sequence(sequence).to_der)
106
+ end
107
+ elsif OpenSSL::PKey::RSA.new.respond_to?(:set_key)
108
+ def create_rsa_key(rsa_parameters)
109
+ OpenSSL::PKey::RSA.new.tap do |rsa_key|
110
+ rsa_key.set_key(rsa_parameters[:n], rsa_parameters[:e], rsa_parameters[:d])
111
+ rsa_key.set_factors(rsa_parameters[:p], rsa_parameters[:q]) if rsa_parameters[:p] && rsa_parameters[:q]
112
+ rsa_key.set_crt_params(rsa_parameters[:dp], rsa_parameters[:dq], rsa_parameters[:qi]) if rsa_parameters[:dp] && rsa_parameters[:dq] && rsa_parameters[:qi]
113
+ end
114
+ end
115
+ else
116
+ def create_rsa_key(rsa_parameters) # rubocop:disable Metrics/AbcSize
117
+ OpenSSL::PKey::RSA.new.tap do |rsa_key|
118
+ rsa_key.n = rsa_parameters[:n]
119
+ rsa_key.e = rsa_parameters[:e]
120
+ rsa_key.d = rsa_parameters[:d] if rsa_parameters[:d]
121
+ rsa_key.p = rsa_parameters[:p] if rsa_parameters[:p]
122
+ rsa_key.q = rsa_parameters[:q] if rsa_parameters[:q]
123
+ rsa_key.dmp1 = rsa_parameters[:dp] if rsa_parameters[:dp]
124
+ rsa_key.dmq1 = rsa_parameters[:dq] if rsa_parameters[:dq]
125
+ rsa_key.iqmp = rsa_parameters[:qi] if rsa_parameters[:qi]
126
+ end
127
+ end
128
+ end
129
+
130
+ def decode_open_ssl_bn(jwk_data)
131
+ return nil unless jwk_data
132
+
133
+ OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWK
5
+ # https://tools.ietf.org/html/rfc7638
6
+ class Thumbprint
7
+ attr_reader :jwk
8
+
9
+ def initialize(jwk)
10
+ @jwk = jwk
11
+ end
12
+
13
+ def generate
14
+ ::Base64.urlsafe_encode64(
15
+ Digest::SHA256.digest(
16
+ JWT::JSON.generate(
17
+ jwk.members.sort.to_h
18
+ )
19
+ ), padding: false
20
+ )
21
+ end
22
+
23
+ alias to_s generate
24
+ end
25
+ end
26
+ end
data/lib/jwt/jwk.rb ADDED
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'jwk/key_finder'
4
+
5
+ module JWT
6
+ module JWK
7
+ class << self
8
+ def import(jwk_data)
9
+ jwk_kty = jwk_data[:kty] || jwk_data['kty']
10
+ raise JWT::JWKError, 'Key type (kty) not provided' unless jwk_kty
11
+
12
+ mappings.fetch(jwk_kty.to_s) do |kty|
13
+ raise JWT::JWKError, "Key type #{kty} not supported"
14
+ end.import(jwk_data)
15
+ end
16
+
17
+ def create_from(keypair, kid = nil)
18
+ mappings.fetch(keypair.class) do |klass|
19
+ raise JWT::JWKError, "Cannot create JWK from a #{klass.name}"
20
+ end.new(keypair, kid)
21
+ end
22
+
23
+ def classes
24
+ @mappings = nil # reset the cached mappings
25
+ @classes ||= []
26
+ end
27
+
28
+ alias new create_from
29
+
30
+ private
31
+
32
+ def mappings
33
+ @mappings ||= generate_mappings
34
+ end
35
+
36
+ def generate_mappings
37
+ classes.each_with_object({}) do |klass, hash|
38
+ next unless klass.const_defined?('KTYS')
39
+
40
+ Array(klass::KTYS).each do |kty|
41
+ hash[kty] = klass
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ require_relative 'jwk/key_base'
50
+ require_relative 'jwk/ec'
51
+ require_relative 'jwk/rsa'
52
+ require_relative 'jwk/hmac'
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ # Collection of security methods
5
+ #
6
+ # @see: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/security_utils.rb
7
+ module SecurityUtils
8
+ module_function
9
+
10
+ def secure_compare(left, right)
11
+ left_bytesize = left.bytesize
12
+
13
+ return false unless left_bytesize == right.bytesize
14
+
15
+ unpacked_left = left.unpack "C#{left_bytesize}"
16
+ result = 0
17
+ right.each_byte { |byte| result |= byte ^ unpacked_left.shift }
18
+ result.zero?
19
+ end
20
+
21
+ def verify_rsa(algorithm, public_key, signing_input, signature)
22
+ public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
23
+ end
24
+
25
+ def verify_ps(algorithm, public_key, signing_input, signature)
26
+ formatted_algorithm = algorithm.sub('PS', 'sha')
27
+
28
+ public_key.verify_pss(formatted_algorithm, signature, signing_input, salt_length: :auto, mgf1_hash: formatted_algorithm)
29
+ end
30
+
31
+ def asn1_to_raw(signature, public_key)
32
+ byte_size = (public_key.group.degree + 7) / 8
33
+ OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
34
+ end
35
+
36
+ def raw_to_asn1(signature, private_key)
37
+ byte_size = (private_key.group.degree + 7) / 8
38
+ sig_bytes = signature[0..(byte_size - 1)]
39
+ sig_char = signature[byte_size..-1] || ''
40
+ OpenSSL::ASN1::Sequence.new([sig_bytes, sig_char].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
41
+ end
42
+
43
+ def rbnacl_fixup(algorithm, key)
44
+ algorithm = algorithm.sub('HS', 'SHA').to_sym
45
+
46
+ return [] unless defined?(RbNaCl) && RbNaCl::HMAC.constants(false).include?(algorithm)
47
+
48
+ authenticator = RbNaCl::HMAC.const_get(algorithm)
49
+
50
+ # Fall back to OpenSSL for keys larger than 32 bytes.
51
+ return [] if key.bytesize > authenticator.key_bytes
52
+
53
+ [
54
+ authenticator,
55
+ key.bytes.fill(0, key.bytesize...authenticator.key_bytes).pack('C*')
56
+ ]
57
+ end
58
+ end
59
+ end