jwt 1.5.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +20 -0
  3. data/.ebert.yml +18 -0
  4. data/.gitignore +11 -0
  5. data/.reek.yml +40 -0
  6. data/.rspec +1 -0
  7. data/.rubocop.yml +98 -0
  8. data/.travis.yml +14 -0
  9. data/CHANGELOG.md +476 -0
  10. data/Gemfile +3 -0
  11. data/LICENSE +7 -0
  12. data/Manifest +3 -1
  13. data/README.md +478 -0
  14. data/Rakefile +8 -15
  15. data/lib/jwt/algos/ecdsa.rb +35 -0
  16. data/lib/jwt/algos/eddsa.rb +23 -0
  17. data/lib/jwt/algos/hmac.rb +33 -0
  18. data/lib/jwt/algos/rsa.rb +19 -0
  19. data/lib/jwt/algos/unsupported.rb +16 -0
  20. data/lib/jwt/decode.rb +49 -0
  21. data/lib/jwt/default_options.rb +15 -0
  22. data/lib/jwt/encode.rb +51 -0
  23. data/lib/jwt/error.rb +16 -0
  24. data/lib/jwt/security_utils.rb +51 -0
  25. data/lib/jwt/signature.rb +50 -0
  26. data/lib/jwt/verify.rb +102 -0
  27. data/lib/jwt/version.rb +24 -0
  28. data/lib/jwt.rb +33 -203
  29. data/ruby-jwt.gemspec +31 -0
  30. data/spec/fixtures/certs/ec256-private.pem +8 -0
  31. data/spec/fixtures/certs/ec256-public.pem +4 -0
  32. data/spec/fixtures/certs/ec256-wrong-private.pem +8 -0
  33. data/spec/fixtures/certs/ec256-wrong-public.pem +4 -0
  34. data/spec/fixtures/certs/ec384-private.pem +9 -0
  35. data/spec/fixtures/certs/ec384-public.pem +5 -0
  36. data/spec/fixtures/certs/ec384-wrong-private.pem +9 -0
  37. data/spec/fixtures/certs/ec384-wrong-public.pem +5 -0
  38. data/spec/fixtures/certs/ec512-private.pem +10 -0
  39. data/spec/fixtures/certs/ec512-public.pem +6 -0
  40. data/spec/fixtures/certs/ec512-wrong-private.pem +10 -0
  41. data/spec/fixtures/certs/ec512-wrong-public.pem +6 -0
  42. data/spec/fixtures/certs/rsa-1024-private.pem +15 -0
  43. data/spec/fixtures/certs/rsa-1024-public.pem +6 -0
  44. data/spec/fixtures/certs/rsa-2048-private.pem +27 -0
  45. data/spec/fixtures/certs/rsa-2048-public.pem +9 -0
  46. data/spec/fixtures/certs/rsa-2048-wrong-private.pem +27 -0
  47. data/spec/fixtures/certs/rsa-2048-wrong-public.pem +9 -0
  48. data/spec/fixtures/certs/rsa-4096-private.pem +51 -0
  49. data/spec/fixtures/certs/rsa-4096-public.pem +14 -0
  50. data/spec/integration/readme_examples_spec.rb +202 -0
  51. data/spec/jwt/verify_spec.rb +232 -0
  52. data/spec/jwt_spec.rb +236 -384
  53. data/spec/spec_helper.rb +28 -0
  54. metadata +187 -26
  55. data/jwt.gemspec +0 -34
  56. data/lib/jwt/json.rb +0 -32
  57. data/spec/helper.rb +0 -19
