oydid 0.5.3 → 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.
data/lib/oydid.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'dag'
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
- ordered_log_array = dag2array(dag, log_array, create_index, [], options)
119
- ordered_log_array << log_array[terminate_index]
120
- currentDID["log"] = ordered_log_array
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 = Oydid.generate_base(content, did, mode, options)
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,38 +171,37 @@ 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
- if mode == "create" || mode == "clone"
178
- operation_mode = 2 # CREATE
179
- if options[:doc_key].nil?
180
- if options[:doc_enc].nil?
181
- privateKey, msg = generate_private_key(options[:doc_pwd].to_s, 'ed25519-priv', options)
182
- else
183
- privateKey, msg = decode_private_key(options[:doc_enc].to_s, options)
184
- end
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
- if options[:rev_key].nil?
192
- if options[:rev_enc].nil?
193
- revocationKey, msg = generate_private_key(options[:rev_pwd].to_s, 'ed25519-priv', options)
194
- else
195
- revocationKey, msg = decode_private_key(options[:rev_enc].to_s, options)
196
- end
197
- else
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
- else # mode == "update" => read information
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
204
+
204
205
  did_info, msg = read(did, options)
205
206
  if did_info.nil?
206
207
  return [nil, nil, nil, "cannot resolve DID (on updating DID)"]
@@ -219,90 +220,64 @@ class Oydid
219
220
  doc_location = hash_split[1]
220
221
  end
221
222
  end
222
- operation_mode = 3 # UPDATE
223
-
224
- # collect relevant information from previous did
225
223
  did_old = did.dup
226
224
  did10_old = did10.dup
227
225
  log_old = did_info["log"]
228
- if options[:old_doc_key].nil?
229
- if options[:old_doc_enc].nil?
230
- if options[:old_doc_pwd].nil?
231
- privateKey_old = read_private_storage(did10_old + "_private_key.enc")
232
- else
233
- privateKey_old, msg = generate_private_key(options[:old_doc_pwd].to_s, 'ed25519-priv', options)
234
- end
235
- else
236
- privateKey_old, msg = decode_private_key(options[:old_doc_enc].to_s, options)
237
- end
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
238
251
  else
239
- privateKey_old, msg = read_private_key(options[:old_doc_key].to_s, options)
240
- end
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
241
258
 
242
- if privateKey_old.nil?
243
- return [nil, nil, nil, "invalid or missing old private document key"]
244
- end
245
- if options[:old_rev_key].nil?
246
- if options[:old_rev_enc].nil?
247
- if options[:old_rev_pwd].nil?
248
- revocationKey_old = read_private_storage(did10_old + "_revocation_key.enc")
249
- else
250
- revocationKey_old, msg = generate_private_key(options[:old_rev_pwd].to_s, 'ed25519-priv', options)
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"]
251
270
  end
252
- else
253
- revocationKey_old, msg = decode_private_key(options[:old_rev_enc].to_s, options)
254
271
  end
255
- else
256
- revocationKey_old, msg = read_private_key(options[:old_rev_key].to_s, options)
257
- end
258
- if revocationKey_old.nil?
259
- return [nil, nil, nil, "invalid or missing old private revocation key"]
260
- end
261
- # key management
262
- if options[:doc_key].nil?
263
- if options[:doc_enc].nil?
264
- privateKey, msg = generate_private_key(options[:doc_pwd].to_s, 'ed25519-priv', options)
265
- else
266
- 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"]
267
274
  end
268
- else
269
- privateKey, msg = read_private_key(options[:doc_key].to_s, options)
270
- end
271
- # if options[:rev_key].nil? && options[:rev_pwd].nil? && options[:rev_enc].nil?
272
- # revocationLog = read_private_storage(did10 + "_revocation.json")
273
- # if revocationLog.nil?
274
- # return [nil, nil, nil, "invalid or missing old revocation log"]
275
- # end
276
- # else
277
- if options[:rev_key].nil?
278
- if options[:rev_enc].nil?
279
- if options[:rev_pwd].nil?
280
- revocationKey, msg = generate_private_key("", 'ed25519-priv', options)
281
- else
282
- revocationKey, msg = generate_private_key(options[:rev_pwd].to_s, 'ed25519-priv', options)
283
- end
284
- else
285
- revocationKey, msg = decode_private_key(options[:rev_enc].to_s, options)
286
- end
287
- else
288
- 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]
289
278
  end
