oydid 0.5.6 → 0.6.0

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.
data/lib/oydid/basic.rb CHANGED
@@ -51,7 +51,10 @@ class Oydid
51
51
  else
52
52
  return [nil, "unsupported digest: '" + method.to_s + "'"]
53
53
  end
54
- encoded = multi_encode(Multihashes.encode(digest, method.to_s), options)
54
+ code = Multicodecs[method].code
55
+ length = digest.bytesize
56
+ encoded = multi_encode([code, length, digest].pack("CCa#{length}"), options)
57
+ # encoded = multi_encode(Multihashes.encode(digest, method.to_s), options)
55
58
  if encoded.first.nil?
56
59
  return [nil, encoded.last]
57
60
  else
@@ -60,15 +63,15 @@ class Oydid
60
63
  end
61
64
 
62
65
  def self.get_digest(message)
63
- decoded_message, error = Oydid.multi_decode(message)
66
+ decoded_message, error = multi_decode(message)
64
67
  if decoded_message.nil?
65
68
  return [nil, error]
66
69
  end
67
- retVal = Multihashes.decode decoded_message
68
- if retVal[:hash_function].to_s != ""
69
- return [retVal[:hash_function].to_s, ""]
70
- end
71
- case Oydid.multi_decode(message).first[0..1].to_s
70
+ # retVal = Multihashes.decode decoded_message
71
+ # if retVal[:hash_function].to_s != ""
72
+ # return [retVal[:hash_function].to_s, ""]
73
+ # end
74
+ case decoded_message[0..1].to_s
72
75
  when "\x02\x10"
73
76
  return ["blake2b-16", ""]
74
77
  when "\x04 "
@@ -76,7 +79,13 @@ class Oydid
76
79
  when "\b@"
77
80
  return ["blake2b-64", ""]
78
81
  else
79
- return [nil, "unknown digest"]
82
+ code, length, digest = decoded_message.unpack('CCa*')
83
+ retVal = Multicodecs[code].name rescue nil
84
+ if !retVal.nil?
85
+ return [retVal, ""]
86
+ else
87
+ return [nil, "unknown digest"]
88
+ end
80
89
  end
81
90
  end
82
91
 
@@ -103,7 +112,60 @@ class Oydid
103
112
  did = did.sub("https://","").sub("@", "%40").sub("http://","http%3A%2F%2F").gsub(":","%3A").sub("did%3Aoyd%3A", "did:oyd:")
104
113
  end
105
114
 
115
+ def self.to_varint(n)
116
+ bytes = []
117
+ loop do
118
+ byte = n & 0x7F
119
+ n >>= 7
120
+ if n == 0
121
+ bytes << byte
122
+ break
123
+ else
124
+ bytes << (byte | 0x80)
125
+ end
126
+ end
127
+ bytes
128
+ end
129
+
130
+ def self.read_varint(str)
131
+ n = shift = 0
132
+ str.each_byte do |byte|
133
+ n |= (byte & 0x7f) << shift
134
+ break unless (byte & 0x80) == 0x80
135
+ shift += 7
136
+ end
137
+ return n
138
+ end
139
+
106
140
  # key management ----------------------------
141
+ def self.get_keytype(input)
142
+ code, length, digest = multi_decode(input).first.unpack('SCa*')
143
+ case Multicodecs[code]&.name
144
+ when 'ed25519-priv', 'p256-priv'
145
+ return Multicodecs[code].name
146
+ else
147
+ pubkey = multi_decode(input).first
148
+ if pubkey.bytes.length == 34
149
+ code = pubkey.bytes.first
150
+ digest = pubkey[-32..]
151
+ else
152
+ if pubkey.start_with?("\x80\x24".dup.force_encoding('ASCII-8BIT'))
153
+ code = 4608 # Bytes 0x80 0x24 sind das Varint-Encoding des Multicodec-Codes 0x1200 (p256-pub)
154
+ # 4608 == Oydid.read_varint("\x80$") oder "\x80\x24".force_encoding('ASCII-8BIT')
155
+ else
156
+ code = pubkey.unpack('n').first
157
+ end
158
+ digest = pubkey[-1*(pubkey.bytes.length-2)..]
159
+ end
160
+ case Multicodecs[code]&.name
161
+ when 'ed25519-pub', 'p256-pub'
162
+ return Multicodecs[code].name
163
+ else
164
+ return nil
165
+ end
166
+ end
167
+ end
168
+
107
169
  def self.generate_private_key(input, method = "ed25519-priv", options = {})
108
170
  begin
109
171
  omc = Multicodecs[method].code
@@ -113,19 +175,35 @@ class Oydid
113
175
 
114
176
  case Multicodecs[method].name
115
177
  when 'ed25519-priv'
