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

Class Method Summary collapse

Methods included from Json

decode_json, encode_json

Class Method Details

.asn1_to_raw(signature, public_key) ⇒ Object



192
193
194
195
# File 'lib/jwt.rb', line 192

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



197
198
199
# File 'lib/jwt.rb', line 197

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

.base64url_encode(str) ⇒ Object



66
67
68
# File 'lib/jwt.rb', line 66

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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# 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

  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
  }

  merged_options = options.merge(custom_options)

  decoder = Decode.new jwt, key, verify, merged_options, &keyfinder
  header, payload, signature, signing_input = decoder.decode_segments

  if verify
    algo, key = signature_algorithm_and_key(header, key, &keyfinder)
    if merged_options[:algorithm] && algo != merged_options[:algorithm]
      raise JWT::IncorrectAlgorithm, 'Expected a different algorithm'
    end
    verify_signature(algo, key, signing_input, signature)
  end

  decoder.verify

  raise(JWT::DecodeError, 'Not enough or too many segments') unless header && payload

  [payload, header]
end

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

Raises:



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/jwt.rb', line 98

def decoded_segments(jwt, key = nil, verify = true, custom_options = {}, &keyfinder)
  raise(JWT::DecodeError, 'Nil JSON web token') unless jwt

  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
  }

  merged_options = 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



89
90
91
92
93
94
95
96
# File 'lib/jwt.rb', line 89

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



70
71
72
73
# File 'lib/jwt.rb', line 70

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:



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

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



80
81
82
83
84
85
86
87
# File 'lib/jwt.rb', line 80

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

.gem_versionObject



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

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

.raw_to_asn1(signature, private_key) ⇒ Object



185
186
187
188
189
190
# File 'lib/jwt.rb', line 185

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



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

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 == 0
end

.sign(algorithm, msg, key) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
# File 'lib/jwt.rb', line 22

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



38
39
40
41
42
43
44
45
46
# File 'lib/jwt.rb', line 38

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



62
63
64
# File 'lib/jwt.rb', line 62

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



34
35
36
# File 'lib/jwt.rb', line 34

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



153
154
155
156
# File 'lib/jwt.rb', line 153

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



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

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



48
49
50
# File 'lib/jwt.rb', line 48

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



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/jwt.rb', line 158

def verify_signature(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
rescue OpenSSL::PKey::PKeyError
  raise JWT::VerificationError, 'Signature verification raised'
ensure
  OpenSSL.errors.clear
end