jwt 1.5.4 → 2.7.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 (75) hide show
  1. checksums.yaml +5 -13
  2. data/AUTHORS +119 -0
  3. data/CHANGELOG.md +812 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/CONTRIBUTING.md +99 -0
  6. data/README.md +400 -79
  7. data/lib/jwt/algos/algo_wrapper.rb +30 -0
  8. data/lib/jwt/algos/ecdsa.rb +62 -0
  9. data/lib/jwt/algos/eddsa.rb +33 -0
  10. data/lib/jwt/algos/hmac.rb +73 -0
  11. data/lib/jwt/algos/hmac_rbnacl.rb +53 -0
  12. data/lib/jwt/algos/hmac_rbnacl_fixed.rb +52 -0
  13. data/lib/jwt/algos/none.rb +19 -0
  14. data/lib/jwt/algos/ps.rb +41 -0
  15. data/lib/jwt/algos/rsa.rb +21 -0
  16. data/lib/jwt/algos/unsupported.rb +19 -0
  17. data/lib/jwt/algos.rb +67 -0
  18. data/lib/jwt/base64.rb +19 -0
  19. data/lib/jwt/claims_validator.rb +37 -0
  20. data/lib/jwt/configuration/container.rb +21 -0
  21. data/lib/jwt/configuration/decode_configuration.rb +46 -0
  22. data/lib/jwt/configuration/jwk_configuration.rb +27 -0
  23. data/lib/jwt/configuration.rb +15 -0
  24. data/lib/jwt/decode.rb +141 -29
  25. data/lib/jwt/encode.rb +79 -0
  26. data/lib/jwt/error.rb +10 -0
  27. data/lib/jwt/json.rb +11 -9
  28. data/lib/jwt/jwk/ec.rb +236 -0
  29. data/lib/jwt/jwk/hmac.rb +103 -0
  30. data/lib/jwt/jwk/key_base.rb +55 -0
  31. data/lib/jwt/jwk/key_finder.rb +46 -0
  32. data/lib/jwt/jwk/kid_as_key_digest.rb +15 -0
  33. data/lib/jwt/jwk/okp_rbnacl.rb +110 -0
  34. data/lib/jwt/jwk/rsa.rb +203 -0
  35. data/lib/jwt/jwk/set.rb +80 -0
  36. data/lib/jwt/jwk/thumbprint.rb +26 -0
  37. data/lib/jwt/jwk.rb +55 -0
  38. data/lib/jwt/security_utils.rb +32 -0
  39. data/lib/jwt/verify.rb +59 -44
  40. data/lib/jwt/version.rb +25 -4
  41. data/lib/jwt/x5c_key_finder.rb +55 -0
  42. data/lib/jwt.rb +16 -162
  43. data/ruby-jwt.gemspec +19 -9
  44. metadata +64 -97
  45. data/.codeclimate.yml +0 -20
  46. data/.gitignore +0 -6
  47. data/.rspec +0 -2
  48. data/.rubocop.yml +0 -2
  49. data/.travis.yml +0 -13
  50. data/Gemfile +0 -4
  51. data/Manifest +0 -8
  52. data/Rakefile +0 -1
  53. data/spec/fixtures/certs/ec256-private.pem +0 -8
  54. data/spec/fixtures/certs/ec256-public.pem +0 -4
  55. data/spec/fixtures/certs/ec256-wrong-private.pem +0 -8
  56. data/spec/fixtures/certs/ec256-wrong-public.pem +0 -4
  57. data/spec/fixtures/certs/ec384-private.pem +0 -9
  58. data/spec/fixtures/certs/ec384-public.pem +0 -5
  59. data/spec/fixtures/certs/ec384-wrong-private.pem +0 -9
  60. data/spec/fixtures/certs/ec384-wrong-public.pem +0 -5
  61. data/spec/fixtures/certs/ec512-private.pem +0 -10
  62. data/spec/fixtures/certs/ec512-public.pem +0 -6
  63. data/spec/fixtures/certs/ec512-wrong-private.pem +0 -10
  64. data/spec/fixtures/certs/ec512-wrong-public.pem +0 -6
  65. data/spec/fixtures/certs/rsa-1024-private.pem +0 -15
  66. data/spec/fixtures/certs/rsa-1024-public.pem +0 -6
  67. data/spec/fixtures/certs/rsa-2048-private.pem +0 -27
  68. data/spec/fixtures/certs/rsa-2048-public.pem +0 -9
  69. data/spec/fixtures/certs/rsa-2048-wrong-private.pem +0 -27
  70. data/spec/fixtures/certs/rsa-2048-wrong-public.pem +0 -9
  71. data/spec/fixtures/certs/rsa-4096-private.pem +0 -51
  72. data/spec/fixtures/certs/rsa-4096-public.pem +0 -14
  73. data/spec/jwt/verify_spec.rb +0 -175
  74. data/spec/jwt_spec.rb +0 -232
  75. data/spec/spec_helper.rb +0 -31