116
- if input != ""
117
- raw_key = Ed25519::SigningKey.new(RbNaCl::Hash.sha256(input))
118
- else
178
+ if input == ""
119
179
  raw_key = Ed25519::SigningKey.generate
180
+ else
181
+ raw_key = Ed25519::SigningKey.new(RbNaCl::Hash.sha256(input))
120
182
  end
121
183
  raw_key = raw_key.to_bytes
122
- length = raw_key.bytesize
184
+ when 'p256-priv'
185
+ key = OpenSSL::PKey::EC.new('prime256v1')
186
+ if input == ""
187
+ key = OpenSSL::PKey::EC.generate('prime256v1')
188
+ else
189
+ # input for p256-priv requires valid base64 encoded private key
190
+ begin
191
+ key = OpenSSL::PKey.read Base64.decode64(input)
192
+ rescue
193
+ return [nil, "invalid input"]
194
+ end
195
+ end
196
+ raw_key = key.private_key.to_s(2)
123
197
  else
124
198
  return [nil, "unsupported key codec"]
125
199
  end
126
-
200
+
201
+ # only encoding without specifying key-type
202
+ # encoded = multi_encode(raw_key, options)
203
+
204
+ # encoding with specyfying key-type
205
+ length = raw_key.bytesize
127
206
  encoded = multi_encode([omc, length, raw_key].pack("SCa#{length}"), options)
128
- # encoded = multi_encode(raw_key.to_bytes, options)
129
207
  if encoded.first.nil?
130
208
  return [nil, encoded.last]
131
209
  else
@@ -133,10 +211,11 @@ class Oydid
133
211
  end
134
212
  end
135
213
 
136
- def self.public_key(private_key, options = {}, method = "ed25519-pub")
214
+ def self.public_key(private_key, options = {}, method = nil)
137
215
  code, length, digest = multi_decode(private_key).first.unpack('SCa*')
138
216
  case Multicodecs[code].name
139
217
  when 'ed25519-priv'
218
+ method = 'ed25519-pub' if method.nil?
140
219
  case method
141
220
  when 'ed25519-pub'
142
221
  public_key = Ed25519::SigningKey.new(digest).verify_key
@@ -145,9 +224,32 @@ class Oydid
145
224
  else
146
225
  return [nil, "unsupported key codec"]
147
226
  end
148
- # encoded = multi_encode(public_key.to_bytes, options)
149
- length = public_key.to_bytes.bytesize
150
- encoded = multi_encode([Multicodecs[method].code, length, public_key].pack("CCa#{length}"), options)
227
+
228
+ # encoding according to https://www.w3.org/TR/vc-di-eddsa/#ed25519verificationkey2020
229
+ encoded = multi_encode(
230
+ Multibases::DecodedByteArray.new(
231
+ ([Multicodecs[method].code, 1] <<
232
+ public_key.to_bytes.bytes).flatten)
233
+ .to_s(Encoding::BINARY),
234
+ options)
235
+
236
+ # previous (up until oydid 0.5.6) wrong encoding (length should not be set!):
237
+ # length = public_key.to_bytes.bytesize
238
+ # encoded = multi_encode([Multicodecs[method].code, length, public_key].pack("CCa#{length}"), options)
239
+ if encoded.first.nil?
240
+ return [nil, encoded.last]
241
+ else
242
+ return [encoded.first, ""]
243
+ end
244
+ when 'p256-priv'
245
+ method = 'p256-pub' if method.nil?
246
+ group = OpenSSL::PKey::EC::Group.new('prime256v1')
247
+ public_key = group.generator.mul(OpenSSL::BN.new(digest, 2))
248
+ encoded = multi_encode(
249
+ Multibases::DecodedByteArray.new(
250
+ (to_varint(0x1200) << public_key.to_bn.to_s(2).bytes)
251
+ .flatten).to_s(Encoding::BINARY),
252
+ options)
151
253
  if encoded.first.nil?
152
254
  return [nil, encoded.last]
153
255
  else
@@ -175,6 +277,9 @@ class Oydid
175
277
  end
176
278
  else
177
279
  privateKey, msg = decode_private_key(enc.to_s, options)
280
+ if msg.nil?
281
+ privateKey = enc.to_s
282
+ end
178
283
  end
179
284
  return [privateKey, msg]
180
285
  end
@@ -249,16 +354,97 @@ class Oydid
249
354
  return [keys.uniq, ""]
250
355
  end
251
356
 
252
- def self.sign(message, private_key, options)
253
- code, length, digest = multi_decode(private_key).first.unpack('SCa*')
254
- case Multicodecs[code].name
357
+ def self.sign(message, private_key, options = {})
358
+ key_type = get_keytype(private_key)
359
+ case key_type
255
360
  when 'ed25519-priv'
