oydid 0.5.4 → 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 171ab373123506d42a8dd20b80df654ebd3cf35ac1d53e65f36580b4a3d92374
4
- data.tar.gz: 2bab378e7f1d284f7e730f32acb52964f7c84456ae5a41a786429d0f6ff11134
3
+ metadata.gz: e6e4098a11373a9b648447adbc7f6ec7b89d0efced1bb920080797dfdc5d17de
4
+ data.tar.gz: bf33e0ddd9abd26a04d92fa945db9183f87000e246f154aa509c55e9b5f4aefd
5
5
  SHA512:
6
- metadata.gz: e3d84952f40b4b7680f1c49cb351516b25b901f696f4db73222a3a5f82a645ee23da62881f24237e29cac195da2857f4392ab289ba78d29f83a14332d970ea91
7
- data.tar.gz: 555e144d9581d897b91eadd480267c9e76bfed1145077cf524537d13cc571be3efca39512aefdb7ce798390de22fc87ea922ac6e53db3e67dfba8dfa65cedd08
6
+ metadata.gz: 6175bb5b1d28443b26573754dd92f97094a7c139d032252c541a73dbd5019028466847b9602b380cd1ca814eeb3e7843576f7a86a77808a5268af62fe89dfba7
7
+ data.tar.gz: a719e0d4c5530340be4ad4a284ffadd46924db95a81c0212fb61e38243cac6a133bf8568ed6407a54d026502048bb06394f5e737f12e0ad588b65c43411bb046
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.4
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
@@ -54,7 +60,11 @@ class Oydid
54
60
  end
55
61
 
56
62
  def self.get_digest(message)
57
- retVal = Multihashes.decode Oydid.multi_decode(message).first
63
+ decoded_message, error = Oydid.multi_decode(message)
64
+ if decoded_message.nil?
65
+ return [nil, error]
66
+ end
67
+ retVal = Multihashes.decode decoded_message
58
68
  if retVal[:hash_function].to_s != ""
59
69
  return [retVal[:hash_function].to_s, ""]
60
70
  end
@@ -94,7 +104,7 @@ class Oydid
94
104
  end
95
105
 
96
106
  # key management ----------------------------
97
- def self.generate_private_key(input, method = "ed25519-priv", options)
107
+ def self.generate_private_key(input, method = "ed25519-priv", options = {})
98
108
  begin
99
109
  omc = Multicodecs[method].code
100
110
  rescue
@@ -104,15 +114,18 @@ class Oydid
104
114
  case Multicodecs[method].name
105
115
  when 'ed25519-priv'
106
116
  if input != ""
107
- raw_key = Ed25519::SigningKey.new(RbNaCl::Hash.sha256(input)).to_bytes
117
+ raw_key = Ed25519::SigningKey.new(RbNaCl::Hash.sha256(input))
108
118
  else
109
- raw_key = Ed25519::SigningKey.generate.to_bytes
119
+ raw_key = Ed25519::SigningKey.generate
110
120
  end
121
+ raw_key = raw_key.to_bytes
122
+ length = raw_key.bytesize
111
123
  else
112
124
  return [nil, "unsupported key codec"]
113
125
  end
114
- length = raw_key.bytesize
126
+
115
127
  encoded = multi_encode([omc, length, raw_key].pack("SCa#{length}"), options)
128
+ # encoded = multi_encode(raw_key.to_bytes, options)
116
129
  if encoded.first.nil?
117
130
  return [nil, encoded.last]
118
131
  else
@@ -120,7 +133,7 @@ class Oydid
120
133
  end
121
134
  end
122
135
 
123
- def self.public_key(private_key, options, method = "ed25519-pub")
136
+ def self.public_key(private_key, options = {}, method = "ed25519-pub")
124
137
  code, length, digest = multi_decode(private_key).first.unpack('SCa*')
125
138
  case Multicodecs[code].name
126
139
  when 'ed25519-priv'
@@ -132,6 +145,7 @@ class Oydid
132
145
  else
133
146
  return [nil, "unsupported key codec"]
134
147
  end
148
+ # encoded = multi_encode(public_key.to_bytes, options)
135
149
  length = public_key.to_bytes.bytesize
136
150
  encoded = multi_encode([Multicodecs[method].code, length, public_key].pack("CCa#{length}"), options)
137
151
  if encoded.first.nil?
@@ -144,6 +158,29 @@ class Oydid
144
158
  end
145
159
  end
146
160
 
161
+ def self.getPrivateKey(enc, pwd, dsk, dfl, options)
162
+ if enc.to_s == "" # usually read from options[:doc_enc]
163
+ if pwd.to_s == "" # usually read from options[:doc_pwd]
164
+ if dsk.to_s == "" # usually read from options[:doc_key]
165
+ if dfl.to_s == "" # default file name for key
166
+ return [nil, "no reference"]
167
+ else
168
+ privateKey, msg = read_private_key(dfl.to_s, options)
169
+ end
170
+ else
171
+ privateKey, msg = read_private_key(dsk.to_s, options)
172
+ end
173
+ else
174
+ privateKey, msg = generate_private_key(pwd, 'ed25519-priv', options)
175
+ end
176
+ else
177
+ privateKey, msg = decode_private_key(enc.to_s, options)
178
+ end
179
+ return [privateKey, msg]
180
+ end
181
+
182
+ # if the identifier is already the public key there is no validation if it is a valid key
183
+ # (this is a privacy-preserving feature)
147
184
  def self.getPubKeyFromDID(did)
148
185
  identifier = did.split(LOCATION_PREFIX).first.split(CGI.escape LOCATION_PREFIX).first rescue did
