Module: JWT

Extended by:
Json
Defined in:
lib/jwt.rb,
lib/jwt/json.rb

Defined Under Namespace

Modules: Json Classes: DecodeError, ExpiredSignature, ImmatureSignature, VerificationError

Class Method Summary collapse

Methods included from Json

decode_json, encode_json

Class Method Details

.base64url_decode(str) ⇒ Object



42
43
44
45
# File 'lib/jwt.rb', line 42

def base64url_decode(str)
  str += "=" * (4 - str.length.modulo(4))
  Base64.decode64(str.tr("-_", "+/"))
end

.base64url_encode(str) ⇒ Object



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

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

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

Raises:



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
# File 'lib/jwt.rb', line 99

def decode(jwt, key=nil, verify=true, options={}, &keyfinder)
  raise 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

  default_options = {
    :verify_expiration => true,
    :verify_not_before => true,
    :leeway => 0
  }
  options = default_options.merge(options)

  if verify
    algo, key = signature_algorithm_and_key(header, key, &keyfinder)
    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])
  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])
  end
  return payload,header
end

.decode_header_and_payload(header_segment, payload_segment) ⇒ Object



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

def decode_header_and_payload(header_segment, payload_segment)
  header = decode_json(base64url_decode(header_segment))
  payload = decode_json(base64url_decode(payload_segment))
  [header, payload]
end

.decoded_segments(jwt, verify = true) ⇒ Object



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

def decoded_segments(jwt, verify=true)
  header_segment, payload_segment, crypto_segment = raw_segments(jwt, verify)
  header, payload = decode_header_and_payload(header_segment, payload_segment)
  signature = base64url_decode(crypto_segment.to_s) if verify
  signing_input = [header_segment, payload_segment].join(".")
  [header, payload, signature, signing_input]
end

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



69
70
71
72
73
74
75
76
# File 'lib/jwt.rb', line 69

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



51
52
53
54
# File 'lib/jwt.rb', line 51

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



56
57
58
# File 'lib/jwt.rb', line 56

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

.encoded_signature(signing_input, key, algorithm) ⇒ Object



60
61
62
63
64
65
66
67
# File 'lib/jwt.rb', line 60

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

.raw_segments(jwt, verify = true) ⇒ Object

Raises:



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

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
  segments
end

.secure_compare(a, b) ⇒ Object

From devise constant-time comparison algorithm to prevent timing attacks



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

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



20
21
22
23
24
25
26
27
28
# File 'lib/jwt.rb', line 20

def sign(algorithm, msg, key)
  if ["HS256", "HS384", "HS512"].include?(algorithm)
    sign_hmac(algorithm, msg, key)
  elsif ["RS256", "RS384", "RS512"].include?(algorithm)
    sign_rsa(algorithm, msg, key)
  else
    raise NotImplementedError.new("Unsupported signing method")
  end
end

.sign_hmac(algorithm, msg, key) ⇒ Object



38
39
40
# File 'lib/jwt.rb', line 38

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



30
31
32
# File 'lib/jwt.rb', line 30

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



126
127
128
129
130
131
# File 'lib/jwt.rb', line 126

def signature_algorithm_and_key(header, key, &keyfinder)
  if keyfinder
    key = keyfinder.call(header)
  end
  [header['alg'], key]
end

.verify_rsa(algorithm, public_key, signing_input, signature) ⇒ Object



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

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



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

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)
    else
      raise JWT::VerificationError.new("Algorithm not supported")
    end
  rescue OpenSSL::PKey::PKeyError
    raise JWT::VerificationError.new("Signature verification failed")
  ensure
    OpenSSL.errors.clear
  end
end