jwt 2.3.0 → 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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHORS +60 -53
  3. data/CHANGELOG.md +73 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/CONTRIBUTING.md +99 -0
  6. data/README.md +188 -40
  7. data/lib/jwt/algos/algo_wrapper.rb +30 -0
  8. data/lib/jwt/algos/ecdsa.rb +39 -12
  9. data/lib/jwt/algos/eddsa.rb +7 -4
  10. data/lib/jwt/algos/hmac.rb +56 -17
  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 +5 -1
  14. data/lib/jwt/algos/ps.rb +6 -8
  15. data/lib/jwt/algos/rsa.rb +7 -5
  16. data/lib/jwt/algos/unsupported.rb +2 -0
  17. data/lib/jwt/algos.rb +38 -15
  18. data/lib/jwt/claims_validator.rb +3 -1
  19. data/lib/jwt/configuration/container.rb +21 -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 +83 -26
  24. data/lib/jwt/encode.rb +30 -20
  25. data/lib/jwt/error.rb +1 -0
  26. data/lib/jwt/jwk/ec.rb +147 -61
  27. data/lib/jwt/jwk/hmac.rb +69 -24
  28. data/lib/jwt/jwk/key_base.rb +43 -6
  29. data/lib/jwt/jwk/key_finder.rb +19 -35
  30. data/lib/jwt/jwk/kid_as_key_digest.rb +15 -0
  31. data/lib/jwt/jwk/okp_rbnacl.rb +110 -0
  32. data/lib/jwt/jwk/rsa.rb +142 -54
  33. data/lib/jwt/jwk/set.rb +80 -0
  34. data/lib/jwt/jwk/thumbprint.rb +26 -0
  35. data/lib/jwt/jwk.rb +15 -11
  36. data/lib/jwt/security_utils.rb +2 -27
  37. data/lib/jwt/verify.rb +10 -2
  38. data/lib/jwt/version.rb +22 -2
  39. data/lib/jwt/x5c_key_finder.rb +55 -0
  40. data/lib/jwt.rb +5 -4
  41. data/ruby-jwt.gemspec +12 -5
  42. metadata +20 -16
  43. data/.github/workflows/test.yml +0 -74
  44. data/.gitignore +0 -11
  45. data/.rspec +0 -2
  46. data/.rubocop.yml +0 -97
  47. data/.rubocop_todo.yml +0 -185
  48. data/.sourcelevel.yml +0 -18
  49. data/Appraisals +0 -10
  50. data/Gemfile +0 -5
  51. data/Rakefile +0 -14
  52. data/lib/jwt/default_options.rb +0 -16
  53. data/lib/jwt/signature.rb +0 -39
data/lib/jwt/jwk/ec.rb CHANGED
@@ -4,61 +4,108 @@ require 'forwardable'
4
4
 
5
5
  module JWT
6
6
  module JWK
7
- class EC < KeyBase
8
- extend Forwardable
9
- def_delegators :@keypair, :public_key
10
-
11
- KTY = 'EC'.freeze
12
- KTYS = [KTY, OpenSSL::PKey::EC].freeze
7
+ class EC < KeyBase # rubocop:disable Metrics/ClassLength
8
+ KTY = 'EC'
9
+ KTYS = [KTY, OpenSSL::PKey::EC, JWT::JWK::EC].freeze
13
10
  BINARY = 2
11
+ EC_PUBLIC_KEY_ELEMENTS = %i[kty crv x y].freeze
12
+ EC_PRIVATE_KEY_ELEMENTS = %i[d].freeze
13
+ EC_KEY_ELEMENTS = (EC_PRIVATE_KEY_ELEMENTS + EC_PUBLIC_KEY_ELEMENTS).freeze
14
+
15
+ def initialize(key, params = nil, options = {})
16
+ params ||= {}
17
+
18
+ # For backwards compatibility when kid was a String
19
+ params = { kid: params } if params.is_a?(String)
20
+
21
+ key_params = extract_key_params(key)
22
+
23
+ params = params.transform_keys(&:to_sym)
24
+ check_jwk_params!(key_params, params)
14
25
 
15
- def initialize(keypair, kid = nil)
16
- raise ArgumentError, 'keypair must be of type OpenSSL::PKey::EC' unless keypair.is_a?(OpenSSL::PKey::EC)
26
+ super(options, key_params.merge(params))
27
+ end
17
28
 
18
- kid ||= generate_kid(keypair)
19
- super(keypair, kid)
29
+ def keypair
30
+ ec_key
20
31
  end
