oydid 0.5.4 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
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