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