Skip to content

Commit 66e1d14

Browse files
authored
crypto.ecdsa: migrate core routines for signing (and verifying), it now requires using OpenSSL 3 (#23705)
1 parent 7e5b58d commit 66e1d14

6 files changed

Lines changed: 241 additions & 21 deletions

File tree

‎cmd/tools/modules/testing/common.v‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession {
253253
skip_files << 'vlib/crypto/ecdsa/ecdsa_test.v' // requires OpenSSL
254254
skip_files << 'vlib/crypto/ecdsa/util_test.v' // requires OpenSSL
255255
skip_files << 'vlib/crypto/ecdsa/example/ecdsa_seed_test.v' // requires OpenSSL
256+
skip_files << 'vlib/crypto/ecdsa/example/ensure_compatibility_with_net_openssl_test.v' // requires OpenSSL
256257
$if tinyc {
257258
skip_files << 'examples/database/orm.v' // try fix it
258259
}
@@ -285,6 +286,7 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession {
285286
skip_files << 'vlib/crypto/ecdsa/ecdsa_test.v' // requires OpenSSL
286287
skip_files << 'vlib/crypto/ecdsa/util_test.v' // requires OpenSSL
287288
skip_files << 'vlib/crypto/ecdsa/example/ecdsa_seed_test.v' // requires OpenSSL
289+
skip_files << 'vlib/crypto/ecdsa/example/ensure_compatibility_with_net_openssl_test.v' // requires OpenSSL
288290
skip_files << 'vlib/x/ttf/ttf_test.v'
289291
skip_files << 'vlib/encoding/iconv/iconv_test.v' // needs libiconv to be installed
290292
}
@@ -293,6 +295,7 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession {
293295
skip_files << 'vlib/crypto/ecdsa/ecdsa_test.v' // requires OpenSSL
294296
skip_files << 'vlib/crypto/ecdsa/util_test.v' // requires OpenSSL
295297
skip_files << 'vlib/crypto/ecdsa/example/ecdsa_seed_test.v' // requires OpenSSL
298+
skip_files << 'vlib/crypto/ecdsa/example/ensure_compatibility_with_net_openssl_test.v' // requires OpenSSL
296299
// Fails compilation with: `/usr/bin/ld: /lib/x86_64-linux-gnu/libpthread.so.0: error adding symbols: DSO missing from command line`
297300
skip_files << 'examples/sokol/sounds/simple_sin_tones.v'
298301
}

‎vlib/crypto/ecdsa/ecdsa.c.v‎

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,20 @@ module ecdsa
77
// should be 0x30000000L, but a lot of EC_KEY method was deprecated on version 3.0
88
// #define OPENSSL_API_COMPAT 0x10100000L
99

10-
#flag darwin -L /opt/homebrew/opt/openssl/lib -I /opt/homebrew/opt/openssl/include
10+
#flag darwin -L/opt/homebrew/opt/openssl/lib
11+
#flag darwin -I/opt/homebrew/opt/openssl/include
12+
#flag darwin -I/usr/local/opt/openssl/include
13+
#flag darwin -L/usr/local/opt/openssl/lib
14+
15+
#flag linux -I/usr/local/include/openssl
16+
#flag linux -L/usr/local/lib64/
1117

1218
#flag -I/usr/include/openssl
19+
1320
#flag -lcrypto
14-
#flag darwin -I/usr/local/opt/openssl/include
15-
#flag darwin -L/usr/local/opt/openssl/lib
21+
1622
#include <openssl/ecdsa.h>
1723
#include <openssl/obj_mac.h>
18-
#include <openssl/objects.h>
1924
#include <openssl/bn.h>
2025
#include <openssl/evp.h>
2126
#include <openssl/ec.h>
@@ -34,11 +39,37 @@ fn C.EVP_PKEY_new() &C.EVP_PKEY
3439
fn C.EVP_PKEY_free(key &C.EVP_PKEY)
3540
fn C.EVP_PKEY_get1_EC_KEY(pkey &C.EVP_PKEY) &C.EC_KEY
3641
fn C.EVP_PKEY_base_id(key &C.EVP_PKEY) int
42+
fn C.EVP_PKEY_get_bits(pkey &C.EVP_PKEY) int
43+
fn C.EVP_PKEY_size(key &C.EVP_PKEY) int
44+
45+
// no-prehash signing (verifying)
46+
fn C.EVP_PKEY_sign(ctx &C.EVP_PKEY_CTX, sig &u8, siglen &usize, tbs &u8, tbslen int) int
47+
fn C.EVP_PKEY_sign_init(ctx &C.EVP_PKEY_CTX) int
48+
fn C.EVP_PKEY_verify_init(ctx &C.EVP_PKEY_CTX) int
49+
fn C.EVP_PKEY_verify(ctx &C.EVP_PKEY_CTX, sig &u8, siglen int, tbs &u8, tbslen int) int
50+
51+
// single shoot digest signing (verifying) routine
52+
fn C.EVP_DigestSign(ctx &C.EVP_MD_CTX, sig &u8, siglen &usize, tbs &u8, tbslen int) int
53+
fn C.EVP_DigestVerify(ctx &C.EVP_MD_CTX, sig &u8, siglen int, tbs &u8, tbslen int) int
54+
55+
// Message digest routines
56+
fn C.EVP_DigestInit(ctx &C.EVP_MD_CTX, md &C.EVP_MD) int
57+
fn C.EVP_DigestUpdate(ctx &C.EVP_MD_CTX, d voidptr, cnt int) int
58+
fn C.EVP_DigestFinal(ctx &C.EVP_MD_CTX, md &u8, s &usize) int
59+
60+
// Recommended hashed signing/verifying routines
61+
fn C.EVP_DigestSignInit(ctx &C.EVP_MD_CTX, pctx &&C.EVP_PKEY_CTX, tipe &C.EVP_MD, e voidptr, pkey &C.EVP_PKEY) int
62+
fn C.EVP_DigestSignUpdate(ctx &C.EVP_MD_CTX, d voidptr, cnt int) int
63+
fn C.EVP_DigestSignFinal(ctx &C.EVP_MD_CTX, sig &u8, siglen &usize) int
64+
fn C.EVP_DigestVerifyInit(ctx &C.EVP_MD_CTX, pctx &&C.EVP_PKEY_CTX, tipe &C.EVP_MD, e voidptr, pkey &C.EVP_PKEY) int
65+
fn C.EVP_DigestVerifyUpdate(ctx &C.EVP_MD_CTX, d voidptr, cnt int) int
66+
fn C.EVP_DigestVerifyFinal(ctx &C.EVP_MD_CTX, sig &u8, siglen int) int
3767

3868
// EVP_PKEY Context
3969
@[typedef]
4070
struct C.EVP_PKEY_CTX {}
4171

72+
fn C.EVP_PKEY_CTX_new(pkey &C.EVP_PKEY, e voidptr) &C.EVP_PKEY_CTX
4273
fn C.EVP_PKEY_CTX_new_id(id int, e voidptr) &C.EVP_PKEY_CTX
4374
fn C.EVP_PKEY_keygen_init(ctx &C.EVP_PKEY_CTX) int
4475
fn C.EVP_PKEY_keygen(ctx &C.EVP_PKEY_CTX, ppkey &&C.EVP_PKEY) int
@@ -124,3 +155,18 @@ struct C.ECDSA_SIG {}
124155
fn C.ECDSA_size(key &C.EC_KEY) u32
125156
fn C.ECDSA_sign(type_ int, dgst &u8, dgstlen int, sig &u8, siglen &u32, eckey &C.EC_KEY) int
126157
fn C.ECDSA_verify(type_ int, dgst &u8, dgstlen int, sig &u8, siglen int, eckey &C.EC_KEY) int
158+
159+
@[typedef]
160+
struct C.EVP_MD_CTX {}
161+
162+
fn C.EVP_MD_CTX_new() &C.EVP_MD_CTX
163+
fn C.EVP_MD_CTX_free(ctx &C.EVP_MD_CTX)
164+
165+
// Wrapper of digest and signing related of the C opaque and functions.
166+
@[typedef]
167+
struct C.EVP_MD {}
168+
169+
fn C.EVP_sha256() &C.EVP_MD
170+
fn C.EVP_sha384() &C.EVP_MD
171+
fn C.EVP_sha512() &C.EVP_MD
172+
fn C.EVP_MD_get_size(md &C.EVP_MD) int // -1 failure

‎vlib/crypto/ecdsa/ecdsa.v‎

Lines changed: 171 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,12 @@ pub fn PrivateKey.new(opt CurveOptions) !PrivateKey {
296296
// sign performs signing the message with the options. By default options,
297297
// it will perform hashing before signing the message.
298298
pub fn (pv PrivateKey) sign(message []u8, opt SignerOpts) ![]u8 {
299-
digest := calc_digest(pv.key, message, opt)!
300-
return pv.sign_message(digest)!
299+
if pv.evpkey != unsafe { nil } {
300+
digest := calc_digest_with_evpkey(pv.evpkey, message, opt)!
301+
return sign_digest(pv.evpkey, digest)!
302+
}
303+
digest := calc_digest_with_eckey(pv.key, message, opt)!
304+
return pv.sign_digest(digest)!
301305
}
302306

303307
// sign_with_options signs message with the options. It will be deprecated,
@@ -307,18 +311,18 @@ pub fn (pv PrivateKey) sign_with_options(message []u8, opt SignerOpts) ![]u8 {
307311
return pv.sign(message, opt)
308312
}
309313

310-
// sign_message sign a message with private key.
311-
fn (priv_key PrivateKey) sign_message(message []u8) ![]u8 {
312-
if message.len == 0 {
313-
return error('Message cannot be null or empty')
314+
// sign_digest sign a digest with private key.
315+
fn (pv PrivateKey) sign_digest(digest []u8) ![]u8 {
316+
if digest.len == 0 {
317+
return error('Digest cannot be null or empty')
314318
}
315319
mut sig_len := u32(0)
316-
sig_size := C.ECDSA_size(priv_key.key)
320+
sig_size := C.ECDSA_size(pv.key)
317321
sig := unsafe { malloc(int(sig_size)) }
318-
res := C.ECDSA_sign(0, message.data, message.len, sig, &sig_len, priv_key.key)
322+
res := C.ECDSA_sign(0, digest.data, digest.len, sig, &sig_len, pv.key)
319323
if res != 1 {
320324
unsafe { free(sig) }
321-
return error('Failed to sign message')
325+
return error('Failed to sign digest')
322326
}
323327
signed_data := unsafe { sig.vbytes(int(sig_len)) }
324328
unsafe { free(sig) }
@@ -469,9 +473,13 @@ pub struct PublicKey {
469473
// verify verifies a message with the signature are valid with public key provided .
470474
// You should provide it with the same SignerOpts used with the `.sign()` call.
471475
// or verify would fail (false).
472-
pub fn (pub_key PublicKey) verify(message []u8, sig []u8, opt SignerOpts) !bool {
473-
digest := calc_digest(pub_key.key, message, opt)!
474-
res := C.ECDSA_verify(0, digest.data, digest.len, sig.data, sig.len, pub_key.key)
476+
pub fn (pb PublicKey) verify(message []u8, sig []u8, opt SignerOpts) !bool {
477+
if pb.evpkey != unsafe { nil } {
478+
digest := calc_digest_with_evpkey(pb.evpkey, message, opt)!
479+
return verify_signature(pb.evpkey, sig, digest)
480+
}
481+
digest := calc_digest_with_eckey(pb.key, message, opt)!
482+
res := C.ECDSA_verify(0, digest.data, digest.len, sig.data, sig.len, pb.key)
475483
if res == -1 {
476484
return error('Failed to verify signature')
477485
}
@@ -582,9 +590,9 @@ fn calc_digest_with_recommended_hash(key &C.EC_KEY, msg []u8) ![]u8 {
582590
}
583591
}
584592

585-
// calc_digest tries to calculates digest (hash) of the message based on options provided.
593+
// calc_digest_with_eckey tries to calculates digest (hash) of the message based on options provided.
586594
// If the options was .with_no_hash, its has the same behaviour with .with_recommended_hash.
587-
fn calc_digest(key &C.EC_KEY, message []u8, opt SignerOpts) ![]u8 {
595+
fn calc_digest_with_eckey(key &C.EC_KEY, message []u8, opt SignerOpts) ![]u8 {
588596
if message.len == 0 {
589597
return error('null-length messages')
590598
}
@@ -640,3 +648,152 @@ fn calc_digest(key &C.EC_KEY, message []u8, opt SignerOpts) ![]u8 {
640648
}
641649
return error('Not should be here')
642650
}
651+
652+
// calc_digest_with_evpkey get the digest of the messages under the EVP_PKEY and options
653+
fn calc_digest_with_evpkey(key &C.EVP_PKEY, message []u8, opt SignerOpts) ![]u8 {
654+
if message.len == 0 {
655+
return error('null-length messages')
656+
}
657+
bits_size := C.EVP_PKEY_get_bits(key)
658+
if bits_size <= 0 {
659+
return error(' bits_size was invalid')
660+
}
661+
key_size := (bits_size + 7) / 8
662+
663+
match opt.hash_config {
664+
.with_no_hash, .with_recommended_hash {
665+
md := default_digest(key)!
666+
return calc_digest_with_md(message, md)!
667+
}
668+
.with_custom_hash {
669+
mut cfg := opt
670+
if !cfg.allow_custom_hash {
671+
return error('custom hash was not allowed, set it into true')
672+
}
673+
if cfg.custom_hash == unsafe { nil } {
674+
return error('Custom hasher was not defined')
675+
}
676+
if key_size > cfg.custom_hash.size() {
677+
if !cfg.allow_smaller_size {
678+
return error('Hash into smaller size than current key size was not allowed')
679+
}
680+
}
681+
// we need to reset the custom hash before writes message
682+
cfg.custom_hash.reset()
683+
_ := cfg.custom_hash.write(message)!
684+
digest := cfg.custom_hash.sum([]u8{})
685+
686+
return digest
687+
}
688+
}
689+
return error('Not should be here')
690+
}
691+
692+
// sign_digest signs the digest with the key. Under the hood, EVP_PKEY_sign() does not
693+
// hash the data to be signed, and therefore is normally used to sign digests.
694+
fn sign_digest(key &C.EVP_PKEY, digest []u8) ![]u8 {
695+
ctx := C.EVP_PKEY_CTX_new(key, 0)
696+
if ctx == 0 {
697+
C.EVP_PKEY_CTX_free(ctx)
698+
return error('EVP_PKEY_CTX_new failed')
699+
}
700+
sin := C.EVP_PKEY_sign_init(ctx)
701+
if sin != 1 {
702+
C.EVP_PKEY_CTX_free(ctx)
703+
return error('EVP_PKEY_sign_init failed')
704+
}
705+
// siglen was used to store the size of the signature output. When EVP_PKEY_sign
706+
// was called with NULL signature buffer, siglen will tell maximum size of signature.
707+
siglen := usize(C.EVP_PKEY_size(key))
708+
st := C.EVP_PKEY_sign(ctx, 0, &siglen, digest.data, digest.len)
709+
if st <= 0 {
710+
C.EVP_PKEY_CTX_free(ctx)
711+
return error('Get null buffer length on EVP_PKEY_sign')
712+
}
713+
sig := []u8{len: int(siglen)}
714+
do := C.EVP_PKEY_sign(ctx, sig.data, &siglen, digest.data, digest.len)
715+
if do <= 0 {
716+
C.EVP_PKEY_CTX_free(ctx)
717+
return error('EVP_PKEY_sign fails to sign message')
718+
}
719+
// siglen now contains actual length of the signature buffer.
720+
signed := sig[..siglen].clone()
721+
722+
// Cleans up
723+
unsafe { sig.free() }
724+
C.EVP_PKEY_CTX_free(ctx)
725+
726+
return signed
727+
}
728+
729+
// verify_signature verifies the signature for the digest under the provided key.
730+
fn verify_signature(key &C.EVP_PKEY, sig []u8, digest []u8) bool {
731+
ctx := C.EVP_PKEY_CTX_new(key, 0)
732+
if ctx == 0 {
733+
C.EVP_PKEY_CTX_free(ctx)
734+
return false
735+
}
736+
vinit := C.EVP_PKEY_verify_init(ctx)
737+
if vinit != 1 {
738+
C.EVP_PKEY_CTX_free(ctx)
739+
return false
740+
}
741+
res := C.EVP_PKEY_verify(ctx, sig.data, sig.len, digest.data, digest.len)
742+
if res <= 0 {
743+
C.EVP_PKEY_CTX_free(ctx)
744+
return false
745+
}
746+
C.EVP_PKEY_CTX_free(ctx)
747+
return res == 1
748+
}
749+
750+
// calc_digest_with_md get the digest of the msg using md digest algorithm
751+
fn calc_digest_with_md(msg []u8, md &C.EVP_MD) ![]u8 {
752+
ctx := C.EVP_MD_CTX_new()
753+
if ctx == 0 {
754+
C.EVP_MD_CTX_free(ctx)
755+
return error('EVP_MD_CTX_new failed')
756+
}
757+
nt := C.EVP_DigestInit(ctx, md)
758+
assert nt == 1
759+
upd := C.EVP_DigestUpdate(ctx, msg.data, msg.len)
760+
assert upd == 1
761+
762+
size := usize(C.EVP_MD_get_size(md))
763+
out := []u8{len: int(size)}
764+
765+
fin := C.EVP_DigestFinal(ctx, out.data, &size)
766+
assert fin == 1
767+
768+
digest := out[..size].clone()
769+
// cleans up
770+
unsafe { out.free() }
771+
C.EVP_MD_CTX_free(ctx)
772+
773+
return digest
774+
}
775+
776+
// default_digest gets the default digest (hash) algorithm for this key.
777+
fn default_digest(key &C.EVP_PKEY) !&C.EVP_MD {
778+
// get bits size of this key
779+
bits_size := C.EVP_PKEY_get_bits(key)
780+
if bits_size <= 0 {
781+
return error(' this size isnt available.')
782+
}
783+
// based on this bits_size, choose appropriate digest algorithm
784+
match true {
785+
bits_size <= 256 {
786+
return voidptr(C.EVP_sha256())
787+
}
788+
bits_size > 256 && bits_size <= 384 {
789+
return voidptr(C.EVP_sha384())
790+
}
791+
bits_size > 384 {
792+
return voidptr(C.EVP_sha512())
793+
}
794+
else {
795+
return error('Unsupported bits size')
796+
}
797+
}
798+
return error('should not here')
799+
}

‎vlib/crypto/ecdsa/example/ecdsa_seed_test.v‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ fn test_new_key_from_seed_with_random_size_and_data() ! {
2525
}
2626
continue
2727
}
28-
ret_seed := pvkey.seed()!
28+
ret_seed := pvkey.bytes()!
2929
assert random_bytes == ret_seed
3030
pvkey.free()
3131
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import net.openssl
2+
import crypto.ecdsa
3+
4+
fn test_openssl_and_crypto_ecdsa_are_compatible() {
5+
pbkey, pvkey := ecdsa.generate_key()!
6+
message_tobe_signed := 'Hello ecdsa'.bytes()
7+
signature := pvkey.sign(message_tobe_signed)!
8+
verified := pbkey.verify(message_tobe_signed, signature)!
9+
assert verified
10+
pbkey.free()
11+
pvkey.free()
12+
c := openssl.SSLConn{}
13+
assert c.str().contains('in_memory_verification: false')
14+
}

‎vlib/crypto/ecdsa/util_test.v‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ fn test_for_pubkey_bytes() ! {
4747
pb := '0421af184ac64c8a13e66c65d4f1ad31677edeaa97af791aef73b66ea26d1623a411f67b6c4d842ba22fa39d1216bd64acef00a1b924ac11a10af679ac3a7eb2fd'
4848
pvkey := new_key_from_seed(hex.decode(pv)!)!
4949

50-
assert pvkey.seed()!.hex() == pv
50+
assert pvkey.bytes()!.hex() == pv
5151
pbkey := pvkey.public_key()!
5252
assert pbkey.bytes()!.hex() == pb
5353
pbkey.free()
@@ -87,7 +87,7 @@ fn test_for_pubkey_bytes() ! {
8787
fn test_load_privkey_from_string_sign_and_verify() ! {
8888
pvkey := privkey_from_string(privatekey_sample)!
8989
expected_pvkey_bytes := '30ce3da288965ac6093f0ba9a9a15b2476bea3eda925e1b3c1f094674f52795cd6cb3cafe235dfc15bec542448ffa715'
90-
assert pvkey.seed()!.hex() == expected_pvkey_bytes
90+
assert pvkey.bytes()!.hex() == expected_pvkey_bytes
9191

9292
// public key part
9393
pbkey := pvkey.public_key()!

0 commit comments

Comments
 (0)