@@ -0,0 +1,15 @@
1
+ module JWT
2
+ module DefaultOptions
3
+ DEFAULT_OPTIONS = {
4
+ verify_expiration: true,
5
+ verify_not_before: true,
6
+ verify_iss: false,
7
+ verify_iat: false,
8
+ verify_jti: false,
9
+ verify_aud: false,
10
+ verify_sub: false,
11
+ leeway: 0,
12
+ algorithms: ['HS256']
13
+ }.freeze
14
+ end
15
+ end
data/lib/jwt/encode.rb ADDED
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ # JWT::Encode module
6
+ module JWT
7
+ # Encoding logic for JWT
8
+ class Encode
9
+ attr_reader :payload, :key, :algorithm, :header_fields, :segments
10
+
11
+ def self.base64url_encode(str)
12
+ Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
13
+ end
14
+
15
+ def initialize(payload, key, algorithm, header_fields)
16
+ @payload = payload
17
+ @key = key
18
+ @algorithm = algorithm
19
+ @header_fields = header_fields
20
+ @segments = encode_segments
21
+ end
22
+
23
+ private
24
+
25
+ def encoded_header
26
+ header = { 'alg' => @algorithm }.merge(@header_fields)
27
+ Encode.base64url_encode(JSON.generate(header))
28
+ end
29
+
30
+ def encoded_payload
31
+ raise InvalidPayload, 'exp claim must be an integer' if @payload && @payload.is_a?(Hash) && @payload.key?('exp') && !@payload['exp'].is_a?(Integer)
32
+ Encode.base64url_encode(JSON.generate(@payload))
33
+ end
34
+
35
+ def encoded_signature(signing_input)
36
+ if @algorithm == 'none'
37
+ ''
38
+ else
39
+ signature = JWT::Signature.sign(@algorithm, signing_input, @key)
40
+ Encode.base64url_encode(signature)
41
+ end
42
+ end
43
+
44
+ def encode_segments
45
+ header = encoded_header
46
+ payload = encoded_payload
47
+ signature = encoded_signature([header, payload].join('.'))
48
+ [header, payload, signature].join('.')
49
+ end
50
+ end
51
+ end
data/lib/jwt/error.rb ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ class EncodeError < StandardError; end
5
+ class DecodeError < StandardError; end
6
+ class VerificationError < DecodeError; end
7
+ class ExpiredSignature < DecodeError; end
8
+ class IncorrectAlgorithm < DecodeError; end
9
+ class ImmatureSignature < DecodeError; end
10
+ class InvalidIssuerError < DecodeError; end
11
+ class InvalidIatError < DecodeError; end
12
+ class InvalidAudError < DecodeError; end
13
+ class InvalidSubError < DecodeError; end
14
+ class InvalidJtiError < DecodeError; end
15
+ class InvalidPayload < DecodeError; end
16
+ end
@@ -0,0 +1,51 @@
1
+ module JWT
2
+ # Collection of security methods
3
+ #
4
+ # @see: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/security_utils.rb
5
+ module SecurityUtils
6
+ module_function
7
+
8
+ def secure_compare(left, right)
9
+ left_bytesize = left.bytesize
10
+
11
+ return false unless left_bytesize == right.bytesize
12
+
13
+ unpacked_left = left.unpack "C#{left_bytesize}"
14
+ result = 0
15
+ right.each_byte { |byte| result |= byte ^ unpacked_left.shift }
16
+ result.zero?
17
+ end
18
+
19
+ def verify_rsa(algorithm, public_key, signing_input, signature)
20
+ public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
21
+ end
22
+
23
+ def asn1_to_raw(signature, public_key)
24
+ byte_size = (public_key.group.degree + 7) / 8
25
+ OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
26
+ end
27
+
28
+ def raw_to_asn1(signature, private_key)
29
+ byte_size = (private_key.group.degree + 7) / 8
30
+ sig_bytes = signature[0..(byte_size - 1)]
31
+ sig_char = signature[byte_size..-1] || ''
32
+ OpenSSL::ASN1::Sequence.new([sig_bytes, sig_char].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
33
+ end
34
+
35
+ def rbnacl_fixup(algorithm, key)
36
+ algorithm = algorithm.sub('HS', 'SHA').to_sym
37
+
38
+ return [] unless defined?(RbNaCl) && RbNaCl::HMAC.constants(false).include?(algorithm)
39
+
40
+ authenticator = RbNaCl::HMAC.const_get(algorithm)
41
+
42
+ # Fall back to OpenSSL for keys larger than 32 bytes.
43
+ return [] if key.bytesize > authenticator.key_bytes
44
+
45
+ [
46
+ authenticator,
47
+ key.bytes.fill(0, key.bytesize...authenticator.key_bytes).pack('C*')
48
+ ]
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt/security_utils'
4
+ require 'openssl'
5
+ require 'jwt/algos/hmac'
6
+ require 'jwt/algos/eddsa'
7
+ require 'jwt/algos/ecdsa'
8
+ require 'jwt/algos/rsa'
9
+ require 'jwt/algos/unsupported'
10
+ begin
11
+ require 'rbnacl'
12
+ rescue LoadError
13
+ raise if defined?(RbNaCl)
14
+ end
15
+
16
+ # JWT::Signature module
17
+ module JWT
18
+ # Signature logic for JWT
19
+ module Signature
20
+ extend self
21
+ ALGOS = [
22
+ Algos::Hmac,
23
+ Algos::Ecdsa,
24
+ Algos::Rsa,
25
+ Algos::Eddsa,
26
+ Algos::Unsupported
27
+ ].freeze
28
+ ToSign = Struct.new(:algorithm, :msg, :key)
29
+ ToVerify = Struct.new(:algorithm, :public_key, :signing_input, :signature)
30
+
31
+ def sign(algorithm, msg, key)
32
+ algo = ALGOS.find do |alg|
33
+ alg.const_get(:SUPPORTED).include? algorithm
34
+ end
35
+ algo.sign ToSign.new(algorithm, msg, key)
36
+ end
37
+
38
+ def verify(algorithm, key, signing_input, signature)
39
+ algo = ALGOS.find do |alg|
40
+ alg.const_get(:SUPPORTED).include? algorithm
41
+ end
42
+ verified = algo.verify(ToVerify.new(algorithm, key, signing_input, signature))
43
+ raise(JWT::VerificationError, 'Signature verification raised') unless verified
44
+ rescue OpenSSL::PKey::PKeyError
45
+ raise JWT::VerificationError, 'Signature verification raised'
46
+ ensure
47
+ OpenSSL.errors.clear
48
+ end
49
+ end
50
+ end
data/lib/jwt/verify.rb ADDED
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt/error'
4
+
5
+ module JWT
6
+ # JWT verify methods
7
+ class Verify
8
+ DEFAULTS = {
9
+ leeway: 0
10
+ }.freeze
11
+
12
+ class << self
13
+ %w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub].each do |method_name|
14
+ define_method method_name do |payload, options|
15
+ new(payload, options).send(method_name)
16
+ end
17
+ end
18
+
19
+ def verify_claims(payload, options)
20
+ options.each do |key, val|
21
+ next unless key.to_s =~ /verify/
22
+ Verify.send(key, payload, options) if val
23
+ end
24
+ end
25
+ end
26
+
27
+ def initialize(payload, options)
28
+ @payload = payload
29
+ @options = DEFAULTS.merge(options)
30
+ end
31
+
32
+ def verify_aud
33
+ return unless (options_aud = @options[:aud])
34
+
35
+ aud = @payload['aud']
36
+ raise(JWT::InvalidAudError, "Invalid audience. Expected #{options_aud}, received #{aud || '<none>'}") if ([*aud] & [*options_aud]).empty?
37
+ end
38
+
39
+ def verify_expiration
40
+ return unless @payload.include?('exp')
41
+ raise(JWT::ExpiredSignature, 'Signature has expired') if @payload['exp'].to_i <= (Time.now.to_i - exp_leeway)
42
+ end
43
+
44
+ def verify_iat
45
+ return unless @payload.include?('iat')
46
+
47
+ iat = @payload['iat']
48
+ raise(JWT::InvalidIatError, 'Invalid iat') if !iat.is_a?(Numeric) || iat.to_f > (Time.now.to_f + iat_leeway)
49
+ end
50
+
51
+ def verify_iss
52
+ return unless (options_iss = @options[:iss])
53
+
54
+ iss = @payload['iss']
55
+
56
+ return if Array(options_iss).map(&:to_s).include?(iss.to_s)
57
+
58
+ raise(JWT::InvalidIssuerError, "Invalid issuer. Expected #{options_iss}, received #{iss || '<none>'}")
59
+ end
60
+
61
+ def verify_jti
62
+ options_verify_jti = @options[:verify_jti]
63
+ jti = @payload['jti']
64
+
65
+ if options_verify_jti.respond_to?(:call)
66
+ verified = options_verify_jti.arity == 2 ? options_verify_jti.call(jti, @payload) : options_verify_jti.call(jti)
67
+ raise(JWT::InvalidJtiError, 'Invalid jti') unless verified
68
+ elsif jti.to_s.strip.empty?
69
+ raise(JWT::InvalidJtiError, 'Missing jti')
70
+ end
71
+ end
72
+
73
+ def verify_not_before
74
+ return unless @payload.include?('nbf')
75
+ raise(JWT::ImmatureSignature, 'Signature nbf has not been reached') if @payload['nbf'].to_i > (Time.now.to_i + nbf_leeway)
76
+ end
77
+
78
+ def verify_sub
79
+ return unless (options_sub = @options[:sub])
80
+ sub = @payload['sub']
81
+ raise(JWT::InvalidSubError, "Invalid subject. Expected #{options_sub}, received #{sub || '<none>'}") unless sub.to_s == options_sub.to_s
82
+ end
83
+
84
+ private
85
+
86
+ def global_leeway
87
+ @options[:leeway]
88
+ end
89
+
90
+ def exp_leeway
91
+ @options[:exp_leeway] || global_leeway
92
+ end
93
+
94
+ def iat_leeway
95
+ @options[:iat_leeway] || global_leeway
96
+ end
97
+
98
+ def nbf_leeway
99
+ @options[:nbf_leeway] || global_leeway
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ # Moments version builder module
5
+ module JWT
6
+ def self.gem_version
7
+ Gem::Version.new VERSION::STRING
8
+ end
9
+
10
+ # Moments version builder module
11
+ module VERSION
12
+ # major version
13
+ MAJOR = 2
14
+ # minor version
15
+ MINOR = 1
16
+ # tiny version
17
+ TINY = 0
18
+ # alpha, beta, etc. tag
19
+ PRE = nil
20
+
21
+ # Build version string
22
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
23
+ end
24
+ end
data/lib/jwt.rb CHANGED
@@ -1,233 +1,63 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'base64'
4
- require 'openssl'
5
- require 'jwt/json'
4
+ require 'jwt/decode'
5
+ require 'jwt/default_options'
6
+ require 'jwt/encode'
7
+ require 'jwt/error'
8
+ require 'jwt/signature'
9
+ require 'jwt/verify'
6
10
 