149
186
  identifier = identifier.delete_prefix("did:oyd:")
@@ -166,6 +203,52 @@ class Oydid
166
203
  end
167
204
  end
168
205
 
206
+ # available key_types
207
+ # * doc - document key
208
+ # * rev - revocation key
209
+ def self.getDelegatedPubKeysFromDID(did, key_type = "doc")
210
+ # retrieve DID
211
+ did_document, msg = read(did, {})
212
+ keys, msg = getDelegatedPubKeysFromFullDidDocument(did_document, key_type)
213
+ if keys.nil?
214
+ return [nil, msg]
215
+ else
216
+ return [keys, ""]
217
+ end
218
+ end
219
+
220
+ def self.getDelegatedPubKeysFromFullDidDocument(did_document, key_type = "doc")
221
+ # get current public key
222
+ case key_type
223
+ when "doc"
224
+ keys = [did_document["doc"]["key"].split(":").first] rescue nil
225
+ when "rev"
226
+ keys = [did_document["doc"]["key"].split(":").last] rescue nil
227
+ else
228
+ return [nil, "invalid key type: " + key_type]
229
+ end
230
+ if keys.nil?
231
+ return [nil, "cannot retrieve current key"]
232
+ end
233
+
234
+ # travers through log and get active delegation public keys
235
+ log = did_document["log"]
236
+ log.each do |item|
237
+ if item["op"] == 5 # DELEGATE
238
+ # !!!OPEN: check if log entry is confirmed / referenced in a termination entry
239
+ item_keys = item["doc"]
240
+ if key_type == "doc" && item_keys[0..3] == "doc:"
241
+ keys << item_keys[4-item_keys.length..]
242
+ elsif key_type == "rev" && item_keys[0..3] == "rev:"
243
+ keys << item_keys[4-item_keys.length..]
244
+ end
245
+ end
246
+ end unless log.nil?
247
+
248
+ # return array
249
+ return [keys.uniq, ""]
250
+ end
251
+
169
252
  def self.sign(message, private_key, options)
170
253
  code, length, digest = multi_decode(private_key).first.unpack('SCa*')
171
254
  case Multicodecs[code].name
@@ -203,7 +286,7 @@ class Oydid
203
286
  end
204
287
  end
205
288
 
206
- def self.encrypt(message, public_key, options)
289
+ def self.encrypt(message, public_key, options = {})
207
290
  begin
208
291
  code, length, digest = multi_decode(public_key).first.unpack('CCa*')
209
292
  case Multicodecs[code].name
@@ -229,7 +312,7 @@ class Oydid
229
312
  end
230
313
  end
231
314
 
232
- def self.decrypt(message, private_key, options)
315
+ def self.decrypt(message, private_key, options = {})
233
316
  begin
234
317
  cipher = [JSON.parse(message)["value"]].pack('H*')
235
318
  nonce = [JSON.parse(message)["nonce"]].pack('H*')
@@ -250,10 +333,160 @@ class Oydid
250
333
  end
251
334
  end
252
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
+
253
486
  def self.read_private_key(filename, options)
254
487
  begin
255
488
  f = File.open(filename)
256
- key_encoded = f.read
489
+ key_encoded = f.read.strip
257
490
  f.close
258
491
  rescue
259
492
  return [nil, "cannot read file"]
@@ -284,6 +517,9 @@ class Oydid
284
517
  when 'ed25519-pub'
285
518
  verify_key = Ed25519::VerifyKey.new(digest)
286
519
  return [verify_key, ""]
520
+ when 'x25519-pub'
521
+ pub_key = RbNaCl::PublicKey.new(digest)
522
+ return [pub_key, ""]
287
523
  else
288
524
  return [nil, "unsupported key codec"]
289
525
  end
@@ -331,9 +567,16 @@ class Oydid
331
567
  case doc_location
332
568
  when /^http/
333
569
  doc_location = doc_location.sub("%3A%2F%2F","://").sub("%3A", ":")
334
- retVal = HTTParty.get(doc_location + "/doc/" + doc_identifier)
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)
335
575
  if retVal.code != 200
336
- msg = retVal.parsed_response("error").to_s rescue "invalid response from " + doc_location.to_s + "/doc/" + doc_identifier.to_s
576
+ msg = retVal.parsed_response["error"].to_s rescue ""
577
+ if msg.to_s == ""
578
+ msg = "invalid response from " + doc_location.to_s + "/doc/" + doc_identifier.to_s
579
+ end
337
580
  return [nil, msg]
338
581
  end
339
582
  if options.transform_keys(&:to_s)["trace"]
data/lib/oydid/didcomm.rb CHANGED
@@ -102,12 +102,12 @@ class Oydid
102
102
  # DID Auth for data container with challenge ---
103
103
  def self.token_from_challenge(host, pwd, options = {})
104
104
  sid = SecureRandom.hex(20).to_s
105
+ public_key = public_key(generate_private_key(pwd, options).first, options).first
105
106
  retVal = HTTParty.post(host + "/oydid/init",
106
107
  headers: { 'Content-Type' => 'application/json' },
107
- body: { "session_id": sid }.to_json )
108
+ body: { "session_id": sid, "public_key": public_key }.to_json )
108
109
  challenge = retVal.parsed_response["challenge"]
109
110
  signed_challenge = sign(challenge, Oydid.generate_private_key(pwd, options).first, options).first
110
- public_key = public_key(generate_private_key(pwd, options).first, options).first
111
111
  retVal = HTTParty.post(host + "/oydid/token",
112
112
  headers: { 'Content-Type' => 'application/json' },
113
113
  body: {