jwt 2.2.1 → 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/algos.rb ADDED
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt/algos/hmac'
4
+ require 'jwt/algos/eddsa'
5
+ require 'jwt/algos/ecdsa'
6
+ require 'jwt/algos/rsa'
7
+ require 'jwt/algos/ps'
8
+ require 'jwt/algos/none'
9
+ require 'jwt/algos/unsupported'
10
+
11
+ # JWT::Signature module
12
+ module JWT
13
+ # Signature logic for JWT
14
+ module Algos
15
+ extend self
16
+
17
+ ALGOS = [
18
+ Algos::Hmac,
19
+ Algos::Ecdsa,
20
+ Algos::Rsa,
21
+ Algos::Eddsa,
22
+ Algos::Ps,
23
+ Algos::None,
24
+ Algos::Unsupported
25
+ ].freeze
26
+
27
+ def find(algorithm)
28
+ indexed[algorithm && algorithm.downcase]
29
+ end
30
+
31
+ private
32
+
33
+ def indexed
34
+ @indexed ||= begin
35
+ fallback = [Algos::Unsupported, nil]
36
+ ALGOS.each_with_object(Hash.new(fallback)) do |alg, hash|
37
+ alg.const_get(:SUPPORTED).each do |code|
38
+ hash[code.downcase] = [alg, code]
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -2,7 +2,7 @@ require_relative './error'
2
2
 
3
3
  module JWT
4
4
  class ClaimsValidator
5
- INTEGER_CLAIMS = %i[
5
+ NUMERIC_CLAIMS = %i[
6
6
  exp
7
7
  iat
8
8
  nbf
@@ -13,21 +13,23 @@ module JWT
13
13
  end
14
14
 
15
15
  def validate!
16
- validate_int_claims
16
+ validate_numeric_claims
17
17
 
18
18
  true
19
19
  end
20
20
 
21
21
  private
22
22
 
23
- def validate_int_claims
24
- INTEGER_CLAIMS.each do |claim|
25
- validate_is_int(claim) if @payload.key?(claim)
23
+ def validate_numeric_claims
24
+ NUMERIC_CLAIMS.each do |claim|
25
+ validate_is_numeric(claim) if @payload.key?(claim)
26
26
  end
27
27
  end
28
28
 
29
- def validate_is_int(claim)
30
- raise InvalidPayload, "#{claim} claim must be an Integer but it is a #{@payload[claim].class}" unless @payload[claim].is_a?(Integer)
29
+ def validate_is_numeric(claim)
30
+ return if @payload[claim].is_a?(Numeric)
31
+
32
+ raise InvalidPayload, "#{claim} claim must be a Numeric value but it is a #{@payload[claim].class}"
31
33
  end
32
34
  end
33
35
  end
data/lib/jwt/decode.rb CHANGED
@@ -33,25 +33,33 @@ module JWT
33
33
  private
34
34
 
35
35
  def verify_signature
36
- @key = find_key(&@keyfinder) if @keyfinder
37
- @key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks]).key_for(header['kid']) if @options[:jwks]
38
-
39
36
  raise(JWT::IncorrectAlgorithm, 'An algorithm must be specified') if allowed_algorithms.empty?
40
37
  raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') unless options_includes_algo_in_header?
41
38
 
39
+ @key = find_key(&@keyfinder) if @keyfinder
40
+ @key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks]).key_for(header['kid']) if @options[:jwks]
41
+
42
42
  Signature.verify(header['alg'], @key, signing_input, @signature)
43
43
  end
44
44
 
45
45
  def options_includes_algo_in_header?
46
- allowed_algorithms.include? header['alg']
46
+ allowed_algorithms.any? { |alg| alg.casecmp(header['alg']).zero? }
47
47
  end
48
48
 
49
49
  def allowed_algorithms
50
- if @options.key?(:algorithm)
51
- [@options[:algorithm]]
50
+ # Order is very important - first check for string keys, next for symbols
51
+ algos = if @options.key?('algorithm')
52
+ @options['algorithm']
53
+ elsif @options.key?(:algorithm)
54
+ @options[:algorithm]
55
+ elsif @options.key?('algorithms')
56
+ @options['algorithms']
57
+ elsif @options.key?(:algorithms)
58
+ @options[:algorithms]
52
59
  else
53
- @options[:algorithms] || []
60
+ []
54
61
  end
62
+ Array(algos)
55
63
  end
56
64
 
57
65
  def find_key(&keyfinder)
@@ -67,6 +75,7 @@ module JWT
67
75
  def validate_segment_count!
