module JWT
JSON Web Token implementation
Should be up to date with the latest spec: tools.ietf.org/html/rfc7519#section-4.1.5
JWT::Decode module
Moments version builder module
Constants
- DEFAULT_OPTIONS
- NAMED_CURVES
Public Class Methods
gem_version()
click to toggle source
# File lib/jwt/version.rb, line 4 def self.gem_version Gem::Version.new VERSION::STRING end
Public Instance Methods
asn1_to_raw(signature, public_key)
click to toggle source
# File lib/jwt.rb, line 183 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
base64url_decode(str)
click to toggle source
# File lib/jwt.rb, line 188 def base64url_decode(str) Decode.base64url_decode(str) end
base64url_encode(str)
click to toggle source
# File lib/jwt.rb, line 77 def base64url_encode(str) Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '') end
decode(jwt, key = nil, verify = true, custom_options = {}, &keyfinder)
click to toggle source
# File lib/jwt.rb, line 118 def decode(jwt, key = nil, verify = true, custom_options = {}, &keyfinder) raise(JWT::DecodeError, 'Nil JSON web token') unless jwt merged_options = DEFAULT_OPTIONS.merge(custom_options) decoder = Decode.new jwt, key, verify, merged_options, &keyfinder header, payload, signature, signing_input = decoder.decode_segments decode_verify_signature(key, header, signature, signing_input, merged_options, &keyfinder) if verify decoder.verify raise(JWT::DecodeError, 'Not enough or too many segments') unless header && payload [payload, header] end
decode_verify_signature(key, header, signature, signing_input, options, &keyfinder)
click to toggle source
# File lib/jwt.rb, line 132 def decode_verify_signature(key, header, signature, signing_input, options, &keyfinder) algo, key = signature_algorithm_and_key(header, key, &keyfinder) if options[:algorithm] && algo != options[:algorithm] raise JWT::IncorrectAlgorithm, 'Expected a different algorithm' end verify_signature(algo, key, signing_input, signature) end
decoded_segments(jwt, key = nil, verify = true, custom_options = {}, &keyfinder)
click to toggle source
# File lib/jwt.rb, line 109 def decoded_segments(jwt, key = nil, verify = true, custom_options = {}, &keyfinder) raise(JWT::DecodeError, 'Nil JSON web token') unless jwt merged_options = DEFAULT_OPTIONS.merge(custom_options) decoder = Decode.new jwt, key, verify, merged_options, &keyfinder decoder.decode_segments end
encode(payload, key, algorithm = 'HS256', header_fields = {})
click to toggle source
# File lib/jwt.rb, line 100 def encode(payload, key, algorithm = 'HS256', header_fields = {}) algorithm ||= 'none' segments = [] segments << encoded_header(algorithm, header_fields) segments << encoded_payload(payload) segments << encoded_signature(segments.join('.'), key, algorithm) segments.join('.') end
encoded_header(algorithm = 'HS256', header_fields = {})
click to toggle source
# File lib/jwt.rb, line 81 def encoded_header(algorithm = 'HS256', header_fields = {}) header = { 'typ' => 'JWT', 'alg' => algorithm }.merge(header_fields) base64url_encode(encode_json(header)) end
encoded_payload(payload)
click to toggle source
# File lib/jwt.rb, line 86 def encoded_payload(payload) raise InvalidPayload, 'exp claim must be an integer' if payload['exp'] && payload['exp'].is_a?(Time) base64url_encode(encode_json(payload)) end
encoded_signature(signing_input, key, algorithm)
click to toggle source
# File lib/jwt.rb, line 91 def encoded_signature(signing_input, key, algorithm) if algorithm == 'none' '' else signature = sign(algorithm, signing_input, key) base64url_encode(signature) end end
raw_to_asn1(signature, private_key)
click to toggle source
# File lib/jwt.rb, line 176 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
secure_compare(a, b)
click to toggle source
From devise constant-time comparison algorithm to prevent timing attacks
# File lib/jwt.rb, line 167 def secure_compare(a, b) return false if a.nil? || b.nil? || a.empty? || b.empty? || a.bytesize != b.bytesize l = a.unpack "C#{a.bytesize}" res = 0 b.each_byte { |byte| res |= byte ^ l.shift } res.zero? end
sign(algorithm, msg, key)
click to toggle source
# File lib/jwt.rb, line 33 def sign(algorithm, msg, key) if %w(HS256 HS384 HS512).include?(algorithm) sign_hmac(algorithm, msg, key) elsif %w(RS256 RS384 RS512).include?(algorithm) sign_rsa(algorithm, msg, key) elsif %w(ES256 ES384 ES512).include?(algorithm) sign_ecdsa(algorithm, msg, key) else raise NotImplementedError, 'Unsupported signing method' end end
sign_ecdsa(algorithm, msg, private_key)
click to toggle source
# File lib/jwt.rb, line 49 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')) asn1_to_raw(private_key.dsa_sign_asn1(digest.digest(msg)), private_key) end
sign_hmac(algorithm, msg, key)
click to toggle source
# File lib/jwt.rb, line 73 def sign_hmac(algorithm, msg, key) OpenSSL::HMAC.digest(OpenSSL::Digest.new(algorithm.sub('HS', 'sha')), key, msg) end
sign_rsa(algorithm, msg, private_key)
click to toggle source
# File lib/jwt.rb, line 45 def sign_rsa(algorithm, msg, private_key) private_key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg) end
signature_algorithm_and_key(header, key) { |header| ... }
click to toggle source
# File lib/jwt.rb, line 140 def signature_algorithm_and_key(header, key, &keyfinder) key = yield(header) if keyfinder [header['alg'], key] end
verify_ecdsa(algorithm, public_key, signing_input, signature)
click to toggle source
# File lib/jwt.rb, line 63 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), raw_to_asn1(signature, public_key)) end
verify_rsa(algorithm, public_key, signing_input, signature)
click to toggle source
# File lib/jwt.rb, line 59 def verify_rsa(algorithm, public_key, signing_input, signature) public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input) end
verify_signature(algo, key, signing_input, signature)
click to toggle source
# File lib/jwt.rb, line 145 def verify_signature(algo, key, signing_input, signature) verify_signature_algo(algo, key, signing_input, signature) rescue OpenSSL::PKey::PKeyError raise JWT::VerificationError, 'Signature verification raised' ensure OpenSSL.errors.clear end
verify_signature_algo(algo, key, signing_input, signature)
click to toggle source
# File lib/jwt.rb, line 153 def verify_signature_algo(algo, key, signing_input, signature) if %w(HS256 HS384 HS512).include?(algo) raise(JWT::VerificationError, 'Signature verification raised') unless secure_compare(signature, sign_hmac(algo, signing_input, key)) elsif %w(RS256 RS384 RS512).include?(algo) raise(JWT::VerificationError, 'Signature verification raised') unless verify_rsa(algo, key, signing_input, signature) elsif %w(ES256 ES384 ES512).include?(algo) raise(JWT::VerificationError, 'Signature verification raised') unless verify_ecdsa(algo, key, signing_input, signature) else raise JWT::VerificationError, 'Algorithm not supported' end end