json_web_token 0.0.2 → 0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d1518a4e0fe92dc8de73a3ed606115ac89ef466b
4
- data.tar.gz: 5d38d88d1307780db38343d0c23d315963fa376d
3
+ metadata.gz: b8edcad4e223e03c0e62c87821bda85d261108a4
4
+ data.tar.gz: 3eaa77429d1e1e114226ad5db6ade121a46c56f8
5
5
  SHA512:
6
- metadata.gz: 781e08e80e4ec08fa00f02e7d7127bca5ba50ce83f2708c7e5135c53b4edeaac53891bac3a0fcc517112511210ab55fd38552e105db62bc631453f3f5df289c1
7
- data.tar.gz: 4c55609063885e415f48ea97c9169076837551fdc2a779f80ee27d14442cdfaf487d3f3dcdeae9fca6ceb6000af210b81d12876e7697250f7ec7ccaff9d976bc
6
+ metadata.gz: 9e39740af0f48f4781537d5b64ad6f94eaa21945d96155a9beed99db588509201233c07f320eda7aa3ba78126a800c857e35a03c26b1fb4d90935e72061c4aee
7
+ data.tar.gz: 84d4f1ad8cdb91063fcfa3ad48706722ebae3df3cf78deb947a5c512c62f56c78fada15408b840e5d2014ea24a20f69f4f4d5bc496bfa7e6fd19d4576f009989
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ - 2.1.6
5
+ - 2.0.0
6
+ # uncomment this line if your project needs to run something other than `rake`:
7
+ script: bundle exec rspec spec
@@ -1,11 +1,16 @@
1
1
  ## Changelog
2
2
 
3
- ### 0.0.2 2015-07-11
3
+ ### v0.1.0 (2015-07-12)
4
+
5
+ * enhancements
6
+ * support ECDSA algorithm
7
+
8
+ ### v0.0.2 (2015-07-11)
4
9
 
5
10
  * enhancements
6
11
  * support RSASSA-PKCS-v1_5 algorithm
7
12
 
8
- ### 0.0.1 2015-07-09
13
+ ### v0.0.1 (2015-07-09)
9
14
 
10
15
  * initial
11
16
  * support HMAC algorithm
data/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # JSON Web Token
2
2
 
3
3
  ## A JSON Web Token implementation for Ruby
4
- **Work in progress -- not yet ready for production**
4
+
5
+ [![code_climate][cc_img]][code_climate]
5
6
 
6
7
  ### Description
7
8
  A Ruby implementation of the JSON Web Token (JWT) Standards Track [RFC 7519][rfc7519]
