jwk 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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: []