21
32
 
22
33
  def private?
23
- @keypair.private_key?
34
+ ec_key.private_key?
24
35
  end
25
36
 
26
- def export(options = {})
27
- crv, x_octets, y_octets = keypair_components(keypair)
28
- exported_hash = {
29
- kty: KTY,
30
- crv: crv,
31
- x: encode_octets(x_octets),
32
- y: encode_octets(y_octets),
33
- kid: kid
34
- }
35
- return exported_hash unless private? && options[:include_private] == true
37
+ def signing_key
38
+ ec_key
39
+ end
36
40
 
37
- append_private_parts(exported_hash)
41
+ def verify_key
42
+ ec_key
38
43
  end
39
44
 
40
- private
45
+ def public_key
46
+ ec_key
47
+ end
41
48
 
42
- def append_private_parts(the_hash)
43
- octets = keypair.private_key.to_bn.to_s(BINARY)
44
- the_hash.merge(
45
- d: encode_octets(octets)
46
- )
49
+ def members
50
+ EC_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] }
47
51
  end
48
52
 
49
- def generate_kid(ec_keypair)
50
- _crv, x_octets, y_octets = keypair_components(ec_keypair)
53
+ def export(options = {})
54
+ exported = parameters.clone
55
+ exported.reject! { |k, _| EC_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true
56
+ exported
57
+ end
58
+
59
+ def key_digest
60
+ _crv, x_octets, y_octets = keypair_components(ec_key)
51
61
  sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(x_octets, BINARY)),
52
62
  OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(y_octets, BINARY))])
53
63
  OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
54
64
  end
55
65
 
66
+ def []=(key, value)
67
+ if EC_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 ec_key
77
+ @ec_key ||= create_ec_key(self[:crv], self[:x], self[:y], self[:d])
78
+ end
79
+
80
+ def extract_key_params(key)
81
+ case key
82
+ when JWT::JWK::EC
83
+ key.export(include_private: true)
84
+ when OpenSSL::PKey::EC # Accept OpenSSL key as input
85
+ @ec_key = key # Preserve the object to avoid recreation
86
+ parse_ec_key(key)
87
+ when Hash
88
+ key.transform_keys(&:to_sym)
89
+ else
90
+ raise ArgumentError, 'key must be of type OpenSSL::PKey::EC 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 (EC_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 EC' unless key_params[:crv] && key_params[:x] && key_params[:y]
98
+ end
99
+
56
100
  def keypair_components(ec_keypair)
57
101
  encoded_point = ec_keypair.public_key.to_bn.to_s(BINARY)
58
102
  case ec_keypair.group.curve_name
59
103
  when 'prime256v1'
60
104
  crv = 'P-256'
61
105
  x_octets, y_octets = encoded_point.unpack('xa32a32')
106
+ when 'secp256k1'
107
+ crv = 'P-256K'
108
+ x_octets, y_octets = encoded_point.unpack('xa32a32')
62
109
  when 'secp384r1'
63
110
  crv = 'P-384'
64
111
  x_octets, y_octets = encoded_point.unpack('xa48a48')
@@ -72,6 +119,8 @@ module JWT
72
119
  end
73
120
 
74
121
  def encode_octets(octets)
122
+ return unless octets
123
+
75
124
  ::JWT::Base64.url_encode(octets)
76
125
  end
77
126
 
@@ -79,39 +128,57 @@ module JWT
79
128
  ::JWT::Base64.url_encode(key_part.to_s(BINARY))
80
129
  end
81
130
 
82
- class << self
83
- def import(jwk_data)
84
- # See https://tools.ietf.org/html/rfc7518#section-6.2.1 for an
85
- # explanation of the relevant parameters.
86
-
87
- jwk_crv, jwk_x, jwk_y, jwk_d, jwk_kid = jwk_attrs(jwk_data, %i[crv x y d kid])
88
- raise JWT::JWKError, 'Key format is invalid for EC' unless jwk_crv && jwk_x && jwk_y
131
+ def parse_ec_key(key)
132
+ crv, x_octets, y_octets = keypair_components(key)
133
+ octets = key.private_key&.to_bn&.to_s(BINARY)
134
+ {
135
+ kty: KTY,
136
+ crv: crv,
137
+ x: encode_octets(x_octets),
138
+ y: encode_octets(y_octets),
139
+ d: encode_octets(octets)
140
+ }.compact
141
+ end
89
142
 