279
+ end # compare old keys with existing DID Document
290
280
 
291
- # re-build revocation document
292
- did_old_doc = did_info["doc"]["doc"]
293
- ts_old = did_info["log"].last["ts"]
294
- publicKey_old = public_key(privateKey_old, options).first
295
- pubRevoKey_old = public_key(revocationKey_old, options).first
296
- did_key_old = publicKey_old + ":" + pubRevoKey_old
297
- subDid = {"doc": did_old_doc, "key": did_key_old}.to_json
298
- subDidHash = multi_hash(canonical(subDid), LOG_HASH_OPTIONS).first
299
- signedSubDidHash = sign(subDidHash, revocationKey_old, options).first
300
- revocationLog = {
301
- "ts": ts_old,
302
- "op": 1, # REVOKE
303
- "doc": subDidHash,
304
- "sig": signedSubDidHash }.transform_keys(&:to_s).to_json
305
- # end
306
281
  revoc_log = JSON.parse(revocationLog)
307
282
  revoc_log["previous"] = [
308
283
  multi_hash(canonical(log_old[did_info["doc_log_id"].to_i]), LOG_HASH_OPTIONS).first,
@@ -332,11 +307,16 @@ class Oydid
332
307
  if !doc_location.nil?
333
308
  l2_doc += LOCATION_PREFIX + doc_location.to_s
334
309
  end
310
+ if options[:confirm_logs].nil?
311
+ previous_array = []
312
+ else
313
+ previous_array = options[:confirm_logs]
314
+ end
335
315
  l2 = { "ts": ts,
336
316
  "op": 0, # TERMINATE
337
317
  "doc": l2_doc,
338
318
  "sig": sign(l2_doc, privateKey, options).first,
339
- "previous": [] }.transform_keys(&:to_s)
319
+ "previous": previous_array }.transform_keys(&:to_s)
340
320
 
341
321
  # build actual DID document
342
322
  log_str = multi_hash(canonical(l2), LOG_HASH_OPTIONS).first
@@ -371,13 +351,27 @@ class Oydid
371
351
  end
372
352
 
373
353
  # build creation log entry
354
+ log_revoke_encrypted_array = nil
374
355
  if operation_mode == 3 # UPDATE
375
356
  l1 = { "ts": ts,
376
357
  "op": operation_mode, # UPDATE
377
358
  "doc": l1_doc,
378
- "sig": sign(l1_doc, privateKey_old, options).first,
359
+ "sig": sign(l1_doc, old_privateKey, options).first,
379
360
  "previous": prev_hash }.transform_keys(&:to_s)