361
+ code, length, digest = multi_decode(private_key).first.unpack('SCa*')
256
362
  encoded = multi_encode(Ed25519::SigningKey.new(digest).sign(message), options)
257
363
  if encoded.first.nil?
258
364
  return [nil, encoded.last]
259
365
  else
260
366
  return [encoded.first, ""]
261
367
  end
368
+ when 'p256-priv'
369
+ # non-deterministic signing
370
+ # ec_key = decode_private_key(private_key).first
371
+ # dgst_bin = OpenSSL::Digest::SHA256.digest(message)
372
+ # der = ec_key.dsa_sign_asn1(dgst_bin)
373
+ # asn1 = OpenSSL::ASN1.decode(der)
374
+ # r_hex = asn1.value[0].value.to_s(16).rjust(64, '0')
375
+ # s_hex = asn1.value[1].value.to_s(16).rjust(64, '0')
376
+ # sig_bin = [r_hex + s_hex].pack('H*')
377
+ # encoded_signature = Base64.strict_encode64(sig_bin).tr('+/', '-_').delete('=')
378
+
379
+ # === deterministic signing with P-256 =====
380
+ # key & constants
381
+ ec_key = decode_private_key(private_key).first
382
+ if (ec_key.respond_to?(:private_key) ? ec_key.private_key : nil).nil?
383
+ return [nil, "invalild private key"]
384
+ end
385
+
386
+ group = OpenSSL::PKey::EC::Group.new('prime256v1')
387
+ n = group.order
388
+ qlen = n.num_bits
389
+ holen = 256
390
+ bx = ec_key.private_key
391
+
392
+ # hash message
393
+ h1_bin = OpenSSL::Digest::SHA256.digest(message)
394
+ h1_int = h1_bin.unpack1('H*').to_i(16)
395
+
396
+ # helper (RFC 6979 Section 2.3)
397
+ int2octets = lambda do |int|
398
+ bin = int.to_s(16).rjust((qlen + 7) / 8 * 2, '0')
399
+ [bin].pack('H*')
400
+ end
401
+ bits2octets = lambda do |bits|
402
+ z1 = bits % n
403
+ int2octets.call(z1)
404
+ end
405
+
406
+ # initialize HMAC-DRBG
407
+ v = "\x01" * (holen / 8)
408
+ k = "\x00" * (holen / 8)
409
+ key_oct = int2octets.call(bx)
410
+ hash_oct = bits2octets.call(h1_int)
411
+
412
+ k = OpenSSL::HMAC.digest('SHA256', k, v + "\x00" + key_oct + hash_oct)
413
+ v = OpenSSL::HMAC.digest('SHA256', k, v)
414
+ k = OpenSSL::HMAC.digest('SHA256', k, v + "\x01" + key_oct + hash_oct)
415
+ v = OpenSSL::HMAC.digest('SHA256', k, v)
416
+
417
+ # identify k (step H in RFC 6979)
418
+ loop do
419
+ v = OpenSSL::HMAC.digest('SHA256', k, v)
420
+ t = v.unpack1('H*').to_i(16)
421
+
422
+ k_candidate = t % n
423
+ if k_candidate.positive? && k_candidate < n
424
+ k_bn = OpenSSL::BN.new(k_candidate)
425
+ # calculate signature (r,s)
426
+ r_bn = group.generator.mul(k_bn).to_bn # Point → BN (uncompressed)
427
+ # extract x
428
+ r_int = r_bn.to_s(2)[1, 32].unpack1('H*').to_i(16) % n
429
+ next if r_int.zero?
430
+
431
+ kinv = k_bn.mod_inverse(n)
432
+ s_int = (kinv * (h1_int + bx.to_i * r_int)) % n
433
+ next if s_int.zero?
434
+ s_int = n.to_i - s_int if s_int > n.to_i / 2
435
+
436
+ # encode r||s -> URL-safe Base64
437
+ r_hex = r_int.to_s(16).rjust(64, '0')
438
+ s_hex = s_int.to_s(16).rjust(64, '0')
439
+ sig = [r_hex + s_hex].pack('H*')
440
+ return [Base64.urlsafe_encode64(sig, padding: false), ""]
441
+ end
442
+
443
+ k = OpenSSL::HMAC.digest('SHA256', k, v + "\x00")
444
+ v = OpenSSL::HMAC.digest('SHA256', k, v)
445
+ end
446
+
447
+ return [encoded_signature, ""]
262
448
  else
263
449
  return [nil, "unsupported key codec"]
264
450
  end
@@ -266,7 +452,19 @@ class Oydid
266
452
 
267
453
  def self.verify(message, signature, public_key)
268
454
  begin
