oydid 0.5.3 → 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,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