@@ -16,10 +17,12 @@ A Ruby implementation of the JSON Web Token (JWT) Standards Track [RFC 7519][rfc
16
17
  - JSON Web Algorithms (JWA) Standards Track [RFC 7518][rfc7518]
17
18
  * Thorough test coverage
18
19
  * Modularity for comprehension and extensibility
20
+ * Fail fast and hard, with maximally strict validation
21
+ - Inspired by [The Harmful Consequences of Postel's Maxim][thomson-postel]
19
22
  * Implement only the REQUIRED elements of the JWT standard (initially)
20
23
 
21
24
  ### Intended Audience
22
- Token authentication of API requests to Rails via these popular gems
25
+ Token authentication of API requests to Rails via these prominent gems:
23
26
 
24
27
  - [Devise][devise]
25
28
  - [Doorkeeper][doorkeeper]
@@ -37,7 +40,7 @@ Returns a JSON Web Token string
37
40
 
38
41
  `options` (optional) hash
39
42
 
40
- * **alg**, default: `HS256`
43
+ * **alg** (optional, default: `HS256`)
41
44
  * **key** (required unless alg is 'none')
42
45
 
43
46
  Example
@@ -49,12 +52,12 @@ require 'json_web_token'
49
52
  jwt = JsonWebToken.create({foo: 'bar'}, key: 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C')
50
53
 
51
54
  # sign with RSA SHA256 algorithm
52
- options = {
55
+ opts = {
53
56
  alg: 'RSA256',
54
57
  key: < RSA private key >
55
58
  }
56
59
 
57
- jwt = JsonWebToken.create({foo: 'bar'}, options)
60
+ jwt = JsonWebToken.create({foo: 'bar'}, opts)
58
61
 
59
62
  # unsecured token (algorithm is 'none')
60
63
  jwt = JsonWebToken.create({foo: 'bar'}, alg: 'none')
@@ -63,13 +66,15 @@ jwt = JsonWebToken.create({foo: 'bar'}, alg: 'none')
63
66
 
64
67
  ### JsonWebToken.validate(jwt, options)
65
68
 
66
- Returns a JWT claims set string or hash, if the MAC signature is valid
69
+ Returns either:
70
+ * a JWT claims set string or hash, if the Message Authentication Code (MAC), or signature, is verified
71
+ * a string, 'Invalid', otherwise
67
72
 
68
73
  `jwt` (required) is a JSON web token string
69
74
 
70
75
  `options` (optional) hash
71
76
 
72
- * **algorithm**, default: `HS256`
77
+ * **alg** (optional, default: `HS256`)
73
78
  * **key** (required unless alg is 'none')
74
79
 
75
80
  Example
@@ -77,24 +82,24 @@ Example
77
82
  ```ruby
78
83
  require 'json_web_token'
79
84
 
80
- secure_jwt = 'eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt.cGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk'
85
+ secure_jwt_example = 'eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt.cGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk'
81
86
 
82
87
  # verify with default algorithm, HMAC SHA256
83
- claims = JsonWebToken.validate(secure_jwt, key: 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C')
88
+ claims = JsonWebToken.validate(secure_jwt_example, key: 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C')
84
89
 
85
90
  # verify with RSA SHA256 algorithm
86
- options = {
91
+ opts = {
87
92
  alg: 'RSA256',
88
93
  key: < RSA public key >
89
94
  }
90
95
 
91
- claims = JsonWebToken.validate(jwt, options)
96
+ claims = JsonWebToken.validate(jwt, opts)
92
97
 
93
98
  # unsecured token (algorithm is 'none')
94
99
 
95
- unsecured_jwt = 'eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt.'
100
+ unsecured_jwt_example = 'eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt.'
96
101
 
97
- claims = JsonWebToken.validate(unsecured_jwt, alg: 'none')
102
+ claims = JsonWebToken.validate(unsecured_jwt_example, alg: 'none')
98
103
 
99
104
  ```
100
105
  ### Supported encryption algorithms
@@ -107,6 +112,9 @@ HS512 | HMAC using SHA-512
107
112
  RS256 | RSASSA-PKCS-v1_5 using SHA-256 per [RFC3447][rfc3447]
108
113
  RS384 | RSASSA-PKCS-v1_5 using SHA-384
109
114
  RS512 | RSASSA-PKCS-v1_5 using SHA-512
115
+ ES256 | ECDSA using P-256 and SHA-256 per [DSS][dss]
116
+ ES384 | ECDSA using P-384 and SHA-384
117
+ ES512 | ECDSA using P-521 and SHA-512
110
118
  none | No digital signature or MAC performed (unsecured)
111
119
 
112
120
  ### Supported Ruby Versions
@@ -115,7 +123,7 @@ Ruby 2.0 and up
115
123
  ### Limitations
116
124
  Future implementation may include these features:
117
125
 
118
- - additional RECOMMENDED or OPTIONAL encryption algorithms
126
+ - processing of OPTIONAL JWT registered claim names (e.g. 'exp')
119
127
  - representation of a JWT as a JSON Web Encryption (JWE) [RFC 7516][rfc7516]
120
128
  - OPTIONAL nested JWTs
121
129
 
@@ -125,9 +133,14 @@ Future implementation may include these features:
125
133
  [rfc7516]: http://tools.ietf.org/html/rfc7516
126
134
  [rfc7518]: http://tools.ietf.org/html/rfc7518
127
135
  [rfc7519]: http://tools.ietf.org/html/rfc7519
136
+ [dss]: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
128
137
 
138
+ [thomson-postel]: https://tools.ietf.org/html/draft-thomson-postel-was-wrong-00
129
139
  [cors]: http://www.w3.org/TR/cors/
130
140
  [devise]: https://github.com/plataformatec/devise
131
141
  [doorkeeper]: https://github.com/doorkeeper-gem/doorkeeper
132
142
  [oauth2]: https://github.com/intridea/oauth2
133
143
  [rack-cors]: https://github.com/cyu/rack-cors
144
+
145
+ [code_climate]: https://codeclimate.com/github/garyf/json_web_token
146
+ [cc_img]: https://codeclimate.com/github/garyf/json_web_token/badges/gpa.svg
@@ -0,0 +1,50 @@
1
+ require 'json_web_token/algorithm/common'
2
+ require 'json_web_token/format/asn1'
3
+
4
+ module JsonWebToken
5
+ module Algorithm
6
+ module Ecdsa
7
+
8
+ extend JsonWebToken::Algorithm::Common
9
+ extend JsonWebToken::Format::Asn1
10
+
11
+ MAC_BYTE_COUNT = {
12
+ '256' => 64, # secp256k1
13
+ '384' => 96, # secp384r1
14
+ '512' => 132 # secp521r1 note difference (not 128) due to using 521-bit integers
15
+ }
16
+
17
+ module_function
18
+
19
+ def signed(sha_bits, private_key, data)
20
+ validate_key(private_key, sha_bits)
21
+ der = private_key.dsa_sign_asn1(ssl_digest_hash sha_bits, data)
22
+ der_to_signature(der, sha_bits)
23
+ end
24
+
25
+ def verified?(signature, sha_bits, key, data)
26
+ validate_key(key, sha_bits)
27
+ validate_signature_size(signature, sha_bits)
28
+ der = signature_to_der(signature, sha_bits)
29
+ key.dsa_verify_asn1(ssl_digest_hash(sha_bits, data), der)
30
+ end
31
+
32
+ # private
33
+
34
+ def validate_key_size(_key, _sha_bits); end
35
+
36
+ def ssl_digest_hash(sha_bits, data)
37
+ digest_new(sha_bits).digest(data)
38
+ end
39
+
40
+ def validate_signature_size(signature, sha_bits)
41
+ n = MAC_BYTE_COUNT[sha_bits]
42
+ fail('Invalid signature') unless signature && signature.bytesize == n
43
+ end
44
+
45
+ private_class_method :validate_key_size,
46
+ :ssl_digest_hash,
47
+ :validate_signature_size
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,61 @@
1
+ require 'openssl'
2
+
3
+ module JsonWebToken
4
+ module Format
5
+ module Asn1
6
+
7
+ KEY_BITS = {
8
+ '256' => 256,
9
+ '384' => 384,
10
+ '512' => 521 # note difference
11
+ }
12
+
13
+ module_function
14
+
15
+ # ASN1 data structures are usually encoded using the Distinguished Encoding Rules (DER).
16
+ # The ASN1 module provides the necessary classes that allow generation of ASN1 data
17
+ # structures and the methods to encode them using a DER encoding. The decode method allows
18
+ # parsing arbitrary DER-encoded data to a Ruby object that can then be modified and
19
+ # re-encoded at will. (see http://docs.ruby-lang.org/en/2.1.0/OpenSSL/ASN1.html)
20
+
21
+ def der_to_signature(der, sha_bits)
22
+ signature_pair = OpenSSL::ASN1.decode(der).value
23
+ width = per_part_byte_count(sha_bits)
24
+ signature_pair.map { |part| part.value.to_s(2).rjust(width, "\x00") }.join
25
+ end
26
+
27
+ def signature_to_der(signature, sha_bits)
28
+ hsh = destructured_sig(signature, sha_bits)
29
+ asn1_seq = OpenSSL::ASN1::Sequence.new([
30
+ asn1_int(hsh[:r]),
31
+ asn1_int(hsh[:s])
32
+ ])
33
+ asn1_seq.to_der
34
+ end
35
+
36
+ # private
37
+
38
+ def per_part_byte_count(sha_bits)
39
+ bits = KEY_BITS[sha_bits]
40
+ bits ? (bits + 7) / 8 : fail('Invalid sha_bits')
41
+ end
42
+
43
+ def destructured_sig(signature, sha_bits)
44
+ n = per_part_byte_count(sha_bits)
45
+ fail('Invalid signature length') unless signature.length == n * 2
46
+ {
47
+ r: signature[0, n],
48
+ s: signature[n, n]
49
+ }
50
+ end
51
+
52
+ def asn1_int(int)
53
+ OpenSSL::ASN1::Integer.new(OpenSSL::BN.new int, 2)
54
+ end
55
+
56
+ private_class_method :per_part_byte_count,
57
+ :destructured_sig,
58
+ :asn1_int
59
+ end
60
+ end
61
+ end
@@ -1,10 +1,11 @@
1
+ require 'json_web_token/algorithm/ecdsa'
1
2
  require 'json_web_token/algorithm/hmac'
2
3
  require 'json_web_token/algorithm/rsa'
3
4
 
4
5
  module JsonWebToken
5
6
  module Jwa
6
7
 
7
- ALGORITHMS = /(HS|RS)(256|384|512)?/i
8
+ ALGORITHMS = /(HS|RS|ES)(256|384|512)?/i
8
9
  ALG_LENGTH = 5
9
10
 
10
11
  module_function
@@ -39,6 +40,7 @@ module JsonWebToken
39
40
  case str
40
41
  when 'hs' then Algorithm::Hmac
41
42
  when 'rs' then Algorithm::Rsa
43
+ when 'es' then Algorithm::Ecdsa
42
44
  else fail('Unsupported algorithm')
43
45
  end
44
46
  end
@@ -1,3 +1,3 @@
1
1
  module JsonWebToken
2
- VERSION = '0.0.2'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -0,0 +1,56 @@
1
+ require 'json_web_token/algorithm/ecdsa'
2
+ require 'support/ecdsa_key'
3
+
4
+ module JsonWebToken
5
+ module Algorithm
6
+ describe Ecdsa do
7
+ describe 'detect changed signature or data' do
8
+ let(:signing_input_0) { 'signing_input_0' }
9
+ let(:signing_input_1) { 'signing_input_1' }
10
+ shared_examples_for '#signed' do
11
+ it 'is #verified?' do
12
+ private_key_0 = EcdsaKey.curve_new(sha_bits)
13
+ public_key_str_0 = EcdsaKey.public_key_str(private_key_0)
14
+ public_key_0 = EcdsaKey.public_key_new(sha_bits, public_key_str_0)
15
+
16
+ mac_0 = Ecdsa.signed(sha_bits, private_key_0, signing_input_0)
17
+ expect(mac_0.bytes.count).to eql expected_mac_byte_count
18
+ expect(Ecdsa.verified? mac_0, sha_bits, public_key_0, signing_input_0).to be true
19
+
20
+ private_key_1 = EcdsaKey.curve_new(sha_bits)
21
+ public_key_str_1 = EcdsaKey.public_key_str(private_key_1)
22
+ public_key_1 = EcdsaKey.public_key_new(sha_bits, public_key_str_1)
23
+
24
+ expect(Ecdsa.verified? mac_0, sha_bits, public_key_0, signing_input_1).to be false
25
+ expect(Ecdsa.verified? mac_0, sha_bits, public_key_1, signing_input_0).to be false
26
+ expect(Ecdsa.verified? mac_0, sha_bits, public_key_1, signing_input_1).to be false
27
+
28
+ mac_1 = Ecdsa.signed(sha_bits, private_key_1, signing_input_1)
29
+ expect(Ecdsa.verified? mac_1, sha_bits, public_key_0, signing_input_0).to be false
30
+ expect(Ecdsa.verified? mac_1, sha_bits, public_key_0, signing_input_1).to be false
31
+ expect(Ecdsa.verified? mac_1, sha_bits, public_key_1, signing_input_0).to be false
32
+ expect(Ecdsa.verified? mac_1, sha_bits, public_key_1, signing_input_1).to be true
33
+ end
34
+ end
35
+
36
+ describe 'ES256' do
37
+ let(:sha_bits) { '256' }
38
+ let(:expected_mac_byte_count) { 64 }
39
+ it_behaves_like '#signed'
40
+ end
41
+
42
+ describe 'ES384' do
43
+ let(:sha_bits) { '384' }
44
+ let(:expected_mac_byte_count) { 96 }
45
+ it_behaves_like '#signed'
46
+ end
47
+
48
+ describe 'ES512' do
49
+ let(:sha_bits) { '512' }
50
+ let(:expected_mac_byte_count) { 132 }
51
+ it_behaves_like '#signed'
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -78,7 +78,6 @@ module JsonWebToken
78
78
  it 'returns a 32-byte MAC string' do
79
79
  mac = Hmac.signed(sha_bits, key, data)
80
80
  expect(mac.bytesize).to eql 32
81
- expect(mac.class).to eql String
82
81
  end
83
82
  end
84
83
  end
@@ -95,7 +94,6 @@ module JsonWebToken
95
94
  it 'returns a 48-byte MAC string' do
96
95
  mac = Hmac.signed(sha_bits, key, data)
97
96
  expect(mac.bytesize).to eql 48
98
- expect(mac.class).to eql String
99
97
  end
100
98
  end
101
99
  end
@@ -112,7 +110,6 @@ module JsonWebToken
112
110
  it 'returns a 64-byte MAC string' do
113
111
  mac = Hmac.signed(sha_bits, key, data)
114
112
  expect(mac.bytesize).to eql 64
115
- expect(mac.class).to eql String
116
113
  end
117
114
  end
118
115
  end
@@ -75,7 +75,6 @@ module JsonWebToken
75
75
  it 'returns a 256-byte MAC string' do
76
76
  mac = Rsa.signed(sha_bits, private_key, data)
77
77
  expect(mac.bytesize).to eql 256
78
- expect(mac.class).to eql String
79
78
  end
80
79
  end
81
80
 
@@ -0,0 +1,105 @@
1
+ require 'json_web_token/format/asn1'
2
+
3
+ module JsonWebToken
4
+ module Format
5
+ describe Asn1 do
6
+ context 'w bytes' do
7
+ let(:der) { der_bytes.map(&:chr).join }
8
+ let(:signature) { signature_bytes.map(&:chr).join }
9
+ shared_examples_for '#der_to_signature' do
10
+ it 'converts' do
11
+ expect(signature.bytes.length).to eql signature_byte_count
12
+ expect(Asn1.der_to_signature(der, sha_bits).bytes).to eql signature_bytes
13
+ end
14
+ end
15
+
16
+ shared_examples_for '#signature_to_der' do
17
+ it 'converts' do
18
+ expect(Asn1.signature_to_der(signature, sha_bits).bytes).to eql der_bytes
19
+ end
20
+ end
21
+
22
+ shared_examples_for 'w/o valid signature' do
23
+ let(:signature_invalid) { (signature_bytes + [123]).map(&:chr).join }
24
+ it '#signature_to_der raises' do
25
+ expect { Asn1.signature_to_der(signature_invalid, sha_bits) }
26
+ .to raise_error(RuntimeError, 'Invalid signature length')
27
+ end
28
+ end
29
+
30
+ context 'for ES256' do
31
+ let(:sha_bits) { '256' }
32
+ let(:der_bytes) { [48, 69, 2, 32, 39, 115, 251, 5, 254, 60, 42, 53, 128, 68, 123, 82,
33
+ 222, 136, 26, 167, 246, 163, 233, 216, 206, 122, 106, 141, 43, 143, 137, 3, 88, 196,
34
+ 235, 161, 2, 33, 0, 143, 213, 54, 244, 194, 216, 188, 161, 77, 28, 87, 205, 16, 160,
35
+ 11, 125, 21, 62, 206, 233, 242, 201, 149, 152, 53, 25, 103, 6, 4, 56, 193, 161] }
36
+ let(:signature_bytes) { [39, 115, 251, 5, 254, 60, 42, 53, 128, 68, 123, 82, 222, 136,
37
+ 26, 167, 246, 163, 233, 216, 206, 122, 106, 141, 43, 143, 137, 3, 88, 196, 235, 161,
38
+ 143, 213, 54, 244, 194, 216, 188, 161, 77, 28, 87, 205, 16, 160, 11, 125, 21, 62,
39
+ 206, 233, 242, 201, 149, 152, 53, 25, 103, 6, 4, 56, 193, 161] }
40
+ let(:signature_byte_count) { 64 }
41
+ it_behaves_like '#der_to_signature'
42
+ it_behaves_like '#signature_to_der'
43
+ it_behaves_like 'w/o valid signature'
44
+
45
+ describe 'invalid sha_bits' do
46
+ let(:invalid_sha_bits) { '257' }
47
+ it '#der_to_signature raises' do
48
+ expect { Asn1.der_to_signature(der, invalid_sha_bits) }
49
+ .to raise_error(RuntimeError, 'Invalid sha_bits')
50
+ end
51
+
52
+ it '#signature_to_der raises' do
53
+ expect { Asn1.signature_to_der(signature, invalid_sha_bits) }
54
+ .to raise_error(RuntimeError, 'Invalid sha_bits')
55
+ end
56
+ end
57
+ end
58
+
59
+ context 'for ES384' do
60
+ let(:sha_bits) { '384' }
61
+ let(:der_bytes) { [48, 101, 2, 48, 22, 221, 123, 224, 5, 100, 163, 31, 98, 78, 240,
62
+ 249, 85, 126, 120, 130, 228, 123, 69, 2, 21, 65, 249, 229, 151, 208, 186, 162, 31,
63
+ 149, 42, 165, 134, 214, 197, 176, 120, 10, 205, 247, 176, 19, 2, 156, 112, 89, 58,
64
+ 234, 2, 49, 0, 255, 43, 120, 92, 206, 84, 88, 29, 109, 225, 254, 162, 37, 255, 127,
65
+ 231, 37, 178, 36, 173, 225, 201, 121, 154, 43, 122, 229, 114, 50, 83, 69, 243, 143,
66
+ 248, 89, 109, 136, 233, 223, 148, 137, 226, 96, 78, 166, 141, 222, 236] }
67
+ let(:signature_bytes) { [22, 221, 123, 224, 5, 100, 163, 31, 98, 78, 240, 249, 85,
68
+ 126, 120, 130, 228, 123, 69, 2, 21, 65, 249, 229, 151, 208, 186, 162, 31, 149, 42,
69
+ 165, 134, 214, 197, 176, 120, 10, 205, 247, 176, 19, 2, 156, 112, 89, 58, 234, 255,
70
+ 43, 120, 92, 206, 84, 88, 29, 109, 225, 254, 162, 37, 255, 127, 231, 37, 178, 36,
71
+ 173, 225, 201, 121, 154, 43, 122, 229, 114, 50, 83, 69, 243, 143, 248, 89, 109, 136,
72
+ 233, 223, 148, 137, 226, 96, 78, 166, 141, 222, 236] }
73
+ let(:signature_byte_count) { 96 }
74
+ it_behaves_like '#der_to_signature'
75
+ it_behaves_like '#signature_to_der'
76
+ it_behaves_like 'w/o valid signature'
77
+ end
78
+
79
+ context 'for ES512' do
80
+ let(:sha_bits) { '512' }
81
+ let(:der_bytes) { [48, 129, 135, 2, 66, 0, 173, 236, 131, 242, 12, 189, 123, 8, 129,
82
+ 2, 239, 202, 73, 168, 134, 216, 173, 241, 30, 1, 216, 177, 69, 61, 2, 196, 126, 145,
83
+ 132, 172, 174, 210, 133, 191, 50, 57, 239, 229, 201, 118, 197, 62, 197, 62, 128,
84
+ 143, 82, 84, 251, 80, 18, 196, 194, 198, 62, 144, 16, 149, 26, 67, 3, 215, 235, 179,
85
+ 146, 2, 65, 40, 137, 198, 254, 15, 50, 214, 252, 43, 65, 203, 163, 140, 204, 66,
86
+ 159, 53, 125, 184, 29, 24, 189, 249, 21, 64, 109, 87, 100, 165, 139, 83, 129, 190,
87
+ 121, 180, 86, 241, 83, 238, 39, 63, 25, 247, 253, 130, 153, 47, 27, 138, 164, 221,
88
+ 25, 151, 135, 144, 84, 240, 46, 59, 94, 99, 147, 138, 103, 67] }
89
+ let(:signature_bytes) { [0, 173, 236, 131, 242, 12, 189, 123, 8, 129, 2, 239, 202, 73,
90
+ 168, 134, 216, 173, 241, 30, 1, 216, 177, 69, 61, 2, 196, 126, 145, 132, 172, 174,
91
+ 210, 133, 191, 50, 57, 239, 229, 201, 118, 197, 62, 197, 62, 128, 143, 82, 84, 251,
92
+ 80, 18, 196, 194, 198, 62, 144, 16, 149, 26, 67, 3, 215, 235, 179, 146, 0, 40, 137,
93
+ 198, 254, 15, 50, 214, 252, 43, 65, 203, 163, 140, 204, 66, 159, 53, 125, 184, 29,
94
+ 24, 189, 249, 21, 64, 109, 87, 100, 165, 139, 83, 129, 190, 121, 180, 86, 241, 83,
95
+ 238, 39, 63, 25, 247, 253, 130, 153, 47, 27, 138, 164, 221, 25, 151, 135, 144, 84,
96
+ 240, 46, 59, 94, 99, 147, 138, 103, 67] }
97
+ let(:signature_byte_count) { 132 }
98
+ it_behaves_like '#der_to_signature'
99
+ it_behaves_like '#signature_to_der'
100
+ it_behaves_like 'w/o valid signature'
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -1,33 +1,50 @@
1
1
  require 'json_web_token/jwa'
2
+ require 'support/ecdsa_key'
2
3
 
3
4
  module JsonWebToken
4
5
  describe Jwa do
5
- context 'detect changed signing_input or MAC' do
6
- let(:signing_input) { 'signing_input' }
7
- let(:changed_signing_input) { 'changed_signing_input' }
8
- shared_examples_for '#signed' do
9
- it 'is #verified?' do
10
- mac = Jwa.signed(algorithm, signing_key, signing_input)
11
- expect(Jwa.verified? mac, algorithm, verifying_key, signing_input).to be true
12
- expect(Jwa.verified? mac, algorithm, verifying_key, changed_signing_input).to be false
13
-
14
- changed_mac = Jwa.signed(algorithm, signing_key, changed_signing_input)
15
- expect(Jwa.verified? changed_mac, algorithm, verifying_key, signing_input).to be false
16
- end
6
+ shared_examples_for 'w #verified?' do
7
+ it 'true' do
8
+ expect(Jwa.verified? mac, algorithm, verifying_key, signing_input).to be true
17
9
  end
18
-
10
+ end
11
+ context '#signed' do
12
+ let(:signing_input) { 'signing_input' }
13
+ let(:mac) { Jwa.signed(algorithm, private_key, signing_input) }
19
14
  describe 'HS256' do
20
15
  let(:algorithm) { 'HS256' }
21
- let(:signing_key) { 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C' }
22
- let(:verifying_key) { signing_key }
23
- it_behaves_like '#signed'
16
+ let(:private_key) { 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C' }
17
+ let(:verifying_key) { private_key }
18
+ it_behaves_like 'w #verified?'
19
+
20
+ it 'returns a 32-byte MAC' do
21
+ expect(mac.bytesize).to eql 32
22
+ end
24
23
  end
25
24
 
26
25
  describe 'RS256' do
27
26
  let(:algorithm) { 'RS256' }
28
- let(:signing_key) { OpenSSL::PKey::RSA.generate(2048) }
29
- let(:verifying_key) { signing_key.public_key }
30
- it_behaves_like '#signed'
27
+ let(:private_key) { OpenSSL::PKey::RSA.generate(2048) }
28
+ let(:verifying_key) { private_key.public_key }
29
+ it_behaves_like 'w #verified?'
30
+
31
+ it 'returns a 256-byte MAC' do
32
+ expect(mac.bytesize).to eql 256
33
+ end
34
+ end
35
+
36
+ describe 'ES256' do
37
+ let(:algorithm) { 'ES256' }
38
+ it 'w #verified? true, returns a 64-byte MAC' do
39
+ private_key = EcdsaKey.curve_new('256')
40
+ public_key_str = EcdsaKey.public_key_str(private_key)
41
+ public_key = EcdsaKey.public_key_new('256', public_key_str)
42
+
43
+ mac = Jwa.signed(algorithm, private_key, signing_input)
44
+ expect(Jwa.verified? mac, algorithm, public_key, signing_input).to be true
45
+
46
+ expect(mac.bytesize).to eql 64
47
+ end
31
48
  end
32
49
  end
33
50
 
@@ -44,25 +61,6 @@ module JsonWebToken
44
61
  end
45
62
  end
46
63
  end
47
-
48
- describe 'HS256' do
49
- let(:algorithm) { 'HS256' }
50
- it 'returns a 32-byte MAC string' do
51
- mac = Jwa.signed(algorithm, key, data)
52
- expect(mac.bytesize).to eql 32
53
- expect(mac.class).to eql String
54
- end
55
- end
56
- end
57
-
58
- describe 'RS256' do
59
- let(:private_key) { OpenSSL::PKey::RSA.generate(2048) }
60
- let(:algorithm) { 'RS256' }
61
- it 'returns a 256-byte MAC string' do
62
- mac = Jwa.signed(algorithm, private_key, data)
63
- expect(mac.bytesize).to eql 256
64
- expect(mac.class).to eql String
65
- end
66
64
  end
67
65
  end
68
66
  end
@@ -1,4 +1,5 @@
1
1
  require 'json_web_token/jws'
2
+ require 'support/ecdsa_key'
2
3
 
3
4
  module JsonWebToken
4
5
  describe Jws do
@@ -6,7 +7,7 @@ module JsonWebToken
6
7
  let(:payload) { 'payload' }
7
8
  context '#message_signature' do
8
9
  shared_examples_for 'w #validate' do
9
- it 'verified' do
10
+ it 'is verified' do
10
11
  jws = Jws.message_signature(header, payload, signing_key)
11
12
  expect(Jws.validate jws, algorithm, verifying_key).to eql jws
12
13
  end
@@ -51,6 +52,21 @@ module JsonWebToken
51
52
  end
52
53
  end
53
54
  end
55
+
56
+ context "w ES256 'alg' header parameter" do
57
+ let(:header) { {alg: 'ES256'} }
58
+ describe 'w passing a matching algorithm to #validate' do
59
+ let(:algorithm) { 'ES256' }
60
+ it 'is verified' do
61
+ private_key = EcdsaKey.curve_new('256')
62
+ public_key_str = EcdsaKey.public_key_str(private_key)
63
+ public_key = EcdsaKey.public_key_new('256', public_key_str)
64
+
65
+ jws = Jws.message_signature(header, payload, private_key)
66
+ expect(Jws.validate jws, algorithm, public_key).to eql jws
67
+ end
68
+ end
69
+ end
54
70
  end
55
71
 
56
72
  context 'header validation' do
@@ -1,11 +1,12 @@
1
1
  require 'json_web_token/jwt'
2
+ require 'support/ecdsa_key'
2
3
  require 'support/plausible_jwt'
3
4
 
4
5
  module JsonWebToken
5
6
  describe Jwt do
6
7
  context '#create' do
7
8
  shared_examples_for 'w #validate' do
8
- it 'verified' do
9
+ it 'is verified' do
9
10
  jwt = Jwt.create(claims, create_options)
10
11
  expect(Jwt.validate jwt, validate_options).to include(claims)
11
12
  end
@@ -30,29 +31,29 @@ module JsonWebToken
30
31
  it_behaves_like 'return message signature'
31
32
  end
32
33
 
33
- describe 'passing header parameters' do
34
- let(:create_options) { {typ: 'JWT', alg: 'HS256', key: signing_key} }
34
+ describe 'w alg option' do
35
+ let(:create_options) { {alg: 'HS256', key: signing_key} }
35
36
  it_behaves_like 'w #validate'
36
37
  it_behaves_like 'return message signature'
37
38
  end
38
39
 
39
- describe "w 'alg':'nil' header parameter" do
40
+ describe 'w alg: nil option' do
40
41
  let(:create_options) { {alg: nil, key: signing_key} }
41
42
  it_behaves_like 'w #validate'
42
43
  it_behaves_like 'return message signature'
43
44
  end
44
45
 
45
- describe "w 'alg':'' header parameter" do
46
+ describe "w alg empty string option" do
46
47
  let(:create_options) { {alg: '', key: signing_key} }
47
48
  it_behaves_like 'w #validate'
48
49
  it_behaves_like 'return message signature'
49
50
  end
50
51
 
51
- describe "w 'alg':'none' header parameter" do
52
- let(:create_options) { {typ: 'JWT', alg: 'none', key: signing_key} }
52
+ describe "w alg: 'none' option" do
53
+ let(:create_options) { {alg: 'none', key: signing_key} }
53
54
  it 'raises' do
54
55
  jwt = Jwt.create(claims, create_options)
55
- expect { Jwt.validate(jwt) }
56
+ expect { Jwt.validate(jwt, validate_options) }
56
57
  .to raise_error(RuntimeError, "Algorithm not matching 'alg' header parameter")
57
58
  end
58
59
  end
@@ -62,9 +63,10 @@ module JsonWebToken
62
63
  let(:signing_key) { OpenSSL::PKey::RSA.generate(2048) }
63
64
  let(:verifying_key) { signing_key.public_key }
64
65
  let(:validate_options) { {alg: 'RS256', key: verifying_key} }
65
- describe 'passing header parameters' do
66
- let(:create_options) { {typ: 'JWT', alg: 'RS256', key: signing_key} }
66
+ describe 'passing matching options' do
67
+ let(:create_options) { {alg: 'RS256', key: signing_key} }
67
68
  it_behaves_like 'w #validate'
69
+
68
70
  it 'plausible' do
69
71
  jwt = Jwt.create(claims, create_options)
70
72
  expect(plausible_message_signature? jwt, 256).to be true
@@ -72,10 +74,29 @@ module JsonWebToken
72
74
  end
73
75
  end
74
76
 
77
+ context "w ES256 'alg' header parameter" do
78
+ let(:algorithm) { 'ES256' }
79
+ describe 'w passing a matching algorithm to #validate' do
80
+ it 'is verified and plausible' do
81
+ private_key = EcdsaKey.curve_new('256')
82
+ public_key_str = EcdsaKey.public_key_str(private_key)
83
+ public_key = EcdsaKey.public_key_new('256', public_key_str)
84
+
85
+ create_options = {alg: algorithm, key: private_key}
86
+ jwt = Jwt.create(claims, create_options)
87
+
88
+ validate_options = {alg: algorithm, key: public_key}
89
+ expect(Jwt.validate jwt, validate_options).to include(claims)
90
+
91
+ expect(plausible_message_signature? jwt, 64).to be true
92
+ end
93
+ end
94
+ end
95
+
75
96
  context 'w/o key' do
76
- context "w alg 'none' header parameter" do
77
- let(:create_options) { {typ: 'JWT', alg: 'none'} }
78
- describe "w validate alg 'none'" do
97
+ context "w alg: 'none' header parameter" do
98
+ let(:create_options) { {alg: 'none'} }
99
+ describe "w validate alg: 'none'" do
79
100
  let(:validate_options) { {alg: 'none'} }
80
101
  it 'validates a plausible unsecured jws' do
81
102
  jwt = Jwt.create(claims, create_options)
@@ -84,7 +105,7 @@ module JsonWebToken
84
105
  end
85
106
  end
86
107
 
87
- describe "w default validate alg" do
108
+ describe 'w default validate alg' do
88
109
  it 'raises' do
89
110
  jwt = Jwt.create(claims, create_options)
90
111
  expect { Jwt.validate(jwt) }
@@ -109,7 +130,7 @@ module JsonWebToken
109
130
  it_behaves_like 'w/o claims'
110
131
  end
111
132
 
112
- describe "w claims ''" do
133
+ describe 'w claims an empty string' do
113
134
  let(:claims) { '' }
114
135
  it_behaves_like 'w/o claims'
115
136
  end
@@ -0,0 +1,30 @@
1
+ require 'openssl'
2
+
3
+ module EcdsaKey
4
+
5
+ BUILT_IN_CURVES = {
6
+ '256' => 'secp256k1',
7
+ '384' => 'secp384r1',
8
+ '512' => 'secp521r1'
9
+ }
10
+
11
+ module_function
12
+
13
+ def curve_new(sha_bits)
14
+ OpenSSL::PKey::EC.new(BUILT_IN_CURVES[sha_bits])
15
+ end
16
+
17
+ def public_key_str(curve, base = 16)
18
+ curve.generate_key unless curve.private_key
19
+ curve.public_key.to_bn.to_s(base)
20
+ end
21
+
22
+ def public_key_new(sha_bits, public_key_str, base = 16)
23
+ curve_name = BUILT_IN_CURVES[sha_bits]
24
+ fail('Unsupported curve') unless curve_name
25
+ group = OpenSSL::PKey::EC::Group.new(curve_name)
26
+ curve = OpenSSL::PKey::EC.new(group)
27
+ curve.public_key = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(public_key_str, base))
28
+ curve
29
+ end
30
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json_web_token
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gary Fleshman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-11 00:00:00.000000000 Z
11
+ date: 2015-07-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -52,6 +52,7 @@ extra_rdoc_files: []
52
52
  files:
53
53
  - ".gitignore"
54
54
  - ".rspec"
55
+ - ".travis.yml"
55
56
  - CHANGELOG.md
56
57
  - Gemfile
57
58
  - LICENSE
@@ -59,16 +60,20 @@ files:
59
60
  - json_web_token.gemspec
60
61
  - lib/json_web_token.rb
61
62
  - lib/json_web_token/algorithm/common.rb
63
+ - lib/json_web_token/algorithm/ecdsa.rb
62
64
  - lib/json_web_token/algorithm/hmac.rb
63
65
  - lib/json_web_token/algorithm/rsa.rb
66
+ - lib/json_web_token/format/asn1.rb
64
67
  - lib/json_web_token/format/base64_url.rb
65
68
  - lib/json_web_token/jwa.rb
66
69
  - lib/json_web_token/jws.rb
67
70
  - lib/json_web_token/jwt.rb
68
71
  - lib/json_web_token/util.rb
69
72
  - lib/json_web_token/version.rb
73
+ - spec/json_web_token/algorithm/ecdsa_spec.rb
70
74
  - spec/json_web_token/algorithm/hmac_spec.rb
71
75
  - spec/json_web_token/algorithm/rsa_spec.rb
76
+ - spec/json_web_token/format/asn1_spec.rb
72
77
  - spec/json_web_token/format/base64_url_spec.rb
73
78
  - spec/json_web_token/jwa_spec.rb
74
79
  - spec/json_web_token/jws_spec.rb
@@ -76,6 +81,7 @@ files:
76
81
  - spec/json_web_token/util_spec.rb
77
82
  - spec/json_web_token_spec.rb
78
83
  - spec/spec_helper.rb
84
+ - spec/support/ecdsa_key.rb
79
85
  - spec/support/plausible_jwt.rb
80
86
  homepage: https://github.com/garyf/json_web_token
81
87
  licenses: