oydid 0.5.4 → 0.5.5
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/VERSION +1 -1
- data/lib/oydid/basic.rb +80 -4
- data/lib/oydid/didcomm.rb +2 -2
- data/lib/oydid/log.rb +144 -39
- data/lib/oydid/vc.rb +4 -2
- data/lib/oydid.rb +417 -179
- metadata +5 -5
data/lib/oydid.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require '
|
4
|
+
require 'simple_dag'
|
5
5
|
require 'jwt'
|
6
6
|
require 'rbnacl'
|
7
7
|
require 'ed25519'
|
@@ -27,6 +27,11 @@ class Oydid
|
|
27
27
|
|
28
28
|
# expected DID format: did:oyd:123
|
29
29
|
def self.read(did, options)
|
30
|
+
|
31
|
+
if did.to_s == ""
|
32
|
+
return [nil, "missing DID"]
|
33
|
+
end
|
34
|
+
|
30
35
|
# setup
|
31
36
|
currentDID = {
|
32
37
|
"did": did,
|
@@ -115,15 +120,12 @@ class Oydid
|
|
115
120
|
if options[:trace]
|
116
121
|
puts " .. DAG with " + dag.vertices.length.to_s + " vertices and " + dag.edges.length.to_s + " edges, CREATE index: " + create_index.to_s
|
117
122
|
end
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
if options[:trace]
|
122
|
-
if options[:silent].nil? || !options[:silent]
|
123
|
-
puts " vertex " + terminate_index.to_s + " at " + log_array[terminate_index]["ts"].to_s + " op: " + log_array[terminate_index]["op"].to_s + " doc: " + log_array[terminate_index]["doc"].to_s
|
124
|
-
end
|
125
|
-
end
|
123
|
+
|
124
|
+
result = dag2array(dag, log_array, create_index, [], options)
|
125
|
+
ordered_log_array = dag2array_terminate(dag, log_array, terminate_index, result, options)
|
126
126
|
currentDID["log"] = ordered_log_array
|
127
|
+
# !!! ugly hack to get access to all delegation keys required in dag_update
|
128
|
+
currentDID["full_log"] = log_array
|
127
129
|
if options[:trace]
|
128
130
|
if options[:silent].nil? || !options[:silent]
|
129
131
|
dag.edges.each do |e|
|
@@ -150,7 +152,7 @@ class Oydid
|
|
150
152
|
end
|
151
153
|
|
152
154
|
def self.simulate_did(content, did, mode, options)
|
153
|
-
did_doc, did_key, did_log, msg =
|
155
|
+
did_doc, did_key, did_log, msg = generate_base(content, did, mode, options)
|
154
156
|
user_did = did_doc[:did]
|
155
157
|
return [user_did, msg]
|
156
158
|
end
|
@@ -169,50 +171,38 @@ class Oydid
|
|
169
171
|
revoc_log = nil
|
170
172
|
doc_location = options[:location]
|
171
173
|
if options[:ts].nil?
|
172
|
-
ts = Time.now.to_i
|
174
|
+
ts = Time.now.utc.to_i
|
173
175
|
else
|
174
176
|
ts = options[:ts]
|
175
177
|
end
|
176
178
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
else
|
186
|
-
privateKey, msg = read_private_key(options[:doc_key].to_s, options)
|
187
|
-
if privateKey.nil?
|
188
|
-
return [nil, nil, nil, "private document key not found"]
|
189
|
-
end
|
179
|
+
# key management
|
180
|
+
tmp_did_hash = did.delete_prefix("did:oyd:") rescue ""
|
181
|
+
tmp_did10 = tmp_did_hash[0,10] + "_private_key.enc" rescue ""
|
182
|
+
privateKey, msg = getPrivateKey(options[:doc_enc], options[:doc_pwd], options[:doc_key], tmp_did10, options)
|
183
|
+
if privateKey.nil?
|
184
|
+
privateKey, msg = generate_private_key("", 'ed25519-priv', options)
|
185
|
+
if privateKey.nil?
|
186
|
+
return [nil, nil, nil, "private document key not found"]
|
190
187
|
end
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
revocationKey, msg = read_private_key(options[:rev_key].to_s, options)
|
199
|
-
if revocationKey.nil?
|
200
|
-
return [nil, nil, nil, "private revocation key not found"]
|
201
|
-
end
|
188
|
+
end
|
189
|
+
tmp_did10 = tmp_did_hash[0,10] + "_revocation_key.enc" rescue ""
|
190
|
+
revocationKey, msg = getPrivateKey(options[:rev_enc], options[:rev_pwd], options[:rev_key], tmp_did10, options)
|
191
|
+
if revocationKey.nil?
|
192
|
+
revocationKey, msg = generate_private_key("", 'ed25519-priv', options)
|
193
|
+
if revocationKey.nil?
|
194
|
+
return [nil, nil, nil, "private revocation key not found"]
|
202
195
|
end
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
did_info, msg = read(did, options)
|
212
|
-
options[:location] = options[:location]
|
213
|
-
options[:doc_location] = options[:doc_location]
|
214
|
-
options[:log_location] = options[:log_location]
|
196
|
+
end
|
197
|
+
|
198
|
+
# mode-specific handling
|
199
|
+
if mode == "create" || mode == "clone"
|
200
|
+
operation_mode = 2 # CREATE
|
201
|
+
|
202
|
+
else # mode == "update" => read information first
|
203
|
+
operation_mode = 3 # UPDATE
|
215
204
|
|
205
|
+
did_info, msg = read(did, options)
|
216
206
|
if did_info.nil?
|
217
207
|
return [nil, nil, nil, "cannot resolve DID (on updating DID)"]
|
218
208
|
end
|
@@ -230,90 +220,64 @@ class Oydid
|
|
230
220
|
doc_location = hash_split[1]
|
231
221
|
end
|
232
222
|
end
|
233
|
-
operation_mode = 3 # UPDATE
|
234
|
-
|
235
|
-
# collect relevant information from previous did
|
236
223
|
did_old = did.dup
|
237
224
|
did10_old = did10.dup
|
238
225
|
log_old = did_info["log"]
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
226
|
+
|
227
|
+
# check if provided old keys are native DID keys or delegates ==================
|
228
|
+
tmp_old_did10 = did10_old + "_private_key.enc" rescue ""
|
229
|
+
old_privateKey, msg = getPrivateKey(options[:old_doc_enc], options[:old_doc_pwd], options[:old_doc_key], tmp_old_did10, options)
|
230
|
+
tmp_old_did10 = did10_old + "_revocation_key.enc" rescue ""
|
231
|
+
old_revocationKey, msg = getPrivateKey(options[:old_rev_enc], options[:old_rev_pwd], options[:old_rev_key], tmp_did10, options)
|
232
|
+
old_publicDocKey = public_key(old_privateKey, {}).first
|
233
|
+
old_publicRevKey = public_key(old_revocationKey, {}).first
|
234
|
+
old_did_key = old_publicDocKey + ":" + old_publicRevKey
|
235
|
+
|
236
|
+
# compare old keys with existing DID Document & generate revocation record
|
237
|
+
if old_did_key.to_s == did_info["doc"]["key"].to_s
|
238
|
+
# provided keys are native DID keys ------------------
|
239
|
+
|
240
|
+
# re-build revocation document
|
241
|
+
old_did_doc = did_info["doc"]["doc"]
|
242
|
+
old_ts = did_info["log"].last["ts"]
|
243
|
+
old_subDid = {"doc": old_did_doc, "key": old_did_key}.to_json
|
244
|
+
old_subDidHash = multi_hash(canonical(old_subDid), LOG_HASH_OPTIONS).first
|
245
|
+
old_signedSubDidHash = sign(old_subDidHash, old_revocationKey, LOG_HASH_OPTIONS).first
|
246
|
+
revocationLog = {
|
247
|
+
"ts": old_ts,
|
248
|
+
"op": 1, # REVOKE
|
249
|
+
"doc": old_subDidHash,
|
250
|
+
"sig": old_signedSubDidHash }.transform_keys(&:to_s).to_json
|
249
251
|
else
|
250
|
-
|
251
|
-
|
252
|
+
# proviced keys are either delegates or invalid ------
|
253
|
+
# * check validity of key-doc delegate
|
254
|
+
pubKeys, msg = getDelegatedPubKeysFromDID(did, "doc")
|
255
|
+
if !pubKeys.include?(old_publicDocKey)
|
256
|
+
return [nil, nil, nil, "invalid or missing old private document key"]
|
257
|
+
end
|
252
258
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
259
|
+
# * check validity of key-rev delegate
|
260
|
+
pubKeys, msg = getDelegatedPubKeysFromDID(did, "rev")
|
261
|
+
if !pubKeys.include?(old_publicRevKey)
|
262
|
+
return [nil, nil, nil, "invalid or missing old private revocation key"]
|
263
|
+
end
|
264
|
+
|
265
|
+
# retrieve revocationLog from previous in key-rev delegate
|
266
|
+
revoc_log = nil
|
267
|
+
log_old.each do |item|
|
268
|
+
if !item["encrypted-revocation-log"].nil?
|
269
|
+
revoc_log = item["encrypted-revocation-log"]
|
262
270
|
end
|
263
|
-
else
|
264
|
-
revocationKey_old, msg = decode_private_key(options[:old_rev_enc].to_s, options)
|
265
271
|
end
|
266
|
-
|
267
|
-
|
268
|
-
end
|
269
|
-
if revocationKey_old.nil?
|
270
|
-
return [nil, nil, nil, "invalid or missing old private revocation key"]
|
271
|
-
end
|
272
|
-
# key management
|
273
|
-
if options[:doc_key].nil?
|
274
|
-
if options[:doc_enc].nil?
|
275
|
-
privateKey, msg = generate_private_key(options[:doc_pwd].to_s, 'ed25519-priv', options)
|
276
|
-
else
|
277
|
-
privateKey, msg = decode_private_key(options[:doc_enc].to_s, options)
|
272
|
+
if revoc_log.nil?
|
273
|
+
return [nil, nil, nil, "cannot retrieve revocation log"]
|
278
274
|
end
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
# if options[:rev_key].nil? && options[:rev_pwd].nil? && options[:rev_enc].nil?
|
283
|
-
# revocationLog = read_private_storage(did10 + "_revocation.json")
|
284
|
-
# if revocationLog.nil?
|
285
|
-
# return [nil, nil, nil, "invalid or missing old revocation log"]
|
286
|
-
# end
|
287
|
-
# else
|
288
|
-
if options[:rev_key].nil?
|
289
|
-
if options[:rev_enc].nil?
|
290
|
-
if options[:rev_pwd].nil?
|
291
|
-
revocationKey, msg = generate_private_key("", 'ed25519-priv', options)
|
292
|
-
else
|
293
|
-
revocationKey, msg = generate_private_key(options[:rev_pwd].to_s, 'ed25519-priv', options)
|
294
|
-
end
|
295
|
-
else
|
296
|
-
revocationKey, msg = decode_private_key(options[:rev_enc].to_s, options)
|
297
|
-
end
|
298
|
-
else
|
299
|
-
revocationKey, msg = read_private_key(options[:rev_key].to_s, options)
|
275
|
+
revocationLog, msg = decrypt(revoc_log.to_json, old_revocationKey.to_s)
|
276
|
+
if revocationLog.nil?
|
277
|
+
return [nil, nil, nil, "cannot decrypt revocation log entry: " + msg]
|
300
278
|
end
|
279
|
+
end # compare old keys with existing DID Document
|
301
280
|
|
302
|
-
# re-build revocation document
|
303
|
-
did_old_doc = did_info["doc"]["doc"]
|
304
|
-
ts_old = did_info["log"].last["ts"]
|
305
|
-
publicKey_old = public_key(privateKey_old, options).first
|
306
|
-
pubRevoKey_old = public_key(revocationKey_old, options).first
|
307
|
-
did_key_old = publicKey_old + ":" + pubRevoKey_old
|
308
|
-
subDid = {"doc": did_old_doc, "key": did_key_old}.to_json
|
309
|
-
subDidHash = multi_hash(canonical(subDid), LOG_HASH_OPTIONS).first
|
310
|
-
signedSubDidHash = sign(subDidHash, revocationKey_old, options).first
|
311
|
-
revocationLog = {
|
312
|
-
"ts": ts_old,
|
313
|
-
"op": 1, # REVOKE
|
314
|
-
"doc": subDidHash,
|
315
|
-
"sig": signedSubDidHash }.transform_keys(&:to_s).to_json
|
316
|
-
# end
|
317
281
|
revoc_log = JSON.parse(revocationLog)
|
318
282
|
revoc_log["previous"] = [
|
319
283
|
multi_hash(canonical(log_old[did_info["doc_log_id"].to_i]), LOG_HASH_OPTIONS).first,
|
@@ -325,13 +289,6 @@ class Oydid
|
|
325
289
|
pubRevoKey = public_key(revocationKey, options).first
|
326
290
|
did_key = publicKey + ":" + pubRevoKey
|
327
291
|
|
328
|
-
# check if pubKeys matches with existing DID Document
|
329
|
-
if mode == "update"
|
330
|
-
if did_key_old.to_s != did_info["doc"]["key"].to_s
|
331
|
-
return [nil, nil, nil, "keys from original DID don't match"]
|
332
|
-
end
|
333
|
-
end
|
334
|
-
|
335
292
|
# build new revocation document
|
336
293
|
subDid = {"doc": did_doc, "key": did_key}.to_json
|
337
294
|
retVal = multi_hash(canonical(subDid), LOG_HASH_OPTIONS)
|
@@ -350,11 +307,16 @@ class Oydid
|
|
350
307
|
if !doc_location.nil?
|
351
308
|
l2_doc += LOCATION_PREFIX + doc_location.to_s
|
352
309
|
end
|
310
|
+
if options[:confirm_logs].nil?
|
311
|
+
previous_array = []
|
312
|
+
else
|
313
|
+
previous_array = options[:confirm_logs]
|
314
|
+
end
|
353
315
|
l2 = { "ts": ts,
|
354
316
|
"op": 0, # TERMINATE
|
355
317
|
"doc": l2_doc,
|
356
318
|
"sig": sign(l2_doc, privateKey, options).first,
|
357
|
-
"previous":
|
319
|
+
"previous": previous_array }.transform_keys(&:to_s)
|
358
320
|
|
359
321
|
# build actual DID document
|
360
322
|
log_str = multi_hash(canonical(l2), LOG_HASH_OPTIONS).first
|
@@ -389,13 +351,27 @@ class Oydid
|
|
389
351
|
end
|
390
352
|
|
391
353
|
# build creation log entry
|
354
|
+
log_revoke_encrypted_array = nil
|
392
355
|
if operation_mode == 3 # UPDATE
|
393
356
|
l1 = { "ts": ts,
|
394
357
|
"op": operation_mode, # UPDATE
|
395
358
|
"doc": l1_doc,
|
396
|
-
"sig": sign(l1_doc,
|
359
|
+
"sig": sign(l1_doc, old_privateKey, options).first,
|
397
360
|
"previous": prev_hash }.transform_keys(&:to_s)
|
398
|
-
|
361
|
+
options[:confirm_logs].each do |el|
|
362
|
+
# read each log entry to check if it is a revocation delegation
|
363
|
+
log_item, msg = retrieve_log_item(el, doc_location, options)
|
364
|
+
if log_item["doc"][0..3] == "rev:"
|
365
|
+
cipher, msg = encrypt(r1.to_json, log_item["encryption-key"], {})
|
366
|
+
cipher[:log] = el.to_s
|
367
|
+
if log_revoke_encrypted_array.nil?
|
368
|
+
log_revoke_encrypted_array = [cipher]
|
369
|
+
else
|
370
|
+
log_revoke_encrypted_array << cipher
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end unless options[:confirm_logs].nil?
|
374
|
+
else
|
399
375
|
l1 = { "ts": ts,
|
400
376
|
"op": operation_mode, # CREATE
|
401
377
|
"doc": l1_doc,
|
@@ -407,7 +383,6 @@ class Oydid
|
|
407
383
|
# did_doc = [did, didDocument, did_old]
|
408
384
|
# did_log = [revoc_log, l1, l2, r1, log_old]
|
409
385
|
# did_key = [privateKey, revocationKey]
|
410
|
-
|
411
386
|
did_doc = {
|
412
387
|
:did => did,
|
413
388
|
:didDocument => didDocument,
|
@@ -420,6 +395,10 @@ class Oydid
|
|
420
395
|
:r1 => r1,
|
421
396
|
:log_old => log_old
|
422
397
|
}
|
398
|
+
if !log_revoke_encrypted_array.nil?
|
399
|
+
did_log[:r1_encrypted] = log_revoke_encrypted_array
|
400
|
+
end
|
401
|
+
|
423
402
|
did_key = {
|
424
403
|
:privateKey => privateKey,
|
425
404
|
:revocationKey => revocationKey
|
@@ -482,6 +461,7 @@ class Oydid
|
|
482
461
|
l1 = did_log[:l1]
|
483
462
|
l2 = did_log[:l2]
|
484
463
|
r1 = did_log[:r1]
|
464
|
+
r1_encrypted = did_log[:r1_encrypted]
|
485
465
|
log_old = did_log[:log_old]
|
486
466
|
privateKey = did_key[:privateKey]
|
487
467
|
revocationKey = did_key[:revocationKey]
|
@@ -504,7 +484,7 @@ class Oydid
|
|
504
484
|
end
|
505
485
|
case doc_location.to_s
|
506
486
|
when /^http/
|
507
|
-
logs = [revoc_log, l1, l2].flatten.compact
|
487
|
+
logs = [revoc_log, l1, l2, r1_encrypted].flatten.compact
|
508
488
|
else
|
509
489
|
logs = [log_old, revoc_log, l1, l2].flatten.compact
|
510
490
|
if !did_old.nil?
|
@@ -540,11 +520,88 @@ class Oydid
|
|
540
520
|
end
|
541
521
|
end
|
542
522
|
|
523
|
+
def self.write_log(did, log, options = {})
|
524
|
+
# validate log
|
525
|
+
if !log.is_a?(Hash)
|
526
|
+
return [nil, "invalid log input"]
|
527
|
+
end
|
528
|
+
log = log.transform_keys(&:to_s)
|
529
|
+
if log["ts"].nil?
|
530
|
+
return [nil, "missing timestamp in log"]
|
531
|
+
end
|
532
|
+
if log["op"].nil?
|
533
|
+
return [nil, "missing operation in log"]
|
534
|
+
end
|
535
|
+
if log["doc"].nil?
|
536
|
+
return [nil, "missing doc entry in log"]
|
537
|
+
end
|
538
|
+
if log["sig"].nil?
|
539
|
+
return [nil, "missing signature in log"]
|
540
|
+
end
|
541
|
+
|
542
|
+
# validate did
|
543
|
+
if did.include?(LOCATION_PREFIX)
|
544
|
+
tmp = did.split(LOCATION_PREFIX)
|
545
|
+
did = tmp[0]
|
546
|
+
source_location = tmp[1]
|
547
|
+
log_location = tmp[1]
|
548
|
+
end
|
549
|
+
if did.include?(CGI.escape LOCATION_PREFIX)
|
550
|
+
tmp = did.split(CGI.escape LOCATION_PREFIX)
|
551
|
+
did = tmp[0]
|
552
|
+
source_location = tmp[1]
|
553
|
+
log_location = tmp[1]
|
554
|
+
end
|
555
|
+
|
556
|
+
if source_location.to_s == ""
|
557
|
+
if options[:doc_location].nil?
|
558
|
+
source_location = DEFAULT_LOCATION
|
559
|
+
else
|
560
|
+
source_location = options[:doc_location]
|
561
|
+
end
|
562
|
+
if options[:log_location].nil?
|
563
|
+
log_location = DEFAULT_LOCATION
|
564
|
+
else
|
565
|
+
log_location = options[:log_location]
|
566
|
+
end
|
567
|
+
end
|
568
|
+
options[:doc_location] = source_location
|
569
|
+
options[:log_location] = log_location
|
570
|
+
source_did, msg = read(did, options)
|
571
|
+
if source_did.nil?
|
572
|
+
return [nil, "cannot resolve DID (on writing logs)"]
|
573
|
+
end
|
574
|
+
if source_did["error"] != 0
|
575
|
+
return [nil, source_did["message"].to_s]
|
576
|
+
end
|
577
|
+
if source_did["doc_log_id"].nil?
|
578
|
+
return [nil, "cannot parse DID log"]
|
579
|
+
end
|
580
|
+
|
581
|
+
# write log
|
582
|
+
source_location = source_location.gsub("%3A",":")
|
583
|
+
source_location = source_location.gsub("%2F%2F","//")
|
584
|
+
retVal = HTTParty.post(source_location + "/log/" + did,
|
585
|
+
headers: { 'Content-Type' => 'application/json' },
|
586
|
+
body: {"log": log}.to_json )
|
587
|
+
code = retVal.code rescue 500
|
588
|
+
if code != 200
|
589
|
+
err_msg = retVal.parsed_response["error"].to_s rescue "invalid response from " + source_location.to_s + "/log"
|
590
|
+
return ["", err_msg]
|
591
|
+
end
|
592
|
+
log_hash = retVal.parsed_response["log"] rescue ""
|
593
|
+
if log_hash == ""
|
594
|
+
err_msg = "missing log hash from " + source_location.to_s + "/log"
|
595
|
+
return ["", err_msg]
|
596
|
+
end
|
597
|
+
return [log_hash, nil]
|
598
|
+
end
|
599
|
+
|
543
600
|
def self.revoke_base(did, options)
|
544
601
|
did_orig = did.dup
|
545
602
|
doc_location = options[:doc_location]
|
546
603
|
if options[:ts].nil?
|
547
|
-
ts = Time.now.to_i
|
604
|
+
ts = Time.now.utc.to_i
|
548
605
|
else
|
549
606
|
ts = options[:ts]
|
550
607
|
end
|
@@ -606,38 +663,111 @@ class Oydid
|
|
606
663
|
end
|
607
664
|
|
608
665
|
if options[:rev_key].nil? && options[:rev_pwd].nil? && options[:rev_enc].nil?
|
609
|
-
revocationKey, msg = read_private_key(did10 + "_revocation_key.enc", options)
|
666
|
+
# revocationKey, msg = read_private_key(did10 + "_revocation_key.enc", options)
|
610
667
|
revocationLog = read_private_storage(did10 + "_revocation.json")
|
611
668
|
else
|
612
|
-
|
669
|
+
|
670
|
+
# check if provided old keys are native DID keys or delegates ==================
|
671
|
+
if options[:doc_key].nil?
|
672
|
+
if options[:doc_enc].nil?
|
673
|
+
old_privateKey, msg = generate_private_key(options[:old_doc_pwd].to_s, 'ed25519-priv', options)
|
674
|
+
else
|
675
|
+
old_privateKey, msg = decode_private_key(options[:old_doc_enc].to_s, options)
|
676
|
+
end
|
677
|
+
else
|
678
|
+
old_privateKey, msg = read_private_key(options[:old_doc_key].to_s, options)
|
679
|
+
end
|
680
|
+
if options[:rev_key].nil?
|
613
681
|
if options[:rev_enc].nil?
|
614
|
-
|
682
|
+
old_revocationKey, msg = generate_private_key(options[:old_rev_pwd].to_s, 'ed25519-priv', options)
|
615
683
|
else
|
616
|
-
|
684
|
+
old_revocationKey, msg = decode_private_key(options[:old_rev_enc].to_s, options)
|
617
685
|
end
|
618
686
|
else
|
619
|
-
|
620
|
-
end
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
"ts"
|
632
|
-
"
|
633
|
-
|
634
|
-
|
687
|
+
old_revocationKey, msg = read_private_key(options[:old_rev_key].to_s, options)
|
688
|
+
end
|
689
|
+
old_publicDocKey = public_key(old_privateKey, {}).first
|
690
|
+
old_publicRevKey = public_key(old_revocationKey, {}).first
|
691
|
+
old_did_key = old_publicDocKey + ":" + old_publicRevKey
|
692
|
+
|
693
|
+
# compare old keys with existing DID Document & generate revocation record
|
694
|
+
if old_did_key.to_s == did_info["doc"]["key"].to_s
|
695
|
+
# provided keys are native DID keys ------------------
|
696
|
+
|
697
|
+
# re-build revocation document
|
698
|
+
old_did_doc = did_info["doc"]["doc"]
|
699
|
+
old_ts = did_info["log"].last["ts"]
|
700
|
+
old_subDid = {"doc": old_did_doc, "key": old_did_key}.to_json
|
701
|
+
old_subDidHash = multi_hash(canonical(old_subDid), LOG_HASH_OPTIONS).first
|
702
|
+
old_signedSubDidHash = sign(old_subDidHash, old_revocationKey, LOG_HASH_OPTIONS).first
|
703
|
+
revocationLog = {
|
704
|
+
"ts": old_ts,
|
705
|
+
"op": 1, # REVOKE
|
706
|
+
"doc": old_subDidHash,
|
707
|
+
"sig": old_signedSubDidHash }.transform_keys(&:to_s).to_json
|
708
|
+
else
|
709
|
+
# proviced keys are either delegates or invalid ------
|
710
|
+
# * check validity of key-doc delegate
|
711
|
+
pubKeys, msg = getDelegatedPubKeysFromDID(did, "doc")
|
712
|
+
if !pubKeys.include?(old_publicDocKey)
|
713
|
+
return [nil, "invalid or missing private document key"]
|
714
|
+
end
|
715
|
+
|
716
|
+
# * check validity of key-rev delegate
|
717
|
+
pubKeys, msg = getDelegatedPubKeysFromDID(did, "rev")
|
718
|
+
if !pubKeys.include?(old_publicRevKey)
|
719
|
+
return [nil, "invalid or missing private revocation key"]
|
720
|
+
end
|
721
|
+
|
722
|
+
# retrieve revocationLog from previous in key-rev delegate
|
723
|
+
revoc_log = nil
|
724
|
+
log_old.each do |item|
|
725
|
+
if !item["encrypted-revocation-log"].nil?
|
726
|
+
revoc_log = item["encrypted-revocation-log"]
|
727
|
+
end
|
728
|
+
end
|
729
|
+
if revoc_log.nil?
|
730
|
+
return [nil, "cannot retrieve revocation log"]
|
731
|
+
end
|
732
|
+
revocationLog, msg = decrypt(revoc_log.to_json, old_revocationKey.to_s)
|
733
|
+
if revocationLog.nil?
|
734
|
+
return [nil, "cannot decrypt revocation log entry: " + msg]
|
735
|
+
end
|
736
|
+
end # compare old keys with existing DID Document
|
737
|
+
|
738
|
+
# if options[:rev_pwd].nil?
|
739
|
+
# if options[:rev_enc].nil?
|
740
|
+
# revocationKey, msg = read_private_key(options[:rev_key].to_s, options)
|
741
|
+
# else
|
742
|
+
# revocationKey, msg = decode_private_key(options[:rev_enc].to_s, options)
|
743
|
+
# end
|
744
|
+
# else
|
745
|
+
# revocationKey, msg = generate_private_key(options[:rev_pwd].to_s, 'ed25519-priv', options)
|
746
|
+
# end
|
747
|
+
# # re-build revocation document
|
748
|
+
# did_old_doc = did_info["doc"]["doc"]
|
749
|
+
# ts_old = did_info["log"].last["ts"]
|
750
|
+
# publicKey_old = public_key(privateKey_old, options).first
|
751
|
+
# pubRevoKey_old = public_key(revocationKey_old, options).first
|
752
|
+
# did_key_old = publicKey_old + ":" + pubRevoKey_old
|
753
|
+
# subDid = {"doc": did_old_doc, "key": did_key_old}.to_json
|
754
|
+
# subDidHash = multi_hash(canonical(subDid), LOG_HASH_OPTIONS).first
|
755
|
+
# signedSubDidHash = sign(subDidHash, revocationKey_old, options).first
|
756
|
+
# revocationLog = {
|
757
|
+
# "ts": ts_old,
|
758
|
+
# "op": 1, # REVOKE
|
759
|
+
# "doc": subDidHash,
|
760
|
+
# "sig": signedSubDidHash }.transform_keys(&:to_s).to_json
|
635
761
|
end
|
636
762
|
|
637
763
|
if revocationLog.nil?
|
638
764
|
return [nil, "private revocation key not found"]
|
639
765
|
end
|
640
766
|
|
767
|
+
# check if REVOCATION hash matches hash in TERMINATION
|
768
|
+
if did_info["log"][did_info["termination_log_id"]]["doc"] != multi_hash(canonical(revocationLog), LOG_HASH_OPTIONS).first
|
769
|
+
return [nil, "invalid revocation information"]
|
770
|
+
end
|
641
771
|
revoc_log = JSON.parse(revocationLog)
|
642
772
|
revoc_log["previous"] = [
|
643
773
|
multi_hash(canonical(log_old[did_info["doc_log_id"].to_i]), LOG_HASH_OPTIONS).first,
|
@@ -736,7 +866,67 @@ class Oydid
|
|
736
866
|
return [retVal, msg]
|
737
867
|
end
|
738
868
|
|
739
|
-
|
869
|
+
def self.delegate(did, options)
|
870
|
+
# check location
|
871
|
+
location = options[:doc_location]
|
872
|
+
if location.to_s == ""
|
873
|
+
location = DEFAULT_LOCATION
|
874
|
+
end
|
875
|
+
if did.include?(LOCATION_PREFIX)
|
876
|
+
tmp = did.split(LOCATION_PREFIX)
|
877
|
+
did = tmp[0]
|
878
|
+
location = tmp[1]
|
879
|
+
end
|
880
|
+
if did.include?(CGI.escape LOCATION_PREFIX)
|
881
|
+
tmp = did.split(CGI.escape LOCATION_PREFIX)
|
882
|
+
did = tmp[0]
|
883
|
+
location = tmp[1]
|
884
|
+
end
|
885
|
+
options[:doc_location] = location
|
886
|
+
options[:log_location] = location
|
887
|
+
|
888
|
+
if options[:ts].nil?
|
889
|
+
ts = Time.now.utc.to_i
|
890
|
+
else
|
891
|
+
ts = options[:ts]
|
892
|
+
end
|
893
|
+
|
894
|
+
# build log record
|
895
|
+
log = {}
|
896
|
+
log["ts"] = ts
|
897
|
+
log["op"] = 5 # DELEGATE
|
898
|
+
pwd = false
|
899
|
+
doc_privateKey, msg = getPrivateKey(options[:doc_enc], options[:doc_pwd], options[:doc_key], "", options)
|
900
|
+
rev_privateKey, msg = getPrivateKey(options[:rev_enc], options[:rev_pwd], options[:rev_key], "", options)
|
901
|
+
if !doc_privateKey.nil?
|
902
|
+
pwd="doc"
|
903
|
+
privateKey = doc_privateKey
|
904
|
+
end
|
905
|
+
if !rev_privateKey.nil?
|
906
|
+
pwd="rev"
|
907
|
+
privateKey = rev_privateKey
|
908
|
+
end
|
909
|
+
if !pwd || privateKey.to_s == ""
|
910
|
+
return [nil, "missing or invalid delegate key"]
|
911
|
+
end
|
912
|
+
log["doc"] = pwd + ":" + public_key(privateKey, options).first.to_s
|
913
|
+
log["sig"] = sign(privateKey, privateKey, options).first
|
914
|
+
log["previous"] = [did] # DID in previous cannot be resolved in the DAG but guarantees unique log hash
|
915
|
+
|
916
|
+
# revocation delegate keys need to specify a public key for encrypting the revocation record
|
917
|
+
if pwd == "rev"
|
918
|
+
publicEncryptionKey, msg = public_key(privateKey, {}, 'x25519-pub')
|
919
|
+
log["encryption-key"] = publicEncryptionKey
|
920
|
+
end
|
921
|
+
log_hash, msg = write_log(did, log, options)
|
922
|
+
if log_hash.nil?
|
923
|
+
return [nil, msg]
|
924
|
+
else
|
925
|
+
return [{"log": log_hash}, ""]
|
926
|
+
end
|
927
|
+
end
|
928
|
+
|
929
|
+
def self.w3c(did_info, options)
|
740
930
|
did = percent_encode(did_info["did"])
|
741
931
|
if !did.start_with?("did:oyd:")
|
742
932
|
did = "did:oyd:" + did
|
@@ -745,6 +935,22 @@ class Oydid
|
|
745
935
|
didDoc = did_info.transform_keys(&:to_s)["doc"]
|
746
936
|
pubDocKey = didDoc["key"].split(":")[0] rescue ""
|
747
937
|
pubRevKey = didDoc["key"].split(":")[1] rescue ""
|
938
|
+
delegateDocKeys = getDelegatedPubKeysFromDID(did, "doc").first - [pubDocKey] rescue []
|
939
|
+
if delegateDocKeys.is_a?(String)
|
940
|
+
if delegateDocKeys == pubDocKey
|
941
|
+
delegateDocKeys = nil
|
942
|
+
else
|
943
|
+
delegateDocKeys = [delegateDocKeys]
|
944
|
+
end
|
945
|
+
end
|
946
|
+
delegateRevKeys = getDelegatedPubKeysFromDID(did, "rev").first - [pubRevKey] rescue []
|
947
|
+
if delegateRevKeys.is_a?(String)
|
948
|
+
if delegateRevKeys == pubRevKey
|
949
|
+
delegateRevKeys = nil
|
950
|
+
else
|
951
|
+
delegateRevKeys = [delegateRevKeys]
|
952
|
+
end
|
953
|
+
end
|
748
954
|
|
749
955
|
wd = {}
|
750
956
|
if didDoc["doc"].is_a?(Hash)
|
@@ -752,9 +958,9 @@ class Oydid
|
|
752
958
|
wd["@context"] = ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2020/v1"]
|
753
959
|
else
|
754
960
|
if didDoc["doc"]["@context"].is_a?(Array)
|
755
|
-
wd["@context"] = ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2020/v1"] + didDoc["doc"]["@context"]
|
961
|
+
wd["@context"] = (["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2020/v1"] + didDoc["doc"]["@context"]).uniq
|
756
962
|
else
|
757
|
-
wd["@context"] = ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2020/v1", didDoc["doc"]["@context"]]
|
963
|
+
wd["@context"] = (["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2020/v1", didDoc["doc"]["@context"]]).uniq
|
758
964
|
end
|
759
965
|
didDoc["doc"].delete("@context")
|
760
966
|
end
|
@@ -773,6 +979,36 @@ class Oydid
|
|
773
979
|
"controller": did,
|
774
980
|
"publicKeyMultibase": pubRevKey
|
775
981
|
}]
|
982
|
+
if !delegateDocKeys.nil? && delegateDocKeys.count > 0
|
983
|
+
i = 0
|
984
|
+
wd["capabilityDelegation"] = []
|
985
|
+
delegateDocKeys.each do |key|
|
986
|
+
i += 1
|
987
|
+
delegaton_object = {
|
988
|
+
"id": did + "#key-delegate-doc-" + i.to_s,
|
989
|
+
"type": "Ed25519VerificationKey2020",
|
990
|
+
"controller": did,
|
991
|
+
"publicKeyMultibase": key
|
992
|
+
}
|
993
|
+
wd["capabilityDelegation"] << delegaton_object
|
994
|
+
end
|
995
|
+
end
|
996
|
+
if !delegateRevKeys.nil? && delegateRevKeys.count > 0
|
997
|
+
i = 0
|
998
|
+
if wd["capabilityDelegation"].nil?
|
999
|
+
wd["capabilityDelegation"] = []
|
1000
|
+
end
|
1001
|
+
delegateRevKeys.each do |key|
|
1002
|
+
i += 1
|
1003
|
+
delegaton_object = {
|
1004
|
+
"id": did + "#key-delegate-rev-" + i.to_s,
|
1005
|
+
"type": "Ed25519VerificationKey2020",
|
1006
|
+
"controller": did,
|
1007
|
+
"publicKeyMultibase": key
|
1008
|
+
}
|
1009
|
+
wd["capabilityDelegation"] << delegaton_object
|
1010
|
+
end
|
1011
|
+
end
|
776
1012
|
|
777
1013
|
equivalentIds = []
|
778
1014
|
did_info["log"].each do |log|
|
@@ -807,28 +1043,30 @@ class Oydid
|
|
807
1043
|
else
|
808
1044
|
payload = nil
|
809
1045
|
if didDoc["doc"].is_a?(Hash)
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
1046
|
+
if didDoc["doc"] != {}
|
1047
|
+
didDoc = didDoc["doc"]
|
1048
|
+
if didDoc["authentication"].to_s != ""
|
1049
|
+
wd["authentication"] = didDoc["authentication"]
|
1050
|
+
didDoc.delete("authentication")
|
1051
|
+
end
|
1052
|
+
if didDoc["assertionMethod"].to_s != ""
|
1053
|
+
wd["assertionMethod"] = didDoc["assertionMethod"]
|
1054
|
+
didDoc.delete("assertionMethod")
|
1055
|
+
end
|
1056
|
+
if didDoc["keyAgreement"].to_s != ""
|
1057
|
+
wd["keyAgreement"] = didDoc["keyAgreement"]
|
1058
|
+
didDoc.delete("keyAgreement")
|
1059
|
+
end
|
1060
|
+
if didDoc["capabilityInvocation"].to_s != ""
|
1061
|
+
wd["capabilityInvocation"] = didDoc["capabilityInvocation"]
|
1062
|
+
didDoc.delete("capabilityInvocation")
|
1063
|
+
end
|
1064
|
+
if didDoc["capabilityDelegation"].to_s != ""
|
1065
|
+
wd["capabilityDelegation"] = didDoc["capabilityDelegation"]
|
1066
|
+
didDoc.delete("capabilityDelegation")
|
1067
|
+
end
|
1068
|
+
payload = didDoc
|
830
1069
|
end
|
831
|
-
payload = didDoc
|
832
1070
|
else
|
833
1071
|
payload = didDoc["doc"]
|
834
1072
|
end
|
@@ -858,7 +1096,7 @@ class Oydid
|
|
858
1096
|
end
|
859
1097
|
|
860
1098
|
|
861
|
-
|
1099
|
+
def self.w3c_legacy(did_info, options)
|
862
1100
|
did = did_info["did"]
|
863
1101
|
if !did.start_with?("did:oyd:")
|
864
1102
|
did = "did:oyd:" + did
|