jwt 2.4.1 → 2.9.3

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +177 -14
  3. data/CONTRIBUTING.md +7 -7
  4. data/README.md +180 -37
  5. data/lib/jwt/base64.rb +33 -0
  6. data/lib/jwt/claims/audience.rb +20 -0
  7. data/lib/jwt/claims/decode_verifier.rb +40 -0
  8. data/lib/jwt/claims/expiration.rb +22 -0
  9. data/lib/jwt/claims/issued_at.rb +15 -0
  10. data/lib/jwt/claims/issuer.rb +24 -0
  11. data/lib/jwt/claims/jwt_id.rb +25 -0
  12. data/lib/jwt/claims/not_before.rb +22 -0
  13. data/lib/jwt/claims/numeric.rb +55 -0
  14. data/lib/jwt/claims/required.rb +23 -0
  15. data/lib/jwt/claims/subject.rb +20 -0
  16. data/lib/jwt/claims/verifier.rb +62 -0
  17. data/lib/jwt/claims.rb +82 -0
  18. data/lib/jwt/claims_validator.rb +3 -24
  19. data/lib/jwt/configuration/container.rb +32 -0
  20. data/lib/jwt/configuration/decode_configuration.rb +46 -0
  21. data/lib/jwt/configuration/jwk_configuration.rb +27 -0
  22. data/lib/jwt/configuration.rb +15 -0
  23. data/lib/jwt/decode.rb +54 -41
  24. data/lib/jwt/deprecations.rb +48 -0
  25. data/lib/jwt/encode.rb +21 -21
  26. data/lib/jwt/error.rb +1 -0
  27. data/lib/jwt/jwa/compat.rb +29 -0
  28. data/lib/jwt/jwa/ecdsa.rb +93 -0
  29. data/lib/jwt/jwa/eddsa.rb +34 -0
  30. data/lib/jwt/jwa/hmac.rb +83 -0
  31. data/lib/jwt/jwa/hmac_rbnacl.rb +49 -0
  32. data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +46 -0
  33. data/lib/jwt/jwa/none.rb +23 -0
  34. data/lib/jwt/jwa/ps.rb +36 -0
  35. data/lib/jwt/jwa/rsa.rb +36 -0
  36. data/lib/jwt/jwa/signing_algorithm.rb +60 -0
  37. data/lib/jwt/jwa/unsupported.rb +19 -0
  38. data/lib/jwt/jwa/wrapper.rb +43 -0
  39. data/lib/jwt/jwa.rb +50 -0
  40. data/lib/jwt/jwk/ec.rb +162 -65
  41. data/lib/jwt/jwk/hmac.rb +69 -24
  42. data/lib/jwt/jwk/key_base.rb +45 -7
  43. data/lib/jwt/jwk/key_finder.rb +19 -35
  44. data/lib/jwt/jwk/kid_as_key_digest.rb +15 -0
  45. data/lib/jwt/jwk/okp_rbnacl.rb +110 -0
  46. data/lib/jwt/jwk/rsa.rb +141 -54
  47. data/lib/jwt/jwk/set.rb +80 -0
  48. data/lib/jwt/jwk/thumbprint.rb +26 -0
  49. data/lib/jwt/jwk.rb +14 -11
  50. data/lib/jwt/verify.rb +10 -89
  51. data/lib/jwt/version.rb +24 -2
  52. data/lib/jwt/x5c_key_finder.rb +3 -6
  53. data/lib/jwt.rb +12 -4
  54. data/ruby-jwt.gemspec +11 -4
  55. metadata +59 -31
  56. data/.codeclimate.yml +0 -8
  57. data/.github/workflows/coverage.yml +0 -27
  58. data/.github/workflows/test.yml +0 -66
  59. data/.gitignore +0 -13
  60. data/.reek.yml +0 -22
  61. data/.rspec +0 -2
  62. data/.rubocop.yml +0 -67
  63. data/.sourcelevel.yml +0 -17
  64. data/Appraisals +0 -13
  65. data/Gemfile +0 -7
  66. data/Rakefile +0 -16
  67. data/lib/jwt/algos/ecdsa.rb +0 -64
  68. data/lib/jwt/algos/eddsa.rb +0 -33
  69. data/lib/jwt/algos/hmac.rb +0 -36
  70. data/lib/jwt/algos/none.rb +0 -17
  71. data/lib/jwt/algos/ps.rb +0 -43
  72. data/lib/jwt/algos/rsa.rb +0 -22
  73. data/lib/jwt/algos/unsupported.rb +0 -19
  74. data/lib/jwt/algos.rb +0 -44
  75. data/lib/jwt/default_options.rb +0 -18
  76. data/lib/jwt/security_utils.rb +0 -59
  77. data/lib/jwt/signature.rb +0 -35
