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.
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,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
- 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
204
- # if a location is provided this is only relevant for writing the DID
205
- update_location = options[:location]
206
- update_doc_location = options[:doc_location]
207
- update_log_location = options[:log_location]
208
- options[:location] = nil
209
- options[:doc_location] = nil
210
- options[:log_location] = nil
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
- if options[:old_doc_key].nil?
240
- if options[:old_doc_enc].nil?
241
- if options[:old_doc_pwd].nil?
242
- privateKey_old = read_private_storage(did10_old + "_private_key.enc")
243
- else
244
- privateKey_old, msg = generate_private_key(options[:old_doc_pwd].to_s, 'ed25519-priv', options)
245
- end
246
- else
247
- privateKey_old, msg = decode_private_key(options[:old_doc_enc].to_s, options)
248
- 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
249
251
  else
250
- privateKey_old, msg = read_private_key(options[:old_doc_key].to_s, options)
251
- 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
252
258
 
253
- if privateKey_old.nil?
254
- return [nil, nil, nil, "invalid or missing old private document key"]
255
- end
256
- if options[:old_rev_key].nil?
257
- if options[:old_rev_enc].nil?
258
- if options[:old_rev_pwd].nil?
259
- revocationKey_old = read_private_storage(did10_old + "_revocation_key.enc")
260
- else
261
- 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"]
262
270
  end
263
- else
264
- revocationKey_old, msg = decode_private_key(options[:old_rev_enc].to_s, options)
265
271
  end
266
- else
267
- revocationKey_old, msg = read_private_key(options[:old_rev_key].to_s, options)
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
- else
280
- privateKey, msg = read_private_key(options[:doc_key].to_s, options)
281
- end
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": [] }.transform_keys(&:to_s)
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, privateKey_old, options).first,
359
+ "sig": sign(l1_doc, old_privateKey, options).first,
397
360
  "previous": prev_hash }.transform_keys(&:to_s)
398
- 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
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
- 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?
613
681
  if options[:rev_enc].nil?
614
- 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)
615
683
  else
616
- 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)
617
685
  end
618
686
  else
619
- revocationKey, msg = generate_private_key(options[:rev_pwd].to_s, 'ed25519-priv', options)
620
- end
621
- # re-build revocation document
622
- did_old_doc = did_info["doc"]["doc"]
623
- ts_old = did_info["log"].last["ts"]
624
- publicKey_old = public_key(privateKey_old, options).first
625
- pubRevoKey_old = public_key(revocationKey_old, options).first
626
- did_key_old = publicKey_old + ":" + pubRevoKey_old
627
- subDid = {"doc": did_old_doc, "key": did_key_old}.to_json
628
- subDidHash = multi_hash(canonical(subDid), LOG_HASH_OPTIONS).first
629
- signedSubDidHash = sign(subDidHash, revocationKey_old, options).first
630
- revocationLog = {
631
- "ts": ts_old,
632
- "op": 1, # REVOKE
633
- "doc": subDidHash,
634
- "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
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
- 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)
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
- didDoc = didDoc["doc"]
811
- if didDoc["authentication"].to_s != ""
812
- wd["authentication"] = didDoc["authentication"]
813
- didDoc.delete("authentication")
814
- end
815
- if didDoc["assertionMethod"].to_s != ""
816
- wd["assertionMethod"] = didDoc["assertionMethod"]
817
- didDoc.delete("assertionMethod")
818
- end
819
- if didDoc["keyAgreement"].to_s != ""
820
- wd["keyAgreement"] = didDoc["keyAgreement"]
821
- didDoc.delete("keyAgreement")
822
- end
823
- if didDoc["capabilityInvocation"].to_s != ""
824
- wd["capabilityInvocation"] = didDoc["capabilityInvocation"]
825
- didDoc.delete("capabilityInvocation")
826
- end
827
- if didDoc["capabilityDelegation"].to_s != ""
828
- wd["capabilityDelegation"] = didDoc["capabilityDelegation"]
829
- 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
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
- def self.w3c_legacy(did_info, options)
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