90
- new(ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d), jwk_kid)
91
- end
143
+ if ::JWT.openssl_3?
144
+ def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d) # rubocop:disable Metrics/MethodLength
145
+ curve = EC.to_openssl_curve(jwk_crv)
92
146
 
93
- def to_openssl_curve(crv)
94
- # The JWK specs and OpenSSL use different names for the same curves.
95
- # See https://tools.ietf.org/html/rfc5480#section-2.1.1.1 for some
96
- # pointers on different names for common curves.
97
- case crv
98
- when 'P-256' then 'prime256v1'
99
- when 'P-384' then 'secp384r1'
100
- when 'P-521' then 'secp521r1'
101
- else raise JWT::JWKError, 'Invalid curve provided'
102
- end
103
- end
147
+ x_octets = decode_octets(jwk_x)
148
+ y_octets = decode_octets(jwk_y)
104
149
 
105
- private
150
+ point = OpenSSL::PKey::EC::Point.new(
151
+ OpenSSL::PKey::EC::Group.new(curve),
152
+ OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
153
+ )
106
154
 
107
- def jwk_attrs(jwk_data, attrs)
108
- attrs.map do |attr|
109
- jwk_data[attr] || jwk_data[attr.to_s]
155
+ sequence = if jwk_d
156
+ # https://datatracker.ietf.org/doc/html/rfc5915.html
157
+ # ECPrivateKey ::= SEQUENCE {
158
+ # version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
159
+ # privateKey OCTET STRING,
160
+ # parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
161
+ # publicKey [1] BIT STRING OPTIONAL
162
+ # }
163
+
164
+ OpenSSL::ASN1::Sequence([
165
+ OpenSSL::ASN1::Integer(1),
166
+ OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
167
+ OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
168
+ OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
169
+ ])
170
+ else
171
+ OpenSSL::ASN1::Sequence([
172
+ OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]),
173
+ OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
174
+ ])
110
175
  end
111
- end
112
176
 
113
- def ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d)
114
- curve = to_openssl_curve(jwk_crv)
177
+ OpenSSL::PKey::EC.new(sequence.to_der)
178
+ end
179
+ else
180
+ def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d)
181
+ curve = EC.to_openssl_curve(jwk_crv)
115
182
 
116
183
  x_octets = decode_octets(jwk_x)
117
184
  y_octets = decode_octets(jwk_y)
@@ -136,13 +203,32 @@ module JWT
136
203
 
137
204
  key
138
205
  end
206
+ end
207
+
208
+ def decode_octets(jwk_data)
209
+ ::JWT::Base64.url_decode(jwk_data)
210
+ end
211
+
212
+ def decode_open_ssl_bn(jwk_data)
213
+ OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
214
+ end
139
215
 
140
- def decode_octets(jwk_data)
141
- ::JWT::Base64.url_decode(jwk_data)
216
+ class << self
217
+ def import(jwk_data)
218
+ new(jwk_data)
142
219
  end
143
220
 
144
- def decode_open_ssl_bn(jwk_data)
145
- OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
221
+ def to_openssl_curve(crv)
222
+ # The JWK specs and OpenSSL use different names for the same curves.
223
+ # See https://tools.ietf.org/html/rfc5480#section-2.1.1.1 for some
224
+ # pointers on different names for common curves.
225
+ case crv
226
+ when 'P-256' then 'prime256v1'
227
+ when 'P-384' then 'secp384r1'
228
+ when 'P-521' then 'secp521r1'
229
+ when 'P-256K' then 'secp256k1'
230
+ else raise JWT::JWKError, 'Invalid curve provided'
231
+ end
146
232
  end
147
233
  end
148
234
  end
data/lib/jwt/jwk/hmac.rb CHANGED
@@ -3,14 +3,28 @@
3
3
  module JWT
4
4
  module JWK
5
5
  class HMAC < KeyBase
6
- KTY = 'oct'.freeze
7
- KTYS = [KTY, String].freeze
6
+ KTY = 'oct'
7
+ KTYS = [KTY, String, JWT::JWK::HMAC].freeze
8
+ HMAC_PUBLIC_KEY_ELEMENTS = %i[kty].freeze
9
+ HMAC_PRIVATE_KEY_ELEMENTS = %i[k].freeze
10
+ HMAC_KEY_ELEMENTS = (HMAC_PRIVATE_KEY_ELEMENTS + HMAC_PUBLIC_KEY_ELEMENTS).freeze
8
11
 