269
- code, length, digest = multi_decode(public_key).first.unpack('CCa*')
455
+ pubkey = multi_decode(public_key).first
456
+ if pubkey.bytes.length == 34
457
+ code = pubkey.bytes.first
458
+ digest = pubkey[-32..]
459
+ else
460
+ if pubkey.start_with?("\x80\x24".dup.force_encoding('ASCII-8BIT'))
461
+ code = 4608 # Bytes 0x80 0x24 sind das Varint-Encoding des Multicodec-Codes 0x1200 (p256-pub)
462
+ # 4608 == Oydid.read_varint("\x80$") oder "\x80\x24".force_encoding('ASCII-8BIT')
463
+ else
464
+ code = pubkey.unpack('n').first
465
+ end
466
+ digest = pubkey[-1*(pubkey.bytes.length-2)..]
467
+ end
270
468
  case Multicodecs[code].name
271
469
  when 'ed25519-pub'
272
470
  verify_key = Ed25519::VerifyKey.new(digest)
@@ -278,6 +476,26 @@ class Oydid
278
476
  signature_verification = false
279
477
  end
280
478
  return [signature_verification, ""]
479
+ when 'p256-pub'
480
+ asn1_public_key = OpenSSL::ASN1::Sequence.new([
481
+ OpenSSL::ASN1::Sequence.new([
482
+ OpenSSL::ASN1::ObjectId.new('id-ecPublicKey'),
483
+ OpenSSL::ASN1::ObjectId.new('prime256v1')
484
+ ]),
485
+ OpenSSL::ASN1::BitString.new(digest)
486
+ ])
487
+ key = OpenSSL::PKey::EC.new(asn1_public_key.to_der)
488
+
489
+ sig_raw = Base64.urlsafe_decode64(signature + "=" * ((4 - signature.size % 4) % 4))
490
+ r_hex = sig_raw[0, 32].unpack1("H*")
491
+ s_hex = sig_raw[32, 32].unpack1("H*")
492
+ asn_r = OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(r_hex, 16))
493
+ asn_s = OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(s_hex, 16))
494
+ sig_der = OpenSSL::ASN1::Sequence.new([asn_r, asn_s]).to_der
495
+
496
+ message_digest = OpenSSL::Digest::SHA256.new
497
+ valid = key.dsa_verify_asn1(message_digest.digest(message), sig_der)
498
+ return [valid, ""]
281
499
  else
282
500
  return [nil, "unsupported key codec"]
283
501
  end
@@ -288,8 +506,15 @@ class Oydid
288
506
 
289
507
  def self.encrypt(message, public_key, options = {})
290
508
  begin
291
- code, length, digest = multi_decode(public_key).first.unpack('CCa*')
292
- case Multicodecs[code].name
509
+ if options[:key_type].to_s == ''
510
+ pk = multi_decode(public_key).first
511
+ code = pk.bytes.first
512
+ digest = pk[-32..]
513
+ key_type = Multicodecs[code].name rescue ''
514
+ else
515
+ key_type = options[:key_type]
516
+ end
517
+ case key_type
293
518
  when 'x25519-pub'
294
519
  pubKey = RbNaCl::PublicKey.new(digest)
295
520
  authHash = RbNaCl::Hash.sha256('auth'.dup.force_encoding('ASCII-8BIT'))
@@ -304,6 +529,43 @@ class Oydid
304
529
  nonce: nonce.unpack('H*')[0]
305
530
  }, ""
306
531
  ]
532
+ when 'p256-pub'
533
+ recipient_pub_key = decode_public_key(public_key).first
534
+
535
+ # a) Ephemeren Sender-Key erzeugen + ECDH
536
+ ephemeral_key = OpenSSL::PKey::EC.generate('prime256v1')
537
+ shared_secret = ephemeral_key.dh_compute_key(recipient_pub_key.public_key)
538
+
539
+ # b) Ableitung Content-Encryption-Key (CEK) – simple HKDF-Light
540
+ cek = OpenSSL::Digest::SHA256.digest(shared_secret)
541
+
542
+ # c) Symmetrische Verschlüsselung (AES-256-GCM)
543
+ cipher = OpenSSL::Cipher.new('aes-256-gcm')
544
+ cipher.encrypt
545
+ cipher.key = cek
546
+ iv = OpenSSL::Random.random_bytes(12) # 96-bit IV wie empfohlen
547
+ cipher.iv = iv
548
+ cipher.auth_data = '' # kein AAD
549
+ ciphertext = cipher.update(message) + cipher.final
550
+ tag = cipher.auth_tag
551
+
552
+ # d) JWE-Header (nur die nötigen Felder)
553
+ header = {
554
+ alg: 'ECDH-ES',
555
+ enc: 'A256GCM',
556
+ kid: options[:kid],
557
+ epk: JWT::JWK.new(ephemeral_key).export.slice(:kty, :crv, :x, :y)
558
+ }
559
+
560
+ # e) JWE-Compact-Serialisierung (EncryptedKey leer bei ECDH-ES)
561
+ jwe_compact = [
562
+ Base64.urlsafe_encode64(header.to_json).delete("="),
563
+ '',
564
+ Base64.urlsafe_encode64(iv).delete("="),
565
+ Base64.urlsafe_encode64(ciphertext).delete("="),
566
+ Base64.urlsafe_encode64(tag).delete("="),
567
+ ].join('.')
568
+ return [jwe_compact, nil]
307
569
  else
