jwt 2.2.2 → 2.2.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.
data/lib/jwt/encode.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './algos'
3
4
  require_relative './claims_validator'
4
5
 
5
6
  # JWT::Encode module
@@ -10,10 +11,10 @@ module JWT
10
11
  ALG_KEY = 'alg'.freeze
11
12
 
12
13
  def initialize(options)
13
- @payload = options[:payload]
14
- @key = options[:key]
15
- @algorithm = options[:algorithm]
16
- @headers = options[:headers].each_with_object({}) { |(key, value), headers| headers[key.to_s] = value }
14
+ @payload = options[:payload]
15
+ @key = options[:key]
16
+ _, @algorithm = Algos.find(options[:algorithm])
17
+ @headers = options[:headers].each_with_object({}) { |(key, value), headers| headers[key.to_s] = value }
17
18
  end
18
19
 
19
20
  def segments
data/lib/jwt/jwk.rb CHANGED
@@ -1,31 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'jwk/rsa'
4
3
  require_relative 'jwk/key_finder'
5
4
 
6
5
  module JWT
7
6
  module JWK
8
- MAPPINGS = {
9
- 'RSA' => ::JWT::JWK::RSA,
10
- OpenSSL::PKey::RSA => ::JWT::JWK::RSA
11
- }.freeze
12
-
13
7
  class << self
14
8
  def import(jwk_data)
15
- raise JWT::JWKError, 'Key type (kty) not provided' unless jwk_data[:kty]
9
+ jwk_kty = jwk_data[:kty] || jwk_data['kty']
10
+ raise JWT::JWKError, 'Key type (kty) not provided' unless jwk_kty
16
11
 
17
- MAPPINGS.fetch(jwk_data[:kty].to_s) do |kty|
12
+ mappings.fetch(jwk_kty.to_s) do |kty|
18
13
  raise JWT::JWKError, "Key type #{kty} not supported"
19
14
  end.import(jwk_data)
20
15
  end
21
16
 
22
17
  def create_from(keypair)
23
- MAPPINGS.fetch(keypair.class) do |klass|
18
+ mappings.fetch(keypair.class) do |klass|
24
19
  raise JWT::JWKError, "Cannot create JWK from a #{klass.name}"
25
20
  end.new(keypair)
26
21
  end
27
22
 
23
+ def classes
24
+ @mappings = nil # reset the cached mappings
25
+ @classes ||= []
26
+ end
27
+
28
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
+ Array(klass::KTYS).each do |kty|
40
+ hash[kty] = klass
41
+ end
42
+ end
43
+ end
29
44
  end
30
45
  end
31
46
  end
