jwt 2.6.0 → 2.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c18ec5fbed5aff7aa65bfe9e7893583c677d0d18269c1ebd9cff7761916e298
4
- data.tar.gz: e1e270fff52673d769982888b97c741a4b5c38b34214ee9d547857c0244ff0db
3
+ metadata.gz: 11007e8ec36d148026cd5b6761681b0a71437b7b461efba4ae492622fc5ff27b
4
+ data.tar.gz: 8090bbba3dce57e42cc203ef168d3d00c624e79e076de0e949b4390b531b4d55
5
5
  SHA512:
6
- metadata.gz: 452b6056da93ed535d8e93fc17d3ec69105a623b217f38d816ab1dc298dbcb93b1e45143732c3d13b752f421495e3b56f8cf51b5a8a802570bc5832072f28a26
7
- data.tar.gz: 0a5616fd089942547a6222eb6a7fd22f7825e0b7f219336a6e5904f58ede7bf58e454390c2ebcb2cc026951549d739661c515db1ec3cda9d2ede7009ea668bc8
6
+ metadata.gz: a035f44be760ad325105329cbaec12e90515afdade9649e798ee5c7cefced3271d4f3084afeef693c03c961541d9e5e45b2876734d1947dccdd24cc194acbca2
7
+ data.tar.gz: 61e3d071ce44809767f3501f12b452cae574f9ea0de668ca937731838d58e2a9fa85831829ce564f6a015177a86b2a05b1b6ddf60ba34ac515ea12c69ac618fe
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [v2.7.1](https://github.com/jwt/ruby-jwt/tree/v2.8.0) (2023-06-09)
4
+
5
+ [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.7.0...v2.8.0)
6
+
7
+ **Fixes and enhancements:**
8
+
9
+ - Handle invalid algorithm when decoding JWT [#559](https://github.com/jwt/ruby-jwt/pull/559) - [@nataliastanko](https://github.com/nataliastanko)
10
+ - Do not raise error when verifying bad HMAC signature [#563](https://github.com/jwt/ruby-jwt/pull/563) - [@hieuk09](https://github.com/hieuk09)
11
+
12
+ ## [v2.7.0](https://github.com/jwt/ruby-jwt/tree/v2.7.0) (2023-02-01)
13
+
14
+ [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.6.0...v2.7.0)
15
+
16
+ **Features:**
17
+
18
+ - Support OKP (Ed25519) keys for JWKs [#540](https://github.com/jwt/ruby-jwt/pull/540) ([@anakinj](https://github.com/anakinj))
19
+ - JWK Sets can now be used for tokens with nil kid [#543](https://github.com/jwt/ruby-jwt/pull/543) ([@bellebaum](https://github.com/bellebaum))
20
+
21
+ **Fixes and enhancements:**
22
+
23
+ - Fix issue with multiple keys returned by keyfinder and multiple allowed algorithms [#545](https://github.com/jwt/ruby-jwt/pull/545) ([@mpospelov](https://github.com/mpospelov))
24
+ - Non-string `kid` header values are now rejected [#543](https://github.com/jwt/ruby-jwt/pull/543) ([@bellebaum](https://github.com/bellebaum))
25
+
3
26
  ## [v2.6.0](https://github.com/jwt/ruby-jwt/tree/v2.6.0) (2022-12-22)
4
27
 
5
28
  [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.5.0...v2.6.0)
@@ -13,7 +36,7 @@
13
36
 
14
37
  **Fixes and enhancements:**
15
38
 
16
- - Raise descriptive error on empty hmac_secret and OpenSSL 3.0/openssl gem <3.0.1[#530](https://github.com/jwt/ruby-jwt/pull/530) ([@jonmchan](https://github.com/jonmchan)).
39
+ - Raise descriptive error on empty hmac_secret and OpenSSL 3.0/openssl gem <3.0.1 [#530](https://github.com/jwt/ruby-jwt/pull/530) ([@jonmchan](https://github.com/jonmchan)).
17
40
 
18
41
  ## [v2.5.0](https://github.com/jwt/ruby-jwt/tree/v2.5.0) (2022-08-25)
19
42
 
data/README.md CHANGED
@@ -569,7 +569,7 @@ end
569
569
 
570
570
  ### JSON Web Key (JWK)
571
571
 
572
- JWK is a JSON structure representing a cryptographic key. This gem currently supports RSA, EC and HMAC keys.
572
+ JWK is a JSON structure representing a cryptographic key. This gem currently supports RSA, EC, OKP and HMAC keys. OKP support requires [RbNaCl](https://github.com/RubyCrypto/rbnacl) and currently only supports the Ed25519 curve.
573
573
 
574
574
  To encode a JWT using your JWK:
575
575
 
@@ -579,7 +579,7 @@ jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), optional_parameters)
579
579
 
580
580
  # Encoding
581
581
  payload = { data: 'data' }
582
- token = JWT.encode(payload, jwk.keypair, jwk[:alg], kid: jwk[:kid])
582
+ token = JWT.encode(payload, jwk.signing_key, jwk[:alg], kid: jwk[:kid])
583
583
 
584
584
  # JSON Web Key Set for advertising your signing keys
585
585
  jwks_hash = JWT::JWK::Set.new(jwk).export
@@ -601,6 +601,9 @@ This can be used to implement caching of remotely fetched JWK Sets.
601
601
  If the requested `kid` is not found from the given set the loader will be called a second time with the `kid_not_found` option set to `true`.
602
602
  The application can choose to implement some kind of JWK cache invalidation or other mechanism to handle such cases.
603
603
 
604
+ Tokens without a specified `kid` are rejected by default.
605
+ This behaviour may be overwritten by setting the `allow_nil_kid` option for `decode` to `true`.
606
+
604
607
  ```ruby
605
608
  jwks_loader = ->(options) do
606
609
  # The jwk loader would fetch the set of JWKs from a trusted source.
@@ -650,8 +653,8 @@ jwk_hash = jwk.export
650
653
  jwk_hash_with_private_key = jwk.export(include_private: true)
651
654
 
652
655
  # Export as OpenSSL key
653
- public_key = jwk.public_key
654
- private_key = jwk.keypair if jwk.private?
656
+ public_key = jwk.verify_key
657
+ private_key = jwk.signing_key if jwk.private?
655
658
 
656
659
  # You can also import and export entire JSON Web Key Sets
657
660
  jwks_hash = { keys: [{ kty: 'oct', k: 'my-secret', kid: 'my-kid' }] }
@@ -11,7 +11,7 @@ module JWT
11
11
  end
12
12
 
13
13
  def valid_alg?(alg_to_check)
14
- alg.casecmp(alg_to_check)&.zero? == true
14
+ alg&.casecmp(alg_to_check)&.zero? == true
15
15
  end
16
16
 
17
17
  def sign(data:, signing_key:)
@@ -20,10 +20,6 @@ module JWT
20
20
 
21
21
  def verify(data:, signature:, verification_key:)
22
22
  cls.verify(alg, verification_key, data, signature)
23
- rescue OpenSSL::PKey::PKeyError # These should be moved to the algorithms that actually need this, but left here to ensure nothing will break.
24
- raise JWT::VerificationError, 'Signature verification raised'
25
- ensure
26
- OpenSSL.errors.clear
27
23
  end
28
24
  end
29
25
  end
@@ -38,7 +38,7 @@ module JWT
38
38
  end
39
39
 
40
40
  digest = OpenSSL::Digest.new(curve_definition[:digest])
41
- SecurityUtils.asn1_to_raw(key.dsa_sign_asn1(digest.digest(msg)), key)
41
+ asn1_to_raw(key.dsa_sign_asn1(digest.digest(msg)), key)
42
42
  end
43
43
 
44
44
  def verify(algorithm, public_key, signing_input, signature)
@@ -49,7 +49,9 @@ module JWT
49
49
  end
50
50
 
51
51
  digest = OpenSSL::Digest.new(curve_definition[:digest])
52
- public_key.dsa_verify_asn1(digest.digest(signing_input), SecurityUtils.raw_to_asn1(signature, public_key))
52
+ public_key.dsa_verify_asn1(digest.digest(signing_input), raw_to_asn1(signature, public_key))
53
+ rescue OpenSSL::PKey::PKeyError
54
+ raise JWT::VerificationError, 'Signature verification raised'
53
55
  end
54
56
 
55
57
  def curve_by_name(name)
@@ -57,6 +59,18 @@ module JWT
57
59
  raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
58
60
  end
59
61
  end
62
+
63
+ def raw_to_asn1(signature, private_key)
64
+ byte_size = (private_key.group.degree + 7) / 8
65
+ sig_bytes = signature[0..(byte_size - 1)]
66
+ sig_char = signature[byte_size..-1] || ''
67
+ OpenSSL::ASN1::Sequence.new([sig_bytes, sig_char].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
68
+ end
69
+
70
+ def asn1_to_raw(signature, public_key)
71
+ byte_size = (public_key.group.degree + 7) / 8
72
+ OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
73
+ end
60
74
  end
61
75
  end
62
76
  end
@@ -28,7 +28,7 @@ module JWT
28
28
  else
29
29
  Hmac.verify(algorithm, key, signing_input, signature)
30
30
  end
31
- rescue ::RbNaCl::BadAuthenticatorError
31
+ rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
32
32
  false
33
33
  end
34
34
 
@@ -36,7 +36,7 @@ module JWT
36
36
  else
37
37
  Hmac.verify(algorithm, key, signing_input, signature)
38
38
  end
39
- rescue ::RbNaCl::BadAuthenticatorError
39
+ rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
40
40
  false
41
41
  end
42
42
 
data/lib/jwt/algos/ps.rb CHANGED
@@ -12,9 +12,7 @@ module JWT
12
12
  def sign(algorithm, msg, key)
13
13
  require_openssl!
14
14
 
15
- key_class = key.class
16
-
17
- raise EncodeError, "The given key is a #{key_class}. It has to be an OpenSSL::PKey::RSA instance." if key_class == String
15
+ raise EncodeError, "The given key is a #{key_class}. It has to be an OpenSSL::PKey::RSA instance." if key.is_a?(String)
18
16
 
19
17
  translated_algorithm = algorithm.sub('PS', 'sha')
20
18
 
@@ -23,8 +21,10 @@ module JWT
23
21
 
24
22
  def verify(algorithm, public_key, signing_input, signature)
25
23
  require_openssl!
26
-
27
- SecurityUtils.verify_ps(algorithm, public_key, signing_input, signature)
24
+ translated_algorithm = algorithm.sub('PS', 'sha')
25
+ public_key.verify_pss(translated_algorithm, signature, signing_input, salt_length: :auto, mgf1_hash: translated_algorithm)
26
+ rescue OpenSSL::PKey::PKeyError
27
+ raise JWT::VerificationError, 'Signature verification raised'
28
28
  end
29
29
 
30
30
  def require_openssl!
data/lib/jwt/algos/rsa.rb CHANGED
@@ -14,7 +14,9 @@ module JWT
14
14
  end
15
15
 
16
16
  def verify(algorithm, public_key, signing_input, signature)
17
- SecurityUtils.verify_rsa(algorithm, public_key, signing_input, signature)
17
+ public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
18
+ rescue OpenSSL::PKey::PKeyError
19
+ raise JWT::VerificationError, 'Signature verification raised'
18
20
  end
19
21
  end
20
22
  end
data/lib/jwt/algos.rb CHANGED
@@ -7,7 +7,6 @@ rescue LoadError
7
7
  end
8
8
  require 'openssl'
9
9
 
10
- require 'jwt/security_utils'
11
10
  require 'jwt/algos/hmac'
12
11
  require 'jwt/algos/eddsa'
13
12
  require 'jwt/algos/ecdsa'
data/lib/jwt/decode.rb CHANGED
@@ -52,25 +52,25 @@ module JWT
52
52
  def verify_algo
53
53
  raise(JWT::IncorrectAlgorithm, 'An algorithm must be specified') if allowed_algorithms.empty?
54
54
  raise(JWT::IncorrectAlgorithm, 'Token is missing alg header') unless alg_in_header
55
- raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') unless valid_alg_in_header?
55
+ raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') if allowed_and_valid_algorithms.empty?
56
56
  end
57
57
 
58
58
  def set_key
59
59
  @key = find_key(&@keyfinder) if @keyfinder
60
- @key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks]).key_for(header['kid']) if @options[:jwks]
60
+ @key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks], allow_nil_kid: @options[:allow_nil_kid]).key_for(header['kid']) if @options[:jwks]
61
61
  if (x5c_options = @options[:x5c])
62
62
  @key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(header['x5c'])
63
63
  end
64
64
  end
65
65
 
66
66
  def verify_signature_for?(key)
67
- allowed_algorithms.any? do |alg|
67
+ allowed_and_valid_algorithms.any? do |alg|
68
68
  alg.verify(data: signing_input, signature: @signature, verification_key: key)
69
69
  end
70
70
  end
71
71
 
72
- def valid_alg_in_header?
73
- allowed_algorithms.any? { |alg| alg.valid_alg?(alg_in_header) }
72
+ def allowed_and_valid_algorithms
73
+ @allowed_and_valid_algorithms ||= allowed_algorithms.select { |alg| alg.valid_alg?(alg_in_header) }
74
74
  end
75
75
 
76
76
  # Order is very important - first check for string keys, next for symbols
data/lib/jwt/jwk/ec.rb CHANGED
@@ -5,9 +5,6 @@ require 'forwardable'
5
5
  module JWT
6
6
  module JWK
7
7
  class EC < KeyBase # rubocop:disable Metrics/ClassLength
8
- extend Forwardable
9
- def_delegators :keypair, :public_key
10
-
11
8
  KTY = 'EC'
12
9
  KTYS = [KTY, OpenSSL::PKey::EC, JWT::JWK::EC].freeze
13
10
  BINARY = 2
@@ -24,17 +21,29 @@ module JWT
24
21
  key_params = extract_key_params(key)
25
22
 
26
23
  params = params.transform_keys(&:to_sym)
27
- check_jwk(key_params, params)
24
+ check_jwk_params!(key_params, params)
28
25
 
29
26
  super(options, key_params.merge(params))
30
27
  end
31
28
 
32
29
  def keypair
33
- @keypair ||= create_ec_key(self[:crv], self[:x], self[:y], self[:d])
30
+ ec_key
34
31
  end
35
32
 
36
33
  def private?
37
- keypair.private_key?
34
+ ec_key.private_key?
35
+ end
36
+
37
+ def signing_key
38
+ ec_key
39
+ end
40
+
41
+ def verify_key
42
+ ec_key
43
+ end
44
+
45
+ def public_key
46
+ ec_key
38
47
  end
39
48
 
40
49
  def members
@@ -48,7 +57,7 @@ module JWT
48
57
  end
49
58
 
50
59
  def key_digest
51
- _crv, x_octets, y_octets = keypair_components(keypair)
60
+ _crv, x_octets, y_octets = keypair_components(ec_key)
52
61
  sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(x_octets, BINARY)),
53
62
  OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(y_octets, BINARY))])
54
63
  OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
@@ -64,12 +73,16 @@ module JWT
64
73
 
65
74
  private
66
75
 
76
+ def ec_key
77
+ @ec_key ||= create_ec_key(self[:crv], self[:x], self[:y], self[:d])
78
+ end
79
+
67
80
  def extract_key_params(key)
68
81
  case key
69
82
  when JWT::JWK::EC
70
83
  key.export(include_private: true)
71
84
  when OpenSSL::PKey::EC # Accept OpenSSL key as input
72
- @keypair = key # Preserve the object to avoid recreation
85
+ @ec_key = key # Preserve the object to avoid recreation
73
86
  parse_ec_key(key)
74
87
  when Hash
75
88
  key.transform_keys(&:to_sym)
@@ -78,10 +91,10 @@ module JWT
78
91
  end
79
92
  end
80
93
 
81
- def check_jwk(keypair, params)
94
+ def check_jwk_params!(key_params, params)
82
95
  raise ArgumentError, 'cannot overwrite cryptographic key attributes' unless (EC_KEY_ELEMENTS & params.keys).empty?
83
- raise JWT::JWKError, "Incorrect 'kty' value: #{keypair[:kty]}, expected #{KTY}" unless keypair[:kty] == KTY
84
- raise JWT::JWKError, 'Key format is invalid for EC' unless keypair[:crv] && keypair[:x] && keypair[:y]
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]
85
98
  end
86
99
 
87
100
  def keypair_components(ec_keypair)
data/lib/jwt/jwk/hmac.rb CHANGED
@@ -24,7 +24,7 @@ module JWT
24
24
  end
25
25
 
26
26
  def keypair
27
- self[:k]
27
+ secret
28
28
  end
29
29
 
30
30
  def private?
@@ -35,6 +35,14 @@ module JWT
35
35
  nil
36
36
  end
37
37
 
38
+ def verify_key
39
+ secret
40
+ end
41
+
42
+ def signing_key
43
+ secret
44
+ end
45
+
38
46
  # See https://tools.ietf.org/html/rfc7517#appendix-A.3
39
47
  def export(options = {})
40
48
  exported = parameters.clone
@@ -46,8 +54,6 @@ module JWT
46
54
  HMAC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] }
47
55
  end
48
56
 
49
- alias signing_key keypair # for backwards compatibility
50
-
51
57
  def key_digest
52
58
  sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::UTF8String.new(signing_key),
53
59
  OpenSSL::ASN1::UTF8String.new(KTY)])
@@ -64,6 +70,10 @@ module JWT
64
70
 
65
71
  private
66
72
 
73
+ def secret
74
+ self[:k]
75
+ end
76
+
67
77
  def extract_key_params(key)
68
78
  case key
69
79
  when JWT::JWK::HMAC
@@ -4,6 +4,7 @@ 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
9
 
9
10
  @jwks_loader = if jwks_or_loader.respond_to?(:call)
@@ -14,28 +15,31 @@ module JWT
14
15
  end
15
16
 
16
17
  def key_for(kid)
17
- 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)
18
20
 
19
21
  jwk = resolve_key(kid)
20
22
 
21
23
  raise ::JWT::DecodeError, 'No keys found in jwks' unless @jwks.any?
22
24
  raise ::JWT::DecodeError, "Could not find public key for kid #{kid}" unless jwk
23
25
 
24
- jwk.keypair
26
+ jwk.verify_key
25
27
  end
26
28
 
27
29
  private
28
30
 
29
31
  def resolve_key(kid)
32
+ key_matcher = ->(key) { (kid.nil? && @allow_nil_kid) || key[:kid] == kid }
33
+
30
34
  # First try without invalidation to facilitate application caching
31
35
  @jwks ||= JWT::JWK::Set.new(@jwks_loader.call(kid: kid))
32
- jwk = @jwks.find { |key| key[:kid] == kid }
36
+ jwk = @jwks.find { |key| key_matcher.call(key) }
33
37
 
34
38
  return jwk if jwk
35
39
 
36
40
  # Second try, invalidate for backwards compatibility
37
41
  @jwks = JWT::JWK::Set.new(@jwks_loader.call(invalidate: true, kid_not_found: true, kid: kid))
38
- @jwks.find { |key| key[:kid] == kid }
42
+ @jwks.find { |key| key_matcher.call(key) }
39
43
  end
40
44
  end
41
45
  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
@@ -22,21 +22,29 @@ module JWT
22
22
  key_params = extract_key_params(key)
23
23
 
24
24
  params = params.transform_keys(&:to_sym)
25
- check_jwk(key_params, params)
25
+ check_jwk_params!(key_params, params)
26
26
 
27
27
  super(options, key_params.merge(params))
28
28
  end
29
29
 
30
30
  def keypair
31
- @keypair ||= self.class.create_rsa_key(jwk_attributes(*(RSA_KEY_ELEMENTS - [:kty])))
31
+ rsa_key
32
32
  end
33
33
 
34
34
  def private?
35
- keypair.private?
35
+ rsa_key.private?
36
36
  end
37
37
 
38
38
  def public_key
39
- keypair.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
40
48
  end
41
49
 
42
50
  def export(options = {})
@@ -65,12 +73,16 @@ module JWT
65
73
 
66
74
  private
67
75
 
76
+ def rsa_key
77
+ @rsa_key ||= self.class.create_rsa_key(jwk_attributes(*(RSA_KEY_ELEMENTS - [:kty])))
78
+ end
79
+
68
80
  def extract_key_params(key)
69
81
  case key
70
82
  when JWT::JWK::RSA
71
83
  key.export(include_private: true)
72
84
  when OpenSSL::PKey::RSA # Accept OpenSSL key as input
73
- @keypair = key # Preserve the object to avoid recreation
85
+ @rsa_key = key # Preserve the object to avoid recreation
74
86
  parse_rsa_key(key)
75
87
  when Hash
76
88
  key.transform_keys(&:to_sym)
@@ -79,10 +91,10 @@ module JWT
79
91
  end
80
92
  end
81
93
 
82
- def check_jwk(keypair, params)
94
+ def check_jwk_params!(key_params, params)
83
95
  raise ArgumentError, 'cannot overwrite cryptographic key attributes' unless (RSA_KEY_ELEMENTS & params.keys).empty?
84
- raise JWT::JWKError, "Incorrect 'kty' value: #{keypair[:kty]}, expected #{KTY}" unless keypair[:kty] == KTY
85
- raise JWT::JWKError, 'Key format is invalid for RSA' unless keypair[:n] && keypair[:e]
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]
86
98
  end
87
99
 
88
100
  def parse_rsa_key(key)
data/lib/jwt/jwk.rb CHANGED
@@ -52,3 +52,4 @@ require_relative 'jwk/key_base'
52
52
  require_relative 'jwk/ec'
53
53
  require_relative 'jwk/rsa'
54
54
  require_relative 'jwk/hmac'
55
+ require_relative 'jwk/okp_rbnacl' if ::JWT.rbnacl?
data/lib/jwt/version.rb CHANGED
@@ -11,9 +11,9 @@ module JWT
11
11
  # major version
12
12
  MAJOR = 2
13
13
  # minor version
14
- MINOR = 6
14
+ MINOR = 7
15
15
  # tiny version
16
- TINY = 0
16
+ TINY = 1
17
17
  # alpha, beta, etc. tag
18
18
  PRE = nil
19
19
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jwt
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.0
4
+ version: 2.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Rudat
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-22 00:00:00.000000000 Z
11
+ date: 2023-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: appraisal
@@ -121,10 +121,10 @@ files:
121
121
  - lib/jwt/jwk/key_base.rb
122
122
  - lib/jwt/jwk/key_finder.rb
123
123
  - lib/jwt/jwk/kid_as_key_digest.rb
124
+ - lib/jwt/jwk/okp_rbnacl.rb
124
125
  - lib/jwt/jwk/rsa.rb
125
126
  - lib/jwt/jwk/set.rb
126
127
  - lib/jwt/jwk/thumbprint.rb
127
- - lib/jwt/security_utils.rb
128
128
  - lib/jwt/verify.rb
129
129
  - lib/jwt/version.rb
130
130
  - lib/jwt/x5c_key_finder.rb
@@ -134,7 +134,7 @@ licenses:
134
134
  - MIT
135
135
  metadata:
136
136
  bug_tracker_uri: https://github.com/jwt/ruby-jwt/issues
137
- changelog_uri: https://github.com/jwt/ruby-jwt/blob/v2.6.0/CHANGELOG.md
137
+ changelog_uri: https://github.com/jwt/ruby-jwt/blob/v2.7.1/CHANGELOG.md
138
138
  rubygems_mfa_required: 'true'
139
139
  post_install_message:
140
140
  rdoc_options: []
@@ -1,32 +0,0 @@
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