308
570
  return [nil, "unsupported key codec"]
309
571
  end
@@ -314,17 +576,40 @@ class Oydid
314
576
 
315
577
  def self.decrypt(message, private_key, options = {})
316
578
  begin
317
- cipher = [JSON.parse(message)["value"]].pack('H*')
318
- nonce = [JSON.parse(message)["nonce"]].pack('H*')
319
- code, length, digest = multi_decode(private_key).first.unpack('SCa*')
320
- case Multicodecs[code].name
579
+ key_type = get_keytype(private_key)
580
+ case key_type
321
581
  when 'ed25519-priv'
582
+ cipher = [JSON.parse(message)["value"]].pack('H*')
583
+ nonce = [JSON.parse(message)["nonce"]].pack('H*')
584
+ code, length, digest = multi_decode(private_key).first.unpack('SCa*')
585
+ if length != 32 # support only encoded keys
586
+ digest = Multibases.unpack(private_key).decode.to_s('ASCII-8BIT')
587
+ code = Multicodecs["ed25519-priv"].code
588
+ end
322
589
  privKey = RbNaCl::PrivateKey.new(digest)
323
590
  authHash = RbNaCl::Hash.sha256('auth'.dup.force_encoding('ASCII-8BIT'))
324
591
  authKey = RbNaCl::PrivateKey.new(authHash).public_key
325
592
  box = RbNaCl::Box.new(authKey, privKey)
326
593
  retVal = box.decrypt(nonce, cipher)
327
594
  return [retVal, ""]
595
+ when 'p256-priv'
596
+ private_key = decode_private_key(private_key).first
597
+ head_b64, _, iv_b64, cipher_b64, tag_b64 = message.split('.')
598
+ decoded_header = JSON.parse(Base64.urlsafe_decode64(head_b64))
599
+ epk_pub = Oydid.decode_public_key(Oydid.public_key_from_jwk(decoded_header['epk']).first).first
600
+
601
+ shared_secret2 = private_key.dh_compute_key(epk_pub.public_key) # ECDH (Gegenseite)
602
+ cek2 = OpenSSL::Digest::SHA256.digest(shared_secret2)
603
+
604
+ decipher = OpenSSL::Cipher.new('aes-256-gcm')
605
+ decipher.decrypt
606
+ decipher.key = cek2
607
+ decipher.iv = Base64.urlsafe_decode64(iv_b64)
608
+ decipher.auth_tag = Base64.urlsafe_decode64(tag_b64)
609
+ decipher.auth_data = ''
610
+ plaintext = decipher.update(Base64.urlsafe_decode64(cipher_b64)) + decipher.final
611
+
612
+ return [plaintext, nil]
328
613
  else
329
614
  return [nil, "unsupported key codec"]
330
615
  end
@@ -333,7 +618,6 @@ class Oydid
333
618
  end
334
619
  end
335
620
 
336
- # key="812b578d2e357270cbbd26a9dd44f93e5c8a3b44462e271348ce8f742dfe08144d06fd1f64d5a15c5b21564695d0dca9d65af322e8f96ef394400fe255d288cf"
337
621
  def jweh(key)
338
622
  pub_key=key[-64..-1]
339
623
  prv_key=key[0..-65]
@@ -342,21 +626,16 @@ class Oydid
342
626
  bin_pub=[hex_pub].pack('H*')
343
627
  int_pub=RbNaCl::PublicKey.new(bin_pub)
344
628
  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
629
+ enc_pub=multi_encode([Multicodecs["x25519-pub"].code,len_pub,int_pub].pack("CCa#{len_pub}"),{}).first
346
630
 
347
631
  hex_prv=prv_key
348
632
  bin_prv=[hex_prv].pack('H*')
349
633
  int_prv=RbNaCl::PrivateKey.new(bin_prv)
350
634
  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
635
+ enc_prv=multi_encode([Multicodecs["ed25519-priv"].code,len_prv,int_prv].pack("SCa#{len_prv}"),{}).first
352
636
  return [enc_pub, enc_prv]
