lib/jwt.rb in jwt-1.5.0 vs lib/jwt.rb in jwt-1.5.1
- old
+ new
@@ -1,15 +1,15 @@
-#
-# JSON Web Token implementation
-#
-# Should be up to date with the latest spec:
-# http://self-issued.info/docs/draft-jones-json-web-token-06.html
+# encoding: utf-8
require 'base64'
require 'openssl'
require 'jwt/json'
+# JSON Web Token implementation
+#
+# Should be up to date with the latest spec:
+# http://self-issued.info/docs/draft-jones-json-web-token-06.html
module JWT
class DecodeError < StandardError; end
class VerificationError < DecodeError; end
class ExpiredSignature < DecodeError; end
class IncorrectAlgorithm < DecodeError; end
@@ -22,11 +22,11 @@
extend JWT::Json
NAMED_CURVES = {
'prime256v1' => 'ES256',
'secp384r1' => 'ES384',
- 'secp521r1' => 'ES512',
+ 'secp521r1' => 'ES512'
}
module_function
def sign(algorithm, msg, key)
@@ -35,40 +35,40 @@
elsif ['RS256', 'RS384', 'RS512'].include?(algorithm)
sign_rsa(algorithm, msg, key)
elsif ['ES256', 'ES384', 'ES512'].include?(algorithm)
sign_ecdsa(algorithm, msg, key)
else
- raise NotImplementedError.new('Unsupported signing method')
+ fail NotImplementedError.new('Unsupported signing method')
end
end
def sign_rsa(algorithm, msg, private_key)
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.new("payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided")
+ fail IncorrectAlgorithm.new("payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided")
end
digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
- private_key.dsa_sign_asn1(digest.digest(msg))
+ asn1_to_raw(private_key.dsa_sign_asn1(digest.digest(msg)), private_key)
end
def verify_rsa(algorithm, public_key, signing_input, signature)
public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), 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.new("payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided")
+ fail IncorrectAlgorithm.new("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), signature)
+ public_key.dsa_verify_asn1(digest.digest(signing_input), raw_to_asn1(signature, public_key))
end
def sign_hmac(algorithm, msg, key)
OpenSSL::HMAC.digest(OpenSSL::Digest.new(algorithm.sub('HS', 'sha')), key, msg)
end
@@ -81,11 +81,11 @@
def base64url_encode(str)
Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
end
def encoded_header(algorithm='HS256', header_fields={})
- header = {'typ' => 'JWT', 'alg' => algorithm}.merge(header_fields)
+ header = { 'typ' => 'JWT', 'alg' => algorithm }.merge(header_fields)
base64url_encode(encode_json(header))
end
def encoded_payload(payload)
base64url_encode(encode_json(payload))
@@ -109,12 +109,12 @@
segments.join('.')
end
def raw_segments(jwt, verify=true)
segments = jwt.split('.')
- required_num_segments = verify ? [3] : [2,3]
- raise JWT::DecodeError.new('Not enough or too many segments') unless required_num_segments.include? segments.length
+ required_num_segments = verify ? [3] : [2, 3]
+ fail JWT::DecodeError.new('Not enough or too many segments') unless required_num_segments.include? segments.length
segments
end
def decode_header_and_payload(header_segment, payload_segment)
header = decode_json(base64url_decode(header_segment))
@@ -129,14 +129,14 @@
signing_input = [header_segment, payload_segment].join('.')
[header, payload, signature, signing_input]
end
def decode(jwt, key=nil, verify=true, options={}, &keyfinder)
- raise JWT::DecodeError.new('Nil JSON web token') unless jwt
+ fail JWT::DecodeError.new('Nil JSON web token') unless jwt
header, payload, signature, signing_input = decoded_segments(jwt, verify)
- raise JWT::DecodeError.new('Not enough or too many segments') unless header && payload
+ fail JWT::DecodeError.new('Not enough or too many segments') unless header && payload
default_options = {
:verify_expiration => true,
:verify_not_before => true,
:verify_iss => false,
@@ -150,68 +150,64 @@
options = default_options.merge(options)
if verify
algo, key = signature_algorithm_and_key(header, key, &keyfinder)
if options[:algorithm] && algo != options[:algorithm]
- raise JWT::IncorrectAlgorithm.new('Expected a different algorithm')
+ fail JWT::IncorrectAlgorithm.new('Expected a different algorithm')
end
verify_signature(algo, key, signing_input, signature)
end
if options[:verify_expiration] && payload.include?('exp')
- raise JWT::ExpiredSignature.new('Signature has expired') unless payload['exp'].to_i > (Time.now.to_i - options[:leeway])
+ fail JWT::ExpiredSignature.new('Signature has expired') unless payload['exp'].to_i > (Time.now.to_i - options[:leeway])
end
if options[:verify_not_before] && payload.include?('nbf')
- raise JWT::ImmatureSignature.new('Signature nbf has not been reached') unless payload['nbf'].to_i < (Time.now.to_i + options[:leeway])
+ fail JWT::ImmatureSignature.new('Signature nbf has not been reached') unless payload['nbf'].to_i < (Time.now.to_i + options[:leeway])
end
- if options[:verify_iss] && payload.include?('iss')
- raise JWT::InvalidIssuerError.new("Invalid issuer. Expected #{options['iss']}, received #{payload['iss']}") unless payload['iss'].to_s == options['iss'].to_s
+ if options[:verify_iss] && options['iss']
+ fail JWT::InvalidIssuerError.new("Invalid issuer. Expected #{options['iss']}, received #{payload['iss'] || '<none>'}") unless payload['iss'].to_s == options['iss'].to_s
end
if options[:verify_iat] && payload.include?('iat')
- raise JWT::InvalidIatError.new('Invalid iat') unless (payload['iat'].is_a?(Integer) and payload['iat'].to_i <= Time.now.to_i)
+ fail JWT::InvalidIatError.new('Invalid iat') unless payload['iat'].is_a?(Integer) && payload['iat'].to_i <= Time.now.to_i
end
- if options[:verify_aud] && payload.include?('aud')
+ if options[:verify_aud] && options['aud']
if payload['aud'].is_a?(Array)
- raise JWT::InvalidAudError.new('Invalid audience') unless payload['aud'].include?(options['aud'])
+ fail JWT::InvalidAudError.new('Invalid audience') unless payload['aud'].include?(options['aud'].to_s)
else
- raise JWT::InvalidAudError.new("Invalid audience. Expected #{options['aud']}, received #{payload['aud']}") unless payload['aud'].to_s == options['aud'].to_s
+ fail JWT::InvalidAudError.new("Invalid audience. Expected #{options['aud']}, received #{payload['aud'] || '<none>'}") unless payload['aud'].to_s == options['aud'].to_s
end
end
if options[:verify_sub] && payload.include?('sub')
- raise JWT::InvalidSubError.new("Invalid subject. Expected #{options['sub']}, received #{payload['sub']}") unless payload['sub'].to_s == options['sub'].to_s
+ fail JWT::InvalidSubError.new("Invalid subject. Expected #{options['sub']}, received #{payload['sub']}") unless payload['sub'].to_s == options['sub'].to_s
end
if options[:verify_jti] && payload.include?('jti')
- raise JWT::InvalidJtiError.new('need iat for verify jwt id') unless payload.include?('iat')
- raise JWT::InvalidJtiError.new('Not a uniq jwt id') unless options['jti'].to_s == Digest::MD5.hexdigest("#{key}:#{payload['iat']}")
+ fail JWT::InvalidJtiError.new('need iat for verify jwt id') unless payload.include?('iat')
+ fail JWT::InvalidJtiError.new('Not a uniq jwt id') unless options['jti'].to_s == Digest::MD5.hexdigest("#{key}:#{payload['iat']}")
end
- return payload,header
+ [payload, header]
end
def signature_algorithm_and_key(header, key, &keyfinder)
- if keyfinder
- key = keyfinder.call(header)
- end
+ key = keyfinder.call(header) if keyfinder
[header['alg'], key]
end
def verify_signature(algo, key, signing_input, signature)
- begin
- if ['HS256', 'HS384', 'HS512'].include?(algo)
- raise JWT::VerificationError.new('Signature verification failed') unless secure_compare(signature, sign_hmac(algo, signing_input, key))
- elsif ['RS256', 'RS384', 'RS512'].include?(algo)
- raise JWT::VerificationError.new('Signature verification failed') unless verify_rsa(algo, key, signing_input, signature)
- elsif ['ES256', 'ES384', 'ES512'].include?(algo)
- raise JWT::VerificationError.new('Signature verification failed') unless verify_ecdsa(algo, key, signing_input, signature)
- else
- raise JWT::VerificationError.new('Algorithm not supported')
- end
- rescue OpenSSL::PKey::PKeyError
- raise JWT::VerificationError.new('Signature verification failed')
- ensure
- OpenSSL.errors.clear
+ if ['HS256', 'HS384', 'HS512'].include?(algo)
+ fail JWT::VerificationError.new('Signature verification failed') unless secure_compare(signature, sign_hmac(algo, signing_input, key))
+ elsif ['RS256', 'RS384', 'RS512'].include?(algo)
+ fail JWT::VerificationError.new('Signature verification failed') unless verify_rsa(algo, key, signing_input, signature)
+ elsif ['ES256', 'ES384', 'ES512'].include?(algo)
+ fail JWT::VerificationError.new('Signature verification failed') unless verify_ecdsa(algo, key, signing_input, signature)
+ else
+ fail JWT::VerificationError.new('Algorithm not supported')
end
+ rescue OpenSSL::PKey::PKeyError
+ raise JWT::VerificationError.new('Signature verification failed')
+ ensure
+ OpenSSL.errors.clear
end
# From devise
# constant-time comparison algorithm to prevent timing attacks
def secure_compare(a, b)
@@ -221,6 +217,17 @@
res = 0
b.each_byte { |byte| res |= byte ^ l.shift }
res == 0
end
+ def raw_to_asn1(signature, private_key)
+ byte_size = (private_key.group.degree + 7) / 8
+ r = signature[0..(byte_size - 1)]
+ s = signature[byte_size..-1]
+ OpenSSL::ASN1::Sequence.new([r, s].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
+ end
+
+ def asn1_to_raw(signature, public_key)
+ byte_size = (public_key.group.degree + 7) / 8
+ OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
+ end
end