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
-
.asn1_to_raw(signature, public_key) ⇒ Object
-
.base64url_decode(str) ⇒ Object
-
.base64url_encode(str) ⇒ Object
-
.decode(jwt, key = nil, verify = true, custom_options = {}, &keyfinder) ⇒ Object
-
.decode_verify_signature(key, header, signature, signing_input, options, &keyfinder) ⇒ Object
-
.decoded_segments(jwt, key = nil, verify = true, custom_options = {}, &keyfinder) ⇒ Object
-
.encode(payload, key, algorithm = 'HS256', header_fields = {}) ⇒ Object
-
.encoded_header(algorithm = 'HS256', header_fields = {}) ⇒ Object
-
.encoded_payload(payload) ⇒ Object
-
.encoded_signature(signing_input, key, algorithm) ⇒ Object
-
.gem_version ⇒ Object
-
.raw_to_asn1(signature, private_key) ⇒ Object
-
.secure_compare(a, b) ⇒ Object
From devise constant-time comparison algorithm to prevent timing attacks.
-
.sign(algorithm, msg, key) ⇒ Object
-
.sign_ecdsa(algorithm, msg, private_key) ⇒ Object
-
.sign_hmac(algorithm, msg, key) ⇒ Object
-
.sign_rsa(algorithm, msg, private_key) ⇒ Object
-
.signature_algorithm_and_key(header, key, &keyfinder) ⇒ Object
-
.verify_ecdsa(algorithm, public_key, signing_input, signature) ⇒ Object
-
.verify_rsa(algorithm, public_key, signing_input, signature) ⇒ Object
-
.verify_signature(algo, key, signing_input, signature) ⇒ Object
-
.verify_signature_algo(algo, key, signing_input, signature) ⇒ Object
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
.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
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
, payload, signature, signing_input = decoder.decode_segments
decode_verify_signature(key, , signature, signing_input, merged_options, &keyfinder) if verify
decoder.verify
raise(JWT::DecodeError, 'Not enough or too many segments') unless && payload
[payload, ]
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, , signature, signing_input, options, &keyfinder)
algo, key = signature_algorithm_and_key(, 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
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', = {})
algorithm ||= 'none'
segments = []
segments << (algorithm, )
segments << encoded_payload(payload)
segments << encoded_signature(segments.join('.'), key, algorithm)
segments.join('.')
end
|
82
83
84
85
|
# File 'lib/jwt.rb', line 82
def (algorithm = 'HS256', = {})
= { 'typ' => 'JWT', 'alg' => algorithm }.merge()
base64url_encode(encode_json())
end
|
.encoded_payload(payload) ⇒ Object
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_version ⇒ Object
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(, key, &keyfinder)
key = yield() if keyfinder
[['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
|