jwk 0.2.0 → 0.3.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: 0075ad292467ceea1135ce68aa74692a5cd4d310
4
- data.tar.gz: 60c2de19b4b277f4a5174199d2834c59b30af298
3
+ metadata.gz: fd3ca2360c804b73c1dd58937b3d8d2a3fad3f37
4
+ data.tar.gz: 658fd06146fd198e7a90f08242f0f31455c4025a
5
5
  SHA512:
6
- metadata.gz: 8eeb3928193130b35d4ce069691458584964d66848a9698ce9f8f67a9129afc57c6f2d6a2d63dfd3e7647940862f8057932caa87431217c2f7c2964d569fd02f
7
- data.tar.gz: f26a41c3204cb59b4f5fe639d37743bed013eb5a6b0058c5fbad17f91e1a14be5862f4d055ba7d205e907008338ff276a2ca95695b8602aaef9074995843fd87
6
+ metadata.gz: 3d96770d90c1f246c2d3529e78d3db5de57084d280251a6e0595cc025869bdc41f86170c93606a674dd7a0082d60bf3ca394108530078f608f13326ea8ab3c74
7
+ data.tar.gz: 5656d037ae9e1bd031aea2082b0806f3ecfe3ff7d43c17b7e95ff73bfe63999489dd81b54aca1fd4d099b53dd423eda31932765cb7191b5be9bf5be259ac53ef
@@ -11,20 +11,14 @@ module JWK
11
11
  sequence(integer(0), *args.map { |n| integer(n) })
12
12
  end
13
13
 
14
- def ec_private_key(crv, d, x, y)
15
- _, raw_x = raw_integer_encoding(x)
16
- _, raw_y = raw_integer_encoding(y)
17
-
18
- raw_x = pad_coord_for_crv(crv, raw_x)
19
- raw_y = pad_coord_for_crv(crv, raw_y)
20
-
14
+ def ec_private_key(crv, d, raw_public_key)
21
15
  object_id = object_id_for_crv(crv)
22
16
 
23
17
  sequence(
24
18
  integer(1),
25
19
  integer_octet_string(d),
26
20
  context_specific(true, 0, object_id),
27
- context_specific(true, 1, bit_string("\x04#{raw_x}#{raw_y}"))
21
+ context_specific(true, 1, bit_string(raw_public_key))
28
22
  )
29
23
  end
30
24
 
@@ -41,17 +35,6 @@ module JWK
41
35
  end
42
36
  end
43
37
 
44
- def pad_coord_for_crv(crv, coord)
45
- case crv
46
- when 'P-256'
47
- coord.rjust(32, "\x00")
48
- when 'P-384'
49
- coord.rjust(48, "\x00")
50
- when 'P-521'
51
- coord.rjust(64, "\x00")
52
- end
53
- end
54
-
55
38
  def rsa_header
56
39
  "\x30\x0D\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01\x05\x00".force_encoding('ASCII-8BIT')
57
40
  end
@@ -2,6 +2,18 @@ require 'jwk/key'
2
2
 
3
3
  module JWK
4
4
  class ECKey < Key
5
+ CURVE_NAMES = {
6
+ 'prime256v1' => 'P-256',
7
+ 'secp384r1' => 'P-384',
8
+ 'secp521r1' => 'P-521'
9
+ }.freeze
10
+
11
+ COORD_SIZE = {
12
+ 'P-256' => 32,
13
+ 'P-384' => 48,
14
+ 'P-521' => 64
15
+ }.freeze
16
+
5
17
  def initialize(key)
6
18
  @key = key
7
19
  validate
@@ -24,12 +36,17 @@ module JWK
24
36
  def to_pem
25
37
  raise NotImplementedError, 'Cannot convert an EC public key to PEM.' unless private?
26
38
 
27
- asn = ASN1.ec_private_key(crv, d, x, y)
39
+ asn = ASN1.ec_private_key(crv, d, raw_public_key)
28
40
  generate_pem('EC PRIVATE', asn)
29
41
  end
30
42
 
31
43
  def to_openssl_key
32
- OpenSSL::PKey.read(to_pem)
44
+ if private?
45
+ OpenSSL::PKey.read(to_pem)
46
+ else
47
+ group = OpenSSL::PKey::EC::Group.new(self.class::CURVE_NAMES.key(crv))
48
+ OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(raw_public_key.unpack('H*')[0], 16))
49
+ end
33
50
  end
34
51
 
35
52
  def to_s
@@ -46,12 +63,31 @@ module JWK
46
63
  end
47
64
  end
48
65
 
