Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added algos/strategy classes + structs for inputs #230

Merged
merged 10 commits into from
Sep 9, 2017
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

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

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
16 changes: 16 additions & 0 deletions lib/jwt/algos/unsupported.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module JWT
module Algos
module Unsupported
module_function

SUPPORTED = Object.new.tap { |object| object.define_singleton_method(:include?) { |*| true } }
def verify(*)
raise JWT::VerificationError, 'Algorithm not supported'
end

def sign(*)
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 = [

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