oydid 0.5.6 → 0.6.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 +4 -4
- data/LICENSE +10 -18
- data/VERSION +1 -1
- data/lib/oydid/basic.rb +534 -58
- data/lib/oydid/didcomm.rb +58 -0
- data/lib/oydid/log.rb +16 -6
- data/lib/oydid/vc.rb +367 -61
- data/lib/oydid.rb +322 -143
- metadata +131 -37
data/lib/oydid/basic.rb
CHANGED
|
@@ -51,7 +51,10 @@ class Oydid
|
|
|
51
51
|
else
|
|
52
52
|
return [nil, "unsupported digest: '" + method.to_s + "'"]
|
|
53
53
|
end
|
|
54
|
-
|
|
54
|
+
code = Multicodecs[method].code
|
|
55
|
+
length = digest.bytesize
|
|
56
|
+
encoded = multi_encode([code, length, digest].pack("CCa#{length}"), options)
|
|
57
|
+
# encoded = multi_encode(Multihashes.encode(digest, method.to_s), options)
|
|
55
58
|
if encoded.first.nil?
|
|
56
59
|
return [nil, encoded.last]
|
|
57
60
|
else
|
|
@@ -60,15 +63,15 @@ class Oydid
|
|
|
60
63
|
end
|
|
61
64
|
|
|
62
65
|
def self.get_digest(message)
|
|
63
|
-
decoded_message, error =
|
|
66
|
+
decoded_message, error = multi_decode(message)
|
|
64
67
|
if decoded_message.nil?
|
|
65
68
|
return [nil, error]
|
|
66
69
|
end
|
|
67
|
-
retVal = Multihashes.decode decoded_message
|
|
68
|
-
if retVal[:hash_function].to_s != ""
|
|
69
|
-
|
|
70
|
-
end
|
|
71
|
-
case
|
|
70
|
+
# retVal = Multihashes.decode decoded_message
|
|
71
|
+
# if retVal[:hash_function].to_s != ""
|
|
72
|
+
# return [retVal[:hash_function].to_s, ""]
|
|
73
|
+
# end
|
|
74
|
+
case decoded_message[0..1].to_s
|
|
72
75
|
when "\x02\x10"
|
|
73
76
|
return ["blake2b-16", ""]
|
|
74
77
|
when "\x04 "
|
|
@@ -76,7 +79,13 @@ class Oydid
|
|
|
76
79
|
when "\b@"
|
|
77
80
|
return ["blake2b-64", ""]
|
|
78
81
|
else
|
|
79
|
-
|
|
82
|
+
code, length, digest = decoded_message.unpack('CCa*')
|
|
83
|
+
retVal = Multicodecs[code].name rescue nil
|
|
84
|
+
if !retVal.nil?
|
|
85
|
+
return [retVal, ""]
|
|
86
|
+
else
|
|
87
|
+
return [nil, "unknown digest"]
|
|
88
|
+
end
|
|
80
89
|
end
|
|
81
90
|
end
|
|
82
91
|
|
|
@@ -103,7 +112,60 @@ class Oydid
|
|
|
103
112
|
did = did.sub("https://","").sub("@", "%40").sub("http://","http%3A%2F%2F").gsub(":","%3A").sub("did%3Aoyd%3A", "did:oyd:")
|
|
104
113
|
end
|
|
105
114
|
|
|
115
|
+
def self.to_varint(n)
|
|
116
|
+
bytes = []
|
|
117
|
+
loop do
|
|
118
|
+
byte = n & 0x7F
|
|
119
|
+
n >>= 7
|
|
120
|
+
if n == 0
|
|
121
|
+
bytes << byte
|
|
122
|
+
break
|
|
123
|
+
else
|
|
124
|
+
bytes << (byte | 0x80)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
bytes
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def self.read_varint(str)
|
|
131
|
+
n = shift = 0
|
|
132
|
+
str.each_byte do |byte|
|
|
133
|
+
n |= (byte & 0x7f) << shift
|
|
134
|
+
break unless (byte & 0x80) == 0x80
|
|
135
|
+
shift += 7
|
|
136
|
+
end
|
|
137
|
+
return n
|
|
138
|
+
end
|
|
139
|
+
|
|
106
140
|
# key management ----------------------------
|
|
141
|
+
def self.get_keytype(input)
|
|
142
|
+
code, length, digest = multi_decode(input).first.unpack('SCa*')
|
|
143
|
+
case Multicodecs[code]&.name
|
|
144
|
+
when 'ed25519-priv', 'p256-priv'
|
|
145
|
+
return Multicodecs[code].name
|
|
146
|
+
else
|
|
147
|
+
pubkey = multi_decode(input).first
|
|
148
|
+
if pubkey.bytes.length == 34
|
|
149
|
+
code = pubkey.bytes.first
|
|
150
|
+
digest = pubkey[-32..]
|
|
151
|
+
else
|
|
152
|
+
if pubkey.start_with?("\x80\x24".dup.force_encoding('ASCII-8BIT'))
|
|
153
|
+
code = 4608 # Bytes 0x80 0x24 sind das Varint-Encoding des Multicodec-Codes 0x1200 (p256-pub)
|
|
154
|
+
# 4608 == Oydid.read_varint("\x80$") oder "\x80\x24".force_encoding('ASCII-8BIT')
|
|
155
|
+
else
|
|
156
|
+
code = pubkey.unpack('n').first
|
|
157
|
+
end
|
|
158
|
+
digest = pubkey[-1*(pubkey.bytes.length-2)..]
|
|
159
|
+
end
|
|
160
|
+
case Multicodecs[code]&.name
|
|
161
|
+
when 'ed25519-pub', 'p256-pub'
|
|
162
|
+
return Multicodecs[code].name
|
|
163
|
+
else
|
|
164
|
+
return nil
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
107
169
|
def self.generate_private_key(input, method = "ed25519-priv", options = {})
|
|
108
170
|
begin
|
|
109
171
|
omc = Multicodecs[method].code
|
|
@@ -113,19 +175,35 @@ class Oydid
|
|
|
113
175
|
|
|
114
176
|
case Multicodecs[method].name
|
|
115
177
|
when 'ed25519-priv'
|
|
116
|
-
if input
|
|
117
|
-
raw_key = Ed25519::SigningKey.new(RbNaCl::Hash.sha256(input))
|
|
118
|
-
else
|
|
178
|
+
if input == ""
|
|
119
179
|
raw_key = Ed25519::SigningKey.generate
|
|
180
|
+
else
|
|
181
|
+
raw_key = Ed25519::SigningKey.new(RbNaCl::Hash.sha256(input))
|
|
120
182
|
end
|
|
121
183
|
raw_key = raw_key.to_bytes
|
|
122
|
-
|
|
184
|
+
when 'p256-priv'
|
|
185
|
+
key = OpenSSL::PKey::EC.new('prime256v1')
|
|
186
|
+
if input == ""
|
|
187
|
+
key = OpenSSL::PKey::EC.generate('prime256v1')
|
|
188
|
+
else
|
|
189
|
+
# input for p256-priv requires valid base64 encoded private key
|
|
190
|
+
begin
|
|
191
|
+
key = OpenSSL::PKey.read Base64.decode64(input)
|
|
192
|
+
rescue
|
|
193
|
+
return [nil, "invalid input"]
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
raw_key = key.private_key.to_s(2)
|
|
123
197
|
else
|
|
124
198
|
return [nil, "unsupported key codec"]
|
|
125
199
|
end
|
|
126
|
-
|
|
200
|
+
|
|
201
|
+
# only encoding without specifying key-type
|
|
202
|
+
# encoded = multi_encode(raw_key, options)
|
|
203
|
+
|
|
204
|
+
# encoding with specyfying key-type
|
|
205
|
+
length = raw_key.bytesize
|
|
127
206
|
encoded = multi_encode([omc, length, raw_key].pack("SCa#{length}"), options)
|
|
128
|
-
# encoded = multi_encode(raw_key.to_bytes, options)
|
|
129
207
|
if encoded.first.nil?
|
|
130
208
|
return [nil, encoded.last]
|
|
131
209
|
else
|
|
@@ -133,10 +211,11 @@ class Oydid
|
|
|
133
211
|
end
|
|
134
212
|
end
|
|
135
213
|
|
|
136
|
-
def self.public_key(private_key, options = {}, method =
|
|
214
|
+
def self.public_key(private_key, options = {}, method = nil)
|
|
137
215
|
code, length, digest = multi_decode(private_key).first.unpack('SCa*')
|
|
138
216
|
case Multicodecs[code].name
|
|
139
217
|
when 'ed25519-priv'
|
|
218
|
+
method = 'ed25519-pub' if method.nil?
|
|
140
219
|
case method
|
|
141
220
|
when 'ed25519-pub'
|
|
142
221
|
public_key = Ed25519::SigningKey.new(digest).verify_key
|
|
@@ -145,9 +224,32 @@ class Oydid
|
|
|
145
224
|
else
|
|
146
225
|
return [nil, "unsupported key codec"]
|
|
147
226
|
end
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
encoded = multi_encode(
|
|
227
|
+
|
|
228
|
+
# encoding according to https://www.w3.org/TR/vc-di-eddsa/#ed25519verificationkey2020
|
|
229
|
+
encoded = multi_encode(
|
|
230
|
+
Multibases::DecodedByteArray.new(
|
|
231
|
+
([Multicodecs[method].code, 1] <<
|
|
232
|
+
public_key.to_bytes.bytes).flatten)
|
|
233
|
+
.to_s(Encoding::BINARY),
|
|
234
|
+
options)
|
|
235
|
+
|
|
236
|
+
# previous (up until oydid 0.5.6) wrong encoding (length should not be set!):
|
|
237
|
+
# length = public_key.to_bytes.bytesize
|
|
238
|
+
# encoded = multi_encode([Multicodecs[method].code, length, public_key].pack("CCa#{length}"), options)
|
|
239
|
+
if encoded.first.nil?
|
|
240
|
+
return [nil, encoded.last]
|
|
241
|
+
else
|
|
242
|
+
return [encoded.first, ""]
|
|
243
|
+
end
|
|
244
|
+
when 'p256-priv'
|
|
245
|
+
method = 'p256-pub' if method.nil?
|
|
246
|
+
group = OpenSSL::PKey::EC::Group.new('prime256v1')
|
|
247
|
+
public_key = group.generator.mul(OpenSSL::BN.new(digest, 2))
|
|
248
|
+
encoded = multi_encode(
|
|
249
|
+
Multibases::DecodedByteArray.new(
|
|
250
|
+
(to_varint(0x1200) << public_key.to_bn.to_s(2).bytes)
|
|
251
|
+
.flatten).to_s(Encoding::BINARY),
|
|
252
|
+
options)
|
|
151
253
|
if encoded.first.nil?
|
|
152
254
|
return [nil, encoded.last]
|
|
153
255
|
else
|
|
@@ -175,6 +277,9 @@ class Oydid
|
|
|
175
277
|
end
|
|
176
278
|
else
|
|
177
279
|
privateKey, msg = decode_private_key(enc.to_s, options)
|
|
280
|
+
if msg.nil?
|
|
281
|
+
privateKey = enc.to_s
|
|
282
|
+
end
|
|
178
283
|
end
|
|
179
284
|
return [privateKey, msg]
|
|
180
285
|
end
|
|
@@ -249,16 +354,97 @@ class Oydid
|
|
|
249
354
|
return [keys.uniq, ""]
|
|
250
355
|
end
|
|
251
356
|
|
|
252
|
-
def self.sign(message, private_key, options)
|
|
253
|
-
|
|
254
|
-
case
|
|
357
|
+
def self.sign(message, private_key, options = {})
|
|
358
|
+
key_type = get_keytype(private_key)
|
|
359
|
+
case key_type
|
|
255
360
|
when 'ed25519-priv'
|
|
361
|
+
code, length, digest = multi_decode(private_key).first.unpack('SCa*')
|
|
256
362
|
encoded = multi_encode(Ed25519::SigningKey.new(digest).sign(message), options)
|
|
257
363
|
if encoded.first.nil?
|
|
258
364
|
return [nil, encoded.last]
|
|
259
365
|
else
|
|
260
366
|
return [encoded.first, ""]
|
|
261
367
|
end
|
|
368
|
+
when 'p256-priv'
|
|
369
|
+
# non-deterministic signing
|
|
370
|
+
# ec_key = decode_private_key(private_key).first
|
|
371
|
+
# dgst_bin = OpenSSL::Digest::SHA256.digest(message)
|
|
372
|
+
# der = ec_key.dsa_sign_asn1(dgst_bin)
|
|
373
|
+
# asn1 = OpenSSL::ASN1.decode(der)
|
|
374
|
+
# r_hex = asn1.value[0].value.to_s(16).rjust(64, '0')
|
|
375
|
+
# s_hex = asn1.value[1].value.to_s(16).rjust(64, '0')
|
|
376
|
+
# sig_bin = [r_hex + s_hex].pack('H*')
|
|
377
|
+
# encoded_signature = Base64.strict_encode64(sig_bin).tr('+/', '-_').delete('=')
|
|
378
|
+
|
|
379
|
+
# === deterministic signing with P-256 =====
|
|
380
|
+
# key & constants
|
|
381
|
+
ec_key = decode_private_key(private_key).first
|
|
382
|
+
if (ec_key.respond_to?(:private_key) ? ec_key.private_key : nil).nil?
|
|
383
|
+
return [nil, "invalild private key"]
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
group = OpenSSL::PKey::EC::Group.new('prime256v1')
|
|
387
|
+
n = group.order
|
|
388
|
+
qlen = n.num_bits
|
|
389
|
+
holen = 256
|
|
390
|
+
bx = ec_key.private_key
|
|
391
|
+
|
|
392
|
+
# hash message
|
|
393
|
+
h1_bin = OpenSSL::Digest::SHA256.digest(message)
|
|
394
|
+
h1_int = h1_bin.unpack1('H*').to_i(16)
|
|
395
|
+
|
|
396
|
+
# helper (RFC 6979 Section 2.3)
|
|
397
|
+
int2octets = lambda do |int|
|
|
398
|
+
bin = int.to_s(16).rjust((qlen + 7) / 8 * 2, '0')
|
|
399
|
+
[bin].pack('H*')
|
|
400
|
+
end
|
|
401
|
+
bits2octets = lambda do |bits|
|
|
402
|
+
z1 = bits % n
|
|
403
|
+
int2octets.call(z1)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# initialize HMAC-DRBG
|
|
407
|
+
v = "\x01" * (holen / 8)
|
|
408
|
+
k = "\x00" * (holen / 8)
|
|
409
|
+
key_oct = int2octets.call(bx)
|
|
410
|
+
hash_oct = bits2octets.call(h1_int)
|
|
411
|
+
|
|
412
|
+
k = OpenSSL::HMAC.digest('SHA256', k, v + "\x00" + key_oct + hash_oct)
|
|
413
|
+
v = OpenSSL::HMAC.digest('SHA256', k, v)
|
|
414
|
+
k = OpenSSL::HMAC.digest('SHA256', k, v + "\x01" + key_oct + hash_oct)
|
|
415
|
+
v = OpenSSL::HMAC.digest('SHA256', k, v)
|
|
416
|
+
|
|
417
|
+
# identify k (step H in RFC 6979)
|
|
418
|
+
loop do
|
|
419
|
+
v = OpenSSL::HMAC.digest('SHA256', k, v)
|
|
420
|
+
t = v.unpack1('H*').to_i(16)
|
|
421
|
+
|
|
422
|
+
k_candidate = t % n
|
|
423
|
+
if k_candidate.positive? && k_candidate < n
|
|
424
|
+
k_bn = OpenSSL::BN.new(k_candidate)
|
|
425
|
+
# calculate signature (r,s)
|
|
426
|
+
r_bn = group.generator.mul(k_bn).to_bn # Point → BN (uncompressed)
|
|
427
|
+
# extract x
|
|
428
|
+
r_int = r_bn.to_s(2)[1, 32].unpack1('H*').to_i(16) % n
|
|
429
|
+
next if r_int.zero?
|
|
430
|
+
|
|
431
|
+
kinv = k_bn.mod_inverse(n)
|
|
432
|
+
s_int = (kinv * (h1_int + bx.to_i * r_int)) % n
|
|
433
|
+
next if s_int.zero?
|
|
434
|
+
s_int = n.to_i - s_int if s_int > n.to_i / 2
|
|
435
|
+
|
|
436
|
+
# encode r||s -> URL-safe Base64
|
|
437
|
+
r_hex = r_int.to_s(16).rjust(64, '0')
|
|
438
|
+
s_hex = s_int.to_s(16).rjust(64, '0')
|
|
439
|
+
sig = [r_hex + s_hex].pack('H*')
|
|
440
|
+
return [Base64.urlsafe_encode64(sig, padding: false), ""]
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
k = OpenSSL::HMAC.digest('SHA256', k, v + "\x00")
|
|
444
|
+
v = OpenSSL::HMAC.digest('SHA256', k, v)
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
return [encoded_signature, ""]
|
|
262
448
|
else
|
|
263
449
|
return [nil, "unsupported key codec"]
|
|
264
450
|
end
|
|
@@ -266,7 +452,19 @@ class Oydid
|
|
|
266
452
|
|
|
267
453
|
def self.verify(message, signature, public_key)
|
|
268
454
|
begin
|
|
269
|
-
|
|
455
|
+
pubkey = multi_decode(public_key).first
|
|
456
|
+
if pubkey.bytes.length == 34
|
|
457
|
+
code = pubkey.bytes.first
|
|
458
|
+
digest = pubkey[-32..]
|
|
459
|
+
else
|
|
460
|
+
if pubkey.start_with?("\x80\x24".dup.force_encoding('ASCII-8BIT'))
|
|
461
|
+
code = 4608 # Bytes 0x80 0x24 sind das Varint-Encoding des Multicodec-Codes 0x1200 (p256-pub)
|
|
462
|
+
# 4608 == Oydid.read_varint("\x80$") oder "\x80\x24".force_encoding('ASCII-8BIT')
|
|
463
|
+
else
|
|
464
|
+
code = pubkey.unpack('n').first
|
|
465
|
+
end
|
|
466
|
+
digest = pubkey[-1*(pubkey.bytes.length-2)..]
|
|
467
|
+
end
|
|
270
468
|
case Multicodecs[code].name
|
|
271
469
|
when 'ed25519-pub'
|
|
272
470
|
verify_key = Ed25519::VerifyKey.new(digest)
|
|
@@ -278,6 +476,26 @@ class Oydid
|
|
|
278
476
|
signature_verification = false
|
|
279
477
|
end
|
|
280
478
|
return [signature_verification, ""]
|
|
479
|
+
when 'p256-pub'
|
|
480
|
+
asn1_public_key = OpenSSL::ASN1::Sequence.new([
|
|
481
|
+
OpenSSL::ASN1::Sequence.new([
|
|
482
|
+
OpenSSL::ASN1::ObjectId.new('id-ecPublicKey'),
|
|
483
|
+
OpenSSL::ASN1::ObjectId.new('prime256v1')
|
|
484
|
+
]),
|
|
485
|
+
OpenSSL::ASN1::BitString.new(digest)
|
|
486
|
+
])
|
|
487
|
+
key = OpenSSL::PKey::EC.new(asn1_public_key.to_der)
|
|
488
|
+
|
|
489
|
+
sig_raw = Base64.urlsafe_decode64(signature + "=" * ((4 - signature.size % 4) % 4))
|
|
490
|
+
r_hex = sig_raw[0, 32].unpack1("H*")
|
|
491
|
+
s_hex = sig_raw[32, 32].unpack1("H*")
|
|
492
|
+
asn_r = OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(r_hex, 16))
|
|
493
|
+
asn_s = OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(s_hex, 16))
|
|
494
|
+
sig_der = OpenSSL::ASN1::Sequence.new([asn_r, asn_s]).to_der
|
|
495
|
+
|
|
496
|
+
message_digest = OpenSSL::Digest::SHA256.new
|
|
497
|
+
valid = key.dsa_verify_asn1(message_digest.digest(message), sig_der)
|
|
498
|
+
return [valid, ""]
|
|
281
499
|
else
|
|
282
500
|
return [nil, "unsupported key codec"]
|
|
283
501
|
end
|
|
@@ -288,8 +506,15 @@ class Oydid
|
|
|
288
506
|
|
|
289
507
|
def self.encrypt(message, public_key, options = {})
|
|
290
508
|
begin
|
|
291
|
-
|
|
292
|
-
|
|
509
|
+
if options[:key_type].to_s == ''
|
|
510
|
+
pk = multi_decode(public_key).first
|
|
511
|
+
code = pk.bytes.first
|
|
512
|
+
digest = pk[-32..]
|
|
513
|
+
key_type = Multicodecs[code].name rescue ''
|
|
514
|
+
else
|
|
515
|
+
key_type = options[:key_type]
|
|
516
|
+
end
|
|
517
|
+
case key_type
|
|
293
518
|
when 'x25519-pub'
|
|
294
519
|
pubKey = RbNaCl::PublicKey.new(digest)
|
|
295
520
|
authHash = RbNaCl::Hash.sha256('auth'.dup.force_encoding('ASCII-8BIT'))
|
|
@@ -304,6 +529,43 @@ class Oydid
|
|
|
304
529
|
nonce: nonce.unpack('H*')[0]
|
|
305
530
|
}, ""
|
|
306
531
|
]
|
|
532
|
+
when 'p256-pub'
|
|
533
|
+
recipient_pub_key = decode_public_key(public_key).first
|
|
534
|
+
|
|
535
|
+
# a) Ephemeren Sender-Key erzeugen + ECDH
|
|
536
|
+
ephemeral_key = OpenSSL::PKey::EC.generate('prime256v1')
|
|
537
|
+
shared_secret = ephemeral_key.dh_compute_key(recipient_pub_key.public_key)
|
|
538
|
+
|
|
539
|
+
# b) Ableitung Content-Encryption-Key (CEK) – simple HKDF-Light
|
|
540
|
+
cek = OpenSSL::Digest::SHA256.digest(shared_secret)
|
|
541
|
+
|
|
542
|
+
# c) Symmetrische Verschlüsselung (AES-256-GCM)
|
|
543
|
+
cipher = OpenSSL::Cipher.new('aes-256-gcm')
|
|
544
|
+
cipher.encrypt
|
|
545
|
+
cipher.key = cek
|
|
546
|
+
iv = OpenSSL::Random.random_bytes(12) # 96-bit IV wie empfohlen
|
|
547
|
+
cipher.iv = iv
|
|
548
|
+
cipher.auth_data = '' # kein AAD
|
|
549
|
+
ciphertext = cipher.update(message) + cipher.final
|
|
550
|
+
tag = cipher.auth_tag
|
|
551
|
+
|
|
552
|
+
# d) JWE-Header (nur die nötigen Felder)
|
|
553
|
+
header = {
|
|
554
|
+
alg: 'ECDH-ES',
|
|
555
|
+
enc: 'A256GCM',
|
|
556
|
+
kid: options[:kid],
|
|
557
|
+
epk: JWT::JWK.new(ephemeral_key).export.slice(:kty, :crv, :x, :y)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
# e) JWE-Compact-Serialisierung (EncryptedKey leer bei ECDH-ES)
|
|
561
|
+
jwe_compact = [
|
|
562
|
+
Base64.urlsafe_encode64(header.to_json).delete("="),
|
|
563
|
+
'',
|
|
564
|
+
Base64.urlsafe_encode64(iv).delete("="),
|
|
565
|
+
Base64.urlsafe_encode64(ciphertext).delete("="),
|
|
566
|
+
Base64.urlsafe_encode64(tag).delete("="),
|
|
567
|
+
].join('.')
|
|
568
|
+
return [jwe_compact, nil]
|
|
307
569
|
else
|
|
308
570
|
return [nil, "unsupported key codec"]
|
|
309
571
|
end
|
|
@@ -314,17 +576,40 @@ class Oydid
|
|
|
314
576
|
|
|
315
577
|
def self.decrypt(message, private_key, options = {})
|
|
316
578
|
begin
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
code, length, digest = multi_decode(private_key).first.unpack('SCa*')
|
|
320
|
-
case Multicodecs[code].name
|
|
579
|
+
key_type = get_keytype(private_key)
|
|
580
|
+
case key_type
|
|
321
581
|
when 'ed25519-priv'
|
|
582
|
+
cipher = [JSON.parse(message)["value"]].pack('H*')
|
|
583
|
+
nonce = [JSON.parse(message)["nonce"]].pack('H*')
|
|
584
|
+
code, length, digest = multi_decode(private_key).first.unpack('SCa*')
|
|
585
|
+
if length != 32 # support only encoded keys
|
|
586
|
+
digest = Multibases.unpack(private_key).decode.to_s('ASCII-8BIT')
|
|
587
|
+
code = Multicodecs["ed25519-priv"].code
|
|
588
|
+
end
|
|
322
589
|
privKey = RbNaCl::PrivateKey.new(digest)
|
|
323
590
|
authHash = RbNaCl::Hash.sha256('auth'.dup.force_encoding('ASCII-8BIT'))
|
|
324
591
|
authKey = RbNaCl::PrivateKey.new(authHash).public_key
|
|
325
592
|
box = RbNaCl::Box.new(authKey, privKey)
|
|
326
593
|
retVal = box.decrypt(nonce, cipher)
|
|
327
594
|
return [retVal, ""]
|
|
595
|
+
when 'p256-priv'
|
|
596
|
+
private_key = decode_private_key(private_key).first
|
|
597
|
+
head_b64, _, iv_b64, cipher_b64, tag_b64 = message.split('.')
|
|
598
|
+
decoded_header = JSON.parse(Base64.urlsafe_decode64(head_b64))
|
|
599
|
+
epk_pub = Oydid.decode_public_key(Oydid.public_key_from_jwk(decoded_header['epk']).first).first
|
|
600
|
+
|
|
601
|
+
shared_secret2 = private_key.dh_compute_key(epk_pub.public_key) # ECDH (Gegenseite)
|
|
602
|
+
cek2 = OpenSSL::Digest::SHA256.digest(shared_secret2)
|
|
603
|
+
|
|
604
|
+
decipher = OpenSSL::Cipher.new('aes-256-gcm')
|
|
605
|
+
decipher.decrypt
|
|
606
|
+
decipher.key = cek2
|
|
607
|
+
decipher.iv = Base64.urlsafe_decode64(iv_b64)
|
|
608
|
+
decipher.auth_tag = Base64.urlsafe_decode64(tag_b64)
|
|
609
|
+
decipher.auth_data = ''
|
|
610
|
+
plaintext = decipher.update(Base64.urlsafe_decode64(cipher_b64)) + decipher.final
|
|
611
|
+
|
|
612
|
+
return [plaintext, nil]
|
|
328
613
|
else
|
|
329
614
|
return [nil, "unsupported key codec"]
|
|
330
615
|
end
|
|
@@ -333,7 +618,6 @@ class Oydid
|
|
|
333
618
|
end
|
|
334
619
|
end
|
|
335
620
|
|
|
336
|
-
# key="812b578d2e357270cbbd26a9dd44f93e5c8a3b44462e271348ce8f742dfe08144d06fd1f64d5a15c5b21564695d0dca9d65af322e8f96ef394400fe255d288cf"
|
|
337
621
|
def jweh(key)
|
|
338
622
|
pub_key=key[-64..-1]
|
|
339
623
|
prv_key=key[0..-65]
|
|
@@ -342,21 +626,16 @@ class Oydid
|
|
|
342
626
|
bin_pub=[hex_pub].pack('H*')
|
|
343
627
|
int_pub=RbNaCl::PublicKey.new(bin_pub)
|
|
344
628
|
len_pub=int_pub.to_bytes.bytesize
|
|
345
|
-
enc_pub=
|
|
629
|
+
enc_pub=multi_encode([Multicodecs["x25519-pub"].code,len_pub,int_pub].pack("CCa#{len_pub}"),{}).first
|
|
346
630
|
|
|
347
631
|
hex_prv=prv_key
|
|
348
632
|
bin_prv=[hex_prv].pack('H*')
|
|
349
633
|
int_prv=RbNaCl::PrivateKey.new(bin_prv)
|
|
350
634
|
len_prv=int_prv.to_bytes.bytesize
|
|
351
|
-
enc_prv=
|
|
635
|
+
enc_prv=multi_encode([Multicodecs["ed25519-priv"].code,len_prv,int_prv].pack("SCa#{len_prv}"),{}).first
|
|
352
636
|
return [enc_pub, enc_prv]
|
|
353
637
|
end
|
|
354
638
|
|
|
355
|
-
# public_key=jweh(key).first
|
|
356
|
-
# require 'oydid'
|
|
357
|
-
# message="hallo"
|
|
358
|
-
# public_key="z6Mv4uEoFYJ369NoE9xUzxG5sm8KpPnvHX6YH6GsYFGSQ32J"
|
|
359
|
-
# jwe=Oydid.encryptJWE(message, public_key).first
|
|
360
639
|
def self.encryptJWE(message, public_key, options = {})
|
|
361
640
|
|
|
362
641
|
jwe_header = {"enc":"XC20P"}
|
|
@@ -378,7 +657,7 @@ class Oydid
|
|
|
378
657
|
|
|
379
658
|
# Key Encryption ---
|
|
380
659
|
snd_prv = RbNaCl::PrivateKey.generate
|
|
381
|
-
code, length, digest =
|
|
660
|
+
code, length, digest = multi_decode(public_key).first.unpack('CCa*')
|
|
382
661
|
buffer = RbNaCl::Util.zeros(RbNaCl::Boxes::Curve25519XSalsa20Poly1305::PublicKey::BYTES)
|
|
383
662
|
RbNaCl::Signatures::Ed25519::VerifyKey.crypto_sign_ed25519_pk_to_curve25519(buffer, digest)
|
|
384
663
|
shared_secret = RbNaCl::GroupElement.new(buffer).mult(snd_prv.to_bytes)
|
|
@@ -434,11 +713,6 @@ class Oydid
|
|
|
434
713
|
return [jwe_full, ""]
|
|
435
714
|
end
|
|
436
715
|
|
|
437
|
-
# require 'oydid'
|
|
438
|
-
# message = '{"protected":"eyJlbmMiOiJYQzIwUCJ9","iv":"G24pK06HSbL0vTlRZMIEBLfa074tJ1tq","ciphertext":"N5bgUr8","tag":"CJeq8cuaercgoBZmrYoGUA","recipients":[{"encrypted_key":"chR8HQh1CRYRU4TdlfBbvon4fcb5PKfWPSo0SgkMC_8","header":{"alg":"ECDH-ES+XC20PKW","iv":"K7lUo8shyJhxC7Nl45VlXes4tbDeZyBL","tag":"2sLCGRv70ESqEAqos3ZhSg","epk":{"kty":"OKP","crv":"X25519","x":"ZpnKcI7Kac6HPwVAGwM0PBweTFKM6wHHVljTHMRWpD4"}}}]}'
|
|
439
|
-
# message = jwe.to_json
|
|
440
|
-
# private_key = "z1S5USmDosHvi2giCLHCCgcq3Cd31mrhMcUy2fcfszxxLkD7"
|
|
441
|
-
# Oydid.decryptJWE(jwe.to_json, private_key)
|
|
442
716
|
def self.decryptJWE(message, private_key, options = {})
|
|
443
717
|
|
|
444
718
|
# JWE parsing
|
|
@@ -463,7 +737,7 @@ class Oydid
|
|
|
463
737
|
cnt_aad = Base64.urlsafe_decode64(cnt_aad_enc)
|
|
464
738
|
|
|
465
739
|
# Key Decryption
|
|
466
|
-
code, length, digest =
|
|
740
|
+
code, length, digest = multi_decode(private_key).first.unpack('SCa*')
|
|
467
741
|
buffer = RbNaCl::Util.zeros(RbNaCl::Boxes::Curve25519XSalsa20Poly1305::PublicKey::BYTES)
|
|
468
742
|
RbNaCl::Signatures::Ed25519::SigningKey.crypto_sign_ed25519_sk_to_curve25519(buffer, digest)
|
|
469
743
|
shared_secret = RbNaCl::GroupElement.new(snd_pub).mult(buffer)
|
|
@@ -491,34 +765,107 @@ class Oydid
|
|
|
491
765
|
rescue
|
|
492
766
|
return [nil, "cannot read file"]
|
|
493
767
|
end
|
|
494
|
-
|
|
768
|
+
key_type = get_keytype(key_encoded) || options[:key_type] rescue options[:key_type]
|
|
769
|
+
if key_type.include?('-')
|
|
770
|
+
key_type = key_type.split('-').first || options[:key_type] rescue options[:key_type]
|
|
771
|
+
end
|
|
772
|
+
if key_type == 'p256'
|
|
773
|
+
begin
|
|
774
|
+
key = decode_private_key(key_encoded).first
|
|
775
|
+
private_key = key.private_key.to_s(2)
|
|
776
|
+
code = Multicodecs["p256-priv"].code
|
|
777
|
+
rescue
|
|
778
|
+
return [nil, "invalid base64 encoded p256-priv key"]
|
|
779
|
+
end
|
|
780
|
+
else
|
|
781
|
+
begin
|
|
782
|
+
code, length, digest = multi_decode(key_encoded).first.unpack('SCa*')
|
|
783
|
+
case Multicodecs[code].name
|
|
784
|
+
when 'ed25519-priv'
|
|
785
|
+
private_key = Ed25519::SigningKey.new(digest).to_bytes
|
|
786
|
+
# when 'p256-priv'
|
|
787
|
+
# key = OpenSSL::PKey::EC.new('prime256v1')
|
|
788
|
+
# key.private_key = OpenSSL::BN.new(digest, 2)
|
|
789
|
+
# private_key = key.private_key.to_s(2)
|
|
790
|
+
else
|
|
791
|
+
return [nil, "unsupported key codec"]
|
|
792
|
+
end
|
|
793
|
+
rescue
|
|
794
|
+
return [nil, "invalid key"]
|
|
795
|
+
end
|
|
796
|
+
end
|
|
797
|
+
length = private_key.bytesize
|
|
798
|
+
return multi_encode([code, length, private_key].pack("SCa#{length}"), options)
|
|
799
|
+
|
|
495
800
|
end
|
|
496
801
|
|
|
497
|
-
def self.decode_private_key(key_encoded, options)
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
802
|
+
def self.decode_private_key(key_encoded, options = {})
|
|
803
|
+
code, length, digest = multi_decode(key_encoded).first.unpack('SCa*')
|
|
804
|
+
case Multicodecs[code].name
|
|
805
|
+
when 'ed25519-priv'
|
|
806
|
+
private_key = Ed25519::SigningKey.new(digest).to_bytes
|
|
807
|
+
when 'p256-priv'
|
|
808
|
+
group = OpenSSL::PKey::EC::Group.new('prime256v1')
|
|
809
|
+
pub_key = group.generator.mul(OpenSSL::BN.new(digest, 2))
|
|
810
|
+
pub_oct = pub_key.to_bn.to_s(2)
|
|
811
|
+
|
|
812
|
+
parameters = OpenSSL::ASN1::ObjectId("prime256v1")
|
|
813
|
+
parameters.tag = 0
|
|
814
|
+
parameters.tagging = :EXPLICIT
|
|
815
|
+
parameters.tag_class = :CONTEXT_SPECIFIC
|
|
816
|
+
|
|
817
|
+
public_key_bitstring = OpenSSL::ASN1::BitString(pub_oct)
|
|
818
|
+
public_key_bitstring.tag = 1
|
|
819
|
+
public_key_bitstring.tagging = :EXPLICIT
|
|
820
|
+
public_key_bitstring.tag_class = :CONTEXT_SPECIFIC
|
|
821
|
+
|
|
822
|
+
ec_private_key_asn1 = OpenSSL::ASN1::Sequence([
|
|
823
|
+
OpenSSL::ASN1::Integer(1),
|
|
824
|
+
OpenSSL::ASN1::OctetString(digest),
|
|
825
|
+
parameters,
|
|
826
|
+
public_key_bitstring
|
|
827
|
+
])
|
|
828
|
+
private_key = OpenSSL::PKey.read(ec_private_key_asn1.to_der)
|
|
829
|
+
|
|
830
|
+
else
|
|
831
|
+
return [nil, "unsupported key codec"]
|
|
510
832
|
end
|
|
833
|
+
return [private_key, nil]
|
|
834
|
+
|
|
511
835
|
end
|
|
512
836
|
|
|
513
837
|
def self.decode_public_key(key_encoded)
|
|
514
838
|
begin
|
|
515
|
-
|
|
839
|
+
pubkey = multi_decode(key_encoded).first
|
|
840
|
+
if pubkey.bytes.length == 34
|
|
841
|
+
code = pubkey.bytes.first
|
|
842
|
+
digest = pubkey[-32..]
|
|
843
|
+
else
|
|
844
|
+
if pubkey.start_with?("\x80\x24".dup.force_encoding('ASCII-8BIT'))
|
|
845
|
+
code = 4608 # Bytes 0x80 0x24 sind das Varint-Encoding des Multicodec-Codes 0x1200 (p256-pub)
|
|
846
|
+
# 4608 == Oydid.read_varint("\x80$") oder "\x80\x24".force_encoding('ASCII-8BIT')
|
|
847
|
+
else
|
|
848
|
+
code = pubkey.unpack('n').first
|
|
849
|
+
end
|
|
850
|
+
digest = pubkey[-1*(pubkey.bytes.length-2)..]
|
|
851
|
+
end
|
|
516
852
|
case Multicodecs[code].name
|
|
517
853
|
when 'ed25519-pub'
|
|
518
854
|
verify_key = Ed25519::VerifyKey.new(digest)
|
|
519
855
|
return [verify_key, ""]
|
|
520
856
|
when 'x25519-pub'
|
|
521
857
|
pub_key = RbNaCl::PublicKey.new(digest)
|
|
858
|
+
return [pub_key, ""]
|
|
859
|
+
when 'p256-pub'
|
|
860
|
+
asn1_public_key = OpenSSL::ASN1::Sequence.new([
|
|
861
|
+
OpenSSL::ASN1::Sequence.new([
|
|
862
|
+
OpenSSL::ASN1::ObjectId.new('id-ecPublicKey'),
|
|
863
|
+
OpenSSL::ASN1::ObjectId.new('prime256v1')
|
|
864
|
+
]),
|
|
865
|
+
OpenSSL::ASN1::BitString.new(digest)
|
|
866
|
+
])
|
|
867
|
+
pub_key = OpenSSL::PKey::EC.new(asn1_public_key.to_der)
|
|
868
|
+
|
|
522
869
|
return [pub_key, ""]
|
|
523
870
|
else
|
|
524
871
|
return [nil, "unsupported key codec"]
|
|
@@ -528,6 +875,135 @@ class Oydid
|
|
|
528
875
|
end
|
|
529
876
|
end
|
|
530
877
|
|
|
878
|
+
def self.private_key_to_jwk(private_key)
|
|
879
|
+
code, length, digest = multi_decode(private_key).first.unpack('SCa*')
|
|
880
|
+
case Multicodecs[code].name
|
|
881
|
+
when 'ed25519-priv'
|
|
882
|
+
return [nil, "not supported yet"]
|
|
883
|
+
when 'p256-priv'
|
|
884
|
+
group = OpenSSL::PKey::EC::Group.new('prime256v1')
|
|
885
|
+
public_key = group.generator.mul(OpenSSL::BN.new(digest, 2))
|
|
886
|
+
point = public_key.to_bn.to_s(2)
|
|
887
|
+
|
|
888
|
+
x_bin = point[1, 32]
|
|
889
|
+
y_bin = point[33, 32]
|
|
890
|
+
x = Base64.urlsafe_encode64(x_bin, padding: false)
|
|
891
|
+
y = Base64.urlsafe_encode64(y_bin, padding: false)
|
|
892
|
+
d = Base64.urlsafe_encode64(digest, padding: false)
|
|
893
|
+
|
|
894
|
+
jwk = {
|
|
895
|
+
kty: "EC",
|
|
896
|
+
crv: "P-256",
|
|
897
|
+
x: x,
|
|
898
|
+
y: y,
|
|
899
|
+
d: d
|
|
900
|
+
}
|
|
901
|
+
return [jwk, ""]
|
|
902
|
+
else
|
|
903
|
+
return [nil, "unsupported key codec"]
|
|
904
|
+
end
|
|
905
|
+
end
|
|
906
|
+
|
|
907
|
+
def self.public_key_to_jwk(public_key)
|
|
908
|
+
begin
|
|
909
|
+
pubkey = multi_decode(public_key).first
|
|
910
|
+
if pubkey.bytes.length == 34
|
|
911
|
+
code = pubkey.bytes.first
|
|
912
|
+
digest = pubkey[-32..]
|
|
913
|
+
else
|
|
914
|
+
if pubkey.start_with?("\x80\x24".dup.force_encoding('ASCII-8BIT'))
|
|
915
|
+
code = 4608 # Bytes 0x80 0x24 sind das Varint-Encoding des Multicodec-Codes 0x1200 (p256-pub)
|
|
916
|
+
# 4608 == Oydid.read_varint("\x80$") oder "\x80\x24".force_encoding('ASCII-8BIT')
|
|
917
|
+
else
|
|
918
|
+
code = pubkey.unpack('n').first
|
|
919
|
+
end
|
|
920
|
+
digest = pubkey[-1*(pubkey.bytes.length-2)..]
|
|
921
|
+
end
|
|
922
|
+
case Multicodecs[code].name
|
|
923
|
+
when 'ed25519-pub'
|
|
924
|
+
return [nil, "not supported yet"]
|
|
925
|
+
when 'p256-pub'
|
|
926
|
+
if digest.bytes.first == 4
|
|
927
|
+
# Unkomprimiertes Format: X (32 Bytes) || Y (32 Bytes)
|
|
928
|
+
x_coord = digest[1..32]
|
|
929
|
+
y_coord = digest[33..64]
|
|
930
|
+
x_base64 = Base64.urlsafe_encode64(x_coord, padding: false)
|
|
931
|
+
y_base64 = Base64.urlsafe_encode64(y_coord, padding: false)
|
|
932
|
+
else
|
|
933
|
+
asn1_public_key = OpenSSL::ASN1::Sequence.new([
|
|
934
|
+
OpenSSL::ASN1::Sequence.new([
|
|
935
|
+
OpenSSL::ASN1::ObjectId.new('id-ecPublicKey'),
|
|
936
|
+
OpenSSL::ASN1::ObjectId.new('prime256v1')
|
|
937
|
+
]),
|
|
938
|
+
OpenSSL::ASN1::BitString.new(digest)
|
|
939
|
+
])
|
|
940
|
+
key = OpenSSL::PKey::EC.new(asn1_public_key.to_der)
|
|
941
|
+
x, y = key.public_key.to_octet_string(:uncompressed)[1..].unpack1('H*').scan(/.{64}/)
|
|
942
|
+
x_base64 = Base64.urlsafe_encode64([x].pack('H*'), padding: false)
|
|
943
|
+
y_base64 = Base64.urlsafe_encode64([y].pack('H*'), padding: false)
|
|
944
|
+
end
|
|
945
|
+
jwk = {
|
|
946
|
+
"kty" => "EC",
|
|
947
|
+
"crv" => "P-256",
|
|
948
|
+
"x" => x_base64,
|
|
949
|
+
"y" => y_base64 }
|
|
950
|
+
return [jwk, ""]
|
|
951
|
+
else
|
|
952
|
+
return [nil, "unsupported key codec"]
|
|
953
|
+
end
|
|
954
|
+
rescue
|
|
955
|
+
return [nil, "unknown key codec"]
|
|
956
|
+
end
|
|
957
|
+
end
|
|
958
|
+
|
|
959
|
+
def self.base64_url_decode(str)
|
|
960
|
+
Base64.urlsafe_decode64(str + '=' * (4 - str.length % 4))
|
|
961
|
+
end
|
|
962
|
+
|
|
963
|
+
def self.public_key_from_jwk(jwk, options = {})
|
|
964
|
+
begin
|
|
965
|
+
if jwk.is_a?(String)
|
|
966
|
+
jwk = JSON.parse(jwk)
|
|
967
|
+
end
|
|
968
|
+
rescue
|
|
969
|
+
return [nil, "invalid input"]
|
|
970
|
+
end
|
|
971
|
+
jwk = jwk.transform_keys(&:to_s)
|
|
972
|
+
if jwk["kty"] == "EC" && jwk["crv"] == "P-256"
|
|
973
|
+
x = base64_url_decode(jwk["x"])
|
|
974
|
+
y = base64_url_decode(jwk["y"])
|
|
975
|
+
digest = OpenSSL::ASN1::BitString.new(OpenSSL::BN.new("\x04" + x + y, 2).to_s(2))
|
|
976
|
+
encoded = multi_encode(
|
|
977
|
+
Multibases::DecodedByteArray.new(
|
|
978
|
+
(to_varint(0x1200) << digest.value.bytes)
|
|
979
|
+
.flatten).to_s(Encoding::BINARY),
|
|
980
|
+
options)
|
|
981
|
+
|
|
982
|
+
# asn1_public_key = OpenSSL::ASN1::Sequence.new([
|
|
983
|
+
# OpenSSL::ASN1::Sequence.new([
|
|
984
|
+
# OpenSSL::ASN1::ObjectId.new('id-ecPublicKey'),
|
|
985
|
+
# OpenSSL::ASN1::ObjectId.new('prime256v1')
|
|
986
|
+
# ]),
|
|
987
|
+
# OpenSSL::ASN1::BitString.new(OpenSSL::BN.new("\x04" + x + y, 2).to_s(2))
|
|
988
|
+
# ])
|
|
989
|
+
# pub_key = OpenSSL::PKey::EC.new(asn1_public_key.to_der)
|
|
990
|
+
# encoded = multi_encode(
|
|
991
|
+
# Multibases::DecodedByteArray.new(
|
|
992
|
+
# (to_varint(0x1200) << pub_key.to_bn.to_s(2).bytes)
|
|
993
|
+
# .flatten).to_s(Encoding::BINARY),
|
|
994
|
+
# options)
|
|
995
|
+
|
|
996
|
+
|
|
997
|
+
if encoded.first.nil?
|
|
998
|
+
return [nil, encoded.last]
|
|
999
|
+
else
|
|
1000
|
+
return [encoded.first, ""]
|
|
1001
|
+
end
|
|
1002
|
+
else
|
|
1003
|
+
return [nil, "unsupported key codec"]
|
|
1004
|
+
end
|
|
1005
|
+
end
|
|
1006
|
+
|
|
531
1007
|
# storage functions -----------------------------
|
|
532
1008
|
def self.write_private_storage(payload, filename)
|
|
533
1009
|
File.open(filename, 'w') {|f| f.write(payload)}
|