66
+ def raw_public_key
67
+ raw_x = Utils.int_to_binary(x)
68
+ raw_y = Utils.int_to_binary(y)
69
+
70
+ raw_x = pad_coord_for_crv(crv, raw_x)
71
+ raw_y = pad_coord_for_crv(crv, raw_y)
72
+
73
+ "\x04#{raw_x}#{raw_y}"
74
+ end
75
+
49
76
  class << self
50
77
  def from_openssl(k)
51
- x, y = coords_from_key(k)
78
+ if k.is_a? OpenSSL::PKey::EC::Point
79
+ from_openssl_public(k)
80
+ else
81
+ from_openssl_private(k)
82
+ end
83
+ end
84
+
85
+ private
52
86
 
53
- names = { 'secp256r1' => 'P-256', 'secp384r1' => 'P-384', 'secp521r1' => 'P-521' }
54
- crv = names[k.group.curve_name]
87
+ def from_openssl_private(k)
88
+ x, y = coords_from_point(k.public_key)
89
+
90
+ crv = CURVE_NAMES[k.group.curve_name]
55
91
 
56
92
  raise NotImplementedError, "Unsupported EC curve type #{k.group.curve_name}" unless crv
57
93
 
@@ -60,10 +96,20 @@ module JWK
60
96
  'd' => Utils.encode_ub64_int(k.private_key.to_i), 'x' => x, 'y' => y)
61
97
  end
62
98
 
63
- private
99
+ def from_openssl_public(k)
100
+ x, y = coords_from_point(k)
64
101
 
65
- def coords_from_key(key)
66
- pb = key.public_key.to_bn.to_s(16)
102
+ crv = CURVE_NAMES[k.group.curve_name]
103
+
104
+ raise NotImplementedError, "Unsupported EC curve type #{k.group.curve_name}" unless crv
105
+
106
+ new('kty' => 'EC',
107
+ 'crv' => crv,
108
+ 'x' => x, 'y' => y)
109
+ end
110
+
111
+ def coords_from_point(point)
112
+ pb = point.to_bn.to_s(16)
67
113
 
68
114
  raise NotImplementedError, 'Cannot convert EC compressed public key' unless pb[0..1] == '04'
69
115
 
@@ -75,5 +121,11 @@ module JWK
75
121
  coords.map { |c| Base64.urlsafe_encode64(Utils.hex_string_to_binary(c)) }
76
122
  end
77
123
  end
124
+
125
+ private
126
+
127
+ def pad_coord_for_crv(crv, coord)
128
+ coord.rjust(self.class::COORD_SIZE[crv], "\x00")
129
+ end
78
130
  end
79
131
  end
@@ -3,13 +3,16 @@ module JWK
3
3
  class << self
4
4
  def from_pem(pem)
5
5
  key = OpenSSL::PKey.read(pem)
6
+ if defined?(OpenSSL::PKey::EC) && key.is_a?(OpenSSL::PKey::EC)
7
+ $stderr.puts('WARNING: EC Keys have bugs on jRuby') if defined?(JRUBY_VERSION)
8
+ end
6
9
  from_openssl(key)
7
10
  end
8
11
 
9
12
  def from_openssl(key)
10
13
  if key.is_a?(OpenSSL::PKey::RSA)
11
14
  RSAKey.from_openssl(key)
12
- elsif key.is_a?(OpenSSL::PKey::EC)
15
+ elsif key.is_a?(OpenSSL::PKey::EC) || key.is_a?(OpenSSL::PKey::EC::Point)
13
16
  ECKey.from_openssl(key)
14
17
  end
15
18
  end
@@ -1,3 +1,3 @@
1
1
  module JWK
2
- VERSION = '0.2.0'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
@@ -41,36 +41,50 @@ describe JWK::ASN1 do
41
41
 
42
42
  describe '.ec_private_key' do
43
43
  let(:known_p256_asn) do