7
11
  # JSON Web Token implementation
8
12
  #
9
13
  # Should be up to date with the latest spec:
10
- # http://self-issued.info/docs/draft-jones-json-web-token-06.html
14
+ # https://tools.ietf.org/html/rfc7519
11
15
  module JWT
12
- class DecodeError < StandardError; end
13
- class VerificationError < DecodeError; end
14
- class ExpiredSignature < DecodeError; end
15
- class IncorrectAlgorithm < DecodeError; end
16
- class ImmatureSignature < DecodeError; end
17
- class InvalidIssuerError < DecodeError; end
18
- class InvalidIatError < DecodeError; end
19
- class InvalidAudError < DecodeError; end
20
- class InvalidSubError < DecodeError; end
21
- class InvalidJtiError < DecodeError; end
22
- extend JWT::Json
23
-
24
- NAMED_CURVES = {
25
- 'prime256v1' => 'ES256',
26
- 'secp384r1' => 'ES384',
27
- 'secp521r1' => 'ES512'
28
- }
16
+ include JWT::DefaultOptions
29
17
 
30
18
  module_function
31
19
 
32
- def sign(algorithm, msg, key)
33
- if ['HS256', 'HS384', 'HS512'].include?(algorithm)
34
- sign_hmac(algorithm, msg, key)
35
- elsif ['RS256', 'RS384', 'RS512'].include?(algorithm)
36
- sign_rsa(algorithm, msg, key)
37
- elsif ['ES256', 'ES384', 'ES512'].include?(algorithm)
38
- sign_ecdsa(algorithm, msg, key)
39
- else
40
- fail NotImplementedError.new('Unsupported signing method')
41
- end
42
- end
43
-
44
- def sign_rsa(algorithm, msg, private_key)
45
- private_key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
46
- end
47
-
48
- def sign_ecdsa(algorithm, msg, private_key)
49
- key_algorithm = NAMED_CURVES[private_key.group.curve_name]
50
- if algorithm != key_algorithm
51
- fail IncorrectAlgorithm.new("payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided")
52
- end
53
-
54
- digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
55
- asn1_to_raw(private_key.dsa_sign_asn1(digest.digest(msg)), private_key)
56
- end
57
-
58
- def verify_rsa(algorithm, public_key, signing_input, signature)
59
- public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
60
- end
61
-
62
- def verify_ecdsa(algorithm, public_key, signing_input, signature)
63
- key_algorithm = NAMED_CURVES[public_key.group.curve_name]
64
- if algorithm != key_algorithm
65
- fail IncorrectAlgorithm.new("payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided")
66
- end
67
-
68
- digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
69
- public_key.dsa_verify_asn1(digest.digest(signing_input), raw_to_asn1(signature, public_key))
20
+ def encode(payload, key, algorithm = 'HS256', header_fields = {})
21
+ encoder = Encode.new payload, key, algorithm, header_fields
22
+ encoder.segments
70
23
  end
