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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e715e178b3d9ea7da5fe0a47421740fec4b6e22f8e3355c9fb7e43a704c6b56
4
- data.tar.gz: 8ba62acc964c24e22411a4e5e5287df54bdda9b1566f98c91a760d5853a3b826
3
+ metadata.gz: 56c65853f4983bcb8214a4027986839c0503adcc1133933f817d8401d8881be7
4
+ data.tar.gz: 2a6048ab2023f8641e83e7b0162a8c15ef6bdb2c0855d70cef8d6c015ca34720
5
5
  SHA512:
6
- metadata.gz: d656097d2b8ab1d9d9b19788cdf5051e397c13580de470cbce0d9f0c917dc5a2a9a3ec8c7eb7cfaf8b3ef8755f37275f0fee39ad27d7e87fcba16fc2bd9dea59
7
- data.tar.gz: cb6ecf4797e57f43c15c401068966688be5fa1f84d8947f2fb5b65b75c96394b7d471cb5c2357f65f6734d0cb6e54f1ea1ded76a4bfa7eb4b2c251e98ba85898
6
+ metadata.gz: 799e94f77486d60beb09c8b8f5101ac5789eb1d000052a1aa396121acbd13e0a935cb06fe0c1849ef4deb75b407da049d1111bd15f0d49bd2863671419da4480
7
+ data.tar.gz: d513c74e0b307a74a9c7fced1bd3ce5bdaef1941290a70aef447a183e3e31da5c44582d25969cd401ec25379412fb7850d55e5e547972e87ee39e4297e79866c
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
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
- public_key = Ed25519::SigningKey.new(digest).verify_key
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['ed25519-pub'].code, length, public_key].pack("CCa#{length}")), ""]
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
- initial_did = currentDID["did"].to_s
156
+ doc_location = ""
157
+ initial_did = currentDID["did"].to_s.dup
156
158
  initial_did = initial_did.delete_prefix("did:oyd:")
157
- initial_did = initial_did.split("@").first
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("@").first
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("@").first
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("@")[1] rescue ""
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("@").first
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("@").first
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 = get_location(next_doc_did)
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("@").first
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 = get_location(new_doc_did)
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("@").first
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[:doc_location]
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["assertionMethod"] = [{
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
- wd["keyAgreement"] = [{
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.0
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-06-13 00:00:00.000000000 Z
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