68
76
  return if segment_length == 3
69
77
  return if !@verify && segment_length == 2 # If no verifying required, the signature is not needed
78
+ return if segment_length == 2 && header['alg'] == 'none'
70
79
 
71
80
  raise(JWT::DecodeError, 'Not enough or too many segments')
72
81
  end
@@ -76,7 +85,7 @@ module JWT
76
85
  end
77
86
 
78
87
  def decode_crypto
79
- @signature = JWT::Base64.url_decode(@segments[2])
88
+ @signature = JWT::Base64.url_decode(@segments[2] || '')
80
89
  end
81
90
 
82
91
  def header
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/error.rb CHANGED
@@ -1,20 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWT
4
- EncodeError = Class.new(StandardError)
5
- DecodeError = Class.new(StandardError)
6
- RequiredDependencyError = Class.new(StandardError)
4
+ class EncodeError < StandardError; end
5
+ class DecodeError < StandardError; end
6
+ class RequiredDependencyError < StandardError; end
7
7
 
8
- VerificationError = Class.new(DecodeError)
9
- ExpiredSignature = Class.new(DecodeError)
10
- IncorrectAlgorithm = Class.new(DecodeError)
11
- ImmatureSignature = Class.new(DecodeError)
12
- InvalidIssuerError = Class.new(DecodeError)
13
- InvalidIatError = Class.new(DecodeError)
14
- InvalidAudError = Class.new(DecodeError)
15
- InvalidSubError = Class.new(DecodeError)
16
- InvalidJtiError = Class.new(DecodeError)
17
- InvalidPayload = Class.new(DecodeError)
8
+ class VerificationError < DecodeError; end
9
+ class ExpiredSignature < DecodeError; end
10
+ class IncorrectAlgorithm < DecodeError; end
11
+ class ImmatureSignature < DecodeError; end
12
+ class InvalidIssuerError < DecodeError; end
13
+ class InvalidIatError < DecodeError; end
14
+ class InvalidAudError < DecodeError; end
15
+ class InvalidSubError < DecodeError; end
16
+ class InvalidJtiError < DecodeError; end
17
+ class InvalidPayload < DecodeError; end
18
18
 
19
- JWKError = Class.new(DecodeError)
19
+ class JWKError < DecodeError; end
20
20
  end
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
@@ -1,46 +1,114 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
4
-
5
3
  module JWT
6
4
  module JWK
7
- class RSA
8
- extend Forwardable
9
-
10
- attr_reader :keypair
11
-
12
- def_delegators :keypair, :private?, :public_key
13
-
5
+ class RSA < KeyBase
14
6
  BINARY = 2
15
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
16
10
 
17
- def initialize(keypair)
11
+ def initialize(keypair, kid = nil)
18
12
  raise ArgumentError, 'keypair must be of type OpenSSL::PKey::RSA' unless keypair.is_a?(OpenSSL::PKey::RSA)
13
+ super(keypair, kid || generate_kid(keypair.public_key))
14
+ end
19
15
 
20
- @keypair = keypair
16
+ def private?
17
+ keypair.private?
21
18
  end
22
19
 
23
- def kid
24
- sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(public_key.n),
25
- OpenSSL::ASN1::Integer.new(public_key.e)])
26
- OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
20
+ def public_key
21
+ keypair.public_key
27
22
  end
28
23
 
29
- def export
30
- {
24
+ def export(options = {})
25
+ exported_hash = {
31
26
  kty: KTY,
32
- n: ::Base64.urlsafe_encode64(public_key.n.to_s(BINARY), padding: false),
33
- e: ::Base64.urlsafe_encode64(public_key.e.to_s(BINARY), padding: false),
27
+ n: encode_open_ssl_bn(public_key.n),
28
+ e: encode_open_ssl_bn(public_key.e),
34
29
  kid: kid
35
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)
40
+ sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(public_key.n),
41
+ OpenSSL::ASN1::Integer.new(public_key.e)])
42
+ OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
43
+ end
44
+
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
+ )
36
54
  end
37
55
 
38
- def self.import(jwk_data)
39
- imported_key = OpenSSL::PKey::RSA.new
40
- imported_key.set_key(OpenSSL::BN.new(::Base64.urlsafe_decode64(jwk_data[:n]), BINARY),
41
- OpenSSL::BN.new(::Base64.urlsafe_decode64(jwk_data[:e]), BINARY),
42
- nil)
43
- self.new(imported_key)
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
92
+ else
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)
111
+ end
44
112
  end
45
113
  end
46
114
  end
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'