@@ -0,0 +1,55 @@
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, params = {})
12
+ options ||= {}
13
+
14
+ @parameters = params.transform_keys(&:to_sym) # Uniform interface
15
+
16
+ # For backwards compatibility, kid_generator may be specified in the parameters
17
+ options[:kid_generator] ||= @parameters.delete(:kid_generator)
18
+
19
+ # Make sure the key has a kid
20
+ kid_generator = options[:kid_generator] || ::JWT.configuration.jwk.kid_generator
21
+ self[:kid] ||= kid_generator.new(self).generate
22
+ end
23
+
24
+ def kid
25
+ self[:kid]
26
+ end
27
+
28
+ def hash
29
+ self[:kid].hash
30
+ end
31
+
32
+ def [](key)
33
+ @parameters[key.to_sym]
34
+ end
35
+
36
+ def []=(key, value)
37
+ @parameters[key.to_sym] = value
38
+ end
39
+
40
+ def ==(other)
41
+ self[:kid] == other[:kid]
42
+ end
43
+
44
+ alias eql? ==
45
+
46
+ def <=>(other)
47
+ self[:kid] <=> other[:kid]
48
+ end
49
+
50
+ private
51
+
52
+ attr_reader :parameters
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWK
5
+ class KeyFinder
6
+ def initialize(options)
7
+ @allow_nil_kid = options[:allow_nil_kid]
8
+ jwks_or_loader = options[:jwks]
9
+
10
+ @jwks_loader = if jwks_or_loader.respond_to?(:call)
11
+ jwks_or_loader
12
+ else
13
+ ->(_options) { jwks_or_loader }
14
+ end
15
+ end
16
+
17
+ def key_for(kid)
18
+ raise ::JWT::DecodeError, 'No key id (kid) found from token headers' unless kid || @allow_nil_kid
19
+ raise ::JWT::DecodeError, 'Invalid type for kid header parameter' unless kid.nil? || kid.is_a?(String)
20
+
21
+ jwk = resolve_key(kid)
22
+
23
+ raise ::JWT::DecodeError, 'No keys found in jwks' unless @jwks.any?
24
+ raise ::JWT::DecodeError, "Could not find public key for kid #{kid}" unless jwk
25
+
26
+ jwk.verify_key
27
+ end
28
+
29
+ private
30
+
31
+ def resolve_key(kid)
32
+ key_matcher = ->(key) { (kid.nil? && @allow_nil_kid) || key[:kid] == kid }
33
+
34
+ # First try without invalidation to facilitate application caching
35
+ @jwks ||= JWT::JWK::Set.new(@jwks_loader.call(kid: kid))
36
+ jwk = @jwks.find { |key| key_matcher.call(key) }
37
+
38
+ return jwk if jwk
39
+
40
+ # Second try, invalidate for backwards compatibility
41
+ @jwks = JWT::JWK::Set.new(@jwks_loader.call(invalidate: true, kid_not_found: true, kid: kid))
42
+ @jwks.find { |key| key_matcher.call(key) }
43
+ end
44
+ end
45
+ end
46
+ 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,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWK
5
+ class OKPRbNaCl < KeyBase
6
+ KTY = 'OKP'
7
+ KTYS = [KTY, JWT::JWK::OKPRbNaCl, RbNaCl::Signatures::Ed25519::SigningKey, RbNaCl::Signatures::Ed25519::VerifyKey].freeze
8
+ OKP_PUBLIC_KEY_ELEMENTS = %i[kty n x].freeze
9
+ OKP_PRIVATE_KEY_ELEMENTS = %i[d].freeze
10
+
11
+ def initialize(key, params = nil, options = {})
12
+ params ||= {}
13
+
14
+ # For backwards compatibility when kid was a String
15
+ params = { kid: params } if params.is_a?(String)
16
+
17
+ key_params = extract_key_params(key)
18
+
19
+ params = params.transform_keys(&:to_sym)
20
+ check_jwk_params!(key_params, params)
21
+ super(options, key_params.merge(params))
22
+ end
23
+
24
+ def verify_key
25
+ return @verify_key if defined?(@verify_key)
26
+
27
+ @verify_key = verify_key_from_parameters
28
+ end
29
+
30
+ def signing_key
31
+ return @signing_key if defined?(@signing_key)
32
+
33
+ @signing_key = signing_key_from_parameters
34
+ end
35
+
36
+ def key_digest
37
+ Thumbprint.new(self).to_s
38
+ end
39
+
40
+ def private?
41
+ !signing_key.nil?
42
+ end
43
+
44
+ def members
45
+ OKP_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] }
46
+ end
47
+
48
+ def export(options = {})
49
+ exported = parameters.clone
50
+ exported.reject! { |k, _| OKP_PRIVATE_KEY_ELEMENTS.include?(k) } unless private? && options[:include_private] == true
51
+ exported
52
+ end
53
+
54
+ private
55
+
56
+ def extract_key_params(key)
57
+ case key
58
+ when JWT::JWK::KeyBase
59
+ key.export(include_private: true)
60
+ when RbNaCl::Signatures::Ed25519::SigningKey
61
+ @signing_key = key
62
+ @verify_key = key.verify_key
63
+ parse_okp_key_params(@verify_key, @signing_key)
64
+ when RbNaCl::Signatures::Ed25519::VerifyKey
65
+ @signing_key = nil
66
+ @verify_key = key
67
+ parse_okp_key_params(@verify_key)
68
+ when Hash
69
+ key.transform_keys(&:to_sym)
70
+ else
71
+ raise ArgumentError, 'key must be of type RbNaCl::Signatures::Ed25519::SigningKey, RbNaCl::Signatures::Ed25519::VerifyKey or Hash with key parameters'
72
+ end
73
+ end
74
+
75
+ def check_jwk_params!(key_params, _given_params)
76
+ raise JWT::JWKError, "Incorrect 'kty' value: #{key_params[:kty]}, expected #{KTY}" unless key_params[:kty] == KTY
77
+ end
78
+
79
+ def parse_okp_key_params(verify_key, signing_key = nil)
80
+ params = {
81
+ kty: KTY,
82
+ crv: 'Ed25519',
83
+ x: ::JWT::Base64.url_encode(verify_key.to_bytes)
84
+ }
85
+
86
+ if signing_key
87
+ params[:d] = ::JWT::Base64.url_encode(signing_key.to_bytes)
88
+ end
89
+
90
+ params
91
+ end
92
+
93
+ def verify_key_from_parameters
94
+ RbNaCl::Signatures::Ed25519::VerifyKey.new(::JWT::Base64.url_decode(self[:x]))
95
+ end
96
+
97
+ def signing_key_from_parameters
98
+ return nil unless self[:d]
99
+
100
+ RbNaCl::Signatures::Ed25519::SigningKey.new(::JWT::Base64.url_decode(self[:d]))
101
+ end
102
+
103
+ class << self
104
+ def import(jwk_data)
105
+ new(jwk_data)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWK
5
+ class RSA < KeyBase # rubocop:disable Metrics/ClassLength
6
+ BINARY = 2
7
+ KTY = 'RSA'
8
+ KTYS = [KTY, OpenSSL::PKey::RSA, JWT::JWK::RSA].freeze
9
+ RSA_PUBLIC_KEY_ELEMENTS = %i[kty n e].freeze
10
+ RSA_PRIVATE_KEY_ELEMENTS = %i[d p q dp dq qi].freeze
11
+ RSA_KEY_ELEMENTS = (RSA_PRIVATE_KEY_ELEMENTS + RSA_PUBLIC_KEY_ELEMENTS).freeze
12
+
13
+ RSA_OPT_PARAMS = %i[p q dp dq qi].freeze
14
+ RSA_ASN1_SEQUENCE = (%i[n e d] + RSA_OPT_PARAMS).freeze # https://www.rfc-editor.org/rfc/rfc3447#appendix-A.1.2
15
+
16
+ def initialize(key, params = nil, options = {})
17
+ params ||= {}
18
+
19
+ # For backwards compatibility when kid was a String
20
+ params = { kid: params } if params.is_a?(String)
21
+
22
+ key_params = extract_key_params(key)
23
+
24
+ params = params.transform_keys(&:to_sym)
25
+ check_jwk_params!(key_params, params)
26
+
27
+ super(options, key_params.merge(params))
28
+ end
29
+
30
+ def keypair
31
+ rsa_key
32
+ end
33
+
34
+ def private?
35
+ rsa_key.private?
36
+ end
37
+
38
+ def public_key
39
+ rsa_key.public_key
40
+ end
41
+
42
+ def signing_key
43
+ rsa_key if private?
44
+ end
45
+
46
+ def verify_key
47
+ rsa_key.public_key
48
+ end
49
+
50
+ def export(options = {})
51
+ exported = parameters.clone
52
+ exported.reject! { |k, _| RSA_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true
53
+ exported
54
+ end
55
+
56
+ def members
57
+ RSA_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] }
58
+ end
59
+
60
+ def key_digest
61
+ sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(public_key.n),
62
+ OpenSSL::ASN1::Integer.new(public_key.e)])
63
+ OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
64
+ end
65
+
66
+ def []=(key, value)
67
+ if RSA_KEY_ELEMENTS.include?(key.to_sym)
68
+ raise ArgumentError, 'cannot overwrite cryptographic key attributes'
69
+ end
70
+
71
+ super(key, value)
72
+ end
73
+
74
+ private
75
+
76
+ def rsa_key
77
+ @rsa_key ||= self.class.create_rsa_key(jwk_attributes(*(RSA_KEY_ELEMENTS - [:kty])))
78
+ end
79
+
80
+ def extract_key_params(key)
81
+ case key
82
+ when JWT::JWK::RSA
83
+ key.export(include_private: true)
84
+ when OpenSSL::PKey::RSA # Accept OpenSSL key as input
85
+ @rsa_key = key # Preserve the object to avoid recreation
86
+ parse_rsa_key(key)
87
+ when Hash
88
+ key.transform_keys(&:to_sym)
89
+ else
90
+ raise ArgumentError, 'key must be of type OpenSSL::PKey::RSA or Hash with key parameters'
91
+ end
92
+ end
93
+
94
+ def check_jwk_params!(key_params, params)
95
+ raise ArgumentError, 'cannot overwrite cryptographic key attributes' unless (RSA_KEY_ELEMENTS & params.keys).empty?
96
+ raise JWT::JWKError, "Incorrect 'kty' value: #{key_params[:kty]}, expected #{KTY}" unless key_params[:kty] == KTY
97
+ raise JWT::JWKError, 'Key format is invalid for RSA' unless key_params[:n] && key_params[:e]
98
+ end
99
+
100
+ def parse_rsa_key(key)
101
+ {
102
+ kty: KTY,
103
+ n: encode_open_ssl_bn(key.n),
104
+ e: encode_open_ssl_bn(key.e),
105
+ d: encode_open_ssl_bn(key.d),
106
+ p: encode_open_ssl_bn(key.p),
107
+ q: encode_open_ssl_bn(key.q),
108
+ dp: encode_open_ssl_bn(key.dmp1),
109
+ dq: encode_open_ssl_bn(key.dmq1),
110
+ qi: encode_open_ssl_bn(key.iqmp)
111
+ }.compact
112
+ end
113
+
114
+ def jwk_attributes(*attributes)
115
+ attributes.each_with_object({}) do |attribute, hash|
116
+ hash[attribute] = decode_open_ssl_bn(self[attribute])
117
+ end
118
+ end
119
+
120
+ def encode_open_ssl_bn(key_part)
121
+ return unless key_part
122
+
123
+ ::JWT::Base64.url_encode(key_part.to_s(BINARY))
124
+ end
125
+
126
+ def decode_open_ssl_bn(jwk_data)
127
+ self.class.decode_open_ssl_bn(jwk_data)
128
+ end
129
+
130
+ class << self
131
+ def import(jwk_data)
132
+ new(jwk_data)
133
+ end
134
+
135
+ def decode_open_ssl_bn(jwk_data)
136
+ return nil unless jwk_data
137
+
138
+ OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
139
+ end
140
+
141
+ def create_rsa_key_using_der(rsa_parameters)
142
+ validate_rsa_parameters!(rsa_parameters)
143
+
144
+ sequence = RSA_ASN1_SEQUENCE.each_with_object([]) do |key, arr|
145
+ next if rsa_parameters[key].nil?
146
+
147
+ arr << OpenSSL::ASN1::Integer.new(rsa_parameters[key])
148
+ end
149
+
150
+ if sequence.size > 2 # Append "two-prime" version for private key
151
+ sequence.unshift(OpenSSL::ASN1::Integer.new(0))
152
+
153
+ raise JWT::JWKError, 'Creating a RSA key with a private key requires the CRT parameters to be defined' if sequence.size < RSA_ASN1_SEQUENCE.size
154
+ end
155
+
156
+ OpenSSL::PKey::RSA.new(OpenSSL::ASN1::Sequence(sequence).to_der)
157
+ end
158
+
159
+ def create_rsa_key_using_sets(rsa_parameters)
160
+ validate_rsa_parameters!(rsa_parameters)
161
+
162
+ OpenSSL::PKey::RSA.new.tap do |rsa_key|
163
+ rsa_key.set_key(rsa_parameters[:n], rsa_parameters[:e], rsa_parameters[:d])
164
+ rsa_key.set_factors(rsa_parameters[:p], rsa_parameters[:q]) if rsa_parameters[:p] && rsa_parameters[:q]
165
+ rsa_key.set_crt_params(rsa_parameters[:dp], rsa_parameters[:dq], rsa_parameters[:qi]) if rsa_parameters[:dp] && rsa_parameters[:dq] && rsa_parameters[:qi]
166
+ end
167
+ end
168
+
169
+ def create_rsa_key_using_accessors(rsa_parameters) # rubocop:disable Metrics/AbcSize
170
+ validate_rsa_parameters!(rsa_parameters)
171
+
172
+ OpenSSL::PKey::RSA.new.tap do |rsa_key|
173
+ rsa_key.n = rsa_parameters[:n]
174
+ rsa_key.e = rsa_parameters[:e]
175
+ rsa_key.d = rsa_parameters[:d] if rsa_parameters[:d]
176
+ rsa_key.p = rsa_parameters[:p] if rsa_parameters[:p]
177
+ rsa_key.q = rsa_parameters[:q] if rsa_parameters[:q]
178
+ rsa_key.dmp1 = rsa_parameters[:dp] if rsa_parameters[:dp]
179
+ rsa_key.dmq1 = rsa_parameters[:dq] if rsa_parameters[:dq]
180
+ rsa_key.iqmp = rsa_parameters[:qi] if rsa_parameters[:qi]
181
+ end
182
+ end
183
+
184
+ def validate_rsa_parameters!(rsa_parameters)
185
+ return unless rsa_parameters.key?(:d)
186
+
187
+ parameters = RSA_OPT_PARAMS - rsa_parameters.keys
188
+ return if parameters.empty? || parameters.size == RSA_OPT_PARAMS.size
189
+
190
+ raise JWT::JWKError, 'When one of p, q, dp, dq or qi is given all the other optimization parameters also needs to be defined' # https://www.rfc-editor.org/rfc/rfc7518.html#section-6.3.2
191
+ end
192
+
193
+ if ::JWT.openssl_3?
194
+ alias create_rsa_key create_rsa_key_using_der
195
+ elsif OpenSSL::PKey::RSA.new.respond_to?(:set_key)
196
+ alias create_rsa_key create_rsa_key_using_sets
197
+ else
198
+ alias create_rsa_key create_rsa_key_using_accessors
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module JWT
6
+ module JWK
7
+ class Set
8
+ include Enumerable
9
+ extend Forwardable
10
+
11
+ attr_reader :keys
12
+
13
+ def initialize(jwks = nil, options = {}) # rubocop:disable Metrics/CyclomaticComplexity
14
+ jwks ||= {}
15
+
16
+ @keys = case jwks
17
+ when JWT::JWK::Set # Simple duplication
18
+ jwks.keys
19
+ when JWT::JWK::KeyBase # Singleton
20
+ [jwks]
21
+ when Hash
22
+ jwks = jwks.transform_keys(&:to_sym)
23
+ [*jwks[:keys]].map { |k| JWT::JWK.new(k, nil, options) }
24
+ when Array
25
+ jwks.map { |k| JWT::JWK.new(k, nil, options) }
26
+ else
27
+ raise ArgumentError, 'Can only create new JWKS from Hash, Array and JWK'
28
+ end
29
+ end
30
+
31
+ def export(options = {})
32
+ { keys: @keys.map { |k| k.export(options) } }
33
+ end
34
+
35
+ def_delegators :@keys, :each, :size, :delete, :dig
36
+
37
+ def select!(&block)
38
+ return @keys.select! unless block
39
+
40
+ self if @keys.select!(&block)
41
+ end
42
+
43
+ def reject!(&block)
44
+ return @keys.reject! unless block
45
+
46
+ self if @keys.reject!(&block)
47
+ end
48
+
49
+ def uniq!(&block)
50
+ self if @keys.uniq!(&block)
51
+ end
52
+
53
+ def merge(enum)
54
+ @keys += JWT::JWK::Set.new(enum.to_a).keys
55
+ self
56
+ end
57
+
58
+ def union(enum)
59
+ dup.merge(enum)
60
+ end
61
+
62
+ def add(key)
63
+ @keys << JWT::JWK.new(key)
64
+ self
65
+ end
66
+
67
+ def ==(other)
68
+ other.is_a?(JWT::JWK::Set) && keys.sort == other.keys.sort
69
+ end
70
+
71
+ alias eql? ==
72
+ alias filter! select!
73
+ alias length size
74
+ # For symbolic manipulation
75
+ alias | union
76
+ alias + union
77
+ alias << add
78
+ end
79
+ end
80
+ 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,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'jwk/key_finder'
4
+ require_relative 'jwk/set'
5
+
6
+ module JWT
7
+ module JWK
8
+ class << self
9
+ def create_from(key, params = nil, options = {})
10
+ if key.is_a?(Hash)
11
+ jwk_kty = key[:kty] || key['kty']
12
+ raise JWT::JWKError, 'Key type (kty) not provided' unless jwk_kty
13
+
14
+ return mappings.fetch(jwk_kty.to_s) do |kty|
15
+ raise JWT::JWKError, "Key type #{kty} not supported"
16
+ end.new(key, params, options)
17
+ end
18
+
19
+ mappings.fetch(key.class) do |klass|
20
+ raise JWT::JWKError, "Cannot create JWK from a #{klass.name}"
21
+ end.new(key, params, options)
22
+ end
23
+
24
+ def classes
25
+ @mappings = nil # reset the cached mappings
26
+ @classes ||= []
27
+ end
28
+
29
+ alias new create_from
30
+ alias import create_from
31
+
32
+ private
33
+
34
+ def mappings
35
+ @mappings ||= generate_mappings
36
+ end
37
+
38
+ def generate_mappings
39
+ classes.each_with_object({}) do |klass, hash|
40
+ next unless klass.const_defined?('KTYS')
41
+
42
+ Array(klass::KTYS).each do |kty|
43
+ hash[kty] = klass
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ require_relative 'jwk/key_base'
52
+ require_relative 'jwk/ec'
53
+ require_relative 'jwk/rsa'
54
+ require_relative 'jwk/hmac'
55
+ require_relative 'jwk/okp_rbnacl' if ::JWT.rbnacl?
@@ -0,0 +1,32 @@
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 verify_rsa(algorithm, public_key, signing_input, signature)
11
+ public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
12
+ end
13
+
14
+ def verify_ps(algorithm, public_key, signing_input, signature)
15
+ formatted_algorithm = algorithm.sub('PS', 'sha')
16
+
17
+ public_key.verify_pss(formatted_algorithm, signature, signing_input, salt_length: :auto, mgf1_hash: formatted_algorithm)
18
+ end
19
+
20
+ def asn1_to_raw(signature, public_key)
21
+ byte_size = (public_key.group.degree + 7) / 8
22
+ OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
23
+ end
24
+
25
+ def raw_to_asn1(signature, private_key)
26
+ byte_size = (private_key.group.degree + 7) / 8
27
+ sig_bytes = signature[0..(byte_size - 1)]
28
+ sig_char = signature[byte_size..-1] || ''
29
+ OpenSSL::ASN1::Sequence.new([sig_bytes, sig_char].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
30
+ end
31
+ end
32
+ end