Module: JWT

Extended by:
Json
Defined in:
lib/jwt.rb,
lib/jwt/json.rb,
lib/jwt/error.rb,
lib/jwt/decode.rb,
lib/jwt/verify.rb,
lib/jwt/version.rb

Overview

Moments version builder module

Defined Under Namespace

Modules: Json, VERSION Classes: Decode, DecodeError, ExpiredSignature, ImmatureSignature, IncorrectAlgorithm, InvalidAudError, InvalidIatError, InvalidIssuerError, InvalidJtiError, InvalidPayload, InvalidSubError, VerificationError, Verify

Constant Summary collapse

NAMED_CURVES =
{
  'prime256v1' => 'ES256',
  'secp384r1' => 'ES384',
  'secp521r1' => 'ES512'
}.freeze
DEFAULT_OPTIONS =
{
  verify_expiration: true,
  verify_not_before: true,
  verify_iss: false,
  verify_iat: false,
  verify_jti: false,
  verify_aud: false,
  verify_sub: false,
  leeway: 0
}.freeze

Class Method Summary collapse

Methods included from Json

decode_json, encode_json

Class Method Details

.asn1_to_raw(signature, public_key) ⇒ Object



184
185
186
187
# File 'lib/jwt.rb', line 184

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) ⇒ Object



189
190
191
# File 'lib/jwt.rb', line 189

def base64url_decode(str)
  Decode.base64url_decode(str)
end

.base64url_encode(str) ⇒ Object



78
79
80
# File 'lib/jwt.rb', line 78

def base64url_encode(str)
  Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
end

.decode(jwt, key = nil, verify = true, custom_options = {}, &keyfinder) ⇒ Object

Raises:



119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/jwt.rb', line 119

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) ⇒ Object



133
134
135
136
137
138
139
# File 'lib/jwt.rb', line 133

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) ⇒ Object

Raises:



110
111
112
113
114
115
116
117
# File 'lib/jwt.rb', line 110

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 = {}) ⇒ Object



101
102
103
104
105
106
107
108
# File 'lib/jwt.rb', line 101

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 = {}) ⇒ Object



82
83
84
85
# File 'lib/jwt.rb', line 82

def encoded_header(algorithm = 'HS256', header_fields = {})
  header = { 'typ' => 'JWT', 'alg' => algorithm }.merge(header_fields)
  base64url_encode(encode_json(header))
end

.encoded_payload(payload) ⇒ Object

Raises:



87
88
89
90
# File 'lib/jwt.rb', line 87

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) ⇒ Object



92
93
94
95
96
97
98
99
# File 'lib/jwt.rb', line 92

def encoded_signature(signing_input, key, algorithm)
  if algorithm == 'none'
    ''
  else
    signature = sign(algorithm, signing_input, key)
    base64url_encode(signature)
  end
end

.gem_versionObject



6
7
8
# File 'lib/jwt/version.rb', line 6

def self.gem_version
  Gem::Version.new VERSION::STRING
end

.raw_to_asn1(signature, private_key) ⇒ Object



177
178
179
180
181
182
# File 'lib/jwt.rb', line 177

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) ⇒ Object

From devise constant-time comparison algorithm to prevent timing attacks



168
169
170
171
172
173
174
175
# File 'lib/jwt.rb', line 168

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) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
# File 'lib/jwt.rb', line 34

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) ⇒ Object



50
51
52
53
54
55
56
57
58
# File 'lib/jwt.rb', line 50

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) ⇒ Object



74
75
76
# File 'lib/jwt.rb', line 74

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) ⇒ Object



46
47
48
# File 'lib/jwt.rb', line 46

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, &keyfinder) ⇒ Object



141
142
143
144
# File 'lib/jwt.rb', line 141

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) ⇒ Object



64
65
66
67
68
69
70
71
72
# File 'lib/jwt.rb', line 64

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) ⇒ Object



60
61
62
# File 'lib/jwt.rb', line 60

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) ⇒ Object



146
147
148
149
150
151
152
# File 'lib/jwt.rb', line 146

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) ⇒ Object



154
155
156
157
158
159
160
161
162
163
164
# File 'lib/jwt.rb', line 154

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