71
24
 
72
- def sign_hmac(algorithm, msg, key)
73
- OpenSSL::HMAC.digest(OpenSSL::Digest.new(algorithm.sub('HS', 'sha')), key, msg)
74
- end
25
+ def decode(jwt, key = nil, verify = true, custom_options = {}, &keyfinder)
26
+ raise(JWT::DecodeError, 'Nil JSON web token') unless jwt
75
27
 
76
- def base64url_decode(str)
77
- str += '=' * (4 - str.length.modulo(4))
78
- Base64.decode64(str.tr('-_', '+/'))
79
- end
28
+ merged_options = DEFAULT_OPTIONS.merge(custom_options)
80
29
 
81
- def base64url_encode(str)
82
- Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
83
- end
30
+ decoder = Decode.new jwt, verify
31
+ header, payload, signature, signing_input = decoder.decode_segments
32
+ decode_verify_signature(key, header, payload, signature, signing_input, merged_options, &keyfinder) if verify
84
33
 
85
- def encoded_header(algorithm='HS256', header_fields={})
86
- header = { 'typ' => 'JWT', 'alg' => algorithm }.merge(header_fields)
87
- base64url_encode(encode_json(header))
88
- end
34
+ Verify.verify_claims(payload, merged_options) if verify
89
35
 