@@ -3,17 +3,55 @@
3
3
  module JWT
4
4
  module JWK
5
5
  class KeyBase
6
- attr_reader :keypair, :kid
7
-
8
- def initialize(keypair, kid = nil)
9
- @keypair = keypair
10
- @kid = kid
11
- end
12
-
13
6
  def self.inherited(klass)
14
7
  super
15
8
  ::JWT::JWK.classes << klass
16
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
+ other.is_a?(::JWT::JWK::KeyBase) && self[:kid] == other[:kid]
42
+ end
43
+
44
+ alias eql? ==
45
+
46
+ def <=>(other)
47
+ return nil unless other.is_a?(::JWT::JWK::KeyBase)
48
+
49
+ self[:kid] <=> other[:kid]
50
+ end
51
+
52
+ private
53
+
54
+ attr_reader :parameters
17
55
  end
18
56
  end
19
57
  end
@@ -4,58 +4,42 @@ module JWT
4
4
  module JWK
5
5
  class KeyFinder
6
6
  def initialize(options)
7
+ @allow_nil_kid = options[:allow_nil_kid]
7
8
  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)
9
+
10
+ @jwks_loader = if jwks_or_loader.respond_to?(:call)
11
+ jwks_or_loader
12
+ else
13
+ ->(_options) { jwks_or_loader }
14
+ end
10
15
  end
11
16
 
12
17
  def key_for(kid)
13
- raise ::JWT::DecodeError, 'No key id (kid) found from token headers' unless 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)
14
20
 
15
21
  jwk = resolve_key(kid)
16
22
 
17
- raise ::JWT::DecodeError, 'No keys found in jwks' if jwks_keys.empty?
23
+ raise ::JWT::DecodeError, 'No keys found in jwks' unless @jwks.any?
18
24
  raise ::JWT::DecodeError, "Could not find public key for kid #{kid}" unless jwk
19
25
 
20
- ::JWT::JWK.import(jwk).keypair
26
+ jwk.verify_key
21
27
  end
22
28
 
23
29
  private
24
30
 
25
31
  def resolve_key(kid)
26
- jwk = find_key(kid)
27
-
28
- return jwk if jwk
29
-
30
- if reloadable?
31
- load_keys(invalidate: true)
32
- return find_key(kid)
33
- end
34
-
35
- nil
36
- end
37
-
38
- def jwks
39
- return @jwks if @jwks
32
+ key_matcher = ->(key) { (kid.nil? && @allow_nil_kid) || key[:kid] == kid }
40
33
 
41
- load_keys
42
- @jwks
43
- end
44
-
45
- def load_keys(opts = {})
46
- @jwks = @jwk_loader.call(opts)
47
- end
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) }
48
37
 
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
38
+ return jwk if jwk
56
39
 
57
- def reloadable?
58
- @jwk_loader
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) }
59
43
  end
60
44
  end
61
45
  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
data/lib/jwt/jwk/rsa.rb CHANGED
@@ -2,96 +2,174 @@
2
2
 
3
3
  module JWT
4
4
  module JWK
5
- class RSA < KeyBase
5
+ class RSA < KeyBase # rubocop:disable Metrics/ClassLength
6
6
  BINARY = 2
7
7
  KTY = 'RSA'
8
- KTYS = [KTY, OpenSSL::PKey::RSA].freeze
9
- RSA_KEY_ELEMENTS = %i[n e d p q dp dq qi].freeze
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
10
12
 
11
- def initialize(keypair, kid = nil)
12
- raise ArgumentError, 'keypair must be of type OpenSSL::PKey::RSA' unless keypair.is_a?(OpenSSL::PKey::RSA)
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
13
15
 
14
- super(keypair, kid || generate_kid(keypair.public_key))
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
15
32
  end
16
33
 
17
34
  def private?
18
- keypair.private?
35
+ rsa_key.private?
19
36
  end
20
37
 
21
38
  def public_key
22
- keypair.public_key
39
+ rsa_key.public_key
23
40
  end
24
41
 
25
- def export(options = {})
26
- exported_hash = {
27
- kty: KTY,
28
- n: encode_open_ssl_bn(public_key.n),
29
- e: encode_open_ssl_bn(public_key.e),
30
- kid: kid
31
- }
42
+ def signing_key
43
+ rsa_key if private?
44
+ end
32
45
 
33
- return exported_hash unless private? && options[:include_private] == true
46
+ def verify_key
47
+ rsa_key.public_key
48
+ end
34
49
 
35
- append_private_parts(exported_hash)
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
36
54
  end
37
55
 
38
- private
56
+ def members
57
+ RSA_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] }
58
+ end
39
59
 
