oydid 0.4.0 → 0.4.4
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 +65 -3
- data/lib/oydid/didcomm.rb +120 -0
- data/lib/oydid/log.rb +17 -14
- data/lib/oydid.rb +12 -6
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 56c65853f4983bcb8214a4027986839c0503adcc1133933f817d8401d8881be7
|
4
|
+
data.tar.gz: 2a6048ab2023f8641e83e7b0162a8c15ef6bdb2c0855d70cef8d6c015ca34720
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 799e94f77486d60beb09c8b8f5101ac5789eb1d000052a1aa396121acbd13e0a935cb06fe0c1849ef4deb75b407da049d1111bd15f0d49bd2863671419da4480
|
7
|
+
data.tar.gz: d513c74e0b307a74a9c7fced1bd3ce5bdaef1941290a70aef447a183e3e31da5c44582d25969cd401ec25379412fb7850d55e5e547972e87ee39e4297e79866c
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4.
|
1
|
+
0.4.4
|
data/lib/oydid/basic.rb
CHANGED
@@ -25,6 +25,11 @@ class Oydid
|
|
25
25
|
message.to_json_c14n
|
26
26
|
end
|
27
27
|
|
28
|
+
def self.percent_encode(did)
|
29
|
+
# remove "https://" from string as it is default
|
30
|
+
did = did.sub("https://","").sub("@", "%40").sub("http://","http%3A%2F%2F")
|
31
|
+
end
|
32
|
+
|
28
33
|
# key management ----------------------------
|
29
34
|
def self.generate_private_key(input, method = "ed25519-priv")
|
30
35
|
begin
|
@@ -47,13 +52,21 @@ class Oydid
|
|
47
52
|
return [encode([omc, length, raw_key].pack("SCa#{length}")), ""]
|
48
53
|
end
|
49
54
|
|
50
|
-
def self.public_key(private_key)
|
55
|
+
def self.public_key(private_key, method = "ed25519-pub")
|
51
56
|
code, length, digest = decode(private_key).unpack('SCa*')
|
52
57
|
case Multicodecs[code].name
|
53
58
|
when 'ed25519-priv'
|
54
|
-
|
59
|
+
case method
|
60
|
+
when 'ed25519-pub'
|
61
|
+
public_key = Ed25519::SigningKey.new(digest).verify_key
|
62
|
+
when 'x25519-pub'
|
63
|
+
public_key = RbNaCl::PrivateKey.new(digest).public_key
|
64
|
+
else
|
65
|
+
return [nil, "unsupported key codec"]
|
66
|
+
end
|
55
67
|
length = public_key.to_bytes.bytesize
|
56
|
-
return [encode([Multicodecs[
|
68
|
+
return [encode([Multicodecs[method].code, length, public_key].pack("CCa#{length}")), ""]
|
69
|
+
|
57
70
|
else
|
58
71
|
return [nil, "unsupported key codec"]
|
59
72
|
end
|
@@ -91,6 +104,53 @@ class Oydid
|
|
91
104
|
end
|
92
105
|
end
|
93
106
|
|
107
|
+
def self.encrypt(message, public_key)
|
108
|
+
begin
|
109
|
+
code, length, digest = decode(public_key).unpack('CCa*')
|
110
|
+
case Multicodecs[code].name
|
111
|
+
when 'x25519-pub'
|
112
|
+
pubKey = RbNaCl::PublicKey.new(digest)
|
113
|
+
authHash = RbNaCl::Hash.sha256('auth'.dup.force_encoding('ASCII-8BIT'))
|
114
|
+
authKey = RbNaCl::PrivateKey.new(authHash)
|
115
|
+
box = RbNaCl::Box.new(pubKey, authKey)
|
116
|
+
nonce = RbNaCl::Random.random_bytes(box.nonce_bytes)
|
117
|
+
msg = message.force_encoding('ASCII-8BIT')
|
118
|
+
cipher = box.encrypt(nonce, msg)
|
119
|
+
return [
|
120
|
+
{
|
121
|
+
value: cipher.unpack('H*')[0],
|
122
|
+
nonce: nonce.unpack('H*')[0]
|
123
|
+
}, ""
|
124
|
+
]
|
125
|
+
else
|
126
|
+
return [nil, "unsupported key codec"]
|
127
|
+
end
|
128
|
+
rescue
|
129
|
+
return [nil, "encryption failed"]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.decrypt(message, private_key)
|
134
|
+
begin
|
135
|
+
cipher = [JSON.parse(message)["value"]].pack('H*')
|
136
|
+
nonce = [JSON.parse(message)["nonce"]].pack('H*')
|
137
|
+
code, length, digest = decode(private_key).unpack('SCa*')
|
138
|
+
case Multicodecs[code].name
|
139
|
+
when 'ed25519-priv'
|
140
|
+
privKey = RbNaCl::PrivateKey.new(digest)
|
141
|
+
authHash = RbNaCl::Hash.sha256('auth'.dup.force_encoding('ASCII-8BIT'))
|
142
|
+
authKey = RbNaCl::PrivateKey.new(authHash).public_key
|
143
|
+
box = RbNaCl::Box.new(authKey, privKey)
|
144
|
+
retVal = box.decrypt(nonce, cipher)
|
145
|
+
return [retVal, ""]
|
146
|
+
else
|
147
|
+
return [nil, "unsupported key codec"]
|
148
|
+
end
|
149
|
+
rescue
|
150
|
+
return [nil, "decryption failed"]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
94
154
|
def self.read_private_key(filename)
|
95
155
|
begin
|
96
156
|
f = File.open(filename)
|
@@ -157,6 +217,7 @@ class Oydid
|
|
157
217
|
|
158
218
|
case doc_location
|
159
219
|
when /^http/
|
220
|
+
doc_location = doc_location.sub("%3A%2F%2F","://")
|
160
221
|
retVal = HTTParty.get(doc_location + "/doc/" + doc_hash)
|
161
222
|
if retVal.code != 200
|
162
223
|
msg = retVal.parsed_response("error").to_s rescue "invalid response from " + doc_location.to_s + "/doc/" + doc_hash.to_s
|
@@ -190,6 +251,7 @@ class Oydid
|
|
190
251
|
|
191
252
|
case doc_location
|
192
253
|
when /^http/
|
254
|
+
doc_location = doc_location.sub("%3A%2F%2F","://")
|
193
255
|
retVal = HTTParty.get(doc_location + "/doc_raw/" + doc_hash)
|
194
256
|
if retVal.code != 200
|
195
257
|
msg = retVal.parsed_response("error").to_s rescue "invalid response from " + doc_location.to_s + "/doc/" + doc_hash.to_s
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class Oydid
|
5
|
+
|
6
|
+
# DIDComm Plain Message ---------------------
|
7
|
+
def self.dcpm(payload, options)
|
8
|
+
dcDoc = {}
|
9
|
+
dcDoc["id"] = SecureRandom.random_number(10e14).to_i
|
10
|
+
dcDoc["type"] = options[:didcomm_type]
|
11
|
+
if !options[:didcomm_from_did].nil?
|
12
|
+
dcDoc["from"] = options[:didcomm_from_did]
|
13
|
+
end
|
14
|
+
dcDoc["to"] = [options[:didcomm_to_did]]
|
15
|
+
dcDoc["created_time"] = Time.now.utc.to_i
|
16
|
+
dcDoc["body"] = payload
|
17
|
+
return [dcDoc, ""]
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
# DIDComm Signed Message --------------------
|
22
|
+
def self.dcsm(payload, private_key_encoded, options)
|
23
|
+
error = ""
|
24
|
+
code, length, digest = decode(private_key_encoded).unpack('SCa*')
|
25
|
+
case Multicodecs[code].name
|
26
|
+
when 'ed25519-priv'
|
27
|
+
private_key = RbNaCl::Signatures::Ed25519::SigningKey.new(digest)
|
28
|
+
token = JWT.encode payload, private_key, 'ED25519', { typ: 'JWM', kid: options[:sign_did].to_s, alg: 'ED25519' }
|
29
|
+
else
|
30
|
+
token = nil
|
31
|
+
error = "unsupported key codec"
|
32
|
+
end
|
33
|
+
return [token, error]
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.dcsm_verify(token, options)
|
37
|
+
error = ""
|
38
|
+
decoded_payload = JWT.decode token, nil, false
|
39
|
+
pubkey_did = decoded_payload.last["kid"]
|
40
|
+
result, msg = Oydid.read(pubkey_did, options)
|
41
|
+
public_key_encoded = Oydid.w3c(result, options)["authentication"].first["publicKeyMultibase"]
|
42
|
+
begin
|
43
|
+
code, length, digest = Oydid.decode(public_key_encoded).unpack('CCa*')
|
44
|
+
case Multicodecs[code].name
|
45
|
+
when 'ed25519-pub'
|
46
|
+
public_key = RbNaCl::Signatures::Ed25519::VerifyKey.new(digest)
|
47
|
+
payload = JWT.decode token.to_s, public_key, true, { algorithm: 'ED25519' }
|
48
|
+
else
|
49
|
+
payload = nil
|
50
|
+
error = "unsupported key codec"
|
51
|
+
end
|
52
|
+
return [payload, error]
|
53
|
+
rescue
|
54
|
+
return [nil, "verification failed"]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# encryption -----------------------------------
|
59
|
+
def self.msg_encrypt(payload, private_key_encoded, did)
|
60
|
+
error = ""
|
61
|
+
code, length, digest = decode(private_key_encoded).unpack('SCa*')
|
62
|
+
case Multicodecs[code].name
|
63
|
+
when 'ed25519-priv'
|
64
|
+
private_key = RbNaCl::Signatures::Ed25519::SigningKey.new(digest)
|
65
|
+
token = JWT.encode payload, private_key, 'ED25519'
|
66
|
+
else
|
67
|
+
token = nil
|
68
|
+
error = "unsupported key codec"
|
69
|
+
end
|
70
|
+
return [token, error]
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.msg_decrypt(token, public_key_encoded)
|
74
|
+
error = ""
|
75
|
+
code, length, digest = Oydid.decode(public_key_encoded).unpack('CCa*')
|
76
|
+
case Multicodecs[code].name
|
77
|
+
when 'ed25519-pub'
|
78
|
+
public_key = RbNaCl::Signatures::Ed25519::VerifyKey.new(digest)
|
79
|
+
payload = JWT.decode token.to_s, public_key, true, { algorithm: 'ED25519' }
|
80
|
+
else
|
81
|
+
payload = nil
|
82
|
+
error = "unsupported key codec"
|
83
|
+
end
|
84
|
+
return [payload, error]
|
85
|
+
end
|
86
|
+
|
87
|
+
# signing for JWS ---------------------------
|
88
|
+
def self.msg_sign(payload, hmac_secret)
|
89
|
+
token = JWT.encode payload, hmac_secret, 'HS256'
|
90
|
+
return [token, ""]
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.msg_verify_jws(token, hmac_secret)
|
94
|
+
begin
|
95
|
+
decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
|
96
|
+
return [decoded_token, ""]
|
97
|
+
rescue
|
98
|
+
return [nil, "verification failed"]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# DID Auth for data container with challenge ---
|
103
|
+
def self.token_from_challenge(host, pwd)
|
104
|
+
sid = SecureRandom.hex(20).to_s
|
105
|
+
retVal = HTTParty.post(host + "/oydid/init",
|
106
|
+
headers: { 'Content-Type' => 'application/json' },
|
107
|
+
body: { "session_id": sid }.to_json )
|
108
|
+
challenge = retVal.parsed_response["challenge"]
|
109
|
+
signed_challenge = Oydid.sign(challenge, Oydid.generate_private_key(pwd).first).first
|
110
|
+
public_key = Oydid.public_key(Oydid.generate_private_key(pwd).first).first
|
111
|
+
retVal = HTTParty.post(host + "/oydid/token",
|
112
|
+
headers: { 'Content-Type' => 'application/json' },
|
113
|
+
body: {
|
114
|
+
"session_id": sid,
|
115
|
+
"signed_challenge": signed_challenge,
|
116
|
+
"public_key": public_key
|
117
|
+
}.to_json)
|
118
|
+
return retVal.parsed_response["access_token"]
|
119
|
+
end
|
120
|
+
end
|
data/lib/oydid/log.rb
CHANGED
@@ -37,6 +37,7 @@ class Oydid
|
|
37
37
|
|
38
38
|
case log_location
|
39
39
|
when /^http/
|
40
|
+
log_location = log_location.sub("%3A%2F%2F","://")
|
40
41
|
retVal = HTTParty.get(log_location + "/log/" + did_hash)
|
41
42
|
if retVal.code != 200
|
42
43
|
msg = retVal.parsed_response("error").to_s rescue
|
@@ -152,20 +153,23 @@ class Oydid
|
|
152
153
|
|
153
154
|
def self.dag_update(currentDID, options)
|
154
155
|
i = 0
|
155
|
-
|
156
|
+
doc_location = ""
|
157
|
+
initial_did = currentDID["did"].to_s.dup
|
156
158
|
initial_did = initial_did.delete_prefix("did:oyd:")
|
157
|
-
|
159
|
+
if initial_did.include?(LOCATION_PREFIX)
|
160
|
+
tmp = initial_did.split(LOCATION_PREFIX)
|
161
|
+
initial_did = tmp[0]
|
162
|
+
doc_location = tmp[1]
|
163
|
+
end
|
158
164
|
current_public_doc_key = ""
|
159
165
|
verification_output = false
|
160
166
|
currentDID["log"].each do |el|
|
161
167
|
case el["op"]
|
162
168
|
when 2,3 # CREATE, UPDATE
|
163
169
|
currentDID["doc_log_id"] = i
|
164
|
-
|
165
170
|
doc_did = el["doc"]
|
166
|
-
doc_location = get_location(doc_did)
|
167
171
|
did_hash = doc_did.delete_prefix("did:oyd:")
|
168
|
-
did_hash = did_hash.split(
|
172
|
+
did_hash = did_hash.split(LOCATION_PREFIX).first
|
169
173
|
did10 = did_hash[0,10]
|
170
174
|
doc = retrieve_document_raw(doc_did, did10 + ".doc", doc_location, {})
|
171
175
|
if doc.first.nil?
|
@@ -217,9 +221,8 @@ class Oydid
|
|
217
221
|
currentDID["termination_log_id"] = i
|
218
222
|
|
219
223
|
doc_did = currentDID["did"]
|
220
|
-
doc_location = get_location(doc_did)
|
221
224
|
did_hash = doc_did.delete_prefix("did:oyd:")
|
222
|
-
did_hash = did_hash.split(
|
225
|
+
did_hash = did_hash.split(LOCATION_PREFIX).first
|
223
226
|
did10 = did_hash[0,10]
|
224
227
|
doc = retrieve_document_raw(doc_did, did10 + ".doc", doc_location, {})
|
225
228
|
# since it retrieves a DID that previously existed, this test is not necessary
|
@@ -230,11 +233,11 @@ class Oydid
|
|
230
233
|
# end
|
231
234
|
doc = doc.first["doc"]
|
232
235
|
term = doc["log"]
|
233
|
-
log_location = term.split(
|
236
|
+
log_location = term.split(LOCATION_PREFIX)[1] rescue ""
|
234
237
|
if log_location.to_s == ""
|
235
238
|
log_location = DEFAULT_LOCATION
|
236
239
|
end
|
237
|
-
term = term.split(
|
240
|
+
term = term.split(LOCATION_PREFIX).first
|
238
241
|
if hash(canonical(el)) != term
|
239
242
|
currentDID["error"] = 1
|
240
243
|
currentDID["message"] = "Log reference and record don't match"
|
@@ -256,7 +259,7 @@ class Oydid
|
|
256
259
|
# check if there is a revocation entry
|
257
260
|
revocation_record = {}
|
258
261
|
revoc_term = el["doc"]
|
259
|
-
revoc_term = revoc_term.split(
|
262
|
+
revoc_term = revoc_term.split(LOCATION_PREFIX).first
|
260
263
|
revoc_term_found = false
|
261
264
|
log_array, msg = retrieve_log(did_hash, did10 + ".log", log_location, options)
|
262
265
|
log_array.each do |log_el|
|
@@ -321,9 +324,9 @@ class Oydid
|
|
321
324
|
currentDID["verification"] += "of next DID Document (Details: https://ownyourdata.github.io/oydid/#verify_signature)" + "\n"
|
322
325
|
|
323
326
|
next_doc_did = log_el["doc"].to_s
|
324
|
-
next_doc_location =
|
327
|
+
next_doc_location = doc_location
|
325
328
|
next_did_hash = next_doc_did.delete_prefix("did:oyd:")
|
326
|
-
next_did_hash = next_did_hash.split(
|
329
|
+
next_did_hash = next_did_hash.split(LOCATION_PREFIX).first
|
327
330
|
next_did10 = next_did_hash[0,10]
|
328
331
|
next_doc = retrieve_document_raw(next_doc_did, next_did10 + ".doc", next_doc_location, {})
|
329
332
|
if next_doc.first.nil?
|
@@ -342,9 +345,9 @@ class Oydid
|
|
342
345
|
currentDID["message"] = "Signature does not match"
|
343
346
|
if verification_output
|
344
347
|
new_doc_did = log_el["doc"].to_s
|
345
|
-
new_doc_location =
|
348
|
+
new_doc_location = doc_location
|
346
349
|
new_did_hash = new_doc_did.delete_prefix("did:oyd:")
|
347
|
-
new_did_hash = new_did_hash.split(
|
350
|
+
new_did_hash = new_did_hash.split(LOCATION_PREFIX).first
|
348
351
|
new_did10 = new_did_hash[0,10]
|
349
352
|
new_doc = retrieve_document(new_doc_did, new_did10 + ".doc", new_doc_location, {}).first
|
350
353
|
currentDID["verification"] += "found UPDATE log record:" + "\n"
|
data/lib/oydid.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'dag'
|
5
|
+
require 'jwt'
|
5
6
|
require 'rbnacl'
|
6
7
|
require 'ed25519'
|
7
8
|
require 'httparty'
|
@@ -11,6 +12,7 @@ require 'multicodecs'
|
|
11
12
|
require 'json/canonicalization'
|
12
13
|
require 'oydid/basic'
|
13
14
|
require 'oydid/log'
|
15
|
+
require 'oydid/didcomm'
|
14
16
|
|
15
17
|
class Oydid
|
16
18
|
|
@@ -137,6 +139,11 @@ class Oydid
|
|
137
139
|
return write(content, did, "update", options)
|
138
140
|
end
|
139
141
|
|
142
|
+
def self.simulate_did(content, did, mode, options)
|
143
|
+
user_did, didDocument, revoc_log, l1, l2, r1, privateKey, revocationKey, did_old, log_old, msg = Oydid.generate_base(content, did, mode, options)
|
144
|
+
return [user_did, msg]
|
145
|
+
end
|
146
|
+
|
140
147
|
def self.generate_base(content, did, mode, options)
|
141
148
|
# input validation
|
142
149
|
did_doc = JSON.parse(content.to_json) rescue nil
|
@@ -147,7 +154,7 @@ class Oydid
|
|
147
154
|
log_old = nil
|
148
155
|
prev_hash = []
|
149
156
|
revoc_log = nil
|
150
|
-
doc_location = options[:
|
157
|
+
doc_location = options[:location]
|
151
158
|
if options[:ts].nil?
|
152
159
|
ts = Time.now.to_i
|
153
160
|
else
|
@@ -670,14 +677,13 @@ class Oydid
|
|
670
677
|
wd = {}
|
671
678
|
wd["@context"] = "https://www.w3.org/ns/did/v1"
|
672
679
|
wd["id"] = did
|
673
|
-
wd["
|
674
|
-
"id": did,
|
680
|
+
wd["verificationMethod"] = [{
|
681
|
+
"id": did + "#key-doc",
|
675
682
|
"type": "Ed25519VerificationKey2020",
|
676
683
|
"controller": did,
|
677
684
|
"publicKeyMultibase": pubDocKey
|
678
|
-
}
|
679
|
-
|
680
|
-
"id": did,
|
685
|
+
},{
|
686
|
+
"id": did + "#key-rev",
|
681
687
|
"type": "Ed25519VerificationKey2020",
|
682
688
|
"controller": did,
|
683
689
|
"publicKeyMultibase": pubRevKey
|
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.4.
|
4
|
+
version: 0.4.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christoph Fabianek
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dag
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 0.0.9
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: jwt
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.4.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.4.1
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rbnacl
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -148,6 +162,7 @@ files:
|
|
148
162
|
- VERSION
|
149
163
|
- lib/oydid.rb
|
150
164
|
- lib/oydid/basic.rb
|
165
|
+
- lib/oydid/didcomm.rb
|
151
166
|
- lib/oydid/log.rb
|
152
167
|
- spec/input/basic/arrays.json
|
153
168
|
- spec/input/basic/french.json
|