90
- def encoded_payload(payload)
91
- base64url_encode(encode_json(payload))
92
- end
93
-
94
- def encoded_signature(signing_input, key, algorithm)
95
- if algorithm == 'none'
96
- ''
97
- else
98
- signature = sign(algorithm, signing_input, key)
99
- base64url_encode(signature)
100
- end
101
- end
102
-
103
- def encode(payload, key, algorithm='HS256', header_fields={})
104
- algorithm ||= 'none'
105
- segments = []
106
- segments << encoded_header(algorithm, header_fields)
107
- segments << encoded_payload(payload)
108
- segments << encoded_signature(segments.join('.'), key, algorithm)
109
- segments.join('.')
110
- end
36
+ raise(JWT::DecodeError, 'Not enough or too many segments') unless header && payload
111
37
 
112
- def raw_segments(jwt, verify=true)
113
- segments = jwt.split('.')
114
- required_num_segments = verify ? [3] : [2, 3]
115
- fail JWT::DecodeError.new('Not enough or too many segments') unless required_num_segments.include? segments.length
116
- segments
117
- end
118
-
119
- def decode_header_and_payload(header_segment, payload_segment)
120
- header = decode_json(base64url_decode(header_segment))
121
- payload = decode_json(base64url_decode(payload_segment))
122
- [header, payload]
123
- end
124
-
125
- def decoded_segments(jwt, verify=true)
126
- header_segment, payload_segment, crypto_segment = raw_segments(jwt, verify)
127
- header, payload = decode_header_and_payload(header_segment, payload_segment)
128
- signature = base64url_decode(crypto_segment.to_s) if verify
129
- signing_input = [header_segment, payload_segment].join('.')
130
- [header, payload, signature, signing_input]
38
+ [payload, header]
131
39
  end