40
- def generate_kid(public_key)
60
+ def key_digest
41
61
  sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(public_key.n),
42
62
  OpenSSL::ASN1::Integer.new(public_key.e)])
43
63
  OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
44
64
  end
45
65
 
46
- def append_private_parts(the_hash)
47
- the_hash.merge(
48
- d: encode_open_ssl_bn(keypair.d),
49
- p: encode_open_ssl_bn(keypair.p),
50
- q: encode_open_ssl_bn(keypair.q),
51
- dp: encode_open_ssl_bn(keypair.dmp1),
52
- dq: encode_open_ssl_bn(keypair.dmq1),
53
- qi: encode_open_ssl_bn(keypair.iqmp)
54
- )
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
55
118
  end
56
119
 
57
120
  def encode_open_ssl_bn(key_part)
58
- Base64.urlsafe_encode64(key_part.to_s(BINARY), padding: false)
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)
59
128
  end
60
129
 
61
130
  class << self
62
131
  def import(jwk_data)
63
- pkey_params = jwk_attributes(jwk_data, *RSA_KEY_ELEMENTS) do |value|
64
- decode_open_ssl_bn(value)
65
- end
66
- kid = jwk_attributes(jwk_data, :kid)[:kid]
67
- new(rsa_pkey(pkey_params), kid)
132
+ new(jwk_data)
68
133
  end
69
134
 
70
- private
135
+ def decode_open_ssl_bn(jwk_data)
136
+ return nil unless jwk_data
71
137
 
72
- def jwk_attributes(jwk_data, *attributes)
73
- attributes.each_with_object({}) do |attribute, hash|
74
- value = jwk_data[attribute] || jwk_data[attribute.to_s]
75
- value = yield(value) if block_given?
76
- hash[attribute] = value
77
- end
138
+ OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
78
139
  end
79
140
 
80
- def rsa_pkey(rsa_parameters)
81
- raise JWT::JWKError, 'Key format is invalid for RSA' unless rsa_parameters[:n] && rsa_parameters[:e]
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))
82
152
 
83
- populate_key(OpenSSL::PKey::RSA.new, rsa_parameters)
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)
84
157
  end
85
158
 
86
- if OpenSSL::PKey::RSA.new.respond_to?(:set_key)
87
- def populate_key(rsa_key, rsa_parameters)
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|
88
163
  rsa_key.set_key(rsa_parameters[:n], rsa_parameters[:e], rsa_parameters[:d])
89
164
  rsa_key.set_factors(rsa_parameters[:p], rsa_parameters[:q]) if rsa_parameters[:p] && rsa_parameters[:q]
90
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]
91
- rsa_key
92
166
  end
93
- else
94
- def populate_key(rsa_key, rsa_parameters)
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|
95
173
  rsa_key.n = rsa_parameters[:n]
96
174
  rsa_key.e = rsa_parameters[:e]
97
175
  rsa_key.d = rsa_parameters[:d] if rsa_parameters[:d]
@@ -100,15 +178,24 @@ module JWT
100
178
  rsa_key.dmp1 = rsa_parameters[:dp] if rsa_parameters[:dp]
101
179
  rsa_key.dmq1 = rsa_parameters[:dq] if rsa_parameters[:dq]
102
180
  rsa_key.iqmp = rsa_parameters[:qi] if rsa_parameters[:qi]
103
-
104
- rsa_key
105
181
  end
106
182
  end
107
183
 
108
- def decode_open_ssl_bn(jwk_data)
109
- return nil unless jwk_data
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
110
189
 
111
- OpenSSL::BN.new(Base64.urlsafe_decode64(jwk_data), BINARY)
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
112
199
  end
113
200
  end
114
201
  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 CHANGED
@@ -1,23 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'jwk/key_finder'
4
+ require_relative 'jwk/set'
4
5
 
5
6
  module JWT
6
7
  module JWK
7
8
  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
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
16
18
 
17
- def create_from(keypair, kid = nil)
18
- mappings.fetch(keypair.class) do |klass|
19
+ mappings.fetch(key.class) do |klass|
19
20
  raise JWT::JWKError, "Cannot create JWK from a #{klass.name}"
20
- end.new(keypair, kid)
21
+ end.new(key, params, options)
21
22
  end
22
23
 
23
24
  def classes
@@ -26,6 +27,7 @@ module JWT
26
27
  end
27
28
 
28
29
  alias new create_from
30
+ alias import create_from
29
31
 
30
32
  private
31
33
 
@@ -50,3 +52,4 @@ require_relative 'jwk/key_base'
50
52
  require_relative 'jwk/ec'
51
53
  require_relative 'jwk/rsa'
52
54
  require_relative 'jwk/hmac'
55
+ require_relative 'jwk/okp_rbnacl' if JWT.rbnacl?