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, InvalidSubError, VerificationError, Verify

Constant Summary collapse

NAMED_CURVES =
{
  'prime256v1' => 'ES256',
  'secp384r1' => 'ES384',
  'secp521r1' => 'ES512'
}

Class Method Summary collapse

Methods included from Json

decode_json, encode_json

Class Method Details

.asn1_to_raw(signature, public_key) ⇒ Object



169
170
171
172
# File 'lib/jwt.rb', line 169

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



174
175
176
# File 'lib/jwt.rb', line 174

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



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/jwt.rb', line 97

def decode(jwt, key = nil, verify = true, custom_options = {}, &keyfinder)
  fail(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
  decoder.verify

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

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

  [payload, header]
end

.encode(payload, key, algorithm = 'HS256', header_fields = {}) ⇒ Object



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

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



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

def encoded_payload(payload)
  base64url_encode(encode_json(payload))
end

.encoded_signature(signing_input, key, algorithm) ⇒ Object



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

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



162
163
164
165
166
167
# File 'lib/jwt.rb', line 162

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



153
154
155
156
157
158
159
160
# File 'lib/jwt.rb', line 153

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
    fail 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
    fail 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



130
131
132
133
# File 'lib/jwt.rb', line 130

def signature_algorithm_and_key(header, key, &keyfinder)
  key = keyfinder.call(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
    fail 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



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/jwt.rb', line 135

def verify_signature(algo, key, signing_input, signature)
  if %w(HS256 HS384 HS512).include?(algo)
    fail(JWT::VerificationError, 'Signature verification raised') unless secure_compare(signature, sign_hmac(algo, signing_input, key))
  elsif %w(RS256 RS384 RS512).include?(algo)
    fail(JWT::VerificationError, 'Signature verification raised') unless verify_rsa(algo, key, signing_input, signature)
  elsif %w(ES256 ES384 ES512).include?(algo)
    fail(JWT::VerificationError, 'Signature verification raised') unless verify_ecdsa(algo, key, signing_input, signature)
  else
    fail JWT::VerificationError, 'Algorithm not supported'
  end
rescue OpenSSL::PKey::PKeyError
  raise JWT::VerificationError, 'Signature verification raised'
ensure
  OpenSSL.errors.clear
end