47
+
48
+ require_relative 'jwk/key_base'
49
+ require_relative 'jwk/ec'
50
+ require_relative 'jwk/rsa'
51
+ require_relative 'jwk/hmac'
data/lib/jwt/jwk/ec.rb ADDED
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module JWT
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
13
+ BINARY = 2
14
+
15
+ def initialize(keypair, kid = nil)
16
+ raise ArgumentError, 'keypair must be of type OpenSSL::PKey::EC' unless keypair.is_a?(OpenSSL::PKey::EC)
17
+
18
+ kid ||= generate_kid(keypair)
19
+ super(keypair, kid)
20
+ end
21
+
22
+ def private?
23
+ @keypair.private_key?
24
+ end
25
+
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
36
+
37
+ append_private_parts(exported_hash)
38
+ end
39
+
40
+ private
41
+
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
+ )
47
+ end
48
+
49
+ def generate_kid(ec_keypair)
50
+ _crv, x_octets, y_octets = keypair_components(ec_keypair)
51
+ sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(x_octets, BINARY)),
52
+ OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(y_octets, BINARY))])
53
+ OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
54
+ end
55
+
56
+ def keypair_components(ec_keypair)
57
+ encoded_point = ec_keypair.public_key.to_bn.to_s(BINARY)
58
+ case ec_keypair.group.curve_name
59
+ when 'prime256v1'
60
+ crv = 'P-256'
61
+ x_octets, y_octets = encoded_point.unpack('xa32a32')
62
+ when 'secp384r1'
63
+ crv = 'P-384'
64
+ x_octets, y_octets = encoded_point.unpack('xa48a48')
65
+ when 'secp521r1'
66
+ crv = 'P-521'
67
+ x_octets, y_octets = encoded_point.unpack('xa66a66')
68
+ else
69
+ raise Jwt::JWKError, "Unsupported curve '#{ec_keypair.group.curve_name}'"
70
+ end
71
+ [crv, x_octets, y_octets]
72
+ end
73
+
74
+ def encode_octets(octets)
75
+ ::JWT::Base64.url_encode(octets)
76
+ end
77
+
78
+ def encode_open_ssl_bn(key_part)
79
+ ::JWT::Base64.url_encode(key_part.to_s(BINARY))
80
+ end
81
+
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
89
+
90
+ new(ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d), jwk_kid)
91
+ end
92
+
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
104
+
105
+ private
106
+
107
+ def jwk_attrs(jwk_data, attrs)
108
+ attrs.map do |attr|
109
+ jwk_data[attr] || jwk_data[attr.to_s]
110
+ end
111
+ end
112
+
113
+ def ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d)
114
+ curve = to_openssl_curve(jwk_crv)
115
+
116
+ x_octets = decode_octets(jwk_x)
117
+ y_octets = decode_octets(jwk_y)
118
+
119
+ key = OpenSSL::PKey::EC.new(curve)
120
+
121
+ # The details of the `Point` instantiation are covered in:
122
+ # - https://docs.ruby-lang.org/en/2.4.0/OpenSSL/PKey/EC.html
123
+ # - https://www.openssl.org/docs/manmaster/man3/EC_POINT_new.html
124
+ # - https://tools.ietf.org/html/rfc5480#section-2.2
125
+ # - https://www.secg.org/SEC1-Ver-1.0.pdf
126
+ # Section 2.3.3 of the last of these references specifies that the
127
+ # encoding of an uncompressed point consists of the byte `0x04` followed
128
+ # by the x value then the y value.
129
+ point = OpenSSL::PKey::EC::Point.new(
130
+ OpenSSL::PKey::EC::Group.new(curve),
131
+ OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
132
+ )
133
+
134
+ key.public_key = point
135
+ key.private_key = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d
136
+
137
+ key
138
+ end
139
+
140
+ def decode_octets(jwk_data)
141
+ ::JWT::Base64.url_decode(jwk_data)
142
+ end
143
+
144
+ def decode_open_ssl_bn(jwk_data)
145
+ OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWK
5
+ class HMAC < KeyBase
6
+ KTY = 'oct'.freeze
7
+ KTYS = [KTY, String].freeze
8
+
9
+ def initialize(keypair, kid = nil)
10
+ raise ArgumentError, 'keypair must be of type String' unless keypair.is_a?(String)
11
+
12
+ super
13
+ @kid = kid || generate_kid
14
+ end
15
+
16
+ def private?
17
+ true
18
+ end
19
+
20
+ def public_key
21
+ nil
22
+ end
23
+
24
+ # See https://tools.ietf.org/html/rfc7517#appendix-A.3
25
+ def export(options = {})
26
+ exported_hash = {
27
+ kty: KTY,
28
+ kid: kid
29
+ }
30
+
31
+ return exported_hash unless private? && options[:include_private] == true
32
+
33
+ exported_hash.merge(
34
+ k: keypair
35
+ )
36
+ end
37
+
38
+ private
39
+
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)
44
+ end
45
+
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']
50
+
51
+ raise JWT::JWKError, 'Key format is invalid for HMAC' unless jwk_k
52
+
53
+ self.new(jwk_k, jwk_kid)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWK
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
+ def self.inherited(klass)
14
+ ::JWT::JWK.classes << klass
15
+ end
16
+ end
17
+ end
18
+ end
@@ -14,6 +14,7 @@ module JWT
14
14
 
15
15
  jwk = resolve_key(kid)
16
16
 
17
+ raise ::JWT::DecodeError, 'No keys found in jwks' if jwks_keys.empty?
17
18
  raise ::JWT::DecodeError, "Could not find public key for kid #{kid}" unless jwk
18
19
 
19
20
  ::JWT::JWK.import(jwk).keypair
@@ -45,8 +46,12 @@ module JWT
45
46
  @jwks = @jwk_loader.call(opts)
46
47
  end
47
48
 
49
+ def jwks_keys
50
+ Array(jwks[:keys] || jwks['keys'])
51
+ end
52
+
48
53
  def find_key(kid)
49
- Array(jwks[:keys]).find { |key| key[:kid] == kid }
54
+ jwks_keys.find { |key| (key[:kid] || key['kid']) == kid }
50
55
  end
51
56
 
52
57
  def reloadable?
data/lib/jwt/jwk/rsa.rb CHANGED
@@ -2,16 +2,15 @@
2
2
 
3
3
  module JWT
4
4
  module JWK
5
- class RSA
6
- attr_reader :keypair
7
-
5
+ class RSA < KeyBase
8
6
  BINARY = 2
9
7
  KTY = 'RSA'.freeze
8
+ KTYS = [KTY, OpenSSL::PKey::RSA].freeze
9
+ RSA_KEY_ELEMENTS = %i[n e d p q dp dq qi].freeze
10
10
 
11
- def initialize(keypair)
11
+ def initialize(keypair, kid = nil)
12
12
  raise ArgumentError, 'keypair must be of type OpenSSL::PKey::RSA' unless keypair.is_a?(OpenSSL::PKey::RSA)
13
-
14
- @keypair = keypair
13
+ super(keypair, kid || generate_kid(keypair.public_key))
15
14
  end
16
15
 
17
16
  def private?
@@ -22,32 +21,94 @@ module JWT
22
21
  keypair.public_key
23
22
  end
24
23
 