353
637
  end
354
638
 
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
639
  def self.encryptJWE(message, public_key, options = {})
361
640
 
362
641
  jwe_header = {"enc":"XC20P"}
@@ -378,7 +657,7 @@ class Oydid
378
657
 
379
658
  # Key Encryption ---
380
659
  snd_prv = RbNaCl::PrivateKey.generate
381
- code, length, digest = Oydid.multi_decode(public_key).first.unpack('CCa*')
660
+ code, length, digest = multi_decode(public_key).first.unpack('CCa*')
382
661
  buffer = RbNaCl::Util.zeros(RbNaCl::Boxes::Curve25519XSalsa20Poly1305::PublicKey::BYTES)
383
662
  RbNaCl::Signatures::Ed25519::VerifyKey.crypto_sign_ed25519_pk_to_curve25519(buffer, digest)
384
663
  shared_secret = RbNaCl::GroupElement.new(buffer).mult(snd_prv.to_bytes)
@@ -434,11 +713,6 @@ class Oydid
434
713
  return [jwe_full, ""]
435
714
  end
436
715
 
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
716
  def self.decryptJWE(message, private_key, options = {})
443
717
 
444
718
  # JWE parsing
@@ -463,7 +737,7 @@ class Oydid
463
737
  cnt_aad = Base64.urlsafe_decode64(cnt_aad_enc)
464
738
 
465
739
  # Key Decryption
466
- code, length, digest = Oydid.multi_decode(private_key).first.unpack('SCa*')
740
+ code, length, digest = multi_decode(private_key).first.unpack('SCa*')
467
741
  buffer = RbNaCl::Util.zeros(RbNaCl::Boxes::Curve25519XSalsa20Poly1305::PublicKey::BYTES)
468
742
  RbNaCl::Signatures::Ed25519::SigningKey.crypto_sign_ed25519_sk_to_curve25519(buffer, digest)
469
743
  shared_secret = RbNaCl::GroupElement.new(snd_pub).mult(buffer)
@@ -491,34 +765,107 @@ class Oydid
491
765
  rescue
492
766
  return [nil, "cannot read file"]
493
767
  end
494
- decode_private_key(key_encoded, options)
768
+ key_type = get_keytype(key_encoded) || options[:key_type] rescue options[:key_type]
769
+ if key_type.include?('-')
770
+ key_type = key_type.split('-').first || options[:key_type] rescue options[:key_type]
771
+ end
772
+ if key_type == 'p256'
773
+ begin
774
+ key = decode_private_key(key_encoded).first
775
+ private_key = key.private_key.to_s(2)
776
+ code = Multicodecs["p256-priv"].code
777
+ rescue
778
+ return [nil, "invalid base64 encoded p256-priv key"]
779
+ end
780
+ else
781
+ begin
782
+ code, length, digest = multi_decode(key_encoded).first.unpack('SCa*')
783
+ case Multicodecs[code].name
784
+ when 'ed25519-priv'
785
+ private_key = Ed25519::SigningKey.new(digest).to_bytes
786
+ # when 'p256-priv'
787
+ # key = OpenSSL::PKey::EC.new('prime256v1')
788
+ # key.private_key = OpenSSL::BN.new(digest, 2)
789
+ # private_key = key.private_key.to_s(2)
790
+ else
791
+ return [nil, "unsupported key codec"]
792
+ end
793
+ rescue
794
+ return [nil, "invalid key"]
795
+ end
796
+ end
797
+ length = private_key.bytesize
798
+ return multi_encode([code, length, private_key].pack("SCa#{length}"), options)
799
+
495
800
  end
496
801
 
497
- def self.decode_private_key(key_encoded, options)
498
- begin
499
- code, length, digest = multi_decode(key_encoded).first.unpack('SCa*')
500
- case Multicodecs[code].name
501
- when 'ed25519-priv'
502
- private_key = Ed25519::SigningKey.new(digest).to_bytes
503
- else
504
- return [nil, "unsupported key codec"]
505
- end
506
- length = private_key.bytesize
507
- return multi_encode([code, length, private_key].pack("SCa#{length}"), options)
508
- rescue
509
- return [nil, "invalid key"]
802
+ def self.decode_private_key(key_encoded, options = {})
803
+ code, length, digest = multi_decode(key_encoded).first.unpack('SCa*')
804
+ case Multicodecs[code].name
805
+ when 'ed25519-priv'
806
+ private_key = Ed25519::SigningKey.new(digest).to_bytes
807
+ when 'p256-priv'
808
+ group = OpenSSL::PKey::EC::Group.new('prime256v1')
809
+ pub_key = group.generator.mul(OpenSSL::BN.new(digest, 2))
810
+ pub_oct = pub_key.to_bn.to_s(2)
811
+
812
+ parameters = OpenSSL::ASN1::ObjectId("prime256v1")
813
+ parameters.tag = 0
814
+ parameters.tagging = :EXPLICIT
815
+ parameters.tag_class = :CONTEXT_SPECIFIC
816
+
817
+ public_key_bitstring = OpenSSL::ASN1::BitString(pub_oct)
818
+ public_key_bitstring.tag = 1
819
+ public_key_bitstring.tagging = :EXPLICIT
820
+ public_key_bitstring.tag_class = :CONTEXT_SPECIFIC
821
+
822
+ ec_private_key_asn1 = OpenSSL::ASN1::Sequence([
823
+ OpenSSL::ASN1::Integer(1),
824
+ OpenSSL::ASN1::OctetString(digest),
825
+ parameters,
826
+ public_key_bitstring
827
+ ])
828
+ private_key = OpenSSL::PKey.read(ec_private_key_asn1.to_der)
829
+
830
+ else
831
+ return [nil, "unsupported key codec"]
510
832
  end
