oydid 0.5.6 → 0.6.1

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