380
- else
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
381
375
  l1 = { "ts": ts,
382
376
  "op": operation_mode, # CREATE
383
377
  "doc": l1_doc,
@@ -389,7 +383,6 @@ class Oydid
389
383
  # did_doc = [did, didDocument, did_old]
390
384
  # did_log = [revoc_log, l1, l2, r1, log_old]
391
385
  # did_key = [privateKey, revocationKey]
392
-
393
386
  did_doc = {
394
387
  :did => did,
395
388
  :didDocument => didDocument,
@@ -402,11 +395,14 @@ class Oydid
402
395
  :r1 => r1,
403
396
  :log_old => log_old
404
397
  }
398
+ if !log_revoke_encrypted_array.nil?
399
+ did_log[:r1_encrypted] = log_revoke_encrypted_array
400
+ end
401
+
405
402
  did_key = {
406
403
  :privateKey => privateKey,
407
404
  :revocationKey => revocationKey
408
405
  }
409
-
410
406
  return [did_doc, did_key, did_log, ""]
411
407
  # return [did, didDocument, revoc_log, l1, l2, r1, privateKey, revocationKey, did_old, log_old, ""]
412
408
  end
@@ -455,6 +451,9 @@ class Oydid
455
451
 
456
452
  def self.write(content, did, mode, options)
457
453
  did_doc, did_key, did_log, msg = generate_base(content, did, mode, options)
454
+ if msg != ""
455
+ return [nil, msg]
456
+ end
458
457
  did = did_doc[:did]
459
458
  didDocument = did_doc[:didDocument]
460
459
  did_old = did_doc[:did_old]
@@ -462,14 +461,12 @@ class Oydid
462
461
  l1 = did_log[:l1]
463
462
  l2 = did_log[:l2]
464
463
  r1 = did_log[:r1]
464
+ r1_encrypted = did_log[:r1_encrypted]
465
465
  log_old = did_log[:log_old]
466
466
  privateKey = did_key[:privateKey]
467
467
  revocationKey = did_key[:revocationKey]
468
468
  # did, didDocument, revoc_log, l1, l2, r1, privateKey, revocationKey, did_old, log_old, msg = generate_base(content, did, mode, options)
469
469
 
470
- if msg != ""
471
- return [nil, msg]
472
- end
473
470
  did_hash = did.delete_prefix("did:oyd:")
474
471
  did10 = did_hash[0,10]
475
472
  did_old_hash = did_old.delete_prefix("did:oyd:") rescue nil
@@ -487,7 +484,7 @@ class Oydid
487
484
  end
488
485
  case doc_location.to_s
489
486
  when /^http/
490
- logs = [revoc_log, l1, l2].flatten.compact
487
+ logs = [revoc_log, l1, l2, r1_encrypted].flatten.compact
491
488
  else
492
489
  logs = [log_old, revoc_log, l1, l2].flatten.compact
493
490
  if !did_old.nil?
@@ -523,11 +520,88 @@ class Oydid
523
520
  end
524
521
  end
525
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
+
526
600
  def self.revoke_base(did, options)
527
601
  did_orig = did.dup
528
602
  doc_location = options[:doc_location]
529
603
  if options[:ts].nil?
530
- ts = Time.now.to_i
604
+ ts = Time.now.utc.to_i
531
605
  else
532
606
  ts = options[:ts]
533
607
  end
@@ -589,38 +663,111 @@ class Oydid
589
663
  end
590
664
 
591
665
  if options[:rev_key].nil? && options[:rev_pwd].nil? && options[:rev_enc].nil?
592
- revocationKey, msg = read_private_key(did10 + "_revocation_key.enc", options)
666
+ # revocationKey, msg = read_private_key(did10 + "_revocation_key.enc", options)
593
667
  revocationLog = read_private_storage(did10 + "_revocation.json")
594
668
  else
595
- if options[:rev_pwd].nil?
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?
596
681
  if options[:rev_enc].nil?
597
- revocationKey, msg = read_private_key(options[:rev_key].to_s, options)
682
+ old_revocationKey, msg = generate_private_key(options[:old_rev_pwd].to_s, 'ed25519-priv', options)
598
683
  else
599
- revocationKey, msg = decode_private_key(options[:rev_enc].to_s, options)
684
+ old_revocationKey, msg = decode_private_key(options[:old_rev_enc].to_s, options)
600
685
  end
601
686
  else
602
- revocationKey, msg = generate_private_key(options[:rev_pwd].to_s, 'ed25519-priv', options)
603
- end
604
- # re-build revocation document
605
- did_old_doc = did_info["doc"]["doc"]
606
- ts_old = did_info["log"].last["ts"]
607
- publicKey_old = public_key(privateKey_old, options).first
608
- pubRevoKey_old = public_key(revocationKey_old, options).first
609
- did_key_old = publicKey_old + ":" + pubRevoKey_old
610
- subDid = {"doc": did_old_doc, "key": did_key_old}.to_json
611
- subDidHash = multi_hash(canonical(subDid), LOG_HASH_OPTIONS).first
612
- signedSubDidHash = sign(subDidHash, revocationKey_old, options).first
613
- revocationLog = {
614
- "ts": ts_old,
615
- "op": 1, # REVOKE
616
- "doc": subDidHash,
617
- "sig": signedSubDidHash }.transform_keys(&:to_s).to_json
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
618
761
  end
619
762
 
620
763
  if revocationLog.nil?
621
764
  return [nil, "private revocation key not found"]
622
765
  end
623
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
624
771
  revoc_log = JSON.parse(revocationLog)
625
772
  revoc_log["previous"] = [
626
773
  multi_hash(canonical(log_old[did_info["doc_log_id"].to_i]), LOG_HASH_OPTIONS).first,
@@ -719,7 +866,67 @@ class Oydid
719
866
  return [retVal, msg]
720
867
  end
721
868
 
722
- def self.w3c(did_info, options)
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)
723
930
  did = percent_encode(did_info["did"])
724
931
  if !did.start_with?("did:oyd:")
725
932
  did = "did:oyd:" + did
@@ -728,6 +935,22 @@ class Oydid
728
935
  didDoc = did_info.transform_keys(&:to_s)["doc"]
729
936
  pubDocKey = didDoc["key"].split(":")[0] rescue ""
730
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
731
954
 
732
955
  wd = {}
733
956
  if didDoc["doc"].is_a?(Hash)
@@ -735,9 +958,9 @@ class Oydid
735
958
  wd["@context"] = ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2020/v1"]
736
959
  else
737
960
  if didDoc["doc"]["@context"].is_a?(Array)
738
- 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
739
962
  else
740
- 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
741
964
  end
742
965
  didDoc["doc"].delete("@context")
743
966
  end
@@ -756,6 +979,36 @@ class Oydid
756
979
  "controller": did,
757
980
  "publicKeyMultibase": pubRevKey
758
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
759
1012
 
760
1013
  equivalentIds = []
761
1014
  did_info["log"].each do |log|
@@ -776,36 +1029,44 @@ class Oydid
776
1029
  location = get_location(did_info["did"].to_s)
777
1030
  end
778
1031
  wd = wd.merge(didDoc["doc"])
779
- wdf = wd["service"].first
780
- wdf = { "id": did + "#payload",
781
- "type": "Custom",
782
- "serviceEndpoint": location }.merge(wdf)
783
- wd["service"] = [wdf] + wd["service"].drop(1)
1032
+ if wd["service"] != []
1033
+ if wd["service"].is_a?(Array)
1034
+ wdf = wd["service"].first
1035
+ else
1036
+ wdf = wd["service"]
1037
+ end
1038
+ wdf = { "id": did + "#payload",
1039
+ "type": "Custom",
1040
+ "serviceEndpoint": location }.merge(wdf)
1041
+ wd["service"] = [wdf] + wd["service"].drop(1)
1042
+ end
784
1043
  else
785
1044
  payload = nil
786
1045
  if didDoc["doc"].is_a?(Hash)
787
- didDoc = didDoc["doc"]
788
- if didDoc["authentication"].to_s != ""
789
- wd["authentication"] = didDoc["authentication"]
790
- didDoc.delete("authentication")
791
- end
792
- if didDoc["assertionMethod"].to_s != ""
793
- wd["assertionMethod"] = didDoc["assertionMethod"]
794
- didDoc.delete("assertionMethod")
795
- end
796
- if didDoc["keyAgreement"].to_s != ""
797
- wd["keyAgreement"] = didDoc["keyAgreement"]
798
- didDoc.delete("keyAgreement")
799
- end
800
- if didDoc["capabilityInvocation"].to_s != ""
801
- wd["capabilityInvocation"] = didDoc["capabilityInvocation"]
802
- didDoc.delete("capabilityInvocation")
803
- end
804
- if didDoc["capabilityDelegation"].to_s != ""
805
- wd["capabilityDelegation"] = didDoc["capabilityDelegation"]
806
- didDoc.delete("capabilityDelegation")
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
807
1069
  end
808
- payload = didDoc
809
1070
  else
810
1071
  payload = didDoc["doc"]
811
1072
  end
@@ -835,7 +1096,7 @@ class Oydid
835
1096
  end
836
1097
 
837
1098
 
838
- def self.w3c_legacy(did_info, options)
1099
+ def self.w3c_legacy(did_info, options)
839
1100
  did = did_info["did"]
840
1101
  if !did.start_with?("did:oyd:")
841
1102
  did = "did:oyd:" + did