833
+ return [private_key, nil]
834
+
511
835
  end
512
836
 
513
837
  def self.decode_public_key(key_encoded)
514
838
  begin
515
- code, length, digest = multi_decode(key_encoded).first.unpack('CCa*')
839
+ pubkey = multi_decode(key_encoded).first
840
+ if pubkey.bytes.length == 34
841
+ code = pubkey.bytes.first
842
+ digest = pubkey[-32..]
843
+ else
844
+ if pubkey.start_with?("\x80\x24".dup.force_encoding('ASCII-8BIT'))
845
+ code = 4608 # Bytes 0x80 0x24 sind das Varint-Encoding des Multicodec-Codes 0x1200 (p256-pub)
846
+ # 4608 == Oydid.read_varint("\x80$") oder "\x80\x24".force_encoding('ASCII-8BIT')
847
+ else
848
+ code = pubkey.unpack('n').first
849
+ end
850
+ digest = pubkey[-1*(pubkey.bytes.length-2)..]
851
+ end
516
852
  case Multicodecs[code].name
517
853
  when 'ed25519-pub'
518
854
  verify_key = Ed25519::VerifyKey.new(digest)
519
855
  return [verify_key, ""]
520
856
  when 'x25519-pub'
521
857
  pub_key = RbNaCl::PublicKey.new(digest)
858
+ return [pub_key, ""]
859
+ when 'p256-pub'
860
+ asn1_public_key = OpenSSL::ASN1::Sequence.new([
861
+ OpenSSL::ASN1::Sequence.new([
862
+ OpenSSL::ASN1::ObjectId.new('id-ecPublicKey'),
863
+ OpenSSL::ASN1::ObjectId.new('prime256v1')
864
+ ]),
865
+ OpenSSL::ASN1::BitString.new(digest)
866
+ ])
867
+ pub_key = OpenSSL::PKey::EC.new(asn1_public_key.to_der)
868
+
522
869
  return [pub_key, ""]
523
870
  else
524
871
  return [nil, "unsupported key codec"]
@@ -528,6 +875,135 @@ class Oydid
528
875
  end
529
876
  end
530
877
 