132
40
 
133
- def decode(jwt, key=nil, verify=true, options={}, &keyfinder)
134
- fail JWT::DecodeError.new('Nil JSON web token') unless jwt
135
-
136
- header, payload, signature, signing_input = decoded_segments(jwt, verify)
137
- fail JWT::DecodeError.new('Not enough or too many segments') unless header && payload
138
-
139
- default_options = {
140
- :verify_expiration => true,
141
- :verify_not_before => true,
142
- :verify_iss => false,
143
- :verify_iat => false,
144
- :verify_jti => false,
145
- :verify_aud => false,
146
- :verify_sub => false,
147
- :leeway => 0
148
- }
41
+ def decode_verify_signature(key, header, payload, signature, signing_input, options, &keyfinder)
42
+ algo, key = signature_algorithm_and_key(header, payload, key, &keyfinder)
149
43
 
150
- options = default_options.merge(options)
44
+ raise(JWT::IncorrectAlgorithm, 'An algorithm must be specified') if allowed_algorithms(options).empty?
45
+ raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') unless allowed_algorithms(options).include?(algo)
151
46
 
152
- if verify
153
- algo, key = signature_algorithm_and_key(header, key, &keyfinder)
154
- if options[:algorithm] && algo != options[:algorithm]
155
- fail JWT::IncorrectAlgorithm.new('Expected a different algorithm')
156
- end
157
- verify_signature(algo, key, signing_input, signature)
158
- end
159
-
160
- if options[:verify_expiration] && payload.include?('exp')
161
- fail JWT::ExpiredSignature.new('Signature has expired') unless payload['exp'].to_i > (Time.now.to_i - options[:leeway])
162
- end
163
- if options[:verify_not_before] && payload.include?('nbf')
164
- fail JWT::ImmatureSignature.new('Signature nbf has not been reached') unless payload['nbf'].to_i < (Time.now.to_i + options[:leeway])
165
- end
166
- if options[:verify_iss] && options['iss']
167
- fail JWT::InvalidIssuerError.new("Invalid issuer. Expected #{options['iss']}, received #{payload['iss'] || '<none>'}") unless payload['iss'].to_s == options['iss'].to_s
168
- end
169
- if options[:verify_iat] && payload.include?('iat')
170
- fail JWT::InvalidIatError.new('Invalid iat') unless payload['iat'].is_a?(Integer) && payload['iat'].to_i <= Time.now.to_i
171
- end
172
- if options[:verify_aud] && options['aud']
173
- if payload['aud'].is_a?(Array)
174
- fail JWT::InvalidAudError.new('Invalid audience') unless payload['aud'].include?(options['aud'].to_s)
175
- else
176
- fail JWT::InvalidAudError.new("Invalid audience. Expected #{options['aud']}, received #{payload['aud'] || '<none>'}") unless payload['aud'].to_s == options['aud'].to_s
177
- end
178
- end
179
- if options[:verify_sub] && payload.include?('sub')
180
- fail JWT::InvalidSubError.new("Invalid subject. Expected #{options['sub']}, received #{payload['sub']}") unless payload['sub'].to_s == options['sub'].to_s
181
- end
182
- if options[:verify_jti] && payload.include?('jti')
183
- fail JWT::InvalidJtiError.new('need iat for verify jwt id') unless payload.include?('iat')
184
- fail JWT::InvalidJtiError.new('Not a uniq jwt id') unless options['jti'].to_s == Digest::MD5.hexdigest("#{key}:#{payload['iat']}")
185
- end
186
-
187
- [payload, header]
47
+ Signature.verify(algo, key, signing_input, signature)
188
48
  end
