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 +4 -4
- data/lib/jwk/asn1.rb +2 -19
- data/lib/jwk/ec_key.rb +60 -8
- data/lib/jwk/key.rb +4 -1
- data/lib/jwk/version.rb +1 -1
- data/spec/jwk/asn1_spec.rb +24 -10
- data/spec/jwk/ec_key_spec.rb +39 -0
- data/spec/jwk/key_spec.rb +37 -2
- metadata +15 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd3ca2360c804b73c1dd58937b3d8d2a3fad3f37
|
4
|
+
data.tar.gz: 658fd06146fd198e7a90f08242f0f31455c4025a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d96770d90c1f246c2d3529e78d3db5de57084d280251a6e0595cc025869bdc41f86170c93606a674dd7a0082d60bf3ca394108530078f608f13326ea8ab3c74
|
7
|
+
data.tar.gz: 5656d037ae9e1bd031aea2082b0806f3ecfe3ff7d43c17b7e95ff73bfe63999489dd81b54aca1fd4d099b53dd423eda31932765cb7191b5be9bf5be259ac53ef
|
data/lib/jwk/asn1.rb
CHANGED
@@ -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,
|
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(
|
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
|
data/lib/jwk/ec_key.rb
CHANGED
@@ -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,
|
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
|
-
|
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
|
-
|
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
|
-
|
54
|
-
|
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
|
-
|
99
|
+
def from_openssl_public(k)
|
100
|
+
x, y = coords_from_point(k)
|
64
101
|
|
65
|
-
|
66
|
-
|
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
|
data/lib/jwk/key.rb
CHANGED
@@ -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
|
data/lib/jwk/version.rb
CHANGED
data/spec/jwk/asn1_spec.rb
CHANGED
@@ -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
|
data/spec/jwk/ec_key_spec.rb
CHANGED
@@ -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
|
data/spec/jwk/key_spec.rb
CHANGED
@@ -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.
|
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
|
+
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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: []
|