9
- def initialize(keypair, kid = nil)
10
- raise ArgumentError, 'keypair must be of type String' unless keypair.is_a?(String)
12
+ def initialize(key, params = nil, options = {})
13
+ params ||= {}
11
14
 
12
- super
13
- @kid = kid || generate_kid
15
+ # For backwards compatibility when kid was a String
16
+ params = { kid: params } if params.is_a?(String)
17
+
18
+ key_params = extract_key_params(key)
19
+
20
+ params = params.transform_keys(&:to_sym)
21
+ check_jwk(key_params, params)
22
+
23
+ super(options, key_params.merge(params))
24
+ end
25
+
26
+ def keypair
27
+ secret
14
28
  end
15
29
 
16
30
  def private?
@@ -21,36 +35,67 @@ module JWT
21
35
  nil
22
36
  end
23
37
 
38
+ def verify_key
39
+ secret
40
+ end
41
+
42
+ def signing_key
43
+ secret
44
+ end
45
+
24
46
  # See https://tools.ietf.org/html/rfc7517#appendix-A.3
25
47
  def export(options = {})
26
- exported_hash = {
27
- kty: KTY,
28
- kid: kid
29
- }
48
+ exported = parameters.clone
49
+ exported.reject! { |k, _| HMAC_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true
50
+ exported
51
+ end
52
+
53
+ def members
54
+ HMAC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] }
55
+ end
56
+
57
+ def key_digest
58
+ sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::UTF8String.new(signing_key),
59
+ OpenSSL::ASN1::UTF8String.new(KTY)])
60
+ OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
61
+ end
30
62
 
31
- return exported_hash unless private? && options[:include_private] == true
63
+ def []=(key, value)
64
+ if HMAC_KEY_ELEMENTS.include?(key.to_sym)
65
+ raise ArgumentError, 'cannot overwrite cryptographic key attributes'
66
+ end
32
67
 
33
- exported_hash.merge(
34
- k: keypair
35
- )
68
+ super(key, value)
36
69
  end
37
70
 
38
71
  private
39
72
 
40
- def generate_kid
41
- sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::UTF8String.new(keypair),
42
- OpenSSL::ASN1::UTF8String.new(KTY)])
43
- OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
73
+ def secret
74
+ self[:k]
44
75
  end
45
76
 
46
- class << self
47
- def import(jwk_data)
48
- jwk_k = jwk_data[:k] || jwk_data['k']
49
- jwk_kid = jwk_data[:kid] || jwk_data['kid']
77
+ def extract_key_params(key)
78
+ case key
79
+ when JWT::JWK::HMAC
80
+ key.export(include_private: true)
81
+ when String # Accept String key as input
82
+ { kty: KTY, k: key }
83
+ when Hash
84
+ key.transform_keys(&:to_sym)
85
+ else
86
+ raise ArgumentError, 'key must be of type String or Hash with key parameters'
87
+ end
88
+ end
50
89
 
51
- raise JWT::JWKError, 'Key format is invalid for HMAC' unless jwk_k
90
+ def check_jwk(keypair, params)
91
+ raise ArgumentError, 'cannot overwrite cryptographic key attributes' unless (HMAC_KEY_ELEMENTS & params.keys).empty?
92
+ raise JWT::JWKError, "Incorrect 'kty' value: #{keypair[:kty]}, expected #{KTY}" unless keypair[:kty] == KTY
93
+ raise JWT::JWKError, 'Key format is invalid for HMAC' unless keypair[:k]
94
+ end
52
95
 
53
- self.new(jwk_k, jwk_kid)
96
+ class << self
97
+ def import(jwk_data)
98
+ new(jwk_data)
54
99
  end
55
100
  end
56
101
  end
@@ -3,16 +3,53 @@
3
3
  module JWT
4
4
  module JWK
5
5
  class KeyBase
6
- attr_reader :keypair, :kid
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)
7
18
 
8
- def initialize(keypair, kid = nil)
9
- @keypair = keypair
10
- @kid = kid
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
11
22
  end
12
23
 
13
- def self.inherited(klass)
14
- ::JWT::JWK.classes << klass
24
+ def kid
25
+ self[:kid]
15
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
16
53
  end
17
54
  end
18
55
  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
32
+ key_matcher = ->(key) { (kid.nil? && @allow_nil_kid) || key[:kid] == kid }
37
33
 
38
- def jwks
39
- return @jwks if @jwks
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) }
40
37
 
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
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