oydid 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/oydid/basic.rb +80 -4
- data/lib/oydid/didcomm.rb +2 -2
- data/lib/oydid/log.rb +144 -39
- data/lib/oydid/vc.rb +62 -16
- data/lib/oydid.rb +431 -170
- metadata +5 -5
data/lib/oydid.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require '
|
4
|
+
require 'simple_dag'
|
5
5
|
require 'jwt'
|
6
6
|
require 'rbnacl'
|
7
7
|
require 'ed25519'
|
@@ -27,6 +27,11 @@ class Oydid
|
|
27
27
|
|
28
28
|
# expected DID format: did:oyd:123
|
29
29
|
def self.read(did, options)
|
30
|
+
|
31
|
+
if did.to_s == ""
|
32
|
+
return [nil, "missing DID"]
|
33
|
+
end
|
34
|
+
|
30
35
|
# setup
|
31
36
|
currentDID = {
|
32
37
|
"did": did,
|
@@ -115,15 +120,12 @@ class Oydid
|
|
115
120
|
if options[:trace]
|
116
121
|
puts " .. DAG with " + dag.vertices.length.to_s + " vertices and " + dag.edges.length.to_s + " edges, CREATE index: " + create_index.to_s
|
117
122
|
end
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
if options[:trace]
|
122
|
-
if options[:silent].nil? || !options[:silent]
|
123
|
-
puts " vertex " + terminate_index.to_s + " at " + log_array[terminate_index]["ts"].to_s + " op: " + log_array[terminate_index]["op"].to_s + " doc: " + log_array[terminate_index]["doc"].to_s
|
124
|
-
end
|
125
|
-
end
|
123
|
+
|
124
|
+
result = dag2array(dag, log_array, create_index, [], options)
|
125
|
+
ordered_log_array = dag2array_terminate(dag, log_array, terminate_index, result, options)
|
126
126
|
currentDID["log"] = ordered_log_array
|
127
|
+
# !!! ugly hack to get access to all delegation keys required in dag_update
|
128
|
+
currentDID["full_log"] = log_array
|
127
129
|
if options[:trace]
|
128
130
|
if options[:silent].nil? || !options[:silent]
|
129
131
|
dag.edges.each do |e|
|
@@ -150,7 +152,7 @@ class Oydid
|
|
150
152
|
end
|
151
153
|
|
152
154
|
def self.simulate_did(content, did, mode, options)
|
153
|
-
did_doc, did_key, did_log, msg =
|
155
|
+
did_doc, did_key, did_log, msg = generate_base(content, did, mode, options)
|
154
156
|
user_did = did_doc[:did]
|
155
157
|
return [user_did, msg]
|
156
158
|
end
|
@@ -169,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
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
else
|
186
|
-
privateKey, msg = read_private_key(options[:doc_key].to_s, options)
|
187
|
-
if privateKey.nil?
|
188
|
-
return [nil, nil, nil, "private document key not found"]
|
189
|
-
end
|
179
|
+
# key management
|
180
|
+
tmp_did_hash = did.delete_prefix("did:oyd:") rescue ""
|
181
|
+
tmp_did10 = tmp_did_hash[0,10] + "_private_key.enc" rescue ""
|
182
|
+
privateKey, msg = getPrivateKey(options[:doc_enc], options[:doc_pwd], options[:doc_key], tmp_did10, options)
|
183
|
+
if privateKey.nil?
|
184
|
+
privateKey, msg = generate_private_key("", 'ed25519-priv', options)
|
185
|
+
if privateKey.nil?
|
186
|
+
return [nil, nil, nil, "private document key not found"]
|
190
187
|
end
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
revocationKey, msg = read_private_key(options[:rev_key].to_s, options)
|
199
|
-
if revocationKey.nil?
|
200
|
-
return [nil, nil, nil, "private revocation key not found"]
|
201
|
-
end
|
188
|
+
end
|
189
|
+
tmp_did10 = tmp_did_hash[0,10] + "_revocation_key.enc" rescue ""
|
190
|
+
revocationKey, msg = getPrivateKey(options[:rev_enc], options[:rev_pwd], options[:rev_key], tmp_did10, options)
|
191
|
+
if revocationKey.nil?
|
192
|
+
revocationKey, msg = generate_private_key("", 'ed25519-priv', options)
|
193
|
+
if revocationKey.nil?
|
194
|
+
return [nil, nil, nil, "private revocation key not found"]
|
202
195
|
end
|
203
|
-
|
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
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
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
|
-
|
240
|
-
|
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
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
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
|
-
|
256
|
-
|
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
|
-
|
269
|
-
|
270
|
-
|
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":
|
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,
|
359
|
+
"sig": sign(l1_doc, old_privateKey, options).first,
|
379
360
|
"previous": prev_hash }.transform_keys(&:to_s)
|
380
|
-
|
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
|
-
|
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
|
-
|
682
|
+
old_revocationKey, msg = generate_private_key(options[:old_rev_pwd].to_s, 'ed25519-priv', options)
|
598
683
|
else
|
599
|
-
|
684
|
+
old_revocationKey, msg = decode_private_key(options[:old_rev_enc].to_s, options)
|
600
685
|
end
|
601
686
|
else
|
602
|
-
|
603
|
-
end
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
"ts"
|
615
|
-
"
|
616
|
-
|
617
|
-
|
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
|
-
|
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
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
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
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
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
|
-
|
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
|