189
49
 
190
- def signature_algorithm_and_key(header, key, &keyfinder)
191
- key = keyfinder.call(header) if keyfinder
50
+ def signature_algorithm_and_key(header, payload, key, &keyfinder)
51
+ key = (keyfinder.arity == 2 ? yield(header, payload) : yield(header)) if keyfinder
52
+ raise JWT::DecodeError, 'No verification key available' unless key
192
53
  [header['alg'], key]
193
54
  end
194
55
 
195
- def verify_signature(algo, key, signing_input, signature)
196
- if ['HS256', 'HS384', 'HS512'].include?(algo)
197
- fail JWT::VerificationError.new('Signature verification failed') unless secure_compare(signature, sign_hmac(algo, signing_input, key))
198
- elsif ['RS256', 'RS384', 'RS512'].include?(algo)
199
- fail JWT::VerificationError.new('Signature verification failed') unless verify_rsa(algo, key, signing_input, signature)
200
- elsif ['ES256', 'ES384', 'ES512'].include?(algo)
201
- fail JWT::VerificationError.new('Signature verification failed') unless verify_ecdsa(algo, key, signing_input, signature)
56
+ def allowed_algorithms(options)
57
+ if options.key?(:algorithm)
58
+ [options[:algorithm]]
202
59
  else
203
- fail JWT::VerificationError.new('Algorithm not supported')
60
+ options[:algorithms] || []
204
61
  end
