jose 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 +7 -0
- data/.editorconfig +20 -0
- data/.gitignore +9 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +20 -0
- data/LICENSE.txt +373 -0
- data/README.md +41 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/jose.gemspec +36 -0
- data/lib/jose.rb +61 -0
- data/lib/jose/jwa.rb +21 -0
- data/lib/jose/jwa/aes_kw.rb +105 -0
- data/lib/jose/jwa/concat_kdf.rb +50 -0
- data/lib/jose/jwa/pkcs1.rb +269 -0
- data/lib/jose/jwa/pkcs7.rb +23 -0
- data/lib/jose/jwe.rb +290 -0
- data/lib/jose/jwe/alg.rb +12 -0
- data/lib/jose/jwe/alg_aes_gcm_kw.rb +98 -0
- data/lib/jose/jwe/alg_aes_kw.rb +57 -0
- data/lib/jose/jwe/alg_dir.rb +40 -0
- data/lib/jose/jwe/alg_ecdh_es.rb +123 -0
- data/lib/jose/jwe/alg_pbes2.rb +90 -0
- data/lib/jose/jwe/alg_rsa.rb +63 -0
- data/lib/jose/jwe/enc.rb +8 -0
- data/lib/jose/jwe/enc_aes_cbc_hmac.rb +80 -0
- data/lib/jose/jwe/enc_aes_gcm.rb +68 -0
- data/lib/jose/jwe/zip.rb +7 -0
- data/lib/jose/jwe/zip_def.rb +28 -0
- data/lib/jose/jwk.rb +347 -0
- data/lib/jose/jwk/kty.rb +34 -0
- data/lib/jose/jwk/kty_ec.rb +179 -0
- data/lib/jose/jwk/kty_oct.rb +104 -0
- data/lib/jose/jwk/kty_rsa.rb +185 -0
- data/lib/jose/jwk/pem.rb +19 -0
- data/lib/jose/jwk/set.rb +2 -0
- data/lib/jose/jws.rb +242 -0
- data/lib/jose/jws/alg.rb +10 -0
- data/lib/jose/jws/alg_ecdsa.rb +41 -0
- data/lib/jose/jws/alg_hmac.rb +41 -0
- data/lib/jose/jws/alg_rsa_pkcs1_v1_5.rb +41 -0
- data/lib/jose/jws/alg_rsa_pss.rb +41 -0
- data/lib/jose/jwt.rb +145 -0
- data/lib/jose/version.rb +3 -0
- metadata +162 -0
data/lib/jose/jwe/zip.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
class JOSE::JWE::ZIP_DEF
|
2
|
+
|
3
|
+
# JOSE::JWE callbacks
|
4
|
+
|
5
|
+
def self.from_map(fields)
|
6
|
+
case fields['zip']
|
7
|
+
when 'DEF'
|
8
|
+
return new(), fields.except('zip')
|
9
|
+
else
|
10
|
+
raise ArgumentError, "invalid 'zip' for JWE: #{fields['zip'].inspect}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_map(fields)
|
15
|
+
return fields.put('zip', 'DEF')
|
16
|
+
end
|
17
|
+
|
18
|
+
# JOSE::JWE::ZIP callbacks
|
19
|
+
|
20
|
+
def compress(plain_text)
|
21
|
+
return Zlib.deflate(plain_text)
|
22
|
+
end
|
23
|
+
|
24
|
+
def uncompress(cipher_text)
|
25
|
+
return Zlib.inflate(cipher_text)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/lib/jose/jwk.rb
ADDED
@@ -0,0 +1,347 @@
|
|
1
|
+
module JOSE
|
2
|
+
class JWK < Struct.new(:keys, :kty, :fields)
|
3
|
+
|
4
|
+
# Decode API
|
5
|
+
|
6
|
+
def self.from(object, modules = nil, key = nil)
|
7
|
+
case object
|
8
|
+
when JOSE::Map, Hash
|
9
|
+
return from_map(object, modules, key)
|
10
|
+
when String
|
11
|
+
return from_binary(object, modules, key)
|
12
|
+
when JOSE::JWK
|
13
|
+
return object
|
14
|
+
else
|
15
|
+
raise ArgumentError, "'object' must be a Hash, String, or JOSE::JWK"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.from_binary(object, modules = nil, key = nil)
|
20
|
+
if (modules.is_a?(String) or modules.is_a?(JOSE::JWK)) and key.nil?
|
21
|
+
key = modules
|
22
|
+
modules = {}
|
23
|
+
end
|
24
|
+
modules ||= {}
|
25
|
+
case object
|
26
|
+
when String
|
27
|
+
if key
|
28
|
+
plain_text, jwe = JOSE::JWE.block_decrypt(key, object)
|
29
|
+
return from_binary(plain_text, modules), jwe
|
30
|
+
else
|
31
|
+
return from_map(JOSE.decode(object), modules)
|
32
|
+
end
|
33
|
+
else
|
34
|
+
raise ArgumentError, "'object' must be a String"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.from_file(file, modules = nil, key = nil)
|
39
|
+
return from_binary(File.binread(file), modules, key)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.from_key(object, modules = {})
|
43
|
+
kty = modules[:kty] || JOSE::JWK::KTY
|
44
|
+
return JOSE::JWK.new(nil, *kty.from_key(object))
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.from_map(object, modules = nil, key = nil)
|
48
|
+
if (modules.is_a?(String) or modules.is_a?(JOSE::JWK)) and key.nil?
|
49
|
+
key = modules
|
50
|
+
modules = {}
|
51
|
+
end
|
52
|
+
modules ||= {}
|
53
|
+
case object
|
54
|
+
when JOSE::Map, Hash
|
55
|
+
if key
|
56
|
+
plain_text, jwe = JOSE::JWE.block_decrypt(key, object)
|
57
|
+
return from_binary(plain_text, modules), jwe
|
58
|
+
else
|
59
|
+
return from_fields(JOSE::JWK.new(nil, nil, JOSE::Map.new(object)), modules)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
raise ArgumentError, "'object' must be a String"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.from_pem(object, modules = nil, password = nil)
|
67
|
+
if modules.is_a?(String) and password.nil?
|
68
|
+
password = modules
|
69
|
+
modules = {}
|
70
|
+
end
|
71
|
+
modules ||= {}
|
72
|
+
kty = modules[:kty] || JOSE::JWK::PEM
|
73
|
+
return JOSE::JWK.new(nil, *kty.from_binary(object, password))
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.from_pem_file(file, modules = nil, password = nil)
|
77
|
+
return from_pem(File.binread(file), modules, password)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.from_oct(object, modules = {})
|
81
|
+
kty = modules[:kty] || JOSE::JWK::KTY_oct
|
82
|
+
return JOSE::JWK.new(nil, *kty.from_oct(object))
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.from_oct_file(file, modules = {})
|
86
|
+
return from_oct(File.binread(file), modules)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Encode API
|
90
|
+
|
91
|
+
def self.to_binary(jwk, key = nil, jwe = nil)
|
92
|
+
return from(jwk).to_binary(key, jwe)
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_binary(key = nil, jwe = nil)
|
96
|
+
if not key.nil?
|
97
|
+
jwe ||= kty.key_encryptor(fields, key)
|
98
|
+
end
|
99
|
+
if key and jwe
|
100
|
+
return to_map(key, jwe).compact
|
101
|
+
else
|
102
|
+
return JOSE.encode(to_map)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.to_file(jwk, file, key = nil, jwe = nil)
|
107
|
+
return from(jwk).to_file(file, key, jwe)
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_file(file, key = nil, jwe = nil)
|
111
|
+
return File.binwrite(file, to_binary(key, jwe))
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.to_key(jwk)
|
115
|
+
return from(jwk).to_key
|
116
|
+
end
|
117
|
+
|
118
|
+
def to_key
|
119
|
+
return kty.to_key
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.to_map(jwk, key = nil, jwe = nil)
|
123
|
+
return from(jwk).to_map(key, jwe)
|
124
|
+
end
|
125
|
+
|
126
|
+
def to_map(key = nil, jwe = nil)
|
127
|
+
if not key.nil?
|
128
|
+
jwe ||= kty.key_encryptor(fields, key)
|
129
|
+
end
|
130
|
+
if key and jwe
|
131
|
+
return JOSE::JWE.block_encrypt(key, to_binary, jwe)
|
132
|
+
else
|
133
|
+
return kty.to_map(fields)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.to_oct(jwk)
|
138
|
+
return from(jwk).to_oct
|
139
|
+
end
|
140
|
+
|
141
|
+
def to_oct
|
142
|
+
return kty.to_oct
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.to_pem(jwk, password = nil)
|
146
|
+
return from(jwk).to_pem(password)
|
147
|
+
end
|
148
|
+
|
149
|
+
def to_pem(password = nil)
|
150
|
+
return kty.to_pem(password)
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.to_public(jwk)
|
154
|
+
return from(jwk).to_public
|
155
|
+
end
|
156
|
+
|
157
|
+
def to_public
|
158
|
+
return JOSE::JWK.from_map(to_public_map)
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.to_public_key(jwk)
|
162
|
+
return from(jwk).to_public_key
|
163
|
+
end
|
164
|
+
|
165
|
+
def to_public_key
|
166
|
+
return to_public.to_key
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.to_public_map(jwk)
|
170
|
+
return from(jwk).to_public_map
|
171
|
+
end
|
172
|
+
|
173
|
+
def to_public_map
|
174
|
+
return kty.to_public_map(fields)
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.to_thumbprint_map(jwk)
|
178
|
+
return from(jwk).to_thumbprint_map
|
179
|
+
end
|
180
|
+
|
181
|
+
def to_thumbprint_map
|
182
|
+
return kty.to_thumbprint_map(fields)
|
183
|
+
end
|
184
|
+
|
185
|
+
# API
|
186
|
+
|
187
|
+
def self.block_decrypt(jwk, encrypted)
|
188
|
+
return from(jwk).block_decrypt(encrypted)
|
189
|
+
end
|
190
|
+
|
191
|
+
def block_decrypt(encrypted)
|
192
|
+
return JOSE::JWE.block_decrypt(self, encrypted)
|
193
|
+
end
|
194
|
+
|
195
|
+
def self.block_encrypt(jwk, plain_text, jwe = nil)
|
196
|
+
return from(jwk).block_encrypt(plain_text, jwe)
|
197
|
+
end
|
198
|
+
|
199
|
+
def block_encrypt(plain_text, jwe = nil)
|
200
|
+
jwe ||= kty.block_encryptor(fields, plain_text)
|
201
|
+
return JOSE::JWE.block_encrypt(self, plain_text, jwe)
|
202
|
+
end
|
203
|
+
|
204
|
+
def self.box_decrypt(jwk, encrypted)
|
205
|
+
return from(jwk).box_decrypt(encrypted)
|
206
|
+
end
|
207
|
+
|
208
|
+
def box_decrypt(encrypted)
|
209
|
+
return JOSE::JWE.block_decrypt(self, encrypted)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Generates an ephemeral private key based on other public key curve.
|
213
|
+
def box_encrypt(plain_text, my_private_jwk = nil, jwe = nil)
|
214
|
+
generated_jwk = nil
|
215
|
+
other_public_jwk = self
|
216
|
+
if my_private_jwk.nil?
|
217
|
+
generated_jwk = my_private_jwk = other_public_jwk.generate_key
|
218
|
+
end
|
219
|
+
if not my_private_jwk.is_a?(JOSE::JWK)
|
220
|
+
my_private_jwk = JOSE::JWK.from(my_private_jwk)
|
221
|
+
end
|
222
|
+
if jwe.nil?
|
223
|
+
jwe = other_public_jwk.kty.block_encryptor(fields, plain_text)
|
224
|
+
end
|
225
|
+
if jwe.is_a?(Hash)
|
226
|
+
jwe = JOSE::Map.new(jwe)
|
227
|
+
end
|
228
|
+
if jwe.is_a?(JOSE::Map)
|
229
|
+
if jwe['apu'].nil?
|
230
|
+
jwe = jwe.put('apu', my_private_jwk.fields['kid'] || my_private_jwk.thumbprint)
|
231
|
+
end
|
232
|
+
if jwe['apv'].nil?
|
233
|
+
jwe = jwe.put('apv', other_public_jwk.fields['kid'] || other_public_jwk.thumbprint)
|
234
|
+
end
|
235
|
+
if jwe['epk'].nil?
|
236
|
+
jwe = jwe.put('epk', my_private_jwk.to_public_map)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
if generated_jwk
|
240
|
+
return JOSE::JWE.block_encrypt([other_public_jwk, my_private_jwk], plain_text, jwe), generated_jwk
|
241
|
+
else
|
242
|
+
return JOSE::JWE.block_encrypt([other_public_jwk, my_private_jwk], plain_text, jwe)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def derive_key(*args)
|
247
|
+
return kty.derive_key(*args)
|
248
|
+
end
|
249
|
+
|
250
|
+
def self.generate_key(params)
|
251
|
+
if params.is_a?(Array) and (params.length == 2 or params.length == 3)
|
252
|
+
case params[0]
|
253
|
+
when :ec
|
254
|
+
return JOSE::JWK.new(nil, *JOSE::JWK::KTY_EC.generate_key(params))
|
255
|
+
when :oct
|
256
|
+
return JOSE::JWK.new(nil, *JOSE::JWK::KTY_oct.generate_key(params))
|
257
|
+
when :rsa
|
258
|
+
return JOSE::JWK.new(nil, *JOSE::JWK::KTY_RSA.generate_key(params))
|
259
|
+
else
|
260
|
+
raise ArgumentError, "invalid key generation params"
|
261
|
+
end
|
262
|
+
elsif params.is_a?(JOSE::JWK)
|
263
|
+
return params.generate_key
|
264
|
+
elsif params.respond_to?(:generate_key)
|
265
|
+
return JOSE::JWK.new(nil, *params.generate_key(JOSE::Map[]))
|
266
|
+
else
|
267
|
+
raise ArgumentError, "invalid key generation params"
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def generate_key
|
272
|
+
return JOSE::JWK.new(nil, *kty.generate_key(fields))
|
273
|
+
end
|
274
|
+
|
275
|
+
def self.sign(jwk, plain_text, jws = nil, header = nil)
|
276
|
+
return from(jwk).sign(plain_text, jws, header)
|
277
|
+
end
|
278
|
+
|
279
|
+
def sign(plain_text, jws = nil, header = nil)
|
280
|
+
jws ||= kty.signer(fields, plain_text)
|
281
|
+
return JOSE::JWS.sign(self, plain_text, jws, header)
|
282
|
+
end
|
283
|
+
|
284
|
+
def self.verify(signed, jwk)
|
285
|
+
return from(jwk).verify(signed)
|
286
|
+
end
|
287
|
+
|
288
|
+
def verify(signed)
|
289
|
+
return JOSE::JWS.verify(self, signed)
|
290
|
+
end
|
291
|
+
|
292
|
+
def self.verify_strict(signed, allow, jwk)
|
293
|
+
return from(jwk).verify_strict(signed, allow)
|
294
|
+
end
|
295
|
+
|
296
|
+
def verify_strict(signed, allow)
|
297
|
+
return JOSE::JWS.verify_strict(self, allow, signed)
|
298
|
+
end
|
299
|
+
|
300
|
+
# See https://tools.ietf.org/html/rfc7638
|
301
|
+
def self.thumbprint(digest_type, jwk = nil)
|
302
|
+
if jwk.nil?
|
303
|
+
jwk = digest_type
|
304
|
+
digest_type = nil
|
305
|
+
end
|
306
|
+
return from(jwk).thumbprint(digest_type)
|
307
|
+
end
|
308
|
+
|
309
|
+
def thumbprint(digest_type = nil)
|
310
|
+
digest_type ||= 'SHA256'
|
311
|
+
thumbprint_binary = JOSE.encode(to_thumbprint_map)
|
312
|
+
return JOSE.urlsafe_encode64(OpenSSL::Digest.new(digest_type).digest(thumbprint_binary))
|
313
|
+
end
|
314
|
+
|
315
|
+
private
|
316
|
+
|
317
|
+
def self.from_fields(jwk, modules)
|
318
|
+
if jwk.fields.has_key?('keys')
|
319
|
+
keys = modules[:keys] || JOSE::JWK::Set
|
320
|
+
jwk.keys, jwk.fields = keys.from_map(jwk.fields)
|
321
|
+
return from_fields(jwk, modules)
|
322
|
+
elsif jwk.fields.has_key?('kty')
|
323
|
+
kty = modules[:kty] || case jwk.fields['kty']
|
324
|
+
when 'EC'
|
325
|
+
JOSE::JWK::KTY_EC
|
326
|
+
when 'oct'
|
327
|
+
JOSE::JWK::KTY_oct
|
328
|
+
when 'RSA'
|
329
|
+
JOSE::JWK::KTY_RSA
|
330
|
+
else
|
331
|
+
raise ArgumentError, "unknown 'kty': #{jwk.fields['kty'].inspect}"
|
332
|
+
end
|
333
|
+
jwk.kty, jwk.fields = kty.from_map(jwk.fields)
|
334
|
+
return from_fields(jwk, modules)
|
335
|
+
elsif jwk.keys.nil? and jwk.kty.nil?
|
336
|
+
raise ArgumentError, "missing required keys: 'keys' or 'kty'"
|
337
|
+
else
|
338
|
+
return jwk
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
require 'jose/jwk/pem'
|
346
|
+
require 'jose/jwk/set'
|
347
|
+
require 'jose/jwk/kty'
|
data/lib/jose/jwk/kty.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module JOSE::JWK::KTY
|
2
|
+
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def from_key(object)
|
6
|
+
case object
|
7
|
+
when OpenSSL::PKey::EC
|
8
|
+
return JOSE::JWK::KTY_EC.from_key(object)
|
9
|
+
when OpenSSL::PKey::RSA
|
10
|
+
return JOSE::JWK::KTY_RSA.from_key(object)
|
11
|
+
else
|
12
|
+
raise ArgumentError, "'object' is not a recognized key type"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def key_encryptor(kty, fields, key)
|
17
|
+
if key.is_a?(String)
|
18
|
+
return JOSE::Map[
|
19
|
+
'alg' => 'PBES2-HS256+A128KW',
|
20
|
+
'cty' => 'jwk+json',
|
21
|
+
'enc' => 'A128GCM',
|
22
|
+
'p2c' => 4096,
|
23
|
+
'p2s' => JOSE.urlsafe_encode64(SecureRandom.random_bytes(16))
|
24
|
+
]
|
25
|
+
else
|
26
|
+
raise ArgumentError, "unhandled key type for key_encryptor"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
require 'jose/jwk/kty_ec'
|
33
|
+
require 'jose/jwk/kty_oct'
|
34
|
+
require 'jose/jwk/kty_rsa'
|
@@ -0,0 +1,179 @@
|
|
1
|
+
class JOSE::JWK::KTY_EC < Struct.new(:key)
|
2
|
+
|
3
|
+
# JOSE::JWK callbacks
|
4
|
+
|
5
|
+
def self.from_map(fields)
|
6
|
+
if fields['kty'] == 'EC' and fields['crv'].is_a?(String) and fields['x'].is_a?(String) and fields['y'].is_a?(String)
|
7
|
+
crv = case fields['crv']
|
8
|
+
when 'P-256'
|
9
|
+
'prime256v1'
|
10
|
+
when 'P-384'
|
11
|
+
'secp384r1'
|
12
|
+
when 'P-521'
|
13
|
+
'secp521r1'
|
14
|
+
else
|
15
|
+
raise ArgumentError, "invalid 'EC' JWK"
|
16
|
+
end
|
17
|
+
ec = OpenSSL::PKey::EC.new(crv)
|
18
|
+
x = JOSE.urlsafe_decode64(fields['x'])
|
19
|
+
y = JOSE.urlsafe_decode64(fields['y'])
|
20
|
+
ec.public_key = OpenSSL::PKey::EC::Point.new(
|
21
|
+
OpenSSL::PKey::EC::Group.new(crv),
|
22
|
+
OpenSSL::BN.new([0x04, x, y].pack('CA*A*'), 2)
|
23
|
+
)
|
24
|
+
if fields['d'].is_a?(String)
|
25
|
+
ec.private_key = OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['d']), 2)
|
26
|
+
end
|
27
|
+
return JOSE::JWK::KTY_EC.new(ec), fields.except('kty', 'crv', 'd', 'x', 'y')
|
28
|
+
else
|
29
|
+
raise ArgumentError, "invalid 'EC' JWK"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_key
|
34
|
+
return key
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_map(fields)
|
38
|
+
ec_point = key.public_key.to_bn.to_s(2)
|
39
|
+
ec_point_x, ec_point_y = case ec_point.bytesize
|
40
|
+
when 65
|
41
|
+
ec_point.unpack('xA32A32')
|
42
|
+
when 97
|
43
|
+
ec_point.unpack('xA48A48')
|
44
|
+
when 133
|
45
|
+
ec_point.unpack('xA66A66')
|
46
|
+
else
|
47
|
+
raise ArgumentError, "unhandled EC point size: #{ec_point.bytesize.inspect}"
|
48
|
+
end
|
49
|
+
crv = case key.group.curve_name
|
50
|
+
when 'prime256v1', 'secp256r1'
|
51
|
+
'P-256'
|
52
|
+
when 'secp384r1'
|
53
|
+
'P-384'
|
54
|
+
when 'secp521r1'
|
55
|
+
'P-521'
|
56
|
+
else
|
57
|
+
raise ArgumentError, "unhandled EC curve name: #{key.group.curve_name.inspect}"
|
58
|
+
end
|
59
|
+
if key.private_key?
|
60
|
+
return fields.
|
61
|
+
put('crv', crv).
|
62
|
+
put('d', JOSE.urlsafe_encode64(key.private_key.to_s(2))).
|
63
|
+
put('kty', 'EC').
|
64
|
+
put('x', JOSE.urlsafe_encode64(ec_point_x)).
|
65
|
+
put('y', JOSE.urlsafe_encode64(ec_point_y))
|
66
|
+
else
|
67
|
+
return fields.
|
68
|
+
put('crv', crv).
|
69
|
+
put('kty', 'EC').
|
70
|
+
put('x', JOSE.urlsafe_encode64(ec_point_x)).
|
71
|
+
put('y', JOSE.urlsafe_encode64(ec_point_y))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_public_map(fields)
|
76
|
+
return to_map(fields).except('d')
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_thumbprint_map(fields)
|
80
|
+
return to_public_map(fields).slice('crv', 'kty', 'x', 'y')
|
81
|
+
end
|
82
|
+
|
83
|
+
# JOSE::JWK::KTY callbacks
|
84
|
+
|
85
|
+
def block_encryptor(fields, plain_text)
|
86
|
+
return JOSE::Map[
|
87
|
+
'alg' => 'ECDH-ES',
|
88
|
+
'enc' => 'A128GCM'
|
89
|
+
]
|
90
|
+
end
|
91
|
+
|
92
|
+
def derive_key(my_private_key)
|
93
|
+
if my_private_key.is_a?(JOSE::JWK)
|
94
|
+
my_private_key = my_private_key.to_key
|
95
|
+
end
|
96
|
+
if my_private_key.private_key?
|
97
|
+
return my_private_key.dh_compute_key(key.public_key)
|
98
|
+
else
|
99
|
+
raise ArgumentError, "derive_key requires a private key as an argument"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.generate_key(curve_name)
|
104
|
+
if curve_name.is_a?(Array) and curve_name.length == 2 and curve_name[0] == :ec
|
105
|
+
curve_name = curve_name[1]
|
106
|
+
end
|
107
|
+
if curve_name.is_a?(String)
|
108
|
+
return from_key(OpenSSL::PKey::EC.new(curve_name).generate_key)
|
109
|
+
else
|
110
|
+
raise ArgumentError, "'curve_name' must be a String"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def generate_key(fields)
|
115
|
+
kty, other_fields = JOSE::JWK::KTY_EC.generate_key([:ec, key.group.curve_name])
|
116
|
+
return kty, fields.delete('kid').merge(other_fields)
|
117
|
+
end
|
118
|
+
|
119
|
+
def key_encryptor(fields, key)
|
120
|
+
return JOSE::JWK::KTY.key_encryptor(self, fields, key)
|
121
|
+
end
|
122
|
+
|
123
|
+
def sign(message, digest_type)
|
124
|
+
asn1_signature = key.dsa_sign_asn1(digest_type.digest(message))
|
125
|
+
asn1_sequence = OpenSSL::ASN1.decode(asn1_signature)
|
126
|
+
rbin = asn1_sequence.value[0].value.to_s(2)
|
127
|
+
sbin = asn1_sequence.value[1].value.to_s(2)
|
128
|
+
size = [rbin.bytesize, sbin.bytesize].max
|
129
|
+
rpad = pad(rbin, size)
|
130
|
+
spad = pad(sbin, size)
|
131
|
+
return rpad.concat(spad)
|
132
|
+
end
|
133
|
+
|
134
|
+
def signer(fields = nil, plain_text = nil)
|
135
|
+
if key.private_key?
|
136
|
+
return JOSE::Map['alg' => 'ES256']
|
137
|
+
else
|
138
|
+
raise ArgumentError, "signing not supported for public keys"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def verify(message, digest_type, signature)
|
143
|
+
n = signature.bytesize.div(2)
|
144
|
+
r = OpenSSL::BN.new(signature[0..(n-1)], 2)
|
145
|
+
s = OpenSSL::BN.new(signature[n..-1], 2)
|
146
|
+
asn1_sequence = OpenSSL::ASN1::Sequence.new([
|
147
|
+
OpenSSL::ASN1::Integer.new(r),
|
148
|
+
OpenSSL::ASN1::Integer.new(s)
|
149
|
+
])
|
150
|
+
asn1_signature = asn1_sequence.to_der
|
151
|
+
return key.dsa_verify_asn1(digest_type.digest(message), asn1_signature)
|
152
|
+
end
|
153
|
+
|
154
|
+
# API functions
|
155
|
+
|
156
|
+
def self.from_key(key)
|
157
|
+
case key
|
158
|
+
when OpenSSL::PKey::EC
|
159
|
+
return JOSE::JWK::KTY_EC.new(key), JOSE::Map[]
|
160
|
+
else
|
161
|
+
raise ArgumentError, "'key' must be a OpenSSL::PKey::EC"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def to_pem(password = nil)
|
166
|
+
return JOSE::JWK::PEM.to_binary(key, password)
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def pad(bin, size)
|
172
|
+
if bin.bytesize == size
|
173
|
+
return bin
|
174
|
+
else
|
175
|
+
return pad([0x00].pack('C').concat(bin), size)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|