oydid 0.5.5 → 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 +175 -8
- data/lib/oydid/log.rb +52 -5
- data/lib/oydid.rb +90 -13
- metadata +99 -99
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e6e4098a11373a9b648447adbc7f6ec7b89d0efced1bb920080797dfdc5d17de
|
4
|
+
data.tar.gz: bf33e0ddd9abd26a04d92fa945db9183f87000e246f154aa509c55e9b5f4aefd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6175bb5b1d28443b26573754dd92f97094a7c139d032252c541a73dbd5019028466847b9602b380cd1ca814eeb3e7843576f7a86a77808a5268af62fe89dfba7
|
7
|
+
data.tar.gz: a719e0d4c5530340be4ad4a284ffadd46924db95a81c0212fb61e38243cac6a133bf8568ed6407a54d026502048bb06394f5e737f12e0ad588b65c43411bb046
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.5.
|
1
|
+
0.5.6
|
data/lib/oydid/basic.rb
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
class Hash
|
5
|
+
def except(*keys)
|
6
|
+
self.reject { |key, _| keys.include?(key) }
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
4
10
|
class Oydid
|
5
11
|
|
6
12
|
# basic functions ---------------------------
|
7
|
-
# %w[multibases multihashes rbnacl json].each { |f| require f }
|
13
|
+
# %w[multibases multihashes rbnacl json multicodecs].each { |f| require f }
|
8
14
|
def self.multi_encode(message, options)
|
9
15
|
method = options[:encode] || DEFAULT_ENCODING rescue DEFAULT_ENCODING
|
10
16
|
case method
|
@@ -98,7 +104,7 @@ class Oydid
|
|
98
104
|
end
|
99
105
|
|
100
106
|
# key management ----------------------------
|
101
|
-
def self.generate_private_key(input, method = "ed25519-priv", options)
|
107
|
+
def self.generate_private_key(input, method = "ed25519-priv", options = {})
|
102
108
|
begin
|
103
109
|
omc = Multicodecs[method].code
|
104
110
|
rescue
|
@@ -108,15 +114,18 @@ class Oydid
|
|
108
114
|
case Multicodecs[method].name
|
109
115
|
when 'ed25519-priv'
|
110
116
|
if input != ""
|
111
|
-
raw_key = Ed25519::SigningKey.new(RbNaCl::Hash.sha256(input))
|
117
|
+
raw_key = Ed25519::SigningKey.new(RbNaCl::Hash.sha256(input))
|
112
118
|
else
|
113
|
-
raw_key = Ed25519::SigningKey.generate
|
119
|
+
raw_key = Ed25519::SigningKey.generate
|
114
120
|
end
|
121
|
+
raw_key = raw_key.to_bytes
|
122
|
+
length = raw_key.bytesize
|
115
123
|
else
|
116
124
|
return [nil, "unsupported key codec"]
|
117
125
|
end
|
118
|
-
|
126
|
+
|
119
127
|
encoded = multi_encode([omc, length, raw_key].pack("SCa#{length}"), options)
|
128
|
+
# encoded = multi_encode(raw_key.to_bytes, options)
|
120
129
|
if encoded.first.nil?
|
121
130
|
return [nil, encoded.last]
|
122
131
|
else
|
@@ -124,7 +133,7 @@ class Oydid
|
|
124
133
|
end
|
125
134
|
end
|
126
135
|
|
127
|
-
def self.public_key(private_key, options, method = "ed25519-pub")
|
136
|
+
def self.public_key(private_key, options = {}, method = "ed25519-pub")
|
128
137
|
code, length, digest = multi_decode(private_key).first.unpack('SCa*')
|
129
138
|
case Multicodecs[code].name
|
130
139
|
when 'ed25519-priv'
|
@@ -136,6 +145,7 @@ class Oydid
|
|
136
145
|
else
|
137
146
|
return [nil, "unsupported key codec"]
|
138
147
|
end
|
148
|
+
# encoded = multi_encode(public_key.to_bytes, options)
|
139
149
|
length = public_key.to_bytes.bytesize
|
140
150
|
encoded = multi_encode([Multicodecs[method].code, length, public_key].pack("CCa#{length}"), options)
|
141
151
|
if encoded.first.nil?
|
@@ -323,10 +333,160 @@ class Oydid
|
|
323
333
|
end
|
324
334
|
end
|
325
335
|
|
336
|
+
# key="812b578d2e357270cbbd26a9dd44f93e5c8a3b44462e271348ce8f742dfe08144d06fd1f64d5a15c5b21564695d0dca9d65af322e8f96ef394400fe255d288cf"
|
337
|
+
def jweh(key)
|
338
|
+
pub_key=key[-64..-1]
|
339
|
+
prv_key=key[0..-65]
|
340
|
+
|
341
|
+
hex_pub=pub_key
|
342
|
+
bin_pub=[hex_pub].pack('H*')
|
343
|
+
int_pub=RbNaCl::PublicKey.new(bin_pub)
|
344
|
+
len_pub=int_pub.to_bytes.bytesize
|
345
|
+
enc_pub=Oydid.multi_encode([Multicodecs["x25519-pub"].code,len_pub,int_pub].pack("CCa#{len_pub}"),{}).first
|
346
|
+
|
347
|
+
hex_prv=prv_key
|
348
|
+
bin_prv=[hex_prv].pack('H*')
|
349
|
+
int_prv=RbNaCl::PrivateKey.new(bin_prv)
|
350
|
+
len_prv=int_prv.to_bytes.bytesize
|
351
|
+
enc_prv=Oydid.multi_encode([Multicodecs["ed25519-priv"].code,len_prv,int_prv].pack("SCa#{len_prv}"),{}).first
|
352
|
+
return [enc_pub, enc_prv]
|
353
|
+
end
|
354
|
+
|
355
|
+
# public_key=jweh(key).first
|
356
|
+
# require 'oydid'
|
357
|
+
# message="hallo"
|
358
|
+
# public_key="z6Mv4uEoFYJ369NoE9xUzxG5sm8KpPnvHX6YH6GsYFGSQ32J"
|
359
|
+
# jwe=Oydid.encryptJWE(message, public_key).first
|
360
|
+
def self.encryptJWE(message, public_key, options = {})
|
361
|
+
|
362
|
+
jwe_header = {"enc":"XC20P"}
|
363
|
+
recipient_alg = 'ECDH-ES+XC20PKW'
|
364
|
+
|
365
|
+
# Content Encryption ---
|
366
|
+
# random nonce for XChaCha20-Poly1305: uses a 192-bit nonce (24 bytes)
|
367
|
+
cnt_nnc = RbNaCl::Random.random_bytes(RbNaCl::AEAD::XChaCha20Poly1305IETF.nonce_bytes)
|
368
|
+
# random key for XChaCha20-Poly1305: uses a 256-bit key (32 bytes)
|
369
|
+
cnt_key = RbNaCl::Random.random_bytes(RbNaCl::AEAD::XChaCha20Poly1305IETF.key_bytes)
|
370
|
+
# addtional data
|
371
|
+
cnt_aad = jwe_header.to_json
|
372
|
+
# setup XChaCha20-Poly1305 for Authenticated Encryption with Associated Data (AEAD)
|
373
|
+
cnt_aead = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(cnt_key)
|
374
|
+
# encrypt
|
375
|
+
msg_enc = cnt_aead.encrypt(cnt_nnc, message, cnt_aad)
|
376
|
+
cnt_enc = msg_enc[0...-cnt_aead.tag_bytes]
|
377
|
+
cnt_tag = msg_enc[-cnt_aead.tag_bytes .. -1]
|
378
|
+
|
379
|
+
# Key Encryption ---
|
380
|
+
snd_prv = RbNaCl::PrivateKey.generate
|
381
|
+
code, length, digest = Oydid.multi_decode(public_key).first.unpack('CCa*')
|
382
|
+
buffer = RbNaCl::Util.zeros(RbNaCl::Boxes::Curve25519XSalsa20Poly1305::PublicKey::BYTES)
|
383
|
+
RbNaCl::Signatures::Ed25519::VerifyKey.crypto_sign_ed25519_pk_to_curve25519(buffer, digest)
|
384
|
+
shared_secret = RbNaCl::GroupElement.new(buffer).mult(snd_prv.to_bytes)
|
385
|
+
jwe_const = [0, 0, 0, 1] +
|
386
|
+
shared_secret.to_bytes.unpack('C*') +
|
387
|
+
[0,0,0,15] +
|
388
|
+
recipient_alg.bytes +
|
389
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]
|
390
|
+
kek = RbNaCl::Hash.sha256(jwe_const.pack('C*'))
|
391
|
+
snd_nnc = RbNaCl::Random.random_bytes(RbNaCl::AEAD::XChaCha20Poly1305IETF.nonce_bytes)
|
392
|
+
snd_aead = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(kek)
|
393
|
+
snd_enc = snd_aead.encrypt(snd_nnc, cnt_key, nil)
|
394
|
+
snd_key = snd_enc[0...-snd_aead.tag_bytes]
|
395
|
+
snd_aut = snd_enc[-snd_aead.tag_bytes .. -1]
|
396
|
+
|
397
|
+
# create JWE ---
|
398
|
+
jwe_protected = Base64.urlsafe_encode64(jwe_header.to_json).delete("=")
|
399
|
+
jwe_encrypted_key = Base64.urlsafe_encode64(snd_key).delete("=")
|
400
|
+
jwe_init_vector = Base64.urlsafe_encode64(cnt_nnc).delete("=")
|
401
|
+
jwe_cipher_text = Base64.urlsafe_encode64(cnt_enc).delete("=")
|
402
|
+
jwe_auth_tag = Base64.urlsafe_encode64(cnt_tag).delete("=")
|
403
|
+
rcp_nnc_enc = Base64.urlsafe_encode64(snd_nnc).delete("=")
|
404
|
+
rcp_tag_enc = Base64.urlsafe_encode64(snd_aut).delete("=")
|
405
|
+
|
406
|
+
jwe_full = {
|
407
|
+
protected: jwe_protected,
|
408
|
+
iv: jwe_init_vector,
|
409
|
+
ciphertext: jwe_cipher_text,
|
410
|
+
tag: jwe_auth_tag,
|
411
|
+
recipients: [
|
412
|
+
{
|
413
|
+
encrypted_key: jwe_encrypted_key,
|
414
|
+
header: {
|
415
|
+
alg: recipient_alg,
|
416
|
+
iv: rcp_nnc_enc,
|
417
|
+
tag: rcp_tag_enc,
|
418
|
+
epk: {
|
419
|
+
kty: "OKP",
|
420
|
+
crv: "X25519",
|
421
|
+
x: Base64.urlsafe_encode64(snd_prv.public_key.to_bytes).delete("=")
|
422
|
+
}
|
423
|
+
}
|
424
|
+
}
|
425
|
+
]
|
426
|
+
}
|
427
|
+
|
428
|
+
jwe = jwe_protected
|
429
|
+
jwe += "." + jwe_encrypted_key
|
430
|
+
jwe += "." + jwe_init_vector
|
431
|
+
jwe += "." + jwe_cipher_text
|
432
|
+
jwe += "." + jwe_auth_tag
|
433
|
+
|
434
|
+
return [jwe_full, ""]
|
435
|
+
end
|
436
|
+
|
437
|
+
# require 'oydid'
|
438
|
+
# message = '{"protected":"eyJlbmMiOiJYQzIwUCJ9","iv":"G24pK06HSbL0vTlRZMIEBLfa074tJ1tq","ciphertext":"N5bgUr8","tag":"CJeq8cuaercgoBZmrYoGUA","recipients":[{"encrypted_key":"chR8HQh1CRYRU4TdlfBbvon4fcb5PKfWPSo0SgkMC_8","header":{"alg":"ECDH-ES+XC20PKW","iv":"K7lUo8shyJhxC7Nl45VlXes4tbDeZyBL","tag":"2sLCGRv70ESqEAqos3ZhSg","epk":{"kty":"OKP","crv":"X25519","x":"ZpnKcI7Kac6HPwVAGwM0PBweTFKM6wHHVljTHMRWpD4"}}}]}'
|
439
|
+
# message = jwe.to_json
|
440
|
+
# private_key = "z1S5USmDosHvi2giCLHCCgcq3Cd31mrhMcUy2fcfszxxLkD7"
|
441
|
+
# Oydid.decryptJWE(jwe.to_json, private_key)
|
442
|
+
def self.decryptJWE(message, private_key, options = {})
|
443
|
+
|
444
|
+
# JWE parsing
|
445
|
+
jwe_full = JSON.parse(message)
|
446
|
+
snd_pub_enc = jwe_full["recipients"].first["header"]["epk"]["x"]
|
447
|
+
snd_key_enc = jwe_full["recipients"].first["encrypted_key"]
|
448
|
+
snd_nnc_enc = jwe_full["recipients"].first["header"]["iv"]
|
449
|
+
snd_tag_enc = jwe_full["recipients"].first["header"]["tag"]
|
450
|
+
cnt_cip_enc = jwe_full["ciphertext"]
|
451
|
+
cnt_tag_enc = jwe_full["tag"]
|
452
|
+
cnt_nnc_enc = jwe_full["iv"]
|
453
|
+
cnt_aad_enc = jwe_full["protected"]
|
454
|
+
recipient_alg = jwe_full["recipients"].first["header"]["alg"]
|
455
|
+
|
456
|
+
snd_pub = Base64.urlsafe_decode64(snd_pub_enc)
|
457
|
+
snd_nnc = Base64.urlsafe_decode64(snd_nnc_enc)
|
458
|
+
snd_key = Base64.urlsafe_decode64(snd_key_enc)
|
459
|
+
snd_tag = Base64.urlsafe_decode64(snd_tag_enc)
|
460
|
+
cnt_nnc = Base64.urlsafe_decode64(cnt_nnc_enc)
|
461
|
+
cnt_cip = Base64.urlsafe_decode64(cnt_cip_enc)
|
462
|
+
cnt_tag = Base64.urlsafe_decode64(cnt_tag_enc)
|
463
|
+
cnt_aad = Base64.urlsafe_decode64(cnt_aad_enc)
|
464
|
+
|
465
|
+
# Key Decryption
|
466
|
+
code, length, digest = Oydid.multi_decode(private_key).first.unpack('SCa*')
|
467
|
+
buffer = RbNaCl::Util.zeros(RbNaCl::Boxes::Curve25519XSalsa20Poly1305::PublicKey::BYTES)
|
468
|
+
RbNaCl::Signatures::Ed25519::SigningKey.crypto_sign_ed25519_sk_to_curve25519(buffer, digest)
|
469
|
+
shared_secret = RbNaCl::GroupElement.new(snd_pub).mult(buffer)
|
470
|
+
jwe_const = [0, 0, 0, 1] +
|
471
|
+
shared_secret.to_bytes.unpack('C*') +
|
472
|
+
[0,0,0,15] +
|
473
|
+
recipient_alg.bytes +
|
474
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]
|
475
|
+
kek = RbNaCl::Hash.sha256(jwe_const.pack('C*'))
|
476
|
+
snd_aead = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(kek)
|
477
|
+
cnt_key = snd_aead.decrypt(snd_nnc, snd_key+snd_tag, nil)
|
478
|
+
|
479
|
+
# Content Decryption
|
480
|
+
cnt_aead = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(cnt_key)
|
481
|
+
cnt_dec = cnt_aead.decrypt(cnt_nnc, cnt_cip+cnt_tag, cnt_aad)
|
482
|
+
|
483
|
+
return [cnt_dec, ""]
|
484
|
+
end
|
485
|
+
|
326
486
|
def self.read_private_key(filename, options)
|
327
487
|
begin
|
328
488
|
f = File.open(filename)
|
329
|
-
key_encoded = f.read
|
489
|
+
key_encoded = f.read.strip
|
330
490
|
f.close
|
331
491
|
rescue
|
332
492
|
return [nil, "cannot read file"]
|
@@ -357,6 +517,9 @@ class Oydid
|
|
357
517
|
when 'ed25519-pub'
|
358
518
|
verify_key = Ed25519::VerifyKey.new(digest)
|
359
519
|
return [verify_key, ""]
|
520
|
+
when 'x25519-pub'
|
521
|
+
pub_key = RbNaCl::PublicKey.new(digest)
|
522
|
+
return [pub_key, ""]
|
360
523
|
else
|
361
524
|
return [nil, "unsupported key codec"]
|
362
525
|
end
|
@@ -404,7 +567,11 @@ class Oydid
|
|
404
567
|
case doc_location
|
405
568
|
when /^http/
|
406
569
|
doc_location = doc_location.sub("%3A%2F%2F","://").sub("%3A", ":")
|
407
|
-
|
570
|
+
option_str = ""
|
571
|
+
if options[:followAlsoKnownAs]
|
572
|
+
option_str = "?followAlsoKnownAs=true"
|
573
|
+
end
|
574
|
+
retVal = HTTParty.get(doc_location + "/doc/" + doc_identifier + option_str)
|
408
575
|
if retVal.code != 200
|
409
576
|
msg = retVal.parsed_response["error"].to_s rescue ""
|
410
577
|
if msg.to_s == ""
|
data/lib/oydid/log.rb
CHANGED
@@ -137,17 +137,26 @@ class Oydid
|
|
137
137
|
terminate_overall = 0
|
138
138
|
terminate_index = nil
|
139
139
|
logs.each do |el|
|
140
|
-
if el["op"].to_i == 0
|
140
|
+
if el["op"].to_i == 0 # TERMINATE
|
141
141
|
if dag.vertices[i].successors.length == 0
|
142
142
|
terminate_entries += 1
|
143
143
|
terminate_index = i
|
144
144
|
end
|
145
145
|
terminate_overall += 1
|
146
|
+
elsif el["op"].to_i == 1 # REVOKE
|
147
|
+
# get terminate_index for revoked DIDs
|
148
|
+
if dag.vertices[i].successors.length == 0
|
149
|
+
dag.vertices[i].predecessors.each do |l|
|
150
|
+
if logs[l[:id]]["op"].to_i == 0 # TERMINATE
|
151
|
+
terminate_index = l[:id]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
146
155
|
end
|
147
156
|
i += 1
|
148
157
|
end unless logs.nil?
|
149
158
|
|
150
|
-
if terminate_entries != 1 && !options[:log_complete]
|
159
|
+
if terminate_entries != 1 && !options[:log_complete] && !options[:followAlsoKnownAs]
|
151
160
|
if options[:silent].nil? || !options[:silent]
|
152
161
|
return [nil, nil, nil, "cannot resolve DID" ]
|
153
162
|
end
|
@@ -217,7 +226,7 @@ class Oydid
|
|
217
226
|
end unless s.predecessors.length < 2
|
218
227
|
dag2array(dag, log_array, s[:id], result, options)
|
219
228
|
end unless dag.vertices[index].successors.count == 0
|
220
|
-
result
|
229
|
+
result.uniq
|
221
230
|
end
|
222
231
|
|
223
232
|
def self.dag2array_terminate(dag, log_array, index, result, options)
|
@@ -237,7 +246,7 @@ class Oydid
|
|
237
246
|
end
|
238
247
|
end unless dag.vertices[index].nil?
|
239
248
|
result << log_array[index]
|
240
|
-
result
|
249
|
+
result.uniq
|
241
250
|
end
|
242
251
|
|
243
252
|
def self.dag_update(currentDID, options)
|
@@ -490,7 +499,45 @@ class Oydid
|
|
490
499
|
break
|
491
500
|
end
|
492
501
|
when 1 # revocation log entry
|
493
|
-
#
|
502
|
+
# handle DID Rotation
|
503
|
+
if (i == (currentDID["log"].length-1))
|
504
|
+
if options[:followAlsoKnownAs]
|
505
|
+
current_doc = currentDID["doc"]
|
506
|
+
if current_doc["doc"].transform_keys(&:to_s).has_key?("alsoKnownAs")
|
507
|
+
rotate_DID = current_doc["doc"].transform_keys(&:to_s)["alsoKnownAs"]
|
508
|
+
if rotate_DID.start_with?("did:")
|
509
|
+
rotate_DID_method = rotate_DID.split(":").take(2).join(":")
|
510
|
+
did_orig = currentDID["did"]
|
511
|
+
if !did_orig.start_with?("did:oyd")
|
512
|
+
did_orig = "did:oyd:" + did_orig
|
513
|
+
end
|
514
|
+
case rotate_DID_method
|
515
|
+
when "did:ebsi"
|
516
|
+
public_resolver = DEFAULT_PUBLIC_RESOLVER
|
517
|
+
rotate_DID_Document = HTTParty.get(public_resolver + rotate_DID)
|
518
|
+
rotate_ddoc = JSON.parse(rotate_DID_Document.parsed_response)
|
519
|
+
rotate_ddoc = rotate_ddoc.except("didDocumentMetadata", "didResolutionMetadata")
|
520
|
+
|
521
|
+
# checks
|
522
|
+
# 1) is original DID revoked -> fulfilled, otherwise we would not be in this branch
|
523
|
+
# 2) das new DID reference back original DID
|
524
|
+
currentDID["did"] = rotate_DID
|
525
|
+
currentDID["doc"]["doc"] = rotate_ddoc
|
526
|
+
if verification_output
|
527
|
+
currentDID["verification"] += "DID rotation to: " + rotate_DID.to_s + "\n"
|
528
|
+
currentDID["verification"] += "✅ original DID (" + did_orig + ") revoked and referenced in alsoKnownAs\n"
|
529
|
+
currentDID["verification"] += "(Details: https://ownyourdata.github.io/oydid/#did_rotation)" + "\n\n"
|
530
|
+
end
|
531
|
+
when "did:oyd"
|
532
|
+
puts "try to resolve did:oyd with our own resolver"
|
533
|
+
puts "add verification text"
|
534
|
+
else
|
535
|
+
# do nothing: DID Rotation is not supported for this DID method yet
|
536
|
+
end
|
537
|
+
end
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
494
541
|
when 5 # DELEGATE
|
495
542
|
# do nothing
|
496
543
|
else
|
data/lib/oydid.rb
CHANGED
@@ -24,10 +24,14 @@ 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)
|
30
|
-
|
31
35
|
if did.to_s == ""
|
32
36
|
return [nil, "missing DID"]
|
33
37
|
end
|
@@ -133,14 +137,24 @@ class Oydid
|
|
133
137
|
end
|
134
138
|
end
|
135
139
|
end
|
136
|
-
|
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
|
137
153
|
if options[:log_complete]
|
138
154
|
currentDID["log"] = log_array
|
139
155
|
end
|
140
|
-
|
141
156
|
return [currentDID, ""]
|
142
157
|
end
|
143
|
-
|
144
158
|
end
|
145
159
|
|
146
160
|
def self.create(content, options)
|
@@ -289,6 +303,27 @@ class Oydid
|
|
289
303
|
pubRevoKey = public_key(revocationKey, options).first
|
290
304
|
did_key = publicKey + ":" + pubRevoKey
|
291
305
|
|
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 = {}
|
320
|
+
end
|
321
|
+
did_doc[:authentication] = [{
|
322
|
+
"id": "#key-doc"
|
323
|
+
}]
|
324
|
+
did_doc = did_doc.transform_keys(&:to_s)
|
325
|
+
end
|
326
|
+
|
292
327
|
# build new revocation document
|
293
328
|
subDid = {"doc": did_doc, "key": did_key}.to_json
|
294
329
|
retVal = multi_hash(canonical(subDid), LOG_HASH_OPTIONS)
|
@@ -494,14 +529,17 @@ class Oydid
|
|
494
529
|
success, msg = publish(did, didDocument, logs, options)
|
495
530
|
|
496
531
|
if success
|
532
|
+
didDocumentBackup = Marshal.load(Marshal.dump(didDocument))
|
497
533
|
w3c_input = {
|
498
|
-
"did" => did,
|
499
|
-
"doc" => didDocument
|
534
|
+
"did" => did.clone,
|
535
|
+
"doc" => didDocument.clone
|
500
536
|
}
|
537
|
+
doc_w3c = Oydid.w3c(w3c_input, options)
|
538
|
+
didDocument = didDocumentBackup
|
501
539
|
retVal = {
|
502
540
|
"did" => did,
|
503
541
|
"doc" => didDocument,
|
504
|
-
"doc_w3c" =>
|
542
|
+
"doc_w3c" => doc_w3c,
|
505
543
|
"log" => logs
|
506
544
|
}
|
507
545
|
if options[:return_secrets]
|
@@ -927,12 +965,19 @@ class Oydid
|
|
927
965
|
end
|
928
966
|
|
929
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
|
930
975
|
did = percent_encode(did_info["did"])
|
931
976
|
if !did.start_with?("did:oyd:")
|
932
977
|
did = "did:oyd:" + did
|
933
978
|
end
|
934
979
|
|
935
|
-
didDoc = did_info.transform_keys(&:to_s)["doc"]
|
980
|
+
didDoc = did_info.dup.transform_keys(&:to_s)["doc"]
|
936
981
|
pubDocKey = didDoc["key"].split(":")[0] rescue ""
|
937
982
|
pubRevKey = didDoc["key"].split(":")[1] rescue ""
|
938
983
|
delegateDocKeys = getDelegatedPubKeysFromDID(did, "doc").first - [pubDocKey] rescue []
|
@@ -1020,7 +1065,7 @@ class Oydid
|
|
1020
1065
|
end
|
1021
1066
|
end unless did_info["log"].nil?
|
1022
1067
|
if equivalentIds.length > 0
|
1023
|
-
wd[
|
1068
|
+
wd["alsoKnownAs"] = equivalentIds
|
1024
1069
|
end
|
1025
1070
|
|
1026
1071
|
if didDoc["doc"].is_a?(Hash) && !didDoc["doc"]["service"].nil?
|
@@ -1046,16 +1091,32 @@ class Oydid
|
|
1046
1091
|
if didDoc["doc"] != {}
|
1047
1092
|
didDoc = didDoc["doc"]
|
1048
1093
|
if didDoc["authentication"].to_s != ""
|
1049
|
-
|
1050
|
-
didDoc
|
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
|
1051
1104
|
end
|
1052
1105
|
if didDoc["assertionMethod"].to_s != ""
|
1053
1106
|
wd["assertionMethod"] = didDoc["assertionMethod"]
|
1054
1107
|
didDoc.delete("assertionMethod")
|
1055
1108
|
end
|
1056
1109
|
if didDoc["keyAgreement"].to_s != ""
|
1057
|
-
|
1058
|
-
didDoc
|
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
|
1059
1120
|
end
|
1060
1121
|
if didDoc["capabilityInvocation"].to_s != ""
|
1061
1122
|
wd["capabilityInvocation"] = didDoc["capabilityInvocation"]
|
@@ -1065,7 +1126,23 @@ class Oydid
|
|
1065
1126
|
wd["capabilityDelegation"] = didDoc["capabilityDelegation"]
|
1066
1127
|
didDoc.delete("capabilityDelegation")
|
1067
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
|
1068
1142
|
payload = didDoc
|
1143
|
+
if payload == {}
|
1144
|
+
payload = nil
|
1145
|
+
end
|
1069
1146
|
end
|
1070
1147
|
else
|
1071
1148
|
payload = didDoc["doc"]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: oydid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christoph Fabianek
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: simple_dag
|
@@ -327,147 +327,147 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
327
327
|
- !ruby/object:Gem::Version
|
328
328
|
version: '0'
|
329
329
|
requirements: []
|
330
|
-
rubygems_version: 3.
|
330
|
+
rubygems_version: 3.5.4
|
331
331
|
signing_key:
|
332
332
|
specification_version: 4
|
333
333
|
summary: Own Your Decentralized Identifier for Ruby.
|
334
334
|
test_files:
|
335
|
-
- spec/spec_helper.rb
|
336
335
|
- spec/oydid_spec.rb
|
337
|
-
- spec/
|
336
|
+
- spec/spec_helper.rb
|
338
337
|
- spec/input/basic/arrays.json
|
338
|
+
- spec/input/basic/french.json
|
339
339
|
- spec/input/basic/structures.json
|
340
340
|
- spec/input/basic/unicode.json
|
341
|
-
- spec/input/basic/wierd.json
|
342
341
|
- spec/input/basic/values.json
|
343
|
-
- spec/
|
342
|
+
- spec/input/basic/wierd.json
|
344
343
|
- spec/output/basic/arrays.json
|
344
|
+
- spec/output/basic/french.json
|
345
345
|
- spec/output/basic/structures.json
|
346
346
|
- spec/output/basic/unicode.json
|
347
|
-
- spec/output/basic/wierd.json
|
348
347
|
- spec/output/basic/values.json
|
349
|
-
- spec/
|
350
|
-
- spec/input/basic/
|
351
|
-
- spec/input/basic/sample_retrieve_document.doc
|
352
|
-
- spec/input/basic/sample_b58_dec.doc
|
353
|
-
- spec/input/basic/sample_sha2-512_b58_hash.doc
|
354
|
-
- spec/input/basic/sample_b32_enc.doc
|
355
|
-
- spec/input/basic/sample_get_location.doc
|
356
|
-
- spec/input/basic/sample_invalid2_verify.doc
|
357
|
-
- spec/input/basic/sample_valid_privkey.doc
|
348
|
+
- spec/output/basic/wierd.json
|
349
|
+
- spec/input/basic/sample2_get_location.doc
|
358
350
|
- spec/input/basic/sample2_retrieve_document.doc
|
359
|
-
- spec/input/basic/
|
360
|
-
- spec/input/basic/sample_blake2b-16_b16_hash.doc
|
351
|
+
- spec/input/basic/sample3_retrieve_document.doc
|
361
352
|
- spec/input/basic/sample4_retrieve_document.doc
|
362
|
-
- spec/input/basic/
|
363
|
-
- spec/input/basic/
|
353
|
+
- spec/input/basic/sample5_retrieve_document.doc
|
354
|
+
- spec/input/basic/sample_b16_dec.doc
|
364
355
|
- spec/input/basic/sample_b16_enc.doc
|
356
|
+
- spec/input/basic/sample_b17_edec.doc
|
365
357
|
- spec/input/basic/sample_b17_enc.doc
|
366
|
-
- spec/input/basic/
|
358
|
+
- spec/input/basic/sample_b32_dec.doc
|
359
|
+
- spec/input/basic/sample_b32_enc.doc
|
360
|
+
- spec/input/basic/sample_b58_dec.doc
|
361
|
+
- spec/input/basic/sample_b58_enc.doc
|
367
362
|
- spec/input/basic/sample_b64_dec.doc
|
368
|
-
- spec/input/basic/
|
369
|
-
- spec/input/basic/
|
363
|
+
- spec/input/basic/sample_b64_enc.doc
|
364
|
+
- spec/input/basic/sample_blake2b-16_b16_hash.doc
|
365
|
+
- spec/input/basic/sample_blake2b-32_b32_hash.doc
|
370
366
|
- spec/input/basic/sample_blake2b-64_b58_hash.doc
|
371
|
-
- spec/input/basic/
|
367
|
+
- spec/input/basic/sample_get_location.doc
|
368
|
+
- spec/input/basic/sample_invalid2_readkey.doc
|
369
|
+
- spec/input/basic/sample_invalid2_verify.doc
|
370
|
+
- spec/input/basic/sample_invalid3_readkey.doc
|
372
371
|
- spec/input/basic/sample_invalid3_verify.doc
|
373
|
-
- spec/input/basic/
|
374
|
-
- spec/input/basic/sample_key.doc
|
375
|
-
- spec/input/basic/sample3_retrieve_document.doc
|
376
|
-
- spec/input/basic/sample_b17_edec.doc
|
377
|
-
- spec/input/basic/sample_verify.doc
|
378
|
-
- spec/input/basic/sample5_retrieve_document.doc
|
379
|
-
- spec/input/basic/sample_sha3-224_b64_hash.doc
|
380
|
-
- spec/input/basic/sample2_get_location.doc
|
372
|
+
- spec/input/basic/sample_invalid_privkey.doc
|
381
373
|
- spec/input/basic/sample_invalid_readkey.doc
|
382
|
-
- spec/input/basic/
|
374
|
+
- spec/input/basic/sample_invalid_sign.doc
|
383
375
|
- spec/input/basic/sample_invalid_verify.doc
|
376
|
+
- spec/input/basic/sample_key.doc
|
377
|
+
- spec/input/basic/sample_readkey.doc
|
378
|
+
- spec/input/basic/sample_retrieve_document.doc
|
384
379
|
- spec/input/basic/sample_sha2-256_b58_hash.doc
|
385
|
-
- spec/input/basic/
|
386
|
-
- spec/input/
|
387
|
-
- spec/input/
|
388
|
-
- spec/input/
|
389
|
-
- spec/input/
|
390
|
-
- spec/input/
|
391
|
-
- spec/input/log/sample3_dag_did.doc
|
380
|
+
- spec/input/basic/sample_sha2-512_b58_hash.doc
|
381
|
+
- spec/input/basic/sample_sha3-224_b64_hash.doc
|
382
|
+
- spec/input/basic/sample_sign.doc
|
383
|
+
- spec/input/basic/sample_valid_privkey.doc
|
384
|
+
- spec/input/basic/sample_verify.doc
|
385
|
+
- spec/input/basic/zQmaBZTghn.doc
|
392
386
|
- spec/input/log/sample0_dag2array.doc
|
393
|
-
- spec/input/log/
|
394
|
-
- spec/input/log/sample_dag_update.doc
|
395
|
-
- spec/input/log/sample1_dag_update.doc
|
396
|
-
- spec/input/log/sample6_retrieve_log.doc
|
387
|
+
- spec/input/log/sample0_dag_did.doc
|
397
388
|
- spec/input/log/sample1_dag_did.doc
|
398
|
-
- spec/input/log/
|
399
|
-
- spec/input/log/
|
389
|
+
- spec/input/log/sample1_dag_update.doc
|
390
|
+
- spec/input/log/sample2_dag_did.doc
|
391
|
+
- spec/input/log/sample2_dag_update.doc
|
400
392
|
- spec/input/log/sample2_retrieve_log.doc
|
393
|
+
- spec/input/log/sample3_dag_did.doc
|
394
|
+
- spec/input/log/sample3_dag_update.doc
|
395
|
+
- spec/input/log/sample3_retrieve_log.doc
|
396
|
+
- spec/input/log/sample4_dag_did.doc
|
397
|
+
- spec/input/log/sample4_dag_update.doc
|
398
|
+
- spec/input/log/sample4_retrieve_log.doc
|
399
|
+
- spec/input/log/sample5_dag_update.doc
|
400
|
+
- spec/input/log/sample5_retrieve_log.doc
|
401
|
+
- spec/input/log/sample6_dag_update.doc
|
402
|
+
- spec/input/log/sample6_retrieve_log.doc
|
401
403
|
- spec/input/log/sample7_dag_update.doc
|
404
|
+
- spec/input/log/sample7_retrieve_log.doc
|
402
405
|
- spec/input/log/sample8_dag_update.doc
|
403
|
-
- spec/input/log/
|
404
|
-
- spec/input/log/
|
406
|
+
- spec/input/log/sample_addhash.doc
|
407
|
+
- spec/input/log/sample_dag_update.doc
|
405
408
|
- spec/input/log/sample_match_log.doc
|
406
|
-
- spec/input/log/
|
407
|
-
- spec/input/log/
|
408
|
-
- spec/input/log/sample3_retrieve_log.doc
|
409
|
-
- spec/input/log/sample2_dag_did.doc
|
410
|
-
- spec/input/log/sample3_dag_update.doc
|
409
|
+
- spec/input/log/sample_op1_addhash.doc
|
410
|
+
- spec/input/log/sample_retrieve_log.doc
|
411
411
|
- spec/input/main/sample0_read.doc
|
412
|
-
- spec/output/basic/
|
413
|
-
- spec/output/basic/sample_b58_enc.doc
|
414
|
-
- spec/output/basic/sample_retrieve_document.doc
|
415
|
-
- spec/output/basic/sample_b58_dec.doc
|
416
|
-
- spec/output/basic/sample_sha2-512_b58_hash.doc
|
417
|
-
- spec/output/basic/sample_b32_enc.doc
|
418
|
-
- spec/output/basic/sample_get_location.doc
|
419
|
-
- spec/output/basic/sample_invalid2_verify.doc
|
420
|
-
- spec/output/basic/sample_valid_privkey.doc
|
412
|
+
- spec/output/basic/sample2_get_location.doc
|
421
413
|
- spec/output/basic/sample2_retrieve_document.doc
|
422
|
-
- spec/output/basic/
|
414
|
+
- spec/output/basic/sample3_retrieve_document.doc
|
423
415
|
- spec/output/basic/sample4_retrieve_document.doc
|
424
|
-
- spec/output/basic/
|
425
|
-
- spec/output/basic/
|
416
|
+
- spec/output/basic/sample5_retrieve_document.doc
|
417
|
+
- spec/output/basic/sample_b16_dec.doc
|
426
418
|
- spec/output/basic/sample_b16_enc.doc
|
419
|
+
- spec/output/basic/sample_b17_edec.doc
|
427
420
|
- spec/output/basic/sample_b17_enc.doc
|
428
|
-
- spec/output/basic/
|
421
|
+
- spec/output/basic/sample_b32_dec.doc
|
422
|
+
- spec/output/basic/sample_b32_enc.doc
|
423
|
+
- spec/output/basic/sample_b58_dec.doc
|
424
|
+
- spec/output/basic/sample_b58_enc.doc
|
429
425
|
- spec/output/basic/sample_b64_dec.doc
|
430
|
-
- spec/output/basic/
|
431
|
-
- spec/output/basic/
|
426
|
+
- spec/output/basic/sample_b64_enc.doc
|
427
|
+
- spec/output/basic/sample_blake2b-16_b16_hash.doc
|
428
|
+
- spec/output/basic/sample_blake2b-32_b32_hash.doc
|
432
429
|
- spec/output/basic/sample_blake2b-64_b58_hash.doc
|
433
|
-
- spec/output/basic/
|
430
|
+
- spec/output/basic/sample_get_location.doc
|
431
|
+
- spec/output/basic/sample_invalid2_readkey.doc
|
432
|
+
- spec/output/basic/sample_invalid2_verify.doc
|
433
|
+
- spec/output/basic/sample_invalid3_readkey.doc
|
434
434
|
- spec/output/basic/sample_invalid3_verify.doc
|
435
|
-
- spec/output/basic/
|
436
|
-
- spec/output/basic/sample_key.doc
|
437
|
-
- spec/output/basic/sample3_retrieve_document.doc
|
438
|
-
- spec/output/basic/sample_b17_edec.doc
|
439
|
-
- spec/output/basic/sample_verify.doc
|
440
|
-
- spec/output/basic/sample5_retrieve_document.doc
|
441
|
-
- spec/output/basic/sample_sha3-224_b64_hash.doc
|
442
|
-
- spec/output/basic/sample2_get_location.doc
|
435
|
+
- spec/output/basic/sample_invalid_privkey.doc
|
443
436
|
- spec/output/basic/sample_invalid_readkey.doc
|
444
|
-
- spec/output/basic/
|
437
|
+
- spec/output/basic/sample_invalid_sign.doc
|
445
438
|
- spec/output/basic/sample_invalid_verify.doc
|
439
|
+
- spec/output/basic/sample_key.doc
|
440
|
+
- spec/output/basic/sample_readkey.doc
|
441
|
+
- spec/output/basic/sample_retrieve_document.doc
|
446
442
|
- spec/output/basic/sample_sha2-256_b58_hash.doc
|
447
|
-
- spec/output/basic/
|
448
|
-
- spec/output/
|
449
|
-
- spec/output/
|
450
|
-
- spec/output/
|
451
|
-
- spec/output/
|
452
|
-
- spec/output/log/sample_addhash.doc
|
453
|
-
- spec/output/log/sample3_dag_did.doc
|
443
|
+
- spec/output/basic/sample_sha2-512_b58_hash.doc
|
444
|
+
- spec/output/basic/sample_sha3-224_b64_hash.doc
|
445
|
+
- spec/output/basic/sample_sign.doc
|
446
|
+
- spec/output/basic/sample_valid_privkey.doc
|
447
|
+
- spec/output/basic/sample_verify.doc
|
454
448
|
- spec/output/log/sample0_dag2array.doc
|
455
|
-
- spec/output/log/
|
456
|
-
- spec/output/log/sample_dag_update.doc
|
457
|
-
- spec/output/log/sample1_dag_update.doc
|
458
|
-
- spec/output/log/sample6_retrieve_log.doc
|
449
|
+
- spec/output/log/sample0_dag_did.doc
|
459
450
|
- spec/output/log/sample1_dag_did.doc
|
460
|
-
- spec/output/log/
|
461
|
-
- spec/output/log/
|
451
|
+
- spec/output/log/sample1_dag_update.doc
|
452
|
+
- spec/output/log/sample2_dag_did.doc
|
453
|
+
- spec/output/log/sample2_dag_update.doc
|
462
454
|
- spec/output/log/sample2_retrieve_log.doc
|
455
|
+
- spec/output/log/sample3_dag_did.doc
|
456
|
+
- spec/output/log/sample3_dag_update.doc
|
457
|
+
- spec/output/log/sample3_retrieve_log.doc
|
458
|
+
- spec/output/log/sample4_dag_did.doc
|
459
|
+
- spec/output/log/sample4_dag_update.doc
|
460
|
+
- spec/output/log/sample4_retrieve_log.doc
|
461
|
+
- spec/output/log/sample5_dag_update.doc
|
462
|
+
- spec/output/log/sample5_retrieve_log.doc
|
463
|
+
- spec/output/log/sample6_dag_update.doc
|
464
|
+
- spec/output/log/sample6_retrieve_log.doc
|
463
465
|
- spec/output/log/sample7_dag_update.doc
|
466
|
+
- spec/output/log/sample7_retrieve_log.doc
|
464
467
|
- spec/output/log/sample8_dag_update.doc
|
465
|
-
- spec/output/log/
|
466
|
-
- spec/output/log/
|
468
|
+
- spec/output/log/sample_addhash.doc
|
469
|
+
- spec/output/log/sample_dag_update.doc
|
467
470
|
- spec/output/log/sample_match_log.doc
|
468
|
-
- spec/output/log/
|
469
|
-
- spec/output/log/
|
470
|
-
- spec/output/log/sample3_retrieve_log.doc
|
471
|
-
- spec/output/log/sample2_dag_did.doc
|
472
|
-
- spec/output/log/sample3_dag_update.doc
|
471
|
+
- spec/output/log/sample_op1_addhash.doc
|
472
|
+
- spec/output/log/sample_retrieve_log.doc
|
473
473
|
- spec/output/main/sample0_read.doc
|