oydid 0.5.4 → 0.5.6
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 +255 -12
- data/lib/oydid/didcomm.rb +2 -2
- data/lib/oydid/log.rb +194 -42
- data/lib/oydid/vc.rb +4 -2
- data/lib/oydid.rb +499 -184
- metadata +102 -102
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'
|
@@ -24,9 +24,18 @@ class Oydid
|
|
24
24
|
DEFAULT_ENCODING = "base58btc"
|
25
25
|
SUPPORTED_ENCODINGS = ["base16", "base32", "base58btc", "base64"]
|
26
26
|
LOG_HASH_OPTIONS = {:digest => "sha2-256", :encode => "base58btc"}
|
27
|
+
DEFAULT_PUBLIC_RESOLVER = "https://dev.uniresolver.io/1.0/identifiers/"
|
28
|
+
|
29
|
+
# full Multicodecs table: https://github.com/multiformats/multicodec/blob/master/table.csv
|
30
|
+
# Multicodecs.register(code: 0x1305, name: 'rsa-priv', tag: 'key')
|
31
|
+
# Multicodecs.register(code: 0x1205, name: 'rsa-pub', tag: 'key')
|
27
32
|
|
28
33
|
# expected DID format: did:oyd:123
|
29
34
|
def self.read(did, options)
|
35
|
+
if did.to_s == ""
|
36
|
+
return [nil, "missing DID"]
|
37
|
+
end
|
38
|
+
|
30
39
|
# setup
|
31
40
|
currentDID = {
|
32
41
|
"did": did,
|
@@ -115,15 +124,12 @@ class Oydid
|
|
115
124
|
if options[:trace]
|
116
125
|
puts " .. DAG with " + dag.vertices.length.to_s + " vertices and " + dag.edges.length.to_s + " edges, CREATE index: " + create_index.to_s
|
117
126
|
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
|
127
|
+
|
128
|
+
result = dag2array(dag, log_array, create_index, [], options)
|
129
|
+
ordered_log_array = dag2array_terminate(dag, log_array, terminate_index, result, options)
|
126
130
|
currentDID["log"] = ordered_log_array
|
131
|
+
# !!! ugly hack to get access to all delegation keys required in dag_update
|
132
|
+
currentDID["full_log"] = log_array
|
127
133
|
if options[:trace]
|
128
134
|
if options[:silent].nil? || !options[:silent]
|
129
135
|
dag.edges.each do |e|
|
@@ -131,14 +137,24 @@ class Oydid
|
|
131
137
|
end
|
132
138
|
end
|
133
139
|
end
|
134
|
-
|
140
|
+
|
141
|
+
# identify if DID Rotation was performed
|
142
|
+
rotated_DID = (currentDID.transform_keys(&:to_s)["doc"]["doc"].has_key?("@context") &&
|
143
|
+
currentDID.transform_keys(&:to_s)["doc"]["doc"].has_key?("id") &&
|
144
|
+
currentDID.transform_keys(&:to_s)["doc"]["doc"]["id"].split(":").first == "did") rescue false
|
145
|
+
|
146
|
+
if rotated_DID
|
147
|
+
doc = currentDID["doc"].dup
|
148
|
+
currentDID = dag_update(currentDID, options)
|
149
|
+
currentDID["doc"] = doc
|
150
|
+
else
|
151
|
+
currentDID = dag_update(currentDID, options)
|
152
|
+
end
|
135
153
|
if options[:log_complete]
|
136
154
|
currentDID["log"] = log_array
|
137
155
|
end
|
138
|
-
|
139
156
|
return [currentDID, ""]
|
140
157
|
end
|
141
|
-
|
142
158
|
end
|
143
159
|
|
144
160
|
def self.create(content, options)
|
@@ -150,7 +166,7 @@ class Oydid
|
|
150
166
|
end
|
151
167
|
|
152
168
|
def self.simulate_did(content, did, mode, options)
|
153
|
-
did_doc, did_key, did_log, msg =
|
169
|
+
did_doc, did_key, did_log, msg = generate_base(content, did, mode, options)
|
154
170
|
user_did = did_doc[:did]
|
155
171
|
return [user_did, msg]
|
156
172
|
end
|
@@ -169,50 +185,38 @@ class Oydid
|
|
169
185
|
revoc_log = nil
|
170
186
|
doc_location = options[:location]
|
171
187
|
if options[:ts].nil?
|
172
|
-
ts = Time.now.to_i
|
188
|
+
ts = Time.now.utc.to_i
|
173
189
|
else
|
174
190
|
ts = options[:ts]
|
175
191
|
end
|
176
192
|
|
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
|
193
|
+
# key management
|
194
|
+
tmp_did_hash = did.delete_prefix("did:oyd:") rescue ""
|
195
|
+
tmp_did10 = tmp_did_hash[0,10] + "_private_key.enc" rescue ""
|
196
|
+
privateKey, msg = getPrivateKey(options[:doc_enc], options[:doc_pwd], options[:doc_key], tmp_did10, options)
|
197
|
+
if privateKey.nil?
|
198
|
+
privateKey, msg = generate_private_key("", 'ed25519-priv', options)
|
199
|
+
if privateKey.nil?
|
200
|
+
return [nil, nil, nil, "private document key not found"]
|
190
201
|
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
|
202
|
+
end
|
203
|
+
tmp_did10 = tmp_did_hash[0,10] + "_revocation_key.enc" rescue ""
|
204
|
+
revocationKey, msg = getPrivateKey(options[:rev_enc], options[:rev_pwd], options[:rev_key], tmp_did10, options)
|
205
|
+
if revocationKey.nil?
|
206
|
+
revocationKey, msg = generate_private_key("", 'ed25519-priv', options)
|
207
|
+
if revocationKey.nil?
|
208
|
+
return [nil, nil, nil, "private revocation key not found"]
|
202
209
|
end
|
203
|
-
|
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]
|
210
|
+
end
|
215
211
|
|
212
|
+
# mode-specific handling
|
213
|
+
if mode == "create" || mode == "clone"
|
214
|
+
operation_mode = 2 # CREATE
|
215
|
+
|
216
|
+
else # mode == "update" => read information first
|
217
|
+
operation_mode = 3 # UPDATE
|
218
|
+
|
219
|
+
did_info, msg = read(did, options)
|
216
220
|
if did_info.nil?
|
217
221
|
return [nil, nil, nil, "cannot resolve DID (on updating DID)"]
|
218
222
|
end
|
@@ -230,90 +234,64 @@ class Oydid
|
|
230
234
|
doc_location = hash_split[1]
|
231
235
|
end
|
232
236
|
end
|
233
|
-
operation_mode = 3 # UPDATE
|
234
|
-
|
235
|
-
# collect relevant information from previous did
|
236
237
|
did_old = did.dup
|
237
238
|
did10_old = did10.dup
|
238
239
|
log_old = did_info["log"]
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
240
|
+
|
241
|
+
# check if provided old keys are native DID keys or delegates ==================
|
242
|
+
tmp_old_did10 = did10_old + "_private_key.enc" rescue ""
|
243
|
+
old_privateKey, msg = getPrivateKey(options[:old_doc_enc], options[:old_doc_pwd], options[:old_doc_key], tmp_old_did10, options)
|
244
|
+
tmp_old_did10 = did10_old + "_revocation_key.enc" rescue ""
|
245
|
+
old_revocationKey, msg = getPrivateKey(options[:old_rev_enc], options[:old_rev_pwd], options[:old_rev_key], tmp_did10, options)
|
246
|
+
old_publicDocKey = public_key(old_privateKey, {}).first
|
247
|
+
old_publicRevKey = public_key(old_revocationKey, {}).first
|
248
|
+
old_did_key = old_publicDocKey + ":" + old_publicRevKey
|
249
|
+
|
250
|
+
# compare old keys with existing DID Document & generate revocation record
|
251
|
+
if old_did_key.to_s == did_info["doc"]["key"].to_s
|
252
|
+
# provided keys are native DID keys ------------------
|
253
|
+
|
254
|
+
# re-build revocation document
|
255
|
+
old_did_doc = did_info["doc"]["doc"]
|
256
|
+
old_ts = did_info["log"].last["ts"]
|
257
|
+
old_subDid = {"doc": old_did_doc, "key": old_did_key}.to_json
|
258
|
+
old_subDidHash = multi_hash(canonical(old_subDid), LOG_HASH_OPTIONS).first
|
259
|
+
old_signedSubDidHash = sign(old_subDidHash, old_revocationKey, LOG_HASH_OPTIONS).first
|
260
|
+
revocationLog = {
|
261
|
+
"ts": old_ts,
|
262
|
+
"op": 1, # REVOKE
|
263
|
+
"doc": old_subDidHash,
|
264
|
+
"sig": old_signedSubDidHash }.transform_keys(&:to_s).to_json
|
249
265
|
else
|
250
|
-
|
251
|
-
|
266
|
+
# proviced keys are either delegates or invalid ------
|
267
|
+
# * check validity of key-doc delegate
|
268
|
+
pubKeys, msg = getDelegatedPubKeysFromDID(did, "doc")
|
269
|
+
if !pubKeys.include?(old_publicDocKey)
|
270
|
+
return [nil, nil, nil, "invalid or missing old private document key"]
|
271
|
+
end
|
252
272
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
273
|
+
# * check validity of key-rev delegate
|
274
|
+
pubKeys, msg = getDelegatedPubKeysFromDID(did, "rev")
|
275
|
+
if !pubKeys.include?(old_publicRevKey)
|
276
|
+
return [nil, nil, nil, "invalid or missing old private revocation key"]
|
277
|
+
end
|
278
|
+
|
279
|
+
# retrieve revocationLog from previous in key-rev delegate
|
280
|
+
revoc_log = nil
|
281
|
+
log_old.each do |item|
|
282
|
+
if !item["encrypted-revocation-log"].nil?
|
283
|
+
revoc_log = item["encrypted-revocation-log"]
|
262
284
|
end
|
263
|
-
else
|
264
|
-
revocationKey_old, msg = decode_private_key(options[:old_rev_enc].to_s, options)
|
265
285
|
end
|
266
|
-
|
267
|
-
|
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)
|
286
|
+
if revoc_log.nil?
|
287
|
+
return [nil, nil, nil, "cannot retrieve revocation log"]
|
278
288
|
end
|
279
|
-
|
280
|
-
|
281
|
-
|
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)
|
289
|
+
revocationLog, msg = decrypt(revoc_log.to_json, old_revocationKey.to_s)
|
290
|
+
if revocationLog.nil?
|
291
|
+
return [nil, nil, nil, "cannot decrypt revocation log entry: " + msg]
|
300
292
|
end
|
293
|
+
end # compare old keys with existing DID Document
|
301
294
|
|
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
295
|
revoc_log = JSON.parse(revocationLog)
|
318
296
|
revoc_log["previous"] = [
|
319
297
|
multi_hash(canonical(log_old[did_info["doc_log_id"].to_i]), LOG_HASH_OPTIONS).first,
|
@@ -325,11 +303,25 @@ class Oydid
|
|
325
303
|
pubRevoKey = public_key(revocationKey, options).first
|
326
304
|
did_key = publicKey + ":" + pubRevoKey
|
327
305
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
306
|
+
if options[:x25519_keyAgreement]
|
307
|
+
if did_doc.nil?
|
308
|
+
did_doc = {}
|
309
|
+
end
|
310
|
+
did_doc[:keyAgreement] = [{
|
311
|
+
"id": "#key-doc-x25519",
|
312
|
+
"type": "X25519KeyAgreementKey2019",
|
313
|
+
"publicKeyMultibase": public_key(privateKey, options, 'x25519-pub').first
|
314
|
+
}]
|
315
|
+
did_doc = did_doc.transform_keys(&:to_s)
|
316
|
+
end
|
317
|
+
if options[:authentication]
|
318
|
+
if did_doc.nil?
|
319
|
+
did_doc = {}
|
332
320
|
end
|
321
|
+
did_doc[:authentication] = [{
|
322
|
+
"id": "#key-doc"
|
323
|
+
}]
|
324
|
+
did_doc = did_doc.transform_keys(&:to_s)
|
333
325
|
end
|
334
326
|
|
335
327
|
# build new revocation document
|
@@ -350,11 +342,16 @@ class Oydid
|
|
350
342
|
if !doc_location.nil?
|
351
343
|
l2_doc += LOCATION_PREFIX + doc_location.to_s
|
352
344
|
end
|
345
|
+
if options[:confirm_logs].nil?
|
346
|
+
previous_array = []
|
347
|
+
else
|
348
|
+
previous_array = options[:confirm_logs]
|
349
|
+
end
|
353
350
|
l2 = { "ts": ts,
|
354
351
|
"op": 0, # TERMINATE
|
355
352
|
"doc": l2_doc,
|
356
353
|
"sig": sign(l2_doc, privateKey, options).first,
|
357
|
-
"previous":
|
354
|
+
"previous": previous_array }.transform_keys(&:to_s)
|
358
355
|
|
359
356
|
# build actual DID document
|
360
357
|
log_str = multi_hash(canonical(l2), LOG_HASH_OPTIONS).first
|
@@ -389,13 +386,27 @@ class Oydid
|
|
389
386
|
end
|
390
387
|
|
391
388
|
# build creation log entry
|
389
|
+
log_revoke_encrypted_array = nil
|
392
390
|
if operation_mode == 3 # UPDATE
|
393
391
|
l1 = { "ts": ts,
|
394
392
|
"op": operation_mode, # UPDATE
|
395
393
|
"doc": l1_doc,
|
396
|
-
"sig": sign(l1_doc,
|
394
|
+
"sig": sign(l1_doc, old_privateKey, options).first,
|
397
395
|
"previous": prev_hash }.transform_keys(&:to_s)
|
398
|
-
|
396
|
+
options[:confirm_logs].each do |el|
|
397
|
+
# read each log entry to check if it is a revocation delegation
|
398
|
+
log_item, msg = retrieve_log_item(el, doc_location, options)
|
399
|
+
if log_item["doc"][0..3] == "rev:"
|
400
|
+
cipher, msg = encrypt(r1.to_json, log_item["encryption-key"], {})
|
401
|
+
cipher[:log] = el.to_s
|
402
|
+
if log_revoke_encrypted_array.nil?
|
403
|
+
log_revoke_encrypted_array = [cipher]
|
404
|
+
else
|
405
|
+
log_revoke_encrypted_array << cipher
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end unless options[:confirm_logs].nil?
|
409
|
+
else
|
399
410
|
l1 = { "ts": ts,
|
400
411
|
"op": operation_mode, # CREATE
|
401
412
|
"doc": l1_doc,
|
@@ -407,7 +418,6 @@ class Oydid
|
|
407
418
|
# did_doc = [did, didDocument, did_old]
|
408
419
|
# did_log = [revoc_log, l1, l2, r1, log_old]
|
409
420
|
# did_key = [privateKey, revocationKey]
|
410
|
-
|
411
421
|
did_doc = {
|
412
422
|
:did => did,
|
413
423
|
:didDocument => didDocument,
|
@@ -420,6 +430,10 @@ class Oydid
|
|
420
430
|
:r1 => r1,
|
421
431
|
:log_old => log_old
|
422
432
|
}
|
433
|
+
if !log_revoke_encrypted_array.nil?
|
434
|
+
did_log[:r1_encrypted] = log_revoke_encrypted_array
|
435
|
+
end
|
436
|
+
|
423
437
|
did_key = {
|
424
438
|
:privateKey => privateKey,
|
425
439
|
:revocationKey => revocationKey
|
@@ -482,6 +496,7 @@ class Oydid
|
|
482
496
|
l1 = did_log[:l1]
|
483
497
|
l2 = did_log[:l2]
|
484
498
|
r1 = did_log[:r1]
|
499
|
+
r1_encrypted = did_log[:r1_encrypted]
|
485
500
|
log_old = did_log[:log_old]
|
486
501
|
privateKey = did_key[:privateKey]
|
487
502
|
revocationKey = did_key[:revocationKey]
|
@@ -504,7 +519,7 @@ class Oydid
|
|
504
519
|
end
|
505
520
|
case doc_location.to_s
|
506
521
|
when /^http/
|
507
|
-
logs = [revoc_log, l1, l2].flatten.compact
|
522
|
+
logs = [revoc_log, l1, l2, r1_encrypted].flatten.compact
|
508
523
|
else
|
509
524
|
logs = [log_old, revoc_log, l1, l2].flatten.compact
|
510
525
|
if !did_old.nil?
|
@@ -514,14 +529,17 @@ class Oydid
|
|
514
529
|
success, msg = publish(did, didDocument, logs, options)
|
515
530
|
|
516
531
|
if success
|
532
|
+
didDocumentBackup = Marshal.load(Marshal.dump(didDocument))
|
517
533
|
w3c_input = {
|
518
|
-
"did" => did,
|
519
|
-
"doc" => didDocument
|
534
|
+
"did" => did.clone,
|
535
|
+
"doc" => didDocument.clone
|
520
536
|
}
|
537
|
+
doc_w3c = Oydid.w3c(w3c_input, options)
|
538
|
+
didDocument = didDocumentBackup
|
521
539
|
retVal = {
|
522
540
|
"did" => did,
|
523
541
|
"doc" => didDocument,
|
524
|
-
"doc_w3c" =>
|
542
|
+
"doc_w3c" => doc_w3c,
|
525
543
|
"log" => logs
|
526
544
|
}
|
527
545
|
if options[:return_secrets]
|
@@ -540,11 +558,88 @@ class Oydid
|
|
540
558
|
end
|
541
559
|
end
|
542
560
|
|
561
|
+
def self.write_log(did, log, options = {})
|
562
|
+
# validate log
|
563
|
+
if !log.is_a?(Hash)
|
564
|
+
return [nil, "invalid log input"]
|
565
|
+
end
|
566
|
+
log = log.transform_keys(&:to_s)
|
567
|
+
if log["ts"].nil?
|
568
|
+
return [nil, "missing timestamp in log"]
|
569
|
+
end
|
570
|
+
if log["op"].nil?
|
571
|
+
return [nil, "missing operation in log"]
|
572
|
+
end
|
573
|
+
if log["doc"].nil?
|
574
|
+
return [nil, "missing doc entry in log"]
|
575
|
+
end
|
576
|
+
if log["sig"].nil?
|
577
|
+
return [nil, "missing signature in log"]
|
578
|
+
end
|
579
|
+
|
580
|
+
# validate did
|
581
|
+
if did.include?(LOCATION_PREFIX)
|
582
|
+
tmp = did.split(LOCATION_PREFIX)
|
583
|
+
did = tmp[0]
|
584
|
+
source_location = tmp[1]
|
585
|
+
log_location = tmp[1]
|
586
|
+
end
|
587
|
+
if did.include?(CGI.escape LOCATION_PREFIX)
|
588
|
+
tmp = did.split(CGI.escape LOCATION_PREFIX)
|
589
|
+
did = tmp[0]
|
590
|
+
source_location = tmp[1]
|
591
|
+
log_location = tmp[1]
|
592
|
+
end
|
593
|
+
|
594
|
+
if source_location.to_s == ""
|
595
|
+
if options[:doc_location].nil?
|
596
|
+
source_location = DEFAULT_LOCATION
|
597
|
+
else
|
598
|
+
source_location = options[:doc_location]
|
599
|
+
end
|
600
|
+
if options[:log_location].nil?
|
601
|
+
log_location = DEFAULT_LOCATION
|
602
|
+
else
|
603
|
+
log_location = options[:log_location]
|
604
|
+
end
|
605
|
+
end
|
606
|
+
options[:doc_location] = source_location
|
607
|
+
options[:log_location] = log_location
|
608
|
+
source_did, msg = read(did, options)
|
609
|
+
if source_did.nil?
|
610
|
+
return [nil, "cannot resolve DID (on writing logs)"]
|
611
|
+
end
|
612
|
+
if source_did["error"] != 0
|
613
|
+
return [nil, source_did["message"].to_s]
|
614
|
+
end
|
615
|
+
if source_did["doc_log_id"].nil?
|
616
|
+
return [nil, "cannot parse DID log"]
|
617
|
+
end
|
618
|
+
|
619
|
+
# write log
|
620
|
+
source_location = source_location.gsub("%3A",":")
|
621
|
+
source_location = source_location.gsub("%2F%2F","//")
|
622
|
+
retVal = HTTParty.post(source_location + "/log/" + did,
|
623
|
+
headers: { 'Content-Type' => 'application/json' },
|
624
|
+
body: {"log": log}.to_json )
|
625
|
+
code = retVal.code rescue 500
|
626
|
+
if code != 200
|
627
|
+
err_msg = retVal.parsed_response["error"].to_s rescue "invalid response from " + source_location.to_s + "/log"
|
628
|
+
return ["", err_msg]
|
629
|
+
end
|
630
|
+
log_hash = retVal.parsed_response["log"] rescue ""
|
631
|
+
if log_hash == ""
|
632
|
+
err_msg = "missing log hash from " + source_location.to_s + "/log"
|
633
|
+
return ["", err_msg]
|
634
|
+
end
|
635
|
+
return [log_hash, nil]
|
636
|
+
end
|
637
|
+
|
543
638
|
def self.revoke_base(did, options)
|
544
639
|
did_orig = did.dup
|
545
640
|
doc_location = options[:doc_location]
|
546
641
|
if options[:ts].nil?
|
547
|
-
ts = Time.now.to_i
|
642
|
+
ts = Time.now.utc.to_i
|
548
643
|
else
|
549
644
|
ts = options[:ts]
|
550
645
|
end
|
@@ -606,38 +701,111 @@ class Oydid
|
|
606
701
|
end
|
607
702
|
|
608
703
|
if options[:rev_key].nil? && options[:rev_pwd].nil? && options[:rev_enc].nil?
|
609
|
-
revocationKey, msg = read_private_key(did10 + "_revocation_key.enc", options)
|
704
|
+
# revocationKey, msg = read_private_key(did10 + "_revocation_key.enc", options)
|
610
705
|
revocationLog = read_private_storage(did10 + "_revocation.json")
|
611
706
|
else
|
612
|
-
|
707
|
+
|
708
|
+
# check if provided old keys are native DID keys or delegates ==================
|
709
|
+
if options[:doc_key].nil?
|
710
|
+
if options[:doc_enc].nil?
|
711
|
+
old_privateKey, msg = generate_private_key(options[:old_doc_pwd].to_s, 'ed25519-priv', options)
|
712
|
+
else
|
713
|
+
old_privateKey, msg = decode_private_key(options[:old_doc_enc].to_s, options)
|
714
|
+
end
|
715
|
+
else
|
716
|
+
old_privateKey, msg = read_private_key(options[:old_doc_key].to_s, options)
|
717
|
+
end
|
718
|
+
if options[:rev_key].nil?
|
613
719
|
if options[:rev_enc].nil?
|
614
|
-
|
720
|
+
old_revocationKey, msg = generate_private_key(options[:old_rev_pwd].to_s, 'ed25519-priv', options)
|
615
721
|
else
|
616
|
-
|
722
|
+
old_revocationKey, msg = decode_private_key(options[:old_rev_enc].to_s, options)
|
617
723
|
end
|
618
724
|
else
|
619
|
-
|
620
|
-
end
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
"ts"
|
632
|
-
"
|
633
|
-
|
634
|
-
|
725
|
+
old_revocationKey, msg = read_private_key(options[:old_rev_key].to_s, options)
|
726
|
+
end
|
727
|
+
old_publicDocKey = public_key(old_privateKey, {}).first
|
728
|
+
old_publicRevKey = public_key(old_revocationKey, {}).first
|
729
|
+
old_did_key = old_publicDocKey + ":" + old_publicRevKey
|
730
|
+
|
731
|
+
# compare old keys with existing DID Document & generate revocation record
|
732
|
+
if old_did_key.to_s == did_info["doc"]["key"].to_s
|
733
|
+
# provided keys are native DID keys ------------------
|
734
|
+
|
735
|
+
# re-build revocation document
|
736
|
+
old_did_doc = did_info["doc"]["doc"]
|
737
|
+
old_ts = did_info["log"].last["ts"]
|
738
|
+
old_subDid = {"doc": old_did_doc, "key": old_did_key}.to_json
|
739
|
+
old_subDidHash = multi_hash(canonical(old_subDid), LOG_HASH_OPTIONS).first
|
740
|
+
old_signedSubDidHash = sign(old_subDidHash, old_revocationKey, LOG_HASH_OPTIONS).first
|
741
|
+
revocationLog = {
|
742
|
+
"ts": old_ts,
|
743
|
+
"op": 1, # REVOKE
|
744
|
+
"doc": old_subDidHash,
|
745
|
+
"sig": old_signedSubDidHash }.transform_keys(&:to_s).to_json
|
746
|
+
else
|
747
|
+
# proviced keys are either delegates or invalid ------
|
748
|
+
# * check validity of key-doc delegate
|
749
|
+
pubKeys, msg = getDelegatedPubKeysFromDID(did, "doc")
|
750
|
+
if !pubKeys.include?(old_publicDocKey)
|
751
|
+
return [nil, "invalid or missing private document key"]
|
752
|
+
end
|
753
|
+
|
754
|
+
# * check validity of key-rev delegate
|
755
|
+
pubKeys, msg = getDelegatedPubKeysFromDID(did, "rev")
|
756
|
+
if !pubKeys.include?(old_publicRevKey)
|
757
|
+
return [nil, "invalid or missing private revocation key"]
|
758
|
+
end
|
759
|
+
|
760
|
+
# retrieve revocationLog from previous in key-rev delegate
|
761
|
+
revoc_log = nil
|
762
|
+
log_old.each do |item|
|
763
|
+
if !item["encrypted-revocation-log"].nil?
|
764
|
+
revoc_log = item["encrypted-revocation-log"]
|
765
|
+
end
|
766
|
+
end
|
767
|
+
if revoc_log.nil?
|
768
|
+
return [nil, "cannot retrieve revocation log"]
|
769
|
+
end
|
770
|
+
revocationLog, msg = decrypt(revoc_log.to_json, old_revocationKey.to_s)
|
771
|
+
if revocationLog.nil?
|
772
|
+
return [nil, "cannot decrypt revocation log entry: " + msg]
|
773
|
+
end
|
774
|
+
end # compare old keys with existing DID Document
|
775
|
+
|
776
|
+
# if options[:rev_pwd].nil?
|
777
|
+
# if options[:rev_enc].nil?
|
778
|
+
# revocationKey, msg = read_private_key(options[:rev_key].to_s, options)
|
779
|
+
# else
|
780
|
+
# revocationKey, msg = decode_private_key(options[:rev_enc].to_s, options)
|
781
|
+
# end
|
782
|
+
# else
|
783
|
+
# revocationKey, msg = generate_private_key(options[:rev_pwd].to_s, 'ed25519-priv', options)
|
784
|
+
# end
|
785
|
+
# # re-build revocation document
|
786
|
+
# did_old_doc = did_info["doc"]["doc"]
|
787
|
+
# ts_old = did_info["log"].last["ts"]
|
788
|
+
# publicKey_old = public_key(privateKey_old, options).first
|
789
|
+
# pubRevoKey_old = public_key(revocationKey_old, options).first
|
790
|
+
# did_key_old = publicKey_old + ":" + pubRevoKey_old
|
791
|
+
# subDid = {"doc": did_old_doc, "key": did_key_old}.to_json
|
792
|
+
# subDidHash = multi_hash(canonical(subDid), LOG_HASH_OPTIONS).first
|
793
|
+
# signedSubDidHash = sign(subDidHash, revocationKey_old, options).first
|
794
|
+
# revocationLog = {
|
795
|
+
# "ts": ts_old,
|
796
|
+
# "op": 1, # REVOKE
|
797
|
+
# "doc": subDidHash,
|
798
|
+
# "sig": signedSubDidHash }.transform_keys(&:to_s).to_json
|
635
799
|
end
|
636
800
|
|
637
801
|
if revocationLog.nil?
|
638
802
|
return [nil, "private revocation key not found"]
|
639
803
|
end
|
640
804
|
|
805
|
+
# check if REVOCATION hash matches hash in TERMINATION
|
806
|
+
if did_info["log"][did_info["termination_log_id"]]["doc"] != multi_hash(canonical(revocationLog), LOG_HASH_OPTIONS).first
|
807
|
+
return [nil, "invalid revocation information"]
|
808
|
+
end
|
641
809
|
revoc_log = JSON.parse(revocationLog)
|
642
810
|
revoc_log["previous"] = [
|
643
811
|
multi_hash(canonical(log_old[did_info["doc_log_id"].to_i]), LOG_HASH_OPTIONS).first,
|
@@ -736,15 +904,98 @@ class Oydid
|
|
736
904
|
return [retVal, msg]
|
737
905
|
end
|
738
906
|
|
739
|
-
|
907
|
+
def self.delegate(did, options)
|
908
|
+
# check location
|
909
|
+
location = options[:doc_location]
|
910
|
+
if location.to_s == ""
|
911
|
+
location = DEFAULT_LOCATION
|
912
|
+
end
|
913
|
+
if did.include?(LOCATION_PREFIX)
|
914
|
+
tmp = did.split(LOCATION_PREFIX)
|
915
|
+
did = tmp[0]
|
916
|
+
location = tmp[1]
|
917
|
+
end
|
918
|
+
if did.include?(CGI.escape LOCATION_PREFIX)
|
919
|
+
tmp = did.split(CGI.escape LOCATION_PREFIX)
|
920
|
+
did = tmp[0]
|
921
|
+
location = tmp[1]
|
922
|
+
end
|
923
|
+
options[:doc_location] = location
|
924
|
+
options[:log_location] = location
|
925
|
+
|
926
|
+
if options[:ts].nil?
|
927
|
+
ts = Time.now.utc.to_i
|
928
|
+
else
|
929
|
+
ts = options[:ts]
|
930
|
+
end
|
931
|
+
|
932
|
+
# build log record
|
933
|
+
log = {}
|
934
|
+
log["ts"] = ts
|
935
|
+
log["op"] = 5 # DELEGATE
|
936
|
+
pwd = false
|
937
|
+
doc_privateKey, msg = getPrivateKey(options[:doc_enc], options[:doc_pwd], options[:doc_key], "", options)
|
938
|
+
rev_privateKey, msg = getPrivateKey(options[:rev_enc], options[:rev_pwd], options[:rev_key], "", options)
|
939
|
+
if !doc_privateKey.nil?
|
940
|
+
pwd="doc"
|
941
|
+
privateKey = doc_privateKey
|
942
|
+
end
|
943
|
+
if !rev_privateKey.nil?
|
944
|
+
pwd="rev"
|
945
|
+
privateKey = rev_privateKey
|
946
|
+
end
|
947
|
+
if !pwd || privateKey.to_s == ""
|
948
|
+
return [nil, "missing or invalid delegate key"]
|
949
|
+
end
|
950
|
+
log["doc"] = pwd + ":" + public_key(privateKey, options).first.to_s
|
951
|
+
log["sig"] = sign(privateKey, privateKey, options).first
|
952
|
+
log["previous"] = [did] # DID in previous cannot be resolved in the DAG but guarantees unique log hash
|
953
|
+
|
954
|
+
# revocation delegate keys need to specify a public key for encrypting the revocation record
|
955
|
+
if pwd == "rev"
|
956
|
+
publicEncryptionKey, msg = public_key(privateKey, {}, 'x25519-pub')
|
957
|
+
log["encryption-key"] = publicEncryptionKey
|
958
|
+
end
|
959
|
+
log_hash, msg = write_log(did, log, options)
|
960
|
+
if log_hash.nil?
|
961
|
+
return [nil, msg]
|
962
|
+
else
|
963
|
+
return [{"log": log_hash}, ""]
|
964
|
+
end
|
965
|
+
end
|
966
|
+
|
967
|
+
def self.w3c(did_info, options)
|
968
|
+
# check if doc is already W3C DID
|
969
|
+
is_already_w3c_did = (did_info.transform_keys(&:to_s)["doc"]["doc"].has_key?("@context") &&
|
970
|
+
did_info.transform_keys(&:to_s)["doc"]["doc"].has_key?("id") &&
|
971
|
+
did_info.transform_keys(&:to_s)["doc"]["doc"]["id"].split(":").first == "did") rescue false
|
972
|
+
if is_already_w3c_did
|
973
|
+
return did_info.transform_keys(&:to_s)["doc"]["doc"]
|
974
|
+
end
|
740
975
|
did = percent_encode(did_info["did"])
|
741
976
|
if !did.start_with?("did:oyd:")
|
742
977
|
did = "did:oyd:" + did
|
743
978
|
end
|
744
979
|
|
745
|
-
didDoc = did_info.transform_keys(&:to_s)["doc"]
|
980
|
+
didDoc = did_info.dup.transform_keys(&:to_s)["doc"]
|
746
981
|
pubDocKey = didDoc["key"].split(":")[0] rescue ""
|
747
982
|
pubRevKey = didDoc["key"].split(":")[1] rescue ""
|
983
|
+
delegateDocKeys = getDelegatedPubKeysFromDID(did, "doc").first - [pubDocKey] rescue []
|
984
|
+
if delegateDocKeys.is_a?(String)
|
985
|
+
if delegateDocKeys == pubDocKey
|
986
|
+
delegateDocKeys = nil
|
987
|
+
else
|
988
|
+
delegateDocKeys = [delegateDocKeys]
|
989
|
+
end
|
990
|
+
end
|
991
|
+
delegateRevKeys = getDelegatedPubKeysFromDID(did, "rev").first - [pubRevKey] rescue []
|
992
|
+
if delegateRevKeys.is_a?(String)
|
993
|
+
if delegateRevKeys == pubRevKey
|
994
|
+
delegateRevKeys = nil
|
995
|
+
else
|
996
|
+
delegateRevKeys = [delegateRevKeys]
|
997
|
+
end
|
998
|
+
end
|
748
999
|
|
749
1000
|
wd = {}
|
750
1001
|
if didDoc["doc"].is_a?(Hash)
|
@@ -752,9 +1003,9 @@ class Oydid
|
|
752
1003
|
wd["@context"] = ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2020/v1"]
|
753
1004
|
else
|
754
1005
|
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"]
|
1006
|
+
wd["@context"] = (["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2020/v1"] + didDoc["doc"]["@context"]).uniq
|
756
1007
|
else
|
757
|
-
wd["@context"] = ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2020/v1", didDoc["doc"]["@context"]]
|
1008
|
+
wd["@context"] = (["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2020/v1", didDoc["doc"]["@context"]]).uniq
|
758
1009
|
end
|
759
1010
|
didDoc["doc"].delete("@context")
|
760
1011
|
end
|
@@ -773,6 +1024,36 @@ class Oydid
|
|
773
1024
|
"controller": did,
|
774
1025
|
"publicKeyMultibase": pubRevKey
|
775
1026
|
}]
|
1027
|
+
if !delegateDocKeys.nil? && delegateDocKeys.count > 0
|
1028
|
+
i = 0
|
1029
|
+
wd["capabilityDelegation"] = []
|
1030
|
+
delegateDocKeys.each do |key|
|
1031
|
+
i += 1
|
1032
|
+
delegaton_object = {
|
1033
|
+
"id": did + "#key-delegate-doc-" + i.to_s,
|
1034
|
+
"type": "Ed25519VerificationKey2020",
|
1035
|
+
"controller": did,
|
1036
|
+
"publicKeyMultibase": key
|
1037
|
+
}
|
1038
|
+
wd["capabilityDelegation"] << delegaton_object
|
1039
|
+
end
|
1040
|
+
end
|
1041
|
+
if !delegateRevKeys.nil? && delegateRevKeys.count > 0
|
1042
|
+
i = 0
|
1043
|
+
if wd["capabilityDelegation"].nil?
|
1044
|
+
wd["capabilityDelegation"] = []
|
1045
|
+
end
|
1046
|
+
delegateRevKeys.each do |key|
|
1047
|
+
i += 1
|
1048
|
+
delegaton_object = {
|
1049
|
+
"id": did + "#key-delegate-rev-" + i.to_s,
|
1050
|
+
"type": "Ed25519VerificationKey2020",
|
1051
|
+
"controller": did,
|
1052
|
+
"publicKeyMultibase": key
|
1053
|
+
}
|
1054
|
+
wd["capabilityDelegation"] << delegaton_object
|
1055
|
+
end
|
1056
|
+
end
|
776
1057
|
|
777
1058
|
equivalentIds = []
|
778
1059
|
did_info["log"].each do |log|
|
@@ -784,7 +1065,7 @@ class Oydid
|
|
784
1065
|
end
|
785
1066
|
end unless did_info["log"].nil?
|
786
1067
|
if equivalentIds.length > 0
|
787
|
-
wd[
|
1068
|
+
wd["alsoKnownAs"] = equivalentIds
|
788
1069
|
end
|
789
1070
|
|
790
1071
|
if didDoc["doc"].is_a?(Hash) && !didDoc["doc"]["service"].nil?
|
@@ -807,28 +1088,62 @@ class Oydid
|
|
807
1088
|
else
|
808
1089
|
payload = nil
|
809
1090
|
if didDoc["doc"].is_a?(Hash)
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
1091
|
+
if didDoc["doc"] != {}
|
1092
|
+
didDoc = didDoc["doc"]
|
1093
|
+
if didDoc["authentication"].to_s != ""
|
1094
|
+
new_authentication = []
|
1095
|
+
didDoc["authentication"].each do |el|
|
1096
|
+
new_el = el.transform_keys(&:to_s)
|
1097
|
+
new_el["id"] = percent_encode(did) + new_el["id"]
|
1098
|
+
new_authentication << new_el
|
1099
|
+
end unless didDoc["authentication"].nil?
|
1100
|
+
if new_authentication.length > 0
|
1101
|
+
wd["authentication"] = new_authentication
|
1102
|
+
didDoc.delete("authentication")
|
1103
|
+
end
|
1104
|
+
end
|
1105
|
+
if didDoc["assertionMethod"].to_s != ""
|
1106
|
+
wd["assertionMethod"] = didDoc["assertionMethod"]
|
1107
|
+
didDoc.delete("assertionMethod")
|
1108
|
+
end
|
1109
|
+
if didDoc["keyAgreement"].to_s != ""
|
1110
|
+
new_keyAgreement = []
|
1111
|
+
didDoc["keyAgreement"].each do |el|
|
1112
|
+
new_el = el.transform_keys(&:to_s)
|
1113
|
+
new_el["id"] = percent_encode(did) + new_el["id"]
|
1114
|
+
new_keyAgreement << new_el
|
1115
|
+
end unless didDoc["keyAgreement"].nil?
|
1116
|
+
if new_keyAgreement.length > 0
|
1117
|
+
wd["keyAgreement"] = new_keyAgreement
|
1118
|
+
didDoc.delete("keyAgreement")
|
1119
|
+
end
|
1120
|
+
end
|
1121
|
+
if didDoc["capabilityInvocation"].to_s != ""
|
1122
|
+
wd["capabilityInvocation"] = didDoc["capabilityInvocation"]
|
1123
|
+
didDoc.delete("capabilityInvocation")
|
1124
|
+
end
|
1125
|
+
if didDoc["capabilityDelegation"].to_s != ""
|
1126
|
+
wd["capabilityDelegation"] = didDoc["capabilityDelegation"]
|
1127
|
+
didDoc.delete("capabilityDelegation")
|
1128
|
+
end
|
1129
|
+
if didDoc["alsoKnownAs"].to_s != ""
|
1130
|
+
if didDoc["alsoKnownAs"].is_a?(Array)
|
1131
|
+
dda = didDoc["alsoKnownAs"]
|
1132
|
+
else
|
1133
|
+
dda = [didDoc["alsoKnownAs"]]
|
1134
|
+
end
|
1135
|
+
if wd["alsoKnownAs"].nil?
|
1136
|
+
wd["alsoKnownAs"] = dda
|
1137
|
+
else
|
1138
|
+
wd["alsoKnownAs"] += dda
|
1139
|
+
end
|
1140
|
+
didDoc.delete("alsoKnownAs")
|
1141
|
+
end
|
1142
|
+
payload = didDoc
|
1143
|
+
if payload == {}
|
1144
|
+
payload = nil
|
1145
|
+
end
|
830
1146
|
end
|
831
|
-
payload = didDoc
|
832
1147
|
else
|
833
1148
|
payload = didDoc["doc"]
|
834
1149
|
end
|
@@ -858,7 +1173,7 @@ class Oydid
|
|
858
1173
|
end
|
859
1174
|
|
860
1175
|
|
861
|
-
|
1176
|
+
def self.w3c_legacy(did_info, options)
|
862
1177
|
did = did_info["did"]
|
863
1178
|
if !did.start_with?("did:oyd:")
|
864
1179
|
did = "did:oyd:" + did
|