jwt 2.7.0 → 2.8.0

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: f2ff80d9f32962a3c98d469c1b1078c84631291153f89481cccd9fc8d311e925
4
- data.tar.gz: af54f20921b46237194671b957f9d0e2d04ec5ac501f8afa767f0d9d97e3acc6
3
+ metadata.gz: 79758529bec5a1fad3183ee054a2e47e800f6611681f9f9052cbb92ea645b4ef
4
+ data.tar.gz: d9bffb9bb0e855a2da3cc04ec0d3ee82174555b2bc23cceec8901b4c50b9a42a
5
5
  SHA512:
6
- metadata.gz: 7bc55b74e5565674e38b6f706ca2e9d70cc25ee679f80dd6dad160d1f5e8070749d919f65e487acfd7131a22246be66b2d82e517deb9c38df7bed86aaf7b61e8
7
- data.tar.gz: ed53859b1ac5423666d2351b7411014d74b6343fa0255bd5ed0b7ef5b58f8b073a6cc7905959866993ca4bbd7540b5e0a1fc3a08ce70f18fe82c9126f8cbc9b1
6
+ metadata.gz: e9622f261b3a037cfc6dddfdd8f452a123c0c0443a3599565830cc000f7a20eec9f5ad673450bd485a47a8c666e6c9c95729c2d8cc123c984e5d0a255ed41dc4
7
+ data.tar.gz: d0ef0c055249ac588d0cb26bc4ead5b1b607ae738eda5c0775723ca62640a9df092c3e07774c9fcb6545b9946d730f0260d40c2fc9e272fb7ec0ec62aa6f2b80
data/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## [v2.8.0](https://github.com/jwt/ruby-jwt/tree/v2.8.0) (2024-02-17)
4
+
5
+ [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.7.1...v2.8.0)
6
+
7
+ **Features:**
8
+
9
+ - Updated rubocop to 1.56 [#573](https://github.com/jwt/ruby-jwt/pull/573) ([@anakinj](https://github.com/anakinj))
10
+ - Run CI on Ruby 3.3 [#577](https://github.com/jwt/ruby-jwt/pull/577) ([@anakinj](https://github.com/anakinj))
11
+ - Deprecation warning added for the HMAC algorithm HS512256 (HMAC-SHA-512 truncated to 256-bits) [#575](https://github.com/jwt/ruby-jwt/pull/575) ([@anakinj](https://github.com/anakinj))
12
+ - Stop using RbNaCl for standard HMAC algorithms [#575](https://github.com/jwt/ruby-jwt/pull/575) ([@anakinj](https://github.com/anakinj))
13
+
14
+ **Fixes and enhancements:**
15
+
16
+ - Fix signature has expired error if payload is a string [#555](https://github.com/jwt/ruby-jwt/pull/555) ([@GobinathAL](https://github.com/GobinathAL))
17
+ - Fix key base equality and spaceship operators [#569](https://github.com/jwt/ruby-jwt/pull/569) ([@magneland](https://github.com/magneland))
18
+ - Remove explicit base64 require from x5c_key_finder [#580](https://github.com/jwt/ruby-jwt/pull/580) ([@anakinj](https://github.com/anakinj))
19
+ - Performance improvements and cleanup of tests [#581](https://github.com/jwt/ruby-jwt/pull/581) ([@anakinj](https://github.com/anakinj))
20
+ - Repair EC x/y coordinates when importing JWK [#585](https://github.com/jwt/ruby-jwt/pull/585) ([@julik](https://github.com/julik))
21
+ - Explicit dependency to the base64 gem [#582](https://github.com/jwt/ruby-jwt/pull/582) ([@anakinj](https://github.com/anakinj))
22
+ - Deprecation warning for decoding content not compliant with RFC 4648 [#582](https://github.com/jwt/ruby-jwt/pull/582) ([@anakinj](https://github.com/anakinj))
23
+ - Algorithms moved under the `::JWT::JWA` module ([@anakinj](https://github.com/anakinj))
24
+
25
+ ## [v2.7.1](https://github.com/jwt/ruby-jwt/tree/v2.8.0) (2023-06-09)
26
+
27
+ [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.7.0...v2.7.1)
28
+
29
+ **Fixes and enhancements:**
30
+
31
+ - Handle invalid algorithm when decoding JWT [#559](https://github.com/jwt/ruby-jwt/pull/559) ([@nataliastanko](https://github.com/nataliastanko))
32
+ - Do not raise error when verifying bad HMAC signature [#563](https://github.com/jwt/ruby-jwt/pull/563) ([@hieuk09](https://github.com/hieuk09))
33
+
3
34
  ## [v2.7.0](https://github.com/jwt/ruby-jwt/tree/v2.7.0) (2023-02-01)
4
35
 
5
36
  [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.6.0...v2.7.0)
@@ -20,14 +51,14 @@
20
51
 
21
52
  **Features:**
22
53
 
23
- - Support custom algorithms by passing algorithm objects[#512](https://github.com/jwt/ruby-jwt/pull/512) ([@anakinj](https://github.com/anakinj)).
24
- - Support descriptive (not key related) JWK parameters[#520](https://github.com/jwt/ruby-jwt/pull/520) ([@bellebaum](https://github.com/bellebaum)).
25
- - Support for JSON Web Key Sets[#525](https://github.com/jwt/ruby-jwt/pull/525) ([@bellebaum](https://github.com/bellebaum)).
26
- - Support HMAC keys over 32 chars when using RbNaCl[#521](https://github.com/jwt/ruby-jwt/pull/521) ([@anakinj](https://github.com/anakinj)).
54
+ - Support custom algorithms by passing algorithm objects [#512](https://github.com/jwt/ruby-jwt/pull/512) ([@anakinj](https://github.com/anakinj))
55
+ - Support descriptive (not key related) JWK parameters [#520](https://github.com/jwt/ruby-jwt/pull/520) ([@bellebaum](https://github.com/bellebaum))
56
+ - Support for JSON Web Key Sets [#525](https://github.com/jwt/ruby-jwt/pull/525) ([@bellebaum](https://github.com/bellebaum))
57
+ - Support HMAC keys over 32 chars when using RbNaCl [#521](https://github.com/jwt/ruby-jwt/pull/521) ([@anakinj](https://github.com/anakinj))
27
58
 
28
59
  **Fixes and enhancements:**
29
60
 
30
- - 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)).
61
+ - 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))
31
62
 
32
63
  ## [v2.5.0](https://github.com/jwt/ruby-jwt/tree/v2.5.0) (2022-08-25)
33
64
 
@@ -35,21 +66,21 @@
35
66
 
36
67
  **Features:**
37
68
 
38
- - Support JWK thumbprints as key ids [#481](https://github.com/jwt/ruby-jwt/pull/481) ([@anakinj](https://github.com/anakinj)).
39
- - Support OpenSSL >= 3.0 [#496](https://github.com/jwt/ruby-jwt/pull/496) ([@anakinj](https://github.com/anakinj)).
69
+ - Support JWK thumbprints as key ids [#481](https://github.com/jwt/ruby-jwt/pull/481) ([@anakinj](https://github.com/anakinj))
70
+ - Support OpenSSL >= 3.0 [#496](https://github.com/jwt/ruby-jwt/pull/496) ([@anakinj](https://github.com/anakinj))
40
71
 
41
72
  **Fixes and enhancements:**
42
- - Bring back the old Base64 (RFC2045) deocode mechanisms [#488](https://github.com/jwt/ruby-jwt/pull/488) ([@anakinj](https://github.com/anakinj)).
43
- - Rescue RbNaCl exception for EdDSA wrong key [#491](https://github.com/jwt/ruby-jwt/pull/491) ([@n-studio](https://github.com/n-studio)).
44
- - New parameter name for cases when kid is not found using JWK key loader proc [#501](https://github.com/jwt/ruby-jwt/pull/501) ([@anakinj](https://github.com/anakinj)).
45
- - Fix NoMethodError when a 2 segment token is missing 'alg' header [#502](https://github.com/jwt/ruby-jwt/pull/502) ([@cmrd-senya](https://github.com/cmrd-senya)).
73
+ - Bring back the old Base64 (RFC2045) deocode mechanisms [#488](https://github.com/jwt/ruby-jwt/pull/488) ([@anakinj](https://github.com/anakinj))
74
+ - Rescue RbNaCl exception for EdDSA wrong key [#491](https://github.com/jwt/ruby-jwt/pull/491) ([@n-studio](https://github.com/n-studio))
75
+ - New parameter name for cases when kid is not found using JWK key loader proc [#501](https://github.com/jwt/ruby-jwt/pull/501) ([@anakinj](https://github.com/anakinj))
76
+ - Fix NoMethodError when a 2 segment token is missing 'alg' header [#502](https://github.com/jwt/ruby-jwt/pull/502) ([@cmrd-senya](https://github.com/cmrd-senya))
46
77
 
47
78
  ## [v2.4.1](https://github.com/jwt/ruby-jwt/tree/v2.4.1) (2022-06-07)
48
79
 
49
80
  [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.4.0...v2.4.1)
50
81
 
51
82
  **Fixes and enhancements:**
52
- - Raise JWT::DecodeError on invalid signature [\#484](https://github.com/jwt/ruby-jwt/pull/484) ([@freakyfelt!](https://github.com/freakyfelt!)).
83
+ - Raise JWT::DecodeError on invalid signature [\#484](https://github.com/jwt/ruby-jwt/pull/484) ([@freakyfelt!](https://github.com/freakyfelt!))
53
84
 
54
85
  ## [v2.4.0](https://github.com/jwt/ruby-jwt/tree/v2.4.0) (2022-06-06)
55
86
 
@@ -57,20 +88,20 @@
57
88
 
58
89
  **Features:**
59
90
 
60
- - Dropped support for Ruby 2.5 and older [#453](https://github.com/jwt/ruby-jwt/pull/453) - [@anakinj](https://github.com/anakinj).
61
- - Use Ruby built-in url-safe base64 methods [#454](https://github.com/jwt/ruby-jwt/pull/454) - [@bdewater](https://github.com/bdewater).
62
- - Updated rubocop to 1.23.0 [#457](https://github.com/jwt/ruby-jwt/pull/457) - [@anakinj](https://github.com/anakinj).
63
- - Add x5c header key finder [#338](https://github.com/jwt/ruby-jwt/pull/338) - [@bdewater](https://github.com/bdewater).
64
- - Author driven changelog process [#463](https://github.com/jwt/ruby-jwt/pull/463) - [@anakinj](https://github.com/anakinj).
65
- - Allow regular expressions and procs to verify issuer [\#437](https://github.com/jwt/ruby-jwt/pull/437) ([rewritten](https://github.com/rewritten)).
66
- - Add Support to be able to verify from multiple keys [\#425](https://github.com/jwt/ruby-jwt/pull/425) ([ritikesh](https://github.com/ritikesh)).
91
+ - Dropped support for Ruby 2.5 and older [#453](https://github.com/jwt/ruby-jwt/pull/453) - ([@anakinj](https://github.com/anakinj))
92
+ - Use Ruby built-in url-safe base64 methods [#454](https://github.com/jwt/ruby-jwt/pull/454) - ([@bdewater](https://github.com/bdewater))
93
+ - Updated rubocop to 1.23.0 [#457](https://github.com/jwt/ruby-jwt/pull/457) - ([@anakinj](https://github.com/anakinj))
94
+ - Add x5c header key finder [#338](https://github.com/jwt/ruby-jwt/pull/338) - ([@bdewater](https://github.com/bdewater))
95
+ - Author driven changelog process [#463](https://github.com/jwt/ruby-jwt/pull/463) - ([@anakinj](https://github.com/anakinj))
96
+ - Allow regular expressions and procs to verify issuer [\#437](https://github.com/jwt/ruby-jwt/pull/437) ([rewritten](https://github.com/rewritten))
97
+ - Add Support to be able to verify from multiple keys [\#425](https://github.com/jwt/ruby-jwt/pull/425) ([ritikesh](https://github.com/ritikesh))
67
98
 
68
99
  **Fixes and enhancements:**
69
- - Readme: Typo fix re MissingRequiredClaim [\#451](https://github.com/jwt/ruby-jwt/pull/451) ([antonmorant](https://github.com/antonmorant)).
70
- - Fix RuboCop TODOs [\#476](https://github.com/jwt/ruby-jwt/pull/476) ([typhoon2099](https://github.com/typhoon2099)).
71
- - Make specific algorithms in README linkable [\#472](https://github.com/jwt/ruby-jwt/pull/472) ([milieu](https://github.com/milieu)).
72
- - Update note about supported JWK types [\#475](https://github.com/jwt/ruby-jwt/pull/475) ([dpashkevich](https://github.com/dpashkevich)).
73
- - Create CODE\_OF\_CONDUCT.md [\#449](https://github.com/jwt/ruby-jwt/pull/449) ([loic5](https://github.com/loic5)).
100
+ - Readme: Typo fix re MissingRequiredClaim [\#451](https://github.com/jwt/ruby-jwt/pull/451) ([antonmorant](https://github.com/antonmorant))
101
+ - Fix RuboCop TODOs [\#476](https://github.com/jwt/ruby-jwt/pull/476) ([typhoon2099](https://github.com/typhoon2099))
102
+ - Make specific algorithms in README linkable [\#472](https://github.com/jwt/ruby-jwt/pull/472) ([milieu](https://github.com/milieu))
103
+ - Update note about supported JWK types [\#475](https://github.com/jwt/ruby-jwt/pull/475) ([dpashkevich](https://github.com/dpashkevich))
104
+ - Create CODE\_OF\_CONDUCT.md [\#449](https://github.com/jwt/ruby-jwt/pull/449) ([loic5](https://github.com/loic5))
74
105
 
75
106
  ## [v2.3.0](https://github.com/jwt/ruby-jwt/tree/v2.3.0) (2021-10-03)
76
107
 
data/README.md CHANGED
@@ -72,7 +72,6 @@ puts decoded_token
72
72
  ### **HMAC**
73
73
 
74
74
  * HS256 - HMAC using SHA-256 hash algorithm
75
- * HS512256 - HMAC using SHA-512-256 hash algorithm (only available with RbNaCl; see note below)
76
75
  * HS384 - HMAC using SHA-384 hash algorithm
77
76
  * HS512 - HMAC using SHA-512 hash algorithm
78
77
 
@@ -95,12 +94,6 @@ decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
95
94
  puts decoded_token
96
95
  ```
97
96
 
98
- Note: If [RbNaCl](https://github.com/RubyCrypto/rbnacl) is loadable, ruby-jwt will use it for HMAC-SHA256, HMAC-SHA512-256, and HMAC-SHA512. RbNaCl prior to 6.0.0 only support a maximum key size of 32 bytes for these algorithms.
99
-
100
- [RbNaCl](https://github.com/RubyCrypto/rbnacl) requires
101
- [libsodium](https://github.com/jedisct1/libsodium), it can be installed
102
- on MacOS with `brew install libsodium`.
103
-
104
97
  ### **RSA**
105
98
 
106
99
  * RS256 - RSA using SHA-256 hash algorithm
@@ -561,7 +554,7 @@ crls = crl_uris.map do |uri|
561
554
  end
562
555
 
563
556
  begin
564
- JWT.decode(token, nil, true, { x5c: { root_certificates: root_certificates, crls: crls })
557
+ JWT.decode(token, nil, true, { x5c: { root_certificates: root_certificates, crls: crls } })
565
558
  rescue JWT::DecodeError
566
559
  # Handle error, e.g. x5c header certificate revoked or expired
567
560
  end
@@ -602,7 +595,7 @@ If the requested `kid` is not found from the given set the loader will be called
602
595
  The application can choose to implement some kind of JWK cache invalidation or other mechanism to handle such cases.
603
596
 
604
597
  Tokens without a specified `kid` are rejected by default.
605
- This behaviour may be overwritten by setting the `allow_nil_jwks` option for `decode` to `true`.
598
+ This behaviour may be overwritten by setting the `allow_nil_kid` option for `decode` to `true`.
606
599
 
607
600
  ```ruby
608
601
  jwks_loader = ->(options) do
data/lib/jwt/base64.rb CHANGED
@@ -3,14 +3,26 @@
3
3
  require 'base64'
4
4
 
5
5
  module JWT
6
- # Base64 helpers
6
+ # Base64 encoding and decoding
7
7
  class Base64
8
8
  class << self
9
+ # Encode a string with URL-safe Base64 complying with RFC 4648 (not padded).
9
10
  def url_encode(str)
10
- ::Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
11
+ ::Base64.urlsafe_encode64(str, padding: false)
11
12
  end
12
13
 
14
+ # Decode a string with URL-safe Base64 complying with RFC 4648.
15
+ # Deprecated support for RFC 2045 remains for now. ("All line breaks or other characters not found in Table 1 must be ignored by decoding software")
13
16
  def url_decode(str)
17
+ ::Base64.urlsafe_decode64(str)
18
+ rescue ArgumentError => e
19
+ raise unless e.message == 'invalid base64'
20
+
21
+ warn('[DEPRECATION] Invalid base64 input detected, could be because of invalid padding, trailing whitespaces or newline chars. Graceful handling of invalid input will be dropped in the next major version of ruby-jwt')
22
+ loose_urlsafe_decode64(str)
23
+ end
24
+
25
+ def loose_urlsafe_decode64(str)
14
26
  str += '=' * (4 - str.length.modulo(4))
15
27
  ::Base64.decode64(str.tr('-_', '+/'))
16
28
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './error'
3
+ require_relative 'error'
4
4
 
5
5
  module JWT
6
6
  class ClaimsValidator
data/lib/jwt/decode.rb CHANGED
@@ -92,13 +92,7 @@ module JWT
92
92
  end
93
93
 
94
94
  def resolve_allowed_algorithms
95
- algs = given_algorithms.map do |alg|
96
- if Algos.implementation?(alg)
97
- alg
98
- else
99
- Algos.create(alg)
100
- end
101
- end
95
+ algs = given_algorithms.map { |alg| JWA.create(alg) }
102
96
 
103
97
  sort_by_alg_header(algs)
104
98
  end
data/lib/jwt/encode.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'algos'
3
+ require_relative 'jwa'
4
4
  require_relative 'claims_validator'
5
5
 
6
6
  # JWT::Encode module
@@ -12,7 +12,7 @@ module JWT
12
12
  def initialize(options)
13
13
  @payload = options[:payload]
14
14
  @key = options[:key]
15
- @algorithm = resolve_algorithm(options[:algorithm])
15
+ @algorithm = JWA.create(options[:algorithm])
16
16
  @headers = options[:headers].transform_keys(&:to_s)
17
17
  @headers[ALG_KEY] = @algorithm.alg
18
18
  end
@@ -24,12 +24,6 @@ module JWT
24
24
 
25
25
  private
26
26
 
27
- def resolve_algorithm(algorithm)
28
- return algorithm if Algos.implementation?(algorithm)
29
-
30
- Algos.create(algorithm)
31
- end
32
-
33
27
  def encoded_header
34
28
  @encoded_header ||= encode_header
35
29
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWT
4
- module Algos
4
+ module JWA
5
5
  module Ecdsa
6
6
  module_function
7
7
 
@@ -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
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ module Eddsa
6
+ SUPPORTED = %w[ED25519 EdDSA].freeze
7
+ SUPPORTED_DOWNCASED = SUPPORTED.map(&:downcase).freeze
8
+
9
+ class << self
10
+ def sign(algorithm, msg, key)
11
+ unless key.is_a?(RbNaCl::Signatures::Ed25519::SigningKey)
12
+ raise EncodeError, "Key given is a #{key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey"
13
+ end
14
+
15
+ validate_algorithm!(algorithm)
16
+
17
+ key.sign(msg)
18
+ end
19
+
20
+ def verify(algorithm, public_key, signing_input, signature)
21
+ unless public_key.is_a?(RbNaCl::Signatures::Ed25519::VerifyKey)
22
+ raise DecodeError, "key given is a #{public_key.class} but has to be a RbNaCl::Signatures::Ed25519::VerifyKey"
23
+ end
24
+
25
+ validate_algorithm!(algorithm)
26
+
27
+ public_key.verify(signature, signing_input)
28
+ rescue RbNaCl::CryptoError
29
+ false
30
+ end
31
+
32
+ private
33
+
34
+ def validate_algorithm!(algorithm)
35
+ return if SUPPORTED_DOWNCASED.include?(algorithm.downcase)
36
+
37
+ raise IncorrectAlgorithm, "Algorithm #{algorithm} not supported. Supported algoritms are #{SUPPORTED.join(', ')}"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWT
4
- module Algos
4
+ module JWA
5
5
  module Hmac
6
6
  module_function
7
7
 
@@ -44,6 +44,7 @@ module JWT
44
44
  OpenSSL.fixed_length_secure_compare(a, b)
45
45
  end
46
46
  else
47
+ # :nocov:
47
48
  def fixed_length_secure_compare(a, b)
48
49
  raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
49
50
 
@@ -53,6 +54,7 @@ module JWT
53
54
  b.each_byte { |byte| res |= byte ^ l.shift }
54
55
  res == 0
55
56
  end
57
+ # :nocov:
56
58
  end
57
59
  module_function :fixed_length_secure_compare
58
60
 
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module Algos
5
+ module HmacRbNaCl
6
+ MAPPING = { 'HS512256' => ::RbNaCl::HMAC::SHA512256 }.freeze
7
+ SUPPORTED = MAPPING.keys
8
+ class << self
9
+ def sign(algorithm, msg, key)
10
+ warn("[DEPRECATION] The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
11
+ if (hmac = resolve_algorithm(algorithm))
12
+ hmac.auth(key_for_rbnacl(hmac, key).encode('binary'), msg.encode('binary'))
13
+ else
14
+ Hmac.sign(algorithm, msg, key)
15
+ end
16
+ end
17
+
18
+ def verify(algorithm, key, signing_input, signature)
19
+ warn("[DEPRECATION] The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
20
+ if (hmac = resolve_algorithm(algorithm))
21
+ hmac.verify(key_for_rbnacl(hmac, key).encode('binary'), signature.encode('binary'), signing_input.encode('binary'))
22
+ else
23
+ Hmac.verify(algorithm, key, signing_input, signature)
24
+ end
25
+ rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
26
+ false
27
+ end
28
+
29
+ private
30
+
31
+ def key_for_rbnacl(hmac, key)
32
+ key ||= ''
33
+ raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
34
+
35
+ return padded_empty_key(hmac.key_bytes) if key == ''
36
+
37
+ key
38
+ end
39
+
40
+ def resolve_algorithm(algorithm)
41
+ MAPPING.fetch(algorithm)
42
+ end
43
+
44
+ def padded_empty_key(length)
45
+ Array.new(length, 0x0).pack('C*').encode('binary')
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module Algos
5
+ module HmacRbNaClFixed
6
+ MAPPING = { 'HS512256' => ::RbNaCl::HMAC::SHA512256 }.freeze
7
+ SUPPORTED = MAPPING.keys
8
+
9
+ class << self
10
+ def sign(algorithm, msg, key)
11
+ key ||= ''
12
+ warn("[DEPRECATION] The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
13
+ raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
14
+
15
+ if (hmac = resolve_algorithm(algorithm)) && key.bytesize <= hmac.key_bytes
16
+ hmac.auth(padded_key_bytes(key, hmac.key_bytes), msg.encode('binary'))
17
+ else
18
+ Hmac.sign(algorithm, msg, key)
19
+ end
20
+ end
21
+
22
+ def verify(algorithm, key, signing_input, signature)
23
+ key ||= ''
24
+ warn("[DEPRECATION] The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
25
+ raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
26
+
27
+ if (hmac = resolve_algorithm(algorithm)) && key.bytesize <= hmac.key_bytes
28
+ hmac.verify(padded_key_bytes(key, hmac.key_bytes), signature.encode('binary'), signing_input.encode('binary'))
29
+ else
30
+ Hmac.verify(algorithm, key, signing_input, signature)
31
+ end
32
+ rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
33
+ false
34
+ end
35
+
36
+ def resolve_algorithm(algorithm)
37
+ MAPPING.fetch(algorithm)
38
+ end
39
+
40
+ def padded_key_bytes(key, bytesize)
41
+ key.bytes.fill(0, key.bytesize...bytesize).pack('C*')
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWT
4
- module Algos
4
+ module JWA
5
5
  module None
6
6
  module_function
7
7
 
data/lib/jwt/jwa/ps.rb ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ module Ps
6
+ # RSASSA-PSS signing algorithms
7
+
8
+ module_function
9
+
10
+ SUPPORTED = %w[PS256 PS384 PS512].freeze
11
+
12
+ def sign(algorithm, msg, key)
13
+ unless key.is_a?(::OpenSSL::PKey::RSA)
14
+ raise EncodeError, "The given key is a #{key_class}. It has to be an OpenSSL::PKey::RSA instance."
15
+ end
16
+
17
+ translated_algorithm = algorithm.sub('PS', 'sha')
18
+
19
+ key.sign_pss(translated_algorithm, msg, salt_length: :digest, mgf1_hash: translated_algorithm)
20
+ end
21
+
22
+ def verify(algorithm, public_key, signing_input, signature)
23
+ translated_algorithm = algorithm.sub('PS', 'sha')
24
+ public_key.verify_pss(translated_algorithm, signature, signing_input, salt_length: :auto, mgf1_hash: translated_algorithm)
25
+ rescue OpenSSL::PKey::PKeyError
26
+ raise JWT::VerificationError, 'Signature verification raised'
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ module Rsa
6
+ module_function
7
+
8
+ SUPPORTED = %w[RS256 RS384 RS512].freeze
9
+
10
+ def sign(algorithm, msg, key)
11
+ unless key.is_a?(OpenSSL::PKey::RSA)
12
+ raise EncodeError, "The given key is a #{key.class}. It has to be an OpenSSL::PKey::RSA instance"
13
+ end
14
+
15
+ key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
16
+ end
17
+
18
+ def verify(algorithm, public_key, signing_input, signature)
19
+ public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
20
+ rescue OpenSSL::PKey::PKeyError
21
+ raise JWT::VerificationError, 'Signature verification raised'
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWT
4
- module Algos
4
+ module JWA
5
5
  module Unsupported
6
6
  module_function
7
7
 
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWT
4
- module Algos
5
- class AlgoWrapper
4
+ module JWA
5
+ class Wrapper
6
6
  attr_reader :alg, :cls
7
7
 
8
8
  def initialize(alg, cls)
@@ -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
data/lib/jwt/jwa.rb ADDED
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ begin
6
+ require 'rbnacl'
7
+ rescue LoadError
8
+ raise if defined?(RbNaCl)
9
+ end
10
+
11
+ require_relative 'jwa/hmac'
12
+ require_relative 'jwa/eddsa'
13
+ require_relative 'jwa/ecdsa'
14
+ require_relative 'jwa/rsa'
15
+ require_relative 'jwa/ps'
16
+ require_relative 'jwa/none'
17
+ require_relative 'jwa/unsupported'
18
+ require_relative 'jwa/wrapper'
19
+
20
+ module JWT
21
+ module JWA
22
+ ALGOS = [Hmac, Ecdsa, Rsa, Eddsa, Ps, None, Unsupported].tap do |l|
23
+ if ::JWT.rbnacl_6_or_greater?
24
+ require_relative 'jwa/hmac_rbnacl'
25
+ l << Algos::HmacRbNaCl
26
+ elsif ::JWT.rbnacl?
27
+ require_relative 'jwa/hmac_rbnacl_fixed'
28
+ l << Algos::HmacRbNaClFixed
29
+ end
30
+ end.freeze
31
+
32
+ class << self
33
+ def find(algorithm)
34
+ indexed[algorithm&.downcase]
35
+ end
36
+
37
+ def create(algorithm)
38
+ return algorithm if JWA.implementation?(algorithm)
39
+
40
+ Wrapper.new(*find(algorithm))
41
+ end
42
+
43
+ def implementation?(algorithm)
44
+ (algorithm.respond_to?(:valid_alg?) && algorithm.respond_to?(:verify)) ||
45
+ (algorithm.respond_to?(:alg) && algorithm.respond_to?(:sign))
46
+ end
47
+
48
+ private
49
+
50
+ def indexed
51
+ @indexed ||= begin
52
+ fallback = [nil, Unsupported]
53
+ ALGOS.each_with_object(Hash.new(fallback)) do |cls, hash|
54
+ cls.const_get(:SUPPORTED).each do |alg|
55
+ hash[alg.downcase] = [alg, cls]
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
data/lib/jwt/jwk/ec.rb CHANGED
@@ -11,6 +11,7 @@ module JWT
11
11
  EC_PUBLIC_KEY_ELEMENTS = %i[kty crv x y].freeze
12
12
  EC_PRIVATE_KEY_ELEMENTS = %i[d].freeze
13
13
  EC_KEY_ELEMENTS = (EC_PRIVATE_KEY_ELEMENTS + EC_PUBLIC_KEY_ELEMENTS).freeze
14
+ ZERO_BYTE = "\0".b.freeze
14
15
 
15
16
  def initialize(key, params = nil, options = {})
16
17
  params ||= {}
@@ -143,7 +144,6 @@ module JWT
143
144
  if ::JWT.openssl_3?
144
145
  def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d) # rubocop:disable Metrics/MethodLength
145
146
  curve = EC.to_openssl_curve(jwk_crv)
146
-
147
147
  x_octets = decode_octets(jwk_x)
148
148
  y_octets = decode_octets(jwk_y)
149
149
 
@@ -205,12 +205,27 @@ module JWT
205
205
  end
206
206
  end
207
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)
208
+ def decode_octets(base64_encoded_coordinate)
209
+ bytes = ::JWT::Base64.url_decode(base64_encoded_coordinate)
210
+ # Some base64 encoders on some platform omit a single 0-byte at
211
+ # the start of either Y or X coordinate of the elliptic curve point.
212
+ # This leads to an encoding error when data is passed to OpenSSL BN.
213
+ # It is know to have happend to exported JWKs on a Java application and
214
+ # on a Flutter/Dart application (both iOS and Android). All that is
215
+ # needed to fix the problem is adding a leading 0-byte. We know the
216
+ # required byte is 0 because with any other byte the point is no longer
217
+ # on the curve - and OpenSSL will actually communicate this via another
218
+ # exception. The indication of a stripped byte will be the fact that the
219
+ # coordinates - once decoded into bytes - should always be an even
220
+ # bytesize. For example, with a P-521 curve, both x and y must be 66 bytes.
221
+ # With a P-256 curve, both x and y must be 32 and so on. The simplest way
222
+ # to check for this truncation is thus to check whether the number of bytes
223
+ # is odd, and restore the leading 0-byte if it is.
224
+ if bytes.bytesize.odd?
225
+ ZERO_BYTE + bytes
226
+ else
227
+ bytes
228
+ end
214
229
  end
215
230
 
216
231
  class << self
@@ -38,12 +38,14 @@ module JWT
38
38
  end
39
39
 
40
40
  def ==(other)
41
- self[:kid] == other[:kid]
41
+ other.is_a?(::JWT::JWK::KeyBase) && self[:kid] == other[:kid]
42
42
  end
43
43
 
44
44
  alias eql? ==
45
45
 
46
46
  def <=>(other)
47
+ return nil unless other.is_a?(::JWT::JWK::KeyBase)
48
+
47
49
  self[:kid] <=> other[:kid]
48
50
  end
49
51
 
data/lib/jwt/jwk.rb CHANGED
@@ -52,4 +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?
55
+ require_relative 'jwk/okp_rbnacl' if JWT.rbnacl?
data/lib/jwt/verify.rb CHANGED
@@ -38,12 +38,12 @@ module JWT
38
38
  end
39
39
 
40
40
  def verify_expiration
41
- return unless @payload.include?('exp')
41
+ return unless contains_key?(@payload, 'exp')
42
42
  raise(JWT::ExpiredSignature, 'Signature has expired') if @payload['exp'].to_i <= (Time.now.to_i - exp_leeway)
43
43
  end
44
44
 
45
45
  def verify_iat
46
- return unless @payload.include?('iat')
46
+ return unless contains_key?(@payload, 'iat')
47
47
 
48
48
  iat = @payload['iat']
49
49
  raise(JWT::InvalidIatError, 'Invalid iat') if !iat.is_a?(Numeric) || iat.to_f > Time.now.to_f
@@ -77,7 +77,7 @@ module JWT
77
77
  end
78
78
 
79
79
  def verify_not_before
80
- return unless @payload.include?('nbf')
80
+ return unless contains_key?(@payload, 'nbf')
81
81
  raise(JWT::ImmatureSignature, 'Signature nbf has not been reached') if @payload['nbf'].to_i > (Time.now.to_i + nbf_leeway)
82
82
  end
83
83
 
@@ -92,7 +92,7 @@ module JWT
92
92
  return unless (options_required_claims = @options[:required_claims])
93
93
 
94
94
  options_required_claims.each do |required_claim|
95
- raise(JWT::MissingRequiredClaim, "Missing required claim #{required_claim}") unless @payload.include?(required_claim)
95
+ raise(JWT::MissingRequiredClaim, "Missing required claim #{required_claim}") unless contains_key?(@payload, required_claim)
96
96
  end
97
97
  end
98
98
 
@@ -109,5 +109,9 @@ module JWT
109
109
  def nbf_leeway
110
110
  @options[:nbf_leeway] || global_leeway
111
111
  end
112
+
113
+ def contains_key?(payload, key)
114
+ payload.respond_to?(:key?) && payload.key?(key)
115
+ end
112
116
  end
113
117
  end
data/lib/jwt/version.rb CHANGED
@@ -11,7 +11,7 @@ module JWT
11
11
  # major version
12
12
  MAJOR = 2
13
13
  # minor version
14
- MINOR = 7
14
+ MINOR = 8
15
15
  # tiny version
16
16
  TINY = 0
17
17
  # alpha, beta, etc. tag
@@ -23,7 +23,8 @@ module JWT
23
23
 
24
24
  def self.openssl_3?
25
25
  return false if OpenSSL::OPENSSL_VERSION.include?('LibreSSL')
26
- return true if OpenSSL::OPENSSL_VERSION_NUMBER >= 3 * 0x10000000
26
+
27
+ true if 3 * 0x10000000 <= OpenSSL::OPENSSL_VERSION_NUMBER
27
28
  end
28
29
 
29
30
  def self.rbnacl?
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'base64'
4
- require 'jwt/error'
5
-
6
3
  module JWT
7
4
  # If the x5c header certificate chain can be validated by trusted root
8
5
  # certificates, and none of the certificates are revoked, returns the public
data/ruby-jwt.gemspec CHANGED
@@ -31,9 +31,12 @@ Gem::Specification.new do |spec|
31
31
  spec.executables = []
32
32
  spec.require_paths = %w[lib]
33
33
 
34
+ spec.add_dependency 'base64'
35
+
34
36
  spec.add_development_dependency 'appraisal'
35
37
  spec.add_development_dependency 'bundler'
36
38
  spec.add_development_dependency 'rake'
37
39
  spec.add_development_dependency 'rspec'
40
+ spec.add_development_dependency 'rubocop'
38
41
  spec.add_development_dependency 'simplecov'
39
42
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jwt
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.0
4
+ version: 2.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Rudat
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-01 00:00:00.000000000 Z
11
+ date: 2024-02-17 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base64
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: appraisal
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +80,20 @@ dependencies:
66
80
  - - ">="
67
81
  - !ruby/object:Gem::Version
68
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
69
97
  - !ruby/object:Gem::Dependency
70
98
  name: simplecov
71
99
  requirement: !ruby/object:Gem::Requirement
@@ -94,17 +122,6 @@ files:
94
122
  - LICENSE
95
123
  - README.md
96
124
  - lib/jwt.rb
97
- - lib/jwt/algos.rb
98
- - lib/jwt/algos/algo_wrapper.rb
99
- - lib/jwt/algos/ecdsa.rb
100
- - lib/jwt/algos/eddsa.rb
101
- - lib/jwt/algos/hmac.rb
102
- - lib/jwt/algos/hmac_rbnacl.rb
103
- - lib/jwt/algos/hmac_rbnacl_fixed.rb
104
- - lib/jwt/algos/none.rb
105
- - lib/jwt/algos/ps.rb
106
- - lib/jwt/algos/rsa.rb
107
- - lib/jwt/algos/unsupported.rb
108
125
  - lib/jwt/base64.rb
109
126
  - lib/jwt/claims_validator.rb
110
127
  - lib/jwt/configuration.rb
@@ -115,6 +132,17 @@ files:
115
132
  - lib/jwt/encode.rb
116
133
  - lib/jwt/error.rb
117
134
  - lib/jwt/json.rb
135
+ - lib/jwt/jwa.rb
136
+ - lib/jwt/jwa/ecdsa.rb
137
+ - lib/jwt/jwa/eddsa.rb
138
+ - lib/jwt/jwa/hmac.rb
139
+ - lib/jwt/jwa/hmac_rbnacl.rb
140
+ - lib/jwt/jwa/hmac_rbnacl_fixed.rb
141
+ - lib/jwt/jwa/none.rb
142
+ - lib/jwt/jwa/ps.rb
143
+ - lib/jwt/jwa/rsa.rb
144
+ - lib/jwt/jwa/unsupported.rb
145
+ - lib/jwt/jwa/wrapper.rb
118
146
  - lib/jwt/jwk.rb
119
147
  - lib/jwt/jwk/ec.rb
120
148
  - lib/jwt/jwk/hmac.rb
@@ -125,7 +153,6 @@ files:
125
153
  - lib/jwt/jwk/rsa.rb
126
154
  - lib/jwt/jwk/set.rb
127
155
  - lib/jwt/jwk/thumbprint.rb
128
- - lib/jwt/security_utils.rb
129
156
  - lib/jwt/verify.rb
130
157
  - lib/jwt/version.rb
131
158
  - lib/jwt/x5c_key_finder.rb
@@ -135,7 +162,7 @@ licenses:
135
162
  - MIT
136
163
  metadata:
137
164
  bug_tracker_uri: https://github.com/jwt/ruby-jwt/issues
138
- changelog_uri: https://github.com/jwt/ruby-jwt/blob/v2.7.0/CHANGELOG.md
165
+ changelog_uri: https://github.com/jwt/ruby-jwt/blob/v2.8.0/CHANGELOG.md
139
166
  rubygems_mfa_required: 'true'
140
167
  post_install_message:
141
168
  rdoc_options: []
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module JWT
4
- module Algos
5
- module Eddsa
6
- module_function
7
-
8
- SUPPORTED = %w[ED25519 EdDSA].freeze
9
-
10
- def sign(algorithm, msg, key)
11
- if key.class != RbNaCl::Signatures::Ed25519::SigningKey
12
- raise EncodeError, "Key given is a #{key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey"
13
- end
14
- unless SUPPORTED.map(&:downcase).map(&:to_sym).include?(algorithm.downcase.to_sym)
15
- raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key.primitive} signing key was provided"
16
- end
17
-
18
- key.sign(msg)
19
- end
20
-
21
- def verify(algorithm, public_key, signing_input, signature)
22
- unless SUPPORTED.map(&:downcase).map(&:to_sym).include?(algorithm.downcase.to_sym)
23
- raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key.primitive} signing key was provided"
24
- end
25
- raise DecodeError, "key given is a #{public_key.class} but has to be a RbNaCl::Signatures::Ed25519::VerifyKey" if public_key.class != RbNaCl::Signatures::Ed25519::VerifyKey
26
-
27
- public_key.verify(signature, signing_input)
28
- rescue RbNaCl::CryptoError
29
- false
30
- end
31
- end
32
- end
33
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module JWT
4
- module Algos
5
- module HmacRbNaCl
6
- module_function
7
-
8
- MAPPING = {
9
- 'HS256' => ::RbNaCl::HMAC::SHA256,
10
- 'HS512256' => ::RbNaCl::HMAC::SHA512256,
11
- 'HS384' => nil,
12
- 'HS512' => ::RbNaCl::HMAC::SHA512
13
- }.freeze
14
-
15
- SUPPORTED = MAPPING.keys
16
-
17
- def sign(algorithm, msg, key)
18
- if (hmac = resolve_algorithm(algorithm))
19
- hmac.auth(key_for_rbnacl(hmac, key).encode('binary'), msg.encode('binary'))
20
- else
21
- Hmac.sign(algorithm, msg, key)
22
- end
23
- end
24
-
25
- def verify(algorithm, key, signing_input, signature)
26
- if (hmac = resolve_algorithm(algorithm))
27
- hmac.verify(key_for_rbnacl(hmac, key).encode('binary'), signature.encode('binary'), signing_input.encode('binary'))
28
- else
29
- Hmac.verify(algorithm, key, signing_input, signature)
30
- end
31
- rescue ::RbNaCl::BadAuthenticatorError
32
- false
33
- end
34
-
35
- def key_for_rbnacl(hmac, key)
36
- key ||= ''
37
- raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
38
-
39
- return padded_empty_key(hmac.key_bytes) if key == ''
40
-
41
- key
42
- end
43
-
44
- def resolve_algorithm(algorithm)
45
- MAPPING.fetch(algorithm)
46
- end
47
-
48
- def padded_empty_key(length)
49
- Array.new(length, 0x0).pack('C*').encode('binary')
50
- end
51
- end
52
- end
53
- end
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module JWT
4
- module Algos
5
- module HmacRbNaClFixed
6
- module_function
7
-
8
- MAPPING = {
9
- 'HS256' => ::RbNaCl::HMAC::SHA256,
10
- 'HS512256' => ::RbNaCl::HMAC::SHA512256,
11
- 'HS384' => nil,
12
- 'HS512' => ::RbNaCl::HMAC::SHA512
13
- }.freeze
14
-
15
- SUPPORTED = MAPPING.keys
16
-
17
- def sign(algorithm, msg, key)
18
- key ||= ''
19
-
20
- raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
21
-
22
- if (hmac = resolve_algorithm(algorithm)) && key.bytesize <= hmac.key_bytes
23
- hmac.auth(padded_key_bytes(key, hmac.key_bytes), msg.encode('binary'))
24
- else
25
- Hmac.sign(algorithm, msg, key)
26
- end
27
- end
28
-
29
- def verify(algorithm, key, signing_input, signature)
30
- key ||= ''
31
-
32
- raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
33
-
34
- if (hmac = resolve_algorithm(algorithm)) && key.bytesize <= hmac.key_bytes
35
- hmac.verify(padded_key_bytes(key, hmac.key_bytes), signature.encode('binary'), signing_input.encode('binary'))
36
- else
37
- Hmac.verify(algorithm, key, signing_input, signature)
38
- end
39
- rescue ::RbNaCl::BadAuthenticatorError
40
- false
41
- end
42
-
43
- def resolve_algorithm(algorithm)
44
- MAPPING.fetch(algorithm)
45
- end
46
-
47
- def padded_key_bytes(key, bytesize)
48
- key.bytes.fill(0, key.bytesize...bytesize).pack('C*')
49
- end
50
- end
51
- end
52
- end
data/lib/jwt/algos/ps.rb DELETED
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module JWT
4
- module Algos
5
- module Ps
6
- # RSASSA-PSS signing algorithms
7
-
8
- module_function
9
-
10
- SUPPORTED = %w[PS256 PS384 PS512].freeze
11
-
12
- def sign(algorithm, msg, key)
13
- require_openssl!
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
18
-
19
- translated_algorithm = algorithm.sub('PS', 'sha')
20
-
21
- key.sign_pss(translated_algorithm, msg, salt_length: :digest, mgf1_hash: translated_algorithm)
22
- end
23
-
24
- def verify(algorithm, public_key, signing_input, signature)
25
- require_openssl!
26
-
27
- SecurityUtils.verify_ps(algorithm, public_key, signing_input, signature)
28
- end
29
-
30
- def require_openssl!
31
- if Object.const_defined?('OpenSSL')
32
- if ::Gem::Version.new(OpenSSL::VERSION) < ::Gem::Version.new('2.1')
33
- raise JWT::RequiredDependencyError, "You currently have OpenSSL #{OpenSSL::VERSION}. PS support requires >= 2.1"
34
- end
35
- else
36
- raise JWT::RequiredDependencyError, 'PS signing requires OpenSSL +2.1'
37
- end
38
- end
39
- end
40
- end
41
- end
data/lib/jwt/algos/rsa.rb DELETED
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module JWT
4
- module Algos
5
- module Rsa
6
- module_function
7
-
8
- SUPPORTED = %w[RS256 RS384 RS512].freeze
9
-
10
- def sign(algorithm, msg, key)
11
- raise EncodeError, "The given key is a #{key.class}. It has to be an OpenSSL::PKey::RSA instance." if key.instance_of?(String)
12
-
13
- key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
14
- end
15
-
16
- def verify(algorithm, public_key, signing_input, signature)
17
- SecurityUtils.verify_rsa(algorithm, public_key, signing_input, signature)
18
- end
19
- end
20
- end
21
- end
data/lib/jwt/algos.rb DELETED
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- begin
4
- require 'rbnacl'
5
- rescue LoadError
6
- raise if defined?(RbNaCl)
7
- end
8
- require 'openssl'
9
-
10
- require 'jwt/security_utils'
11
- require 'jwt/algos/hmac'
12
- require 'jwt/algos/eddsa'
13
- require 'jwt/algos/ecdsa'
14
- require 'jwt/algos/rsa'
15
- require 'jwt/algos/ps'
16
- require 'jwt/algos/none'
17
- require 'jwt/algos/unsupported'
18
- require 'jwt/algos/algo_wrapper'
19
-
20
- module JWT
21
- module Algos
22
- extend self
23
-
24
- ALGOS = [Algos::Ecdsa,
25
- Algos::Rsa,
26
- Algos::Eddsa,
27
- Algos::Ps,
28
- Algos::None,
29
- Algos::Unsupported].tap do |l|
30
- if ::JWT.rbnacl_6_or_greater?
31
- require_relative 'algos/hmac_rbnacl'
32
- l.unshift(Algos::HmacRbNaCl)
33
- elsif ::JWT.rbnacl?
34
- require_relative 'algos/hmac_rbnacl_fixed'
35
- l.unshift(Algos::HmacRbNaClFixed)
36
- else
37
- l.unshift(Algos::Hmac)
38
- end
39
- end.freeze
40
-
41
- def find(algorithm)
42
- indexed[algorithm && algorithm.downcase]
43
- end
44
-
45
- def create(algorithm)
46
- Algos::AlgoWrapper.new(*find(algorithm))
47
- end
48
-
49
- def implementation?(algorithm)
50
- (algorithm.respond_to?(:valid_alg?) && algorithm.respond_to?(:verify)) ||
51
- (algorithm.respond_to?(:alg) && algorithm.respond_to?(:sign))
52
- end
53
-
54
- private
55
-
56
- def indexed
57
- @indexed ||= begin
58
- fallback = [nil, Algos::Unsupported]
59
- ALGOS.each_with_object(Hash.new(fallback)) do |cls, hash|
60
- cls.const_get(:SUPPORTED).each do |alg|
61
- hash[alg.downcase] = [alg, cls]
62
- end
63
- end
64
- end
65
- end
66
- end
67
- end
@@ -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