25
- def kid
24
+ def export(options = {})
25
+ exported_hash = {
26
+ kty: KTY,
27
+ n: encode_open_ssl_bn(public_key.n),
28
+ e: encode_open_ssl_bn(public_key.e),
29
+ kid: kid
30
+ }
31
+
32
+ return exported_hash unless private? && options[:include_private] == true
33
+
34
+ append_private_parts(exported_hash)
35
+ end
36
+
37
+ private
38
+
39
+ def generate_kid(public_key)
26
40
  sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(public_key.n),
27
41
  OpenSSL::ASN1::Integer.new(public_key.e)])
28
42
  OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
29
43
  end
30
44
 
31
- def export
32
- {
33
- kty: KTY,
34
- n: ::Base64.urlsafe_encode64(public_key.n.to_s(BINARY), padding: false),
35
- e: ::Base64.urlsafe_encode64(public_key.e.to_s(BINARY), padding: false),
36
- kid: kid
37
- }
45
+ def append_private_parts(the_hash)
46
+ the_hash.merge(
47
+ d: encode_open_ssl_bn(keypair.d),
48
+ p: encode_open_ssl_bn(keypair.p),
49
+ q: encode_open_ssl_bn(keypair.q),
50
+ dp: encode_open_ssl_bn(keypair.dmp1),
51
+ dq: encode_open_ssl_bn(keypair.dmq1),
52
+ qi: encode_open_ssl_bn(keypair.iqmp)
53
+ )
38
54
  end
39
55
 
40
- def self.import(jwk_data)
41
- imported_key = OpenSSL::PKey::RSA.new
42
- if imported_key.respond_to?(:set_key)
43
- imported_key.set_key(OpenSSL::BN.new(::Base64.urlsafe_decode64(jwk_data[:n]), BINARY),
44
- OpenSSL::BN.new(::Base64.urlsafe_decode64(jwk_data[:e]), BINARY),
45
- nil)
56
+ def encode_open_ssl_bn(key_part)
57
+ ::JWT::Base64.url_encode(key_part.to_s(BINARY))
58
+ end
59
+
60
+ class << self
61
+ def import(jwk_data)
62
+ pkey_params = jwk_attributes(jwk_data, *RSA_KEY_ELEMENTS) do |value|
63
+ decode_open_ssl_bn(value)
64
+ end
65
+ kid = jwk_attributes(jwk_data, :kid)[:kid]
66
+ self.new(rsa_pkey(pkey_params), kid)
67
+ end
68
+
69
+ private
70
+
71
+ def jwk_attributes(jwk_data, *attributes)
72
+ attributes.each_with_object({}) do |attribute, hash|
73
+ value = jwk_data[attribute] || jwk_data[attribute.to_s]
74
+ value = yield(value) if block_given?
75
+ hash[attribute] = value
76
+ end
77
+ end
78
+
79
+ def rsa_pkey(rsa_parameters)
80
+ raise JWT::JWKError, 'Key format is invalid for RSA' unless rsa_parameters[:n] && rsa_parameters[:e]
81
+
82
+ populate_key(OpenSSL::PKey::RSA.new, rsa_parameters)
83
+ end
84
+
85
+ if OpenSSL::PKey::RSA.new.respond_to?(:set_key)
86
+ def populate_key(rsa_key, rsa_parameters)
87
+ rsa_key.set_key(rsa_parameters[:n], rsa_parameters[:e], rsa_parameters[:d])
88
+ rsa_key.set_factors(rsa_parameters[:p], rsa_parameters[:q]) if rsa_parameters[:p] && rsa_parameters[:q]
89
+ rsa_key.set_crt_params(rsa_parameters[:dp], rsa_parameters[:dq], rsa_parameters[:qi]) if rsa_parameters[:dp] && rsa_parameters[:dq] && rsa_parameters[:qi]
90
+ rsa_key
91
+ end
46
92
  else
47
- imported_key.n = OpenSSL::BN.new(::Base64.urlsafe_decode64(jwk_data[:n]), BINARY)
48
- imported_key.e = OpenSSL::BN.new(::Base64.urlsafe_decode64(jwk_data[:e]), BINARY)
93
+ def populate_key(rsa_key, rsa_parameters)
94
+ rsa_key.n = rsa_parameters[:n]
95
+ rsa_key.e = rsa_parameters[:e]
96
+ rsa_key.d = rsa_parameters[:d] if rsa_parameters[:d]
97
+ rsa_key.p = rsa_parameters[:p] if rsa_parameters[:p]
98
+ rsa_key.q = rsa_parameters[:q] if rsa_parameters[:q]
99
+ rsa_key.dmp1 = rsa_parameters[:dp] if rsa_parameters[:dp]
100
+ rsa_key.dmq1 = rsa_parameters[:dq] if rsa_parameters[:dq]
101
+ rsa_key.iqmp = rsa_parameters[:qi] if rsa_parameters[:qi]
102
+
103
+ rsa_key
104
+ end
105
+ end
106
+
107
+ def decode_open_ssl_bn(jwk_data)
108
+ return nil unless jwk_data
109
+
110
+ OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
49
111
  end
50
- self.new(imported_key)
51
112
  end
52
113
  end
53
114
  end