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