878
+ def self.private_key_to_jwk(private_key)
879
+ code, length, digest = multi_decode(private_key).first.unpack('SCa*')
880
+ case Multicodecs[code].name
881
+ when 'ed25519-priv'
882
+ return [nil, "not supported yet"]
883
+ when 'p256-priv'
884
+ group = OpenSSL::PKey::EC::Group.new('prime256v1')
885
+ public_key = group.generator.mul(OpenSSL::BN.new(digest, 2))
886
+ point = public_key.to_bn.to_s(2)
887
+
888
+ x_bin = point[1, 32]
889
+ y_bin = point[33, 32]
890
+ x = Base64.urlsafe_encode64(x_bin, padding: false)
891
+ y = Base64.urlsafe_encode64(y_bin, padding: false)
892
+ d = Base64.urlsafe_encode64(digest, padding: false)
893
+
894
+ jwk = {
895
+ kty: "EC",
896
+ crv: "P-256",
897
+ x: x,
898
+ y: y,
899
+ d: d
900
+ }
901
+ return [jwk, ""]
902
+ else
903
+ return [nil, "unsupported key codec"]
904
+ end
905
+ end
906
+
907
+ def self.public_key_to_jwk(public_key)
908
+ begin
909
+ pubkey = multi_decode(public_key).first
910
+ if pubkey.bytes.length == 34
911
+ code = pubkey.bytes.first
912
+ digest = pubkey[-32..]
913
+ else
914
+ if pubkey.start_with?("\x80\x24".dup.force_encoding('ASCII-8BIT'))
915
+ code = 4608 # Bytes 0x80 0x24 sind das Varint-Encoding des Multicodec-Codes 0x1200 (p256-pub)
916
+ # 4608 == Oydid.read_varint("\x80$") oder "\x80\x24".force_encoding('ASCII-8BIT')
917
+ else
918
+ code = pubkey.unpack('n').first
919
+ end
920
+ digest = pubkey[-1*(pubkey.bytes.length-2)..]
921
+ end
922
+ case Multicodecs[code].name
923
+ when 'ed25519-pub'
924
+ return [nil, "not supported yet"]
925
+ when 'p256-pub'
926
+ if digest.bytes.first == 4
927
+ # Unkomprimiertes Format: X (32 Bytes) || Y (32 Bytes)
928
+ x_coord = digest[1..32]
929
+ y_coord = digest[33..64]
930
+ x_base64 = Base64.urlsafe_encode64(x_coord, padding: false)
931
+ y_base64 = Base64.urlsafe_encode64(y_coord, padding: false)
932
+ else
933
+ asn1_public_key = OpenSSL::ASN1::Sequence.new([
934
+ OpenSSL::ASN1::Sequence.new([
935
+ OpenSSL::ASN1::ObjectId.new('id-ecPublicKey'),
936
+ OpenSSL::ASN1::ObjectId.new('prime256v1')
937
+ ]),
938
+ OpenSSL::ASN1::BitString.new(digest)
939
+ ])
940
+ key = OpenSSL::PKey::EC.new(asn1_public_key.to_der)
941
+ x, y = key.public_key.to_octet_string(:uncompressed)[1..].unpack1('H*').scan(/.{64}/)
942
+ x_base64 = Base64.urlsafe_encode64([x].pack('H*'), padding: false)
943
+ y_base64 = Base64.urlsafe_encode64([y].pack('H*'), padding: false)
944
+ end
945
+ jwk = {
946
+ "kty" => "EC",
947
+ "crv" => "P-256",
948
+ "x" => x_base64,
949
+ "y" => y_base64 }
950
+ return [jwk, ""]
951
+ else
952
+ return [nil, "unsupported key codec"]
953
+ end
954
+ rescue
955
+ return [nil, "unknown key codec"]
956
+ end
957
+ end
958
+
959
+ def self.base64_url_decode(str)
960
+ Base64.urlsafe_decode64(str + '=' * (4 - str.length % 4))
961
+ end
962
+
963
+ def self.public_key_from_jwk(jwk, options = {})
964
+ begin
965
+ if jwk.is_a?(String)
966
+ jwk = JSON.parse(jwk)
967
+ end
968
+ rescue
969
+ return [nil, "invalid input"]
970
+ end
971
+ jwk = jwk.transform_keys(&:to_s)
972
+ if jwk["kty"] == "EC" && jwk["crv"] == "P-256"
973
+ x = base64_url_decode(jwk["x"])
974
+ y = base64_url_decode(jwk["y"])
975
+ digest = OpenSSL::ASN1::BitString.new(OpenSSL::BN.new("\x04" + x + y, 2).to_s(2))
976
+ encoded = multi_encode(
977
+ Multibases::DecodedByteArray.new(
978
+ (to_varint(0x1200) << digest.value.bytes)
979
+ .flatten).to_s(Encoding::BINARY),
980
+ options)
981
+
982
+ # asn1_public_key = OpenSSL::ASN1::Sequence.new([
983
+ # OpenSSL::ASN1::Sequence.new([
984
+ # OpenSSL::ASN1::ObjectId.new('id-ecPublicKey'),
985
+ # OpenSSL::ASN1::ObjectId.new('prime256v1')
986
+ # ]),
987
+ # OpenSSL::ASN1::BitString.new(OpenSSL::BN.new("\x04" + x + y, 2).to_s(2))
988
+ # ])
989
+ # pub_key = OpenSSL::PKey::EC.new(asn1_public_key.to_der)
990
+ # encoded = multi_encode(
991
+ # Multibases::DecodedByteArray.new(
992
+ # (to_varint(0x1200) << pub_key.to_bn.to_s(2).bytes)
993
+ # .flatten).to_s(Encoding::BINARY),
994
+ # options)
995
+
996
+
997
+ if encoded.first.nil?
998
+ return [nil, encoded.last]
999
+ else
1000
+ return [encoded.first, ""]
1001
+ end
1002
+ else
1003
+ return [nil, "unsupported key codec"]
1004
+ end
1005
+ end
1006
+
531
1007
  # storage functions -----------------------------
532
1008
  def self.write_private_storage(payload, filename)
533
1009
  File.open(filename, 'w') {|f| f.write(payload)}