From 13dd33357c1440563e6b870f4d2455eeb18e25b4 Mon Sep 17 00:00:00 2001 From: Joakim Antman Date: Sun, 17 Dec 2023 19:12:53 +0200 Subject: [PATCH] Algorithm cleanup. - Algos module renamed to JWA - Standard HMAC algorithms provided explicitly by OpenSSL (#550) - Prepare to remove support for the HS512256 algorithm provided by RbNaCl (#549) --- CHANGELOG.md | 3 + README.md | 7 -- lib/jwt/algos.rb | 66 ------------------- lib/jwt/algos/eddsa.rb | 33 ---------- lib/jwt/algos/hmac_rbnacl.rb | 53 --------------- lib/jwt/algos/hmac_rbnacl_fixed.rb | 52 --------------- lib/jwt/decode.rb | 8 +-- lib/jwt/encode.rb | 10 +-- lib/jwt/jwa.rb | 62 +++++++++++++++++ lib/jwt/{algos => jwa}/ecdsa.rb | 2 +- lib/jwt/jwa/eddsa.rb | 42 ++++++++++++ lib/jwt/{algos => jwa}/hmac.rb | 4 +- lib/jwt/jwa/hmac_rbnacl.rb | 50 ++++++++++++++ lib/jwt/jwa/hmac_rbnacl_fixed.rb | 46 +++++++++++++ lib/jwt/{algos => jwa}/none.rb | 2 +- lib/jwt/{algos => jwa}/ps.rb | 19 ++---- lib/jwt/{algos => jwa}/rsa.rb | 6 +- lib/jwt/{algos => jwa}/unsupported.rb | 2 +- .../{algos/algo_wrapper.rb => jwa/wrapper.rb} | 4 +- spec/jwt/algos/hmac_rbnacl_fixed_spec.rb | 54 --------------- spec/jwt/algos/hmac_rbnacl_spec.rb | 54 --------------- spec/jwt/{algos => jwa}/ecdsa_spec.rb | 2 +- spec/jwt/{algos => jwa}/hmac_spec.rb | 2 +- spec/jwt/jwt_spec.rb | 2 +- 24 files changed, 225 insertions(+), 360 deletions(-) delete mode 100644 lib/jwt/algos.rb delete mode 100644 lib/jwt/algos/eddsa.rb delete mode 100644 lib/jwt/algos/hmac_rbnacl.rb delete mode 100644 lib/jwt/algos/hmac_rbnacl_fixed.rb create mode 100644 lib/jwt/jwa.rb rename lib/jwt/{algos => jwa}/ecdsa.rb (99%) create mode 100644 lib/jwt/jwa/eddsa.rb rename lib/jwt/{algos => jwa}/hmac.rb (97%) create mode 100644 lib/jwt/jwa/hmac_rbnacl.rb create mode 100644 lib/jwt/jwa/hmac_rbnacl_fixed.rb rename lib/jwt/{algos => jwa}/none.rb (93%) rename lib/jwt/{algos => jwa}/ps.rb (57%) rename lib/jwt/{algos => jwa}/rsa.rb (75%) rename lib/jwt/{algos => jwa}/unsupported.rb (95%) rename lib/jwt/{algos/algo_wrapper.rb => jwa/wrapper.rb} (92%) delete mode 100644 spec/jwt/algos/hmac_rbnacl_fixed_spec.rb delete mode 100644 spec/jwt/algos/hmac_rbnacl_spec.rb rename spec/jwt/{algos => jwa}/ecdsa_spec.rb (96%) rename spec/jwt/{algos => jwa}/hmac_spec.rb (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5e20d82..932f299a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ - Updated rubocop to 1.56 [#573](https://github.com/jwt/ruby-jwt/pull/573) - [@anakinj](https://github.com/anakinj). - Run CI on Ruby 3.3 [#577](https://github.com/jwt/ruby-jwt/pull/577) - [@anakinj](https://github.com/anakinj). +- 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)). +- Stop using RbNaCl for standard HMAC algorithms [#575](https://github.com/jwt/ruby-jwt/pull/575) ([@anakinj](https://github.com/anakinj)). - Your contribution here **Fixes and enhancements:** @@ -19,6 +21,7 @@ - Repair EC x/y coordinates when importing JWK [#585](https://github.com/jwt/ruby-jwt/pull/585) - [@julik](https://github.com/julik). - Explicit dependency to the base64 gem [#582](https://github.com/jwt/ruby-jwt/pull/582) - [@anakinj](https://github.com/anakinj). - Deprecation warning for decoding content not compliant with RFC 4648 [#582](https://github.com/jwt/ruby-jwt/pull/582) - [@anakinj](https://github.com/anakinj). +- Algorithms moved under the `::JWT::JWA` module ([@anakinj](https://github.com/anakinj)). - Your contribution here ## [v2.7.1](https://github.com/jwt/ruby-jwt/tree/v2.8.0) (2023-06-09) diff --git a/README.md b/README.md index 52427485..ca9cf98a 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,6 @@ puts decoded_token ### **HMAC** * HS256 - HMAC using SHA-256 hash algorithm -* HS512256 - HMAC using SHA-512-256 hash algorithm (only available with RbNaCl; see note below) * HS384 - HMAC using SHA-384 hash algorithm * HS512 - HMAC using SHA-512 hash algorithm @@ -95,12 +94,6 @@ decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' } puts decoded_token ``` -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. - -[RbNaCl](https://github.com/RubyCrypto/rbnacl) requires -[libsodium](https://github.com/jedisct1/libsodium), it can be installed -on MacOS with `brew install libsodium`. - ### **RSA** * RS256 - RSA using SHA-256 hash algorithm diff --git a/lib/jwt/algos.rb b/lib/jwt/algos.rb deleted file mode 100644 index 96f71b63..00000000 --- a/lib/jwt/algos.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -begin - require 'rbnacl' -rescue LoadError - raise if defined?(RbNaCl) -end -require 'openssl' - -require 'jwt/algos/hmac' -require 'jwt/algos/eddsa' -require 'jwt/algos/ecdsa' -require 'jwt/algos/rsa' -require 'jwt/algos/ps' -require 'jwt/algos/none' -require 'jwt/algos/unsupported' -require 'jwt/algos/algo_wrapper' - -module JWT - module Algos - extend self - - ALGOS = [Algos::Ecdsa, - Algos::Rsa, - Algos::Eddsa, - Algos::Ps, - Algos::None, - Algos::Unsupported].tap do |l| - if ::JWT.rbnacl_6_or_greater? - require_relative 'algos/hmac_rbnacl' - l.unshift(Algos::HmacRbNaCl) - elsif ::JWT.rbnacl? - require_relative 'algos/hmac_rbnacl_fixed' - l.unshift(Algos::HmacRbNaClFixed) - else - l.unshift(Algos::Hmac) - end - end.freeze - - def find(algorithm) - indexed[algorithm && algorithm.downcase] - end - - def create(algorithm) - Algos::AlgoWrapper.new(*find(algorithm)) - end - - def implementation?(algorithm) - (algorithm.respond_to?(:valid_alg?) && algorithm.respond_to?(:verify)) || - (algorithm.respond_to?(:alg) && algorithm.respond_to?(:sign)) - end - - private - - def indexed - @indexed ||= begin - fallback = [nil, Algos::Unsupported] - ALGOS.each_with_object(Hash.new(fallback)) do |cls, hash| - cls.const_get(:SUPPORTED).each do |alg| - hash[alg.downcase] = [alg, cls] - end - end - end - end - end -end diff --git a/lib/jwt/algos/eddsa.rb b/lib/jwt/algos/eddsa.rb deleted file mode 100644 index cf21ad94..00000000 --- a/lib/jwt/algos/eddsa.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -module JWT - module Algos - module Eddsa - module_function - - SUPPORTED = %w[ED25519 EdDSA].freeze - - def sign(algorithm, msg, key) - if key.class != RbNaCl::Signatures::Ed25519::SigningKey - raise EncodeError, "Key given is a #{key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey" - end - unless SUPPORTED.map(&:downcase).map(&:to_sym).include?(algorithm.downcase.to_sym) - raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key.primitive} signing key was provided" - end - - key.sign(msg) - end - - def verify(algorithm, public_key, signing_input, signature) - unless SUPPORTED.map(&:downcase).map(&:to_sym).include?(algorithm.downcase.to_sym) - raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key.primitive} signing key was provided" - end - 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 - - public_key.verify(signature, signing_input) - rescue RbNaCl::CryptoError - false - end - end - end -end diff --git a/lib/jwt/algos/hmac_rbnacl.rb b/lib/jwt/algos/hmac_rbnacl.rb deleted file mode 100644 index d6b03705..00000000 --- a/lib/jwt/algos/hmac_rbnacl.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -module JWT - module Algos - module HmacRbNaCl - module_function - - MAPPING = { - 'HS256' => ::RbNaCl::HMAC::SHA256, - 'HS512256' => ::RbNaCl::HMAC::SHA512256, - 'HS384' => nil, - 'HS512' => ::RbNaCl::HMAC::SHA512 - }.freeze - - SUPPORTED = MAPPING.keys - - def sign(algorithm, msg, key) - if (hmac = resolve_algorithm(algorithm)) - hmac.auth(key_for_rbnacl(hmac, key).encode('binary'), msg.encode('binary')) - else - Hmac.sign(algorithm, msg, key) - end - end - - def verify(algorithm, key, signing_input, signature) - if (hmac = resolve_algorithm(algorithm)) - hmac.verify(key_for_rbnacl(hmac, key).encode('binary'), signature.encode('binary'), signing_input.encode('binary')) - else - Hmac.verify(algorithm, key, signing_input, signature) - end - rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError - false - end - - def key_for_rbnacl(hmac, key) - key ||= '' - raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String) - - return padded_empty_key(hmac.key_bytes) if key == '' - - key - end - - def resolve_algorithm(algorithm) - MAPPING.fetch(algorithm) - end - - def padded_empty_key(length) - Array.new(length, 0x0).pack('C*').encode('binary') - end - end - end -end diff --git a/lib/jwt/algos/hmac_rbnacl_fixed.rb b/lib/jwt/algos/hmac_rbnacl_fixed.rb deleted file mode 100644 index 386ddd83..00000000 --- a/lib/jwt/algos/hmac_rbnacl_fixed.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -module JWT - module Algos - module HmacRbNaClFixed - module_function - - MAPPING = { - 'HS256' => ::RbNaCl::HMAC::SHA256, - 'HS512256' => ::RbNaCl::HMAC::SHA512256, - 'HS384' => nil, - 'HS512' => ::RbNaCl::HMAC::SHA512 - }.freeze - - SUPPORTED = MAPPING.keys - - def sign(algorithm, msg, key) - key ||= '' - - raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String) - - if (hmac = resolve_algorithm(algorithm)) && key.bytesize <= hmac.key_bytes - hmac.auth(padded_key_bytes(key, hmac.key_bytes), msg.encode('binary')) - else - Hmac.sign(algorithm, msg, key) - end - end - - def verify(algorithm, key, signing_input, signature) - key ||= '' - - raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String) - - if (hmac = resolve_algorithm(algorithm)) && key.bytesize <= hmac.key_bytes - hmac.verify(padded_key_bytes(key, hmac.key_bytes), signature.encode('binary'), signing_input.encode('binary')) - else - Hmac.verify(algorithm, key, signing_input, signature) - end - rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError - false - end - - def resolve_algorithm(algorithm) - MAPPING.fetch(algorithm) - end - - def padded_key_bytes(key, bytesize) - key.bytes.fill(0, key.bytesize...bytesize).pack('C*') - end - end - end -end diff --git a/lib/jwt/decode.rb b/lib/jwt/decode.rb index 16217943..0d56a0ef 100644 --- a/lib/jwt/decode.rb +++ b/lib/jwt/decode.rb @@ -92,13 +92,7 @@ def allowed_algorithms end def resolve_allowed_algorithms - algs = given_algorithms.map do |alg| - if Algos.implementation?(alg) - alg - else - Algos.create(alg) - end - end + algs = given_algorithms.map { |alg| JWA.create(alg) } sort_by_alg_header(algs) end diff --git a/lib/jwt/encode.rb b/lib/jwt/encode.rb index 252ddf9b..8527834e 100644 --- a/lib/jwt/encode.rb +++ b/lib/jwt/encode.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative 'algos' +require_relative 'jwa' require_relative 'claims_validator' # JWT::Encode module @@ -12,7 +12,7 @@ class Encode def initialize(options) @payload = options[:payload] @key = options[:key] - @algorithm = resolve_algorithm(options[:algorithm]) + @algorithm = JWA.create(options[:algorithm]) @headers = options[:headers].transform_keys(&:to_s) @headers[ALG_KEY] = @algorithm.alg end @@ -24,12 +24,6 @@ def segments private - def resolve_algorithm(algorithm) - return algorithm if Algos.implementation?(algorithm) - - Algos.create(algorithm) - end - def encoded_header @encoded_header ||= encode_header end diff --git a/lib/jwt/jwa.rb b/lib/jwt/jwa.rb new file mode 100644 index 00000000..6e88f514 --- /dev/null +++ b/lib/jwt/jwa.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'openssl' + +begin + require 'rbnacl' +rescue LoadError + raise if defined?(RbNaCl) +end + +require_relative 'jwa/hmac' +require_relative 'jwa/eddsa' +require_relative 'jwa/ecdsa' +require_relative 'jwa/rsa' +require_relative 'jwa/ps' +require_relative 'jwa/none' +require_relative 'jwa/unsupported' +require_relative 'jwa/wrapper' + +module JWT + module JWA + ALGOS = [Hmac, Ecdsa, Rsa, Eddsa, Ps, None, Unsupported].tap do |l| + if ::JWT.rbnacl_6_or_greater? + require_relative 'jwa/hmac_rbnacl' + l << Algos::HmacRbNaCl + elsif ::JWT.rbnacl? + require_relative 'jwa/hmac_rbnacl_fixed' + l << Algos::HmacRbNaClFixed + end + end.freeze + + class << self + def find(algorithm) + indexed[algorithm&.downcase] + end + + def create(algorithm) + return algorithm if JWA.implementation?(algorithm) + + Wrapper.new(*find(algorithm)) + end + + def implementation?(algorithm) + (algorithm.respond_to?(:valid_alg?) && algorithm.respond_to?(:verify)) || + (algorithm.respond_to?(:alg) && algorithm.respond_to?(:sign)) + end + + private + + def indexed + @indexed ||= begin + fallback = [nil, Unsupported] + ALGOS.each_with_object(Hash.new(fallback)) do |cls, hash| + cls.const_get(:SUPPORTED).each do |alg| + hash[alg.downcase] = [alg, cls] + end + end + end + end + end + end +end diff --git a/lib/jwt/algos/ecdsa.rb b/lib/jwt/jwa/ecdsa.rb similarity index 99% rename from lib/jwt/algos/ecdsa.rb rename to lib/jwt/jwa/ecdsa.rb index 86c1611e..8d9313c1 100644 --- a/lib/jwt/algos/ecdsa.rb +++ b/lib/jwt/jwa/ecdsa.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module JWT - module Algos + module JWA module Ecdsa module_function diff --git a/lib/jwt/jwa/eddsa.rb b/lib/jwt/jwa/eddsa.rb new file mode 100644 index 00000000..eef27f1b --- /dev/null +++ b/lib/jwt/jwa/eddsa.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module JWT + module JWA + module Eddsa + SUPPORTED = %w[ED25519 EdDSA].freeze + SUPPORTED_DOWNCASED = SUPPORTED.map(&:downcase).freeze + + class << self + def sign(algorithm, msg, key) + unless key.is_a?(RbNaCl::Signatures::Ed25519::SigningKey) + raise EncodeError, "Key given is a #{key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey" + end + + validate_algorithm!(algorithm) + + key.sign(msg) + end + + def verify(algorithm, public_key, signing_input, signature) + unless public_key.is_a?(RbNaCl::Signatures::Ed25519::VerifyKey) + raise DecodeError, "key given is a #{public_key.class} but has to be a RbNaCl::Signatures::Ed25519::VerifyKey" + end + + validate_algorithm!(algorithm) + + public_key.verify(signature, signing_input) + rescue RbNaCl::CryptoError + false + end + + private + + def validate_algorithm!(algorithm) + return if SUPPORTED_DOWNCASED.include?(algorithm.downcase) + + raise IncorrectAlgorithm, "Algorithm #{algorithm} not supported. Supported algoritms are #{SUPPORTED.join(', ')}" + end + end + end + end +end diff --git a/lib/jwt/algos/hmac.rb b/lib/jwt/jwa/hmac.rb similarity index 97% rename from lib/jwt/algos/hmac.rb rename to lib/jwt/jwa/hmac.rb index e959b3cd..da73ff64 100644 --- a/lib/jwt/algos/hmac.rb +++ b/lib/jwt/jwa/hmac.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module JWT - module Algos + module JWA module Hmac module_function @@ -44,6 +44,7 @@ def fixed_length_secure_compare(a, b) OpenSSL.fixed_length_secure_compare(a, b) end else + # :nocov: def fixed_length_secure_compare(a, b) raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize @@ -53,6 +54,7 @@ def fixed_length_secure_compare(a, b) b.each_byte { |byte| res |= byte ^ l.shift } res == 0 end + # :nocov: end module_function :fixed_length_secure_compare diff --git a/lib/jwt/jwa/hmac_rbnacl.rb b/lib/jwt/jwa/hmac_rbnacl.rb new file mode 100644 index 00000000..e67309c7 --- /dev/null +++ b/lib/jwt/jwa/hmac_rbnacl.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module JWT + module Algos + module HmacRbNaCl + MAPPING = { 'HS512256' => ::RbNaCl::HMAC::SHA512256 }.freeze + SUPPORTED = MAPPING.keys + class << self + def sign(algorithm, msg, key) + warn("[DEPRECATION] The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt") + if (hmac = resolve_algorithm(algorithm)) + hmac.auth(key_for_rbnacl(hmac, key).encode('binary'), msg.encode('binary')) + else + Hmac.sign(algorithm, msg, key) + end + end + + def verify(algorithm, key, signing_input, signature) + warn("[DEPRECATION] The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt") + if (hmac = resolve_algorithm(algorithm)) + hmac.verify(key_for_rbnacl(hmac, key).encode('binary'), signature.encode('binary'), signing_input.encode('binary')) + else + Hmac.verify(algorithm, key, signing_input, signature) + end + rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError + false + end + + private + + def key_for_rbnacl(hmac, key) + key ||= '' + raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String) + + return padded_empty_key(hmac.key_bytes) if key == '' + + key + end + + def resolve_algorithm(algorithm) + MAPPING.fetch(algorithm) + end + + def padded_empty_key(length) + Array.new(length, 0x0).pack('C*').encode('binary') + end + end + end + end +end diff --git a/lib/jwt/jwa/hmac_rbnacl_fixed.rb b/lib/jwt/jwa/hmac_rbnacl_fixed.rb new file mode 100644 index 00000000..9712a3cc --- /dev/null +++ b/lib/jwt/jwa/hmac_rbnacl_fixed.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module JWT + module Algos + module HmacRbNaClFixed + MAPPING = { 'HS512256' => ::RbNaCl::HMAC::SHA512256 }.freeze + SUPPORTED = MAPPING.keys + + class << self + def sign(algorithm, msg, key) + key ||= '' + warn("[DEPRECATION] The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt") + raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String) + + if (hmac = resolve_algorithm(algorithm)) && key.bytesize <= hmac.key_bytes + hmac.auth(padded_key_bytes(key, hmac.key_bytes), msg.encode('binary')) + else + Hmac.sign(algorithm, msg, key) + end + end + + def verify(algorithm, key, signing_input, signature) + key ||= '' + warn("[DEPRECATION] The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt") + raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String) + + if (hmac = resolve_algorithm(algorithm)) && key.bytesize <= hmac.key_bytes + hmac.verify(padded_key_bytes(key, hmac.key_bytes), signature.encode('binary'), signing_input.encode('binary')) + else + Hmac.verify(algorithm, key, signing_input, signature) + end + rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError + false + end + + def resolve_algorithm(algorithm) + MAPPING.fetch(algorithm) + end + + def padded_key_bytes(key, bytesize) + key.bytes.fill(0, key.bytesize...bytesize).pack('C*') + end + end + end + end +end diff --git a/lib/jwt/algos/none.rb b/lib/jwt/jwa/none.rb similarity index 93% rename from lib/jwt/algos/none.rb rename to lib/jwt/jwa/none.rb index 84ea99da..d6fd7e62 100644 --- a/lib/jwt/algos/none.rb +++ b/lib/jwt/jwa/none.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module JWT - module Algos + module JWA module None module_function diff --git a/lib/jwt/algos/ps.rb b/lib/jwt/jwa/ps.rb similarity index 57% rename from lib/jwt/algos/ps.rb rename to lib/jwt/jwa/ps.rb index 1163932d..f7488bd0 100644 --- a/lib/jwt/algos/ps.rb +++ b/lib/jwt/jwa/ps.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module JWT - module Algos + module JWA module Ps # RSASSA-PSS signing algorithms @@ -10,9 +10,9 @@ module Ps SUPPORTED = %w[PS256 PS384 PS512].freeze def sign(algorithm, msg, key) - require_openssl! - - raise EncodeError, "The given key is a #{key_class}. It has to be an OpenSSL::PKey::RSA instance." if key.is_a?(String) + unless key.is_a?(::OpenSSL::PKey::RSA) + raise EncodeError, "The given key is a #{key_class}. It has to be an OpenSSL::PKey::RSA instance." + end translated_algorithm = algorithm.sub('PS', 'sha') @@ -20,22 +20,11 @@ def sign(algorithm, msg, key) end def verify(algorithm, public_key, signing_input, signature) - require_openssl! translated_algorithm = algorithm.sub('PS', 'sha') public_key.verify_pss(translated_algorithm, signature, signing_input, salt_length: :auto, mgf1_hash: translated_algorithm) rescue OpenSSL::PKey::PKeyError raise JWT::VerificationError, 'Signature verification raised' end - - def require_openssl! - if Object.const_defined?('OpenSSL') - if ::Gem::Version.new(OpenSSL::VERSION) < ::Gem::Version.new('2.1') - raise JWT::RequiredDependencyError, "You currently have OpenSSL #{OpenSSL::VERSION}. PS support requires >= 2.1" - end - else - raise JWT::RequiredDependencyError, 'PS signing requires OpenSSL +2.1' - end - end end end end diff --git a/lib/jwt/algos/rsa.rb b/lib/jwt/jwa/rsa.rb similarity index 75% rename from lib/jwt/algos/rsa.rb rename to lib/jwt/jwa/rsa.rb index 2b792d6d..f9970bda 100644 --- a/lib/jwt/algos/rsa.rb +++ b/lib/jwt/jwa/rsa.rb @@ -1,14 +1,16 @@ # frozen_string_literal: true module JWT - module Algos + module JWA module Rsa module_function SUPPORTED = %w[RS256 RS384 RS512].freeze def sign(algorithm, msg, key) - raise EncodeError, "The given key is a #{key.class}. It has to be an OpenSSL::PKey::RSA instance." if key.instance_of?(String) + unless key.is_a?(OpenSSL::PKey::RSA) + raise EncodeError, "The given key is a #{key.class}. It has to be an OpenSSL::PKey::RSA instance" + end key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg) end diff --git a/lib/jwt/algos/unsupported.rb b/lib/jwt/jwa/unsupported.rb similarity index 95% rename from lib/jwt/algos/unsupported.rb rename to lib/jwt/jwa/unsupported.rb index f5e38341..681a6a75 100644 --- a/lib/jwt/algos/unsupported.rb +++ b/lib/jwt/jwa/unsupported.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module JWT - module Algos + module JWA module Unsupported module_function diff --git a/lib/jwt/algos/algo_wrapper.rb b/lib/jwt/jwa/wrapper.rb similarity index 92% rename from lib/jwt/algos/algo_wrapper.rb rename to lib/jwt/jwa/wrapper.rb index e4fa072d..e53713e1 100644 --- a/lib/jwt/algos/algo_wrapper.rb +++ b/lib/jwt/jwa/wrapper.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module JWT - module Algos - class AlgoWrapper + module JWA + class Wrapper attr_reader :alg, :cls def initialize(alg, cls) diff --git a/spec/jwt/algos/hmac_rbnacl_fixed_spec.rb b/spec/jwt/algos/hmac_rbnacl_fixed_spec.rb deleted file mode 100644 index eefd236c..00000000 --- a/spec/jwt/algos/hmac_rbnacl_fixed_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe '::JWT::Algos::HmacRbNaClFixed' do - before do - skip('Requires rbnacl gem < 6.0') ::JWT.rbnacl? - end - - let(:described_class) { JWT::Algos::HmacRbNaClFixed } - - let(:data) { 'this_is_the_string_to_be_signed' } - let(:key) { 'the secret key' } - - describe '.verify' do - context 'when signature is generated with OpenSSL' do - let!(:signature) { JWT::Algos::Hmac.sign('HS256', data, key) } - it 'verifies the signature' do - allow(OpenSSL::HMAC).to receive(:digest).and_call_original - expect(described_class.verify('HS256', key, data, signature)).to eq(true) - expect(OpenSSL::HMAC).not_to have_received(:digest) - end - end - - context 'when signature is generated with OpenSSL and key is very long' do - let(:key) { 'a' * 100 } - let!(:signature) { JWT::Algos::Hmac.sign('HS256', data, key) } - - it 'verifies the signature using OpenSSL features' do - allow(OpenSSL::HMAC).to receive(:digest).and_call_original - expect(described_class.verify('HS256', key, data, signature)).to eq(true) - expect(OpenSSL::HMAC).to have_received(:digest).once - end - end - - context 'when signature is invalid' do - let(:key) { 'a' * 100 } - let(:signature) { JWT::Base64.url_decode('some_random_signature') } - - it 'can verify without error' do - allow(OpenSSL::HMAC).to receive(:digest).and_call_original - expect(described_class.verify('HS256', key, data, signature)).to eq(false) - expect(OpenSSL::HMAC).not_to have_received(:digest) - end - end - end - - describe '.sign' do - context 'when signature is generated by RbNaCl' do - let!(:signature) { described_class.sign('HS256', data, key) } - it 'can verify the signature with OpenSSL' do - expect(JWT::Algos::Hmac.verify('HS256', key, data, signature)).to eq(true) - end - end - end -end diff --git a/spec/jwt/algos/hmac_rbnacl_spec.rb b/spec/jwt/algos/hmac_rbnacl_spec.rb deleted file mode 100644 index 8e54b6c4..00000000 --- a/spec/jwt/algos/hmac_rbnacl_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe '::JWT::Algos::HmacRbNaCl' do - before do - skip('Requires the rbnacl gem greater than 6.0') unless JWT.rbnacl_6_or_greater? - end - - let(:described_class) { JWT::Algos::HmacRbNaCl } - - let(:data) { 'this_is_the_string_to_be_signed' } - let(:key) { 'the secret key' } - - describe '.verify' do - context 'when signature is generated with OpenSSL' do - let!(:signature) { JWT::Algos::Hmac.sign('HS256', data, key) } - it 'verifies the signature' do - allow(OpenSSL::HMAC).to receive(:digest).and_call_original - expect(described_class.verify('HS256', key, data, signature)).to eq(true) - expect(OpenSSL::HMAC).not_to have_received(:digest) - end - end - - context 'when signature is generated with OpenSSL and key is very long' do - let(:key) { 'a' * 100 } - let!(:signature) { JWT::Algos::Hmac.sign('HS256', data, key) } - - it 'verifies the signature using OpenSSL features' do - allow(OpenSSL::HMAC).to receive(:digest).and_call_original - expect(described_class.verify('HS256', key, data, signature)).to eq(true) - expect(OpenSSL::HMAC).not_to have_received(:digest) - end - end - - context 'when signature is invalid' do - let(:key) { 'a' * 100 } - let(:signature) { JWT::Base64.url_decode('some_random_signature') } - - it 'can verify without error' do - allow(OpenSSL::HMAC).to receive(:digest).and_call_original - expect(described_class.verify('HS256', key, data, signature)).to eq(false) - expect(OpenSSL::HMAC).not_to have_received(:digest) - end - end - end - - describe '.sign' do - context 'when signature is generated by RbNaCl' do - let!(:signature) { described_class.sign('HS256', data, key) } - it 'can verify the signature with OpenSSL' do - expect(JWT::Algos::Hmac.verify('HS256', key, data, signature)).to eq(true) - end - end - end -end diff --git a/spec/jwt/algos/ecdsa_spec.rb b/spec/jwt/jwa/ecdsa_spec.rb similarity index 96% rename from spec/jwt/algos/ecdsa_spec.rb rename to spec/jwt/jwa/ecdsa_spec.rb index 32d1bfd4..72d36738 100644 --- a/spec/jwt/algos/ecdsa_spec.rb +++ b/spec/jwt/jwa/ecdsa_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe JWT::Algos::Ecdsa do +RSpec.describe JWT::JWA::Ecdsa do describe '.curve_by_name' do subject { described_class.curve_by_name(curve_name) } diff --git a/spec/jwt/algos/hmac_spec.rb b/spec/jwt/jwa/hmac_spec.rb similarity index 99% rename from spec/jwt/algos/hmac_spec.rb rename to spec/jwt/jwa/hmac_spec.rb index c25e8ded..84bc6734 100644 --- a/spec/jwt/algos/hmac_spec.rb +++ b/spec/jwt/jwa/hmac_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe JWT::Algos::Hmac do +RSpec.describe JWT::JWA::Hmac do describe '.sign' do subject { described_class.sign('HS256', 'test', hmac_secret) } diff --git a/spec/jwt/jwt_spec.rb b/spec/jwt/jwt_spec.rb index edf1b20a..13d0beec 100644 --- a/spec/jwt/jwt_spec.rb +++ b/spec/jwt/jwt_spec.rb @@ -778,7 +778,7 @@ let(:token) { JWT.encode(payload, 'secret', 'HS256') } it 'starts trying with the algorithm referred in the header' do - expect(JWT::Algos::Rsa).not_to receive(:verify) + expect(JWT::JWA::Rsa).not_to receive(:verify) JWT.decode(token, 'secret', true, algorithm: ['RS512', 'HS256']) end end