jwt 1.5.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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-----