205
- rescue OpenSSL::PKey::PKeyError
206
- raise JWT::VerificationError.new('Signature verification failed')
207
- ensure
208
- OpenSSL.errors.clear
209
- end
210
-
211
- # From devise
212
- # constant-time comparison algorithm to prevent timing attacks
213
- def secure_compare(a, b)
214
- return false if a.nil? || b.nil? || a.empty? || b.empty? || a.bytesize != b.bytesize
215
- l = a.unpack "C#{a.bytesize}"
216
-
217
- res = 0
218
- b.each_byte { |byte| res |= byte ^ l.shift }
219
- res == 0
220
- end
221
-
222
- def raw_to_asn1(signature, private_key)
223
- byte_size = (private_key.group.degree + 7) / 8
224
- r = signature[0..(byte_size - 1)]
225
- s = signature[byte_size..-1]
226
- OpenSSL::ASN1::Sequence.new([r, s].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
227
- end
228
-
229
- def asn1_to_raw(signature, public_key)
230
- byte_size = (public_key.group.degree + 7) / 8
231
- OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
232
62
  end
233
63
  end
data/ruby-jwt.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'jwt/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'jwt'
7
+ spec.version = JWT.gem_version
8
+ spec.authors = [
9
+ 'Tim Rudat'
10
+ ]
11
+ spec.email = 'timrudat@gmail.com'
12
+ spec.summary = 'JSON Web Token implementation in Ruby'
13
+ spec.description = 'A pure ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard.'
14
+ spec.homepage = 'http://github.com/jwt/ruby-jwt'
15
+ spec.license = 'MIT'
16
+ spec.required_ruby_version = '>= 2.1'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0")
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = %w[lib]
22
+
23
+ spec.add_development_dependency 'bundler'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'rspec'
26
+ spec.add_development_dependency 'simplecov'
27
+ spec.add_development_dependency 'simplecov-json'
28
+ spec.add_development_dependency 'codeclimate-test-reporter'
29
+ spec.add_development_dependency 'codacy-coverage'
30
+ spec.add_development_dependency 'rbnacl'
31
+ end
@@ -0,0 +1,8 @@
1
+ -----BEGIN EC PARAMETERS-----
2
+ BggqhkjOPQMBBw==
3
+ -----END EC PARAMETERS-----
4
+ -----BEGIN EC PRIVATE KEY-----
5
+ MHcCAQEEIJmVse5uPfj6B4TcXrUAvf9/8pJh+KrKKYLNcmOnp/vPoAoGCCqGSM49
6
+ AwEHoUQDQgAEAr+WbDE5VtIDGhtYMxvEc6cMsDBc/DX1wuhIMu8dQzOLSt0tpqK9
7
+ MVfXbVfrKdayVFgoWzs8MilcYq0QIhKx/w==
8
+ -----END EC PRIVATE KEY-----
@@ -0,0 +1,4 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAr+WbDE5VtIDGhtYMxvEc6cMsDBc
3
+ /DX1wuhIMu8dQzOLSt0tpqK9MVfXbVfrKdayVFgoWzs8MilcYq0QIhKx/w==
4
+ -----END PUBLIC KEY-----
@@ -0,0 +1,8 @@
1
+ -----BEGIN EC PARAMETERS-----
2
+ BgUrgQQACg==
3
+ -----END EC PARAMETERS-----
4
+ -----BEGIN EC PRIVATE KEY-----
5
+ MHQCAQEEICfA4AaomONdmPTzeyrx5U/jugYXTERyb5U3ETTv7Hx7oAcGBSuBBAAK
6
+ oUQDQgAEPmuXZT3jpJnEMVPOW6RMsmxeGLOCE1PN6fwvUwOsxv7YnyoQ5/bpo64n
7
+ +Jp4slSl1aUNoCBF2oz9bS0iyBo3jg==
8
+ -----END EC PRIVATE KEY-----
@@ -0,0 +1,4 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEPmuXZT3jpJnEMVPOW6RMsmxeGLOCE1PN
3
+ 6fwvUwOsxv7YnyoQ5/bpo64n+Jp4slSl1aUNoCBF2oz9bS0iyBo3jg==
4
+ -----END PUBLIC KEY-----
@@ -0,0 +1,9 @@
1
+ -----BEGIN EC PARAMETERS-----
2
+ BgUrgQQAIg==
3
+ -----END EC PARAMETERS-----
4
+ -----BEGIN EC PRIVATE KEY-----
5
+ MIGkAgEBBDDxOljqUKw9YNhkluSJIBAYO1YXcNtS+vckd5hpTZ5toxsOlwbmyrnU
6
+ Tn+D5Xma1m2gBwYFK4EEACKhZANiAASQwYTiRvXu1hMHceSosMs/8uf50sJI3jvK
7
+ kdSkvuRAPxSzhtrUvCQDnVsThFq4aOdZZY1qh2ErJGtzmrx+pEsJvJnvfOTG3NGU
8
+ KRalek+LQfVqAUSvDMKlxdkz2e67tso=
9
+ -----END EC PRIVATE KEY-----
@@ -0,0 +1,5 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEkMGE4kb17tYTB3HkqLDLP/Ln+dLCSN47
3
+ ypHUpL7kQD8Us4ba1LwkA51bE4RauGjnWWWNaodhKyRrc5q8fqRLCbyZ73zkxtzR
4
+ lCkWpXpPi0H1agFErwzCpcXZM9nuu7bK
5
+ -----END PUBLIC KEY-----
@@ -0,0 +1,9 @@
1
+ -----BEGIN EC PARAMETERS-----
2
+ BgUrgQQAIg==
3
+ -----END EC PARAMETERS-----
4
+ -----BEGIN EC PRIVATE KEY-----
5
+ MIGkAgEBBDAfZW47dSKnC5JkSVOk1ERxCIi/IJ1p1WBnVGx4hnrNHy+dxtaZJaF+
6
+ YLInFQ/QbYegBwYFK4EEACKhZANiAAQwXkx4BFBGLXbzl5yVrfxK7er8hSi38iDE
7
+ K2+7cdrR137Wn5JUnL4WTwXTzkyUgfBOL3sHNozwfgU03GD/EOUEKqzsIJiz2cbP
8
+ bFALd4hS+8T4szDLVC9Jl1W6k0CAtmM=
9
+ -----END EC PRIVATE KEY-----