Skip to content

Commit

Permalink
Algorithm cleanup.
Browse files Browse the repository at this point in the history
 - Algos module renamed to JWA
 - Standard HMAC algorithms provided explicitly by OpenSSL (jwt#550)
 - Prepare to remove support for the HS512256 algorithm provided by RbNaCl (jwt#549)
  • Loading branch information
anakinj committed Dec 28, 2023
1 parent 965ca2b commit 24c7a5b
Show file tree
Hide file tree
Showing 24 changed files with 219 additions and 360 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@

- 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:**

- Fix signature has expired error if payload is a string [#555](https://github.com/jwt/ruby-jwt/pull/555) - [@GobinathAL](https://github.com/GobinathAL).
- Fix key base equality and spaceship operators [#569](https://github.com/jwt/ruby-jwt/pull/569) - [@magneland](https://github.com/magneland).
- 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)
Expand Down
7 changes: 0 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
66 changes: 0 additions & 66 deletions lib/jwt/algos.rb

This file was deleted.

33 changes: 0 additions & 33 deletions lib/jwt/algos/eddsa.rb

This file was deleted.

53 changes: 0 additions & 53 deletions lib/jwt/algos/hmac_rbnacl.rb

This file was deleted.

52 changes: 0 additions & 52 deletions lib/jwt/algos/hmac_rbnacl_fixed.rb

This file was deleted.

8 changes: 1 addition & 7 deletions lib/jwt/decode.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 2 additions & 8 deletions lib/jwt/encode.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require_relative 'algos'
require_relative 'jwa'
require_relative 'claims_validator'

# JWT::Encode module
Expand All @@ -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
Expand All @@ -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
Expand Down
56 changes: 56 additions & 0 deletions lib/jwt/jwa.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# frozen_string_literal: true

require 'openssl'

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 'algos/hmac_rbnacl'
l << Algos::HmacRbNaCl
elsif ::JWT.rbnacl?
require_relative 'algos/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
2 changes: 1 addition & 1 deletion lib/jwt/algos/ecdsa.rb → lib/jwt/jwa/ecdsa.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module JWT
module Algos
module JWA
module Ecdsa
module_function

Expand Down
42 changes: 42 additions & 0 deletions lib/jwt/jwa/eddsa.rb
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 24c7a5b

Please sign in to comment.