44
- Base64.decode64('MFgCAQEEAaCgCgYIKoZIzj0DAQehRANCAAQAAAAAAAAAAAAAAAAAAAAA' +
45
- 'AAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
44
+ Base64.decode64('MFgCAQEEAaCgCgYIKoZIzj0DAQehRANCAAQAAAAAAAAAAAAAAAAAAAAA' \
45
+ 'AAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \
46
46
  'AAAAAAAD')
47
47
  end
48
48
 
49
49
  let(:known_p384_asn) do
50
- Base64.decode64('MHUCAQEEAaCgBwYFK4EEACKhZANiAAQAAAAAAAAAAAAAAAAAAAAAAAAA' +
51
- 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAA' +
50
+ Base64.decode64('MHUCAQEEAaCgBwYFK4EEACKhZANiAAQAAAAAAAAAAAAAAAAAAAAAAAAA' \
51
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAA' \
52
52
  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM=')
53
53
  end
54
54
 
55
55
  let(:known_p521_asn) do
56
- Base64.decode64('MIGXAgEBBAGgoAcGBSuBBAAjoYGFA4GCAAQAAAAAAAAAAAAAAAAAAAAA' +
57
- 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
58
- 'AAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
56
+ Base64.decode64('MIGXAgEBBAGgoAcGBSuBBAAjoYGFA4GCAAQAAAAAAAAAAAAAAAAAAAAA' \
57
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \
58
+ 'AAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \
59
59
  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAw==')
60
60
  end
61
61
 
62
+ def pad_coord_for_crv(crv, coord)
63
+ coord.rjust(JWK::ECKey::COORD_SIZE[crv], "\x00")
64
+ end
65
+
66
+ def raw_public_key(crv, x, y)
67
+ raw_x = JWK::Utils.int_to_binary(x)
68
+ raw_y = JWK::Utils.int_to_binary(y)
69
+
70
+ raw_x = pad_coord_for_crv(crv, raw_x)
71
+ raw_y = pad_coord_for_crv(crv, raw_y)
72
+
73
+ "\x04#{raw_x}#{raw_y}"
74
+ end
75
+
62
76
  it 'generates valid ASN1 for a P-256 EC Private Key' do
63
- result = JWK::ASN1.ec_private_key('P-256', 0xA0, 2, 3)
77
+ result = JWK::ASN1.ec_private_key('P-256', 0xA0, raw_public_key('P-256', 2, 3))
64
78
  expect(result).to eq(known_p256_asn)
65
79
  end
66
80
 
67
81
  it 'generates valid ASN1 for a P-384 EC Private Key' do
68
- result = JWK::ASN1.ec_private_key('P-384', 0xA0, 2, 3)
82
+ result = JWK::ASN1.ec_private_key('P-384', 0xA0, raw_public_key('P-384', 2, 3))
69
83
  expect(result).to eq(known_p384_asn)
70
84
  end
71
85
 
72
86
  it 'generates valid ASN1 for a P-521 EC Private Key' do
73
- result = JWK::ASN1.ec_private_key('P-521', 0xA0, 2, 3)
87
+ result = JWK::ASN1.ec_private_key('P-521', 0xA0, raw_public_key('P-521', 2, 3))
74
88
  expect(result).to eq(known_p521_asn)
75
89
  end
76
90
  end
@@ -47,6 +47,45 @@ describe JWK::ECKey do
47
47
  raise e unless defined?(JRUBY_VERSION)
48
48
  end
49
49
  end
50
+
51
+ it 'converts the public keys to an openssl point' do
52
+ key = JWK::Key.from_json(public_jwk)
53
+
54
+ begin
55
+ expect(key.to_openssl_key).to be_a OpenSSL::PKey::EC::Point
56
+ rescue Exception => e
57
+ # This is expected to fail on old jRuby versions
58
+ raise e unless defined?(JRUBY_VERSION)
59
+ end
60
+ end
61
+
62
+ # See: http://blogs.adobe.com/security/2017/03/critical-vulnerability-uncovered-in-json-encryption.html
63
+ # See: https://github.com/asanso/jwe-sender/blob/master/jwe-sender.js
64
+ it 'is protected against Invalid Curve Attack' do
65
+ k1 = {
66
+ 'kty' => 'EC',
67
+ 'x' => 'WJiccv00-OX6udOeWKfiRhZzzkoAnfG9JOIDprQYpH8', 'y' => 'tAjB2i8hs-7i6GLRcgMTtCoPbybmoPRWhS9qUBf2ldc',
68
+ 'crv' => 'P-256'
69
+ }.to_json
70
+
71
+ k2 = {
72
+ 'kty' => 'EC',
73
+ 'x' => 'XOXGQ9_6QCvBg3u8vCI-UdBvICAEcNNBrfqd7tG7oQ4', 'y' => 'hQoWNotnNzKlwiCneJkLIqDnTNw7IsdBC53VUqVjVJc',
74
+ 'crv' => 'P-256'
75
+ }.to_json
76
+
77
+ jwk1 = JWK::Key.from_json(k1)
78
+ jwk2 = JWK::Key.from_json(k2)
79
+
80
+ begin
81
+ expect { jwk1.to_openssl_key }.to raise_error(OpenSSL::PKey::EC::Point::Error)
82
+ expect { jwk2.to_openssl_key }.to raise_error(OpenSSL::PKey::EC::Point::Error)
83
+ rescue NameError => e
84
+ # This is expected to fail on old jRuby versions
85
+ # Not because it's unsafe, but because EC were not implemented.
86
+ raise e unless defined?(JRUBY_VERSION)
87
+ end
88
+ end
50
89
  end
51
90
 
52
91
  describe '#to_json' do
@@ -27,7 +27,7 @@ describe JWK::Key do
27
27
  expect(jwk.to_pem).to eq key.to_pem
28
28
  end
29
29
 
30
- it 'creates an ECKey for EC keys' do
30
+ it 'creates an ECKey for EC private keys' do
31
31
  begin
32
32
  key = OpenSSL::PKey::EC.new('secp384r1')
33
33
  key.generate_key
@@ -42,7 +42,7 @@ describe JWK::Key do
42
42
  # jRuby 9k OpenSSL generates a bad PEM file with private key only, skipping
43
43
  # the public part. This is in contrast with all other OpenSSL implementations.
44
44
  # And it makes this test fail.
45
- it 'creates an ECKey for EC keys that resolves to the same parameters' do
45
+ it 'creates an ECKey for EC private keys that resolves to the same parameters' do
46
46
  begin
47
47
  key = OpenSSL::PKey::EC.new('secp384r1')
48
48
  key.generate_key
@@ -53,6 +53,30 @@ describe JWK::Key do
53
53
  raise e unless defined?(JRUBY_VERSION)
54
54
  end
55
55
  end
56
+
57
+ it 'creates an ECKey for EC public keys' do
58
+ begin
59
+ key = OpenSSL::PKey::EC.new('secp384r1')
60
+ key.generate_key
61
+ jwk = JWK::Key.from_openssl(key.public_key)
62
+
63
+ expect(jwk).to be_a JWK::ECKey
64
+ rescue NameError => e
65
+ raise e unless defined?(JRUBY_VERSION)
66
+ end
67
+ end
68
+
69
+ it 'creates an ECKey for EC public keys that resolves to the same parameters' do
70
+ begin
71
+ key = OpenSSL::PKey::EC.new('secp384r1')
72
+ key.generate_key
73
+ jwk = JWK::Key.from_openssl(key.public_key)
74
+
75
+ expect(jwk.to_openssl_key).to eq key.public_key
76
+ rescue NameError => e
77
+ raise e unless defined?(JRUBY_VERSION)
78
+ end
79
+ end
56
80
  end
57
81
 
58
82
  describe '.from_pem' do
@@ -62,5 +86,16 @@ describe JWK::Key do
62
86
 
63
87
  expect(jwk).to be_a JWK::RSAKey
64
88
  end
89
+
90
+ it 'generates an ECKey for EC Keys' do
91
+ begin
92
+ pem = OpenSSL::PKey::EC.new('prime256v1').generate_key.to_pem
93
+ jwk = JWK::Key.from_pem(pem)
94
+
95
+ expect(jwk).to be_a JWK::ECKey
96
+ rescue ArgumentError, NameError => e
97
+ raise e unless defined?(JRUBY_VERSION)
98
+ end
99
+ end
65
100
  end
66
101
  end
metadata CHANGED
@@ -1,66 +1,66 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jwk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Francesco Boffa
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-11 00:00:00.000000000 Z
11
+ date: 2017-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rspec
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
18
  version: '0'
20
- type: :development
19
+ name: rspec
21
20
  prerelease: false
21
+ type: :development
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rake
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
30
  - - ">="
32
31
  - !ruby/object:Gem::Version
33
32
  version: '0'
34
- type: :development
33
+ name: rake
35
34
  prerelease: false
35
+ type: :development
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: simplecov
43
42
  requirement: !ruby/object:Gem::Requirement
44
43
  requirements:
45
44
  - - ">="
46
45
  - !ruby/object:Gem::Version
47
46
  version: '0'
48
- type: :development
47
+ name: simplecov
49
48
  prerelease: false
49
+ type: :development
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: codeclimate-test-reporter
57
56
  requirement: !ruby/object:Gem::Requirement
58
57
  requirements:
59
58
  - - ">="
60
59
  - !ruby/object:Gem::Version
61
60
  version: '0'
62
- type: :development
61
+ name: codeclimate-test-reporter
63
62
  prerelease: false
63
+ type: :development
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
@@ -114,7 +114,7 @@ homepage: https://github.com/aomega08/jwk
114
114
  licenses:
115
115
  - MIT
116
116
  metadata: {}
117
- post_install_message:
117
+ post_install_message:
118
118
  rdoc_options: []
119
119
  require_paths:
120
120
  - lib
@@ -129,9 +129,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
129
  - !ruby/object:Gem::Version
130
130
  version: '0'
131
131
  requirements: []
132
- rubyforge_project:
133
- rubygems_version: 2.6.11
134
- signing_key:
132
+ rubyforge_project:
133
+ rubygems_version: 2.4.8
134
+ signing_key:
135
135
  specification_version: 4
136
136
  summary: JSON Web Keys implementation in Ruby
137
137
  test_files: []