Skip to content
35 changes: 35 additions & 0 deletions lib/jwt/algos/ecdsa.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module JWT
module Algos
module Ecdsa
module_function

SUPPORTED = %(ES256 ES384 ES512).freeze
NAMED_CURVES = {
'prime256v1' => 'ES256',
'secp384r1' => 'ES384',
'secp521r1' => 'ES512'
}.freeze

def sign(to_sign)
algorithm, msg, key = to_sign.values
key_algorithm = NAMED_CURVES[key.group.curve_name]
if algorithm != key_algorithm
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided"
end

digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
SecurityUtils.asn1_to_raw(key.dsa_sign_asn1(digest.digest(msg)), key)
end

def verify(to_verify)
algorithm, public_key, signing_input, signature = to_verify.values
key_algorithm = NAMED_CURVES[public_key.group.curve_name]
if algorithm != key_algorithm
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided"
end
digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
public_key.dsa_verify_asn1(digest.digest(signing_input), SecurityUtils.raw_to_asn1(signature, public_key))
end
end
end
end
23 changes: 23 additions & 0 deletions lib/jwt/algos/eddsa.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module JWT
module Algos
module Eddsa
module_function

SUPPORTED = %w(ED25519).freeze

def sign(to_sign)
algorithm, msg, key = to_sign.values
raise EncodeError, "Key given is a #{key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey" if key.class != RbNaCl::Signatures::Ed25519::SigningKey
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key.primitive} signing key was provided" if algorithm.downcase.to_sym != key.primitive
key.sign(msg)
end

def verify(to_verify)
algorithm, public_key, signing_input, signature = to_verify.values
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{public_key.primitive} verification key was provided" if algorithm.downcase.to_sym != public_key.primitive
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)
end
end
end
end
33 changes: 33 additions & 0 deletions lib/jwt/algos/hmac.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module JWT
module Algos
module Hmac
module_function
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep a blank line after module_function.


SUPPORTED = %w(HS256 HS512256 HS384 HS512).freeze

def sign(to_sign)
algorithm, msg, key = to_sign.values
authenticator, padded_key = SecurityUtils.rbnacl_fixup(algorithm, key)
if authenticator && padded_key
authenticator.auth(padded_key, msg.encode('binary'))
else
OpenSSL::HMAC.digest(OpenSSL::Digest.new(algorithm.sub('HS', 'sha')), key, msg)
end
end

def verify(to_verify)
algorithm, public_key, signing_input, signature = to_verify.values
authenticator, padded_key = SecurityUtils.rbnacl_fixup(algorithm, public_key)
if authenticator && padded_key
begin
authenticator.verify(padded_key, signature.encode('binary'), signing_input.encode('binary'))
rescue RbNaCl::BadAuthenticatorError
false
end
else
SecurityUtils.secure_compare(signature, sign(JWT::Signature::ToSign.new(algorithm, signing_input, public_key)))
end
end
end
end
end
19 changes: 19 additions & 0 deletions lib/jwt/algos/rsa.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module JWT
module Algos
module Rsa
module_function
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep a blank line after module_function.


SUPPORTED = %w(RS256 RS384 RS512).freeze

def sign(to_sign)
algorithm, msg, key = to_sign.values
raise EncodeError, "The given key is a #{key.class}. It has to be an OpenSSL::PKey::RSA instance." if key.class == String
key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
end

def verify(to_verify)
SecurityUtils.verify_rsa(to_verify.algorithm, to_verify.public_key, to_verify.signing_input, to_verify.signature)
end
end
end
end
19 changes: 19 additions & 0 deletions lib/jwt/algos/unsupported.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module JWT
module Algos
module Unsupported
module_function

supported_true = Object.new
def supported_true.include?(*)
true
end
SUPPORTED = supported_true
def verify(*)
raise JWT::VerificationError, 'Algorithm not supported'
end
def sign(*)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use empty lines between method definitions.

raise NotImplementedError, 'Unsupported signing method'
end
end
end
end
114 changes: 21 additions & 93 deletions lib/jwt/signature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

require 'jwt/security_utils'
require 'openssl'
require 'jwt/algos/hmac'
require 'jwt/algos/eddsa'
require 'jwt/algos/ecdsa'
require 'jwt/algos/rsa'
require 'jwt/algos/unsupported'
begin
require 'rbnacl'
rescue LoadError => e
Expand All @@ -13,110 +18,33 @@ module JWT
# Signature logic for JWT
module Signature
extend self

HMAC_ALGORITHMS = %w[HS256 HS512256 HS384 HS512].freeze
RSA_ALGORITHMS = %w[RS256 RS384 RS512].freeze
ECDSA_ALGORITHMS = %w[ES256 ES384 ES512].freeze
EDDSA_ALGORITHMS = %w[ED25519].freeze

NAMED_CURVES = {
'prime256v1' => 'ES256',
'secp384r1' => 'ES384',
'secp521r1' => 'ES512'
}.freeze
ALGOS = [
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Freeze mutable objects assigned to constants.

Algos::Hmac,
Algos::Ecdsa,
Algos::Rsa,
Algos::Eddsa,
Algos::Unsupported
].freeze
ToSign = Struct.new(:algorithm, :msg, :key)
ToVerify = Struct.new(:algorithm, :public_key, :signing_input, :signature)

def sign(algorithm, msg, key)
if HMAC_ALGORITHMS.include?(algorithm)
sign_hmac(algorithm, msg, key)
elsif RSA_ALGORITHMS.include?(algorithm)
sign_rsa(algorithm, msg, key)
elsif ECDSA_ALGORITHMS.include?(algorithm)
sign_ecdsa(algorithm, msg, key)
elsif EDDSA_ALGORITHMS.include?(algorithm)
sign_eddsa(algorithm, msg, key)
else
raise NotImplementedError, 'Unsupported signing method'
algo = ALGOS.find do |alg|
alg.const_get(:SUPPORTED).include? algorithm
end
algo.sign ToSign.new(algorithm, msg, key)
end

def verify(algo, key, signing_input, signature)
verified = if HMAC_ALGORITHMS.include?(algo)
verify_hmac(algo, key, signing_input, signature)
elsif RSA_ALGORITHMS.include?(algo)
SecurityUtils.verify_rsa(algo, key, signing_input, signature)
elsif ECDSA_ALGORITHMS.include?(algo)
verify_ecdsa(algo, key, signing_input, signature)
elsif EDDSA_ALGORITHMS.include?(algo)
verify_eddsa(algo, key, signing_input, signature)
else
raise JWT::VerificationError, 'Algorithm not supported'
def verify(algorithm, key, signing_input, signature)
algo = ALGOS.find do |alg|
alg.const_get(:SUPPORTED).include? algorithm
end

verified = algo.verify(ToVerify.new(algorithm, key, signing_input, signature))
raise(JWT::VerificationError, 'Signature verification raised') unless verified
rescue OpenSSL::PKey::PKeyError
raise JWT::VerificationError, 'Signature verification raised'
ensure
OpenSSL.errors.clear
end

private

def sign_rsa(algorithm, msg, private_key)
raise EncodeError, "The given key is a #{private_key.class}. It has to be an OpenSSL::PKey::RSA instance." if private_key.class == String
private_key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
end

def sign_ecdsa(algorithm, msg, private_key)
key_algorithm = NAMED_CURVES[private_key.group.curve_name]
if algorithm != key_algorithm
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided"
end

digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
SecurityUtils.asn1_to_raw(private_key.dsa_sign_asn1(digest.digest(msg)), private_key)
end

def sign_eddsa(algorithm, msg, private_key)
raise EncodeError, "Key given is a #{private_key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey" if private_key.class != RbNaCl::Signatures::Ed25519::SigningKey
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{private_key.primitive} signing key was provided" if algorithm.downcase.to_sym != private_key.primitive
private_key.sign(msg)
end

def sign_hmac(algorithm, msg, key)
authenticator, padded_key = SecurityUtils.rbnacl_fixup(algorithm, key)
if authenticator && padded_key
authenticator.auth(padded_key, msg.encode('binary'))
else
OpenSSL::HMAC.digest(OpenSSL::Digest.new(algorithm.sub('HS', 'sha')), key, msg)
end
end

def verify_eddsa(algorithm, public_key, signing_input, signature)
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{public_key.primitive} verification key was provided" if algorithm.downcase.to_sym != public_key.primitive
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)
end

def verify_ecdsa(algorithm, public_key, signing_input, signature)
key_algorithm = NAMED_CURVES[public_key.group.curve_name]
if algorithm != key_algorithm
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided"
end
digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
public_key.dsa_verify_asn1(digest.digest(signing_input), SecurityUtils.raw_to_asn1(signature, public_key))
end

def verify_hmac(algorithm, public_key, signing_input, signature)
authenticator, padded_key = SecurityUtils.rbnacl_fixup(algorithm, public_key)
if authenticator && padded_key
begin
authenticator.verify(padded_key, signature.encode('binary'), signing_input.encode('binary'))
rescue RbNaCl::BadAuthenticatorError
false
end
else
SecurityUtils.secure_compare(signature, sign_hmac(algorithm, signing_input, public_key))
end
end
end
end