Skip to content

Commit bc3fc12

Browse files
authored
crypto.ecdsa: migrate new_key_from_seed to use high opaque, simplify the logic (#23876)
1 parent d59f217 commit bc3fc12

File tree

2 files changed

+207
-80
lines changed

2 files changed

+207
-80
lines changed

vlib/crypto/ecdsa/ecdsa.c.v

+27-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ module ecdsa
2727
#include <openssl/x509.h>
2828
#include <openssl/bio.h>
2929
#include <openssl/pem.h>
30+
#include <openssl/param_build.h>
3031

3132
// The following header is available on OpenSSL 3.0, but not in OpenSSL 1.1.1f
3233
//#include <openssl/core.h>
@@ -39,8 +40,12 @@ fn C.EVP_PKEY_new() &C.EVP_PKEY
3940
fn C.EVP_PKEY_free(key &C.EVP_PKEY)
4041
fn C.EVP_PKEY_get1_EC_KEY(pkey &C.EVP_PKEY) &C.EC_KEY
4142
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_bits(pkey &C.EVP_PKEY) int
4344
fn C.EVP_PKEY_size(key &C.EVP_PKEY) int
45+
fn C.EVP_PKEY_eq(a &C.EVP_PKEY, b &C.EVP_PKEY) int
46+
47+
fn C.EVP_PKEY_fromdata_init(ctx &C.EVP_PKEY_CTX) int
48+
fn C.EVP_PKEY_fromdata(ctx &C.EVP_PKEY_CTX, ppkey &&C.EVP_PKEY, selection int, params &C.OSSL_PARAM) int
4449

4550
// no-prehash signing (verifying)
4651
fn C.EVP_PKEY_sign(ctx &C.EVP_PKEY_CTX, sig &u8, siglen &usize, tbs &u8, tbslen int) int
@@ -55,7 +60,7 @@ fn C.EVP_DigestVerify(ctx &C.EVP_MD_CTX, sig &u8, siglen int, tbs &u8, tbslen in
5560
// Message digest routines
5661
fn C.EVP_DigestInit(ctx &C.EVP_MD_CTX, md &C.EVP_MD) int
5762
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
63+
fn C.EVP_DigestFinal(ctx &C.EVP_MD_CTX, md &u8, s &u32) int
5964

6065
// Recommended hashed signing/verifying routines
6166
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
@@ -77,6 +82,8 @@ fn C.EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx &C.EVP_PKEY_CTX, nid int) int
7782
fn C.EVP_PKEY_CTX_set_ec_param_enc(ctx &C.EVP_PKEY_CTX, param_enc int) int
7883
fn C.EVP_PKEY_CTX_free(ctx &C.EVP_PKEY_CTX)
7984

85+
fn C.EVP_PKEY_get_bits(pkey &C.EVP_PKEY) int
86+
8087
// Elliptic curve keypair declarations
8188
@[typedef]
8289
struct C.EC_KEY {}
@@ -118,6 +125,7 @@ struct C.EC_POINT {}
118125
fn C.EC_POINT_new(group &C.EC_GROUP) &C.EC_POINT
119126
fn C.EC_POINT_mul(group &C.EC_GROUP, r &C.EC_POINT, n &C.BIGNUM, q &C.EC_POINT, m &C.BIGNUM, ctx &C.BN_CTX) int
120127
fn C.EC_POINT_point2oct(g &C.EC_GROUP, p &C.EC_POINT, form int, buf &u8, max_out int, ctx &C.BN_CTX) int
128+
fn C.EC_POINT_point2buf(group &C.EC_GROUP, point &C.EC_POINT, form int, pbuf &&u8, ctx &C.BN_CTX) int
121129
fn C.EC_POINT_cmp(group &C.EC_GROUP, a &C.EC_POINT, b &C.EC_POINT, ctx &C.BN_CTX) int
122130
fn C.EC_POINT_free(point &C.EC_POINT)
123131

@@ -129,6 +137,7 @@ fn C.EC_GROUP_free(group &C.EC_GROUP)
129137
fn C.EC_GROUP_get_degree(g &C.EC_GROUP) int
130138
fn C.EC_GROUP_get_curve_name(g &C.EC_GROUP) int
131139
fn C.EC_GROUP_cmp(a &C.EC_GROUP, b &C.EC_GROUP, ctx &C.BN_CTX) int
140+
fn C.EC_GROUP_new_by_curve_name(nid int) &C.EC_GROUP
132141

133142
// Elliptic BIGNUM related declarations.
134143
@[typedef]
@@ -170,3 +179,19 @@ fn C.EVP_sha256() &C.EVP_MD
170179
fn C.EVP_sha384() &C.EVP_MD
171180
fn C.EVP_sha512() &C.EVP_MD
172181
fn C.EVP_MD_get_size(md &C.EVP_MD) int // -1 failure
182+
183+
fn C.OPENSSL_free(addr voidptr)
184+
185+
@[typedef]
186+
struct C.OSSL_PARAM {}
187+
188+
@[typedef]
189+
struct C.OSSL_PARAM_BLD {}
190+
191+
fn C.OSSL_PARAM_free(params &C.OSSL_PARAM)
192+
fn C.OSSL_PARAM_BLD_free(param_bld &C.OSSL_PARAM_BLD)
193+
fn C.OSSL_PARAM_BLD_new() &C.OSSL_PARAM_BLD
194+
fn C.OSSL_PARAM_BLD_push_utf8_string(bld &C.OSSL_PARAM_BLD, key &char, buf &char, bsize int) int
195+
fn C.OSSL_PARAM_BLD_push_BN(bld &C.OSSL_PARAM_BLD, key &u8, bn &C.BIGNUM) int
196+
fn C.OSSL_PARAM_BLD_push_octet_string(bld &C.OSSL_PARAM_BLD, key &u8, buf voidptr, bsize int) int
197+
fn C.OSSL_PARAM_BLD_to_param(bld &C.OSSL_PARAM_BLD) &C.OSSL_PARAM

vlib/crypto/ecdsa/ecdsa.v

+180-78
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,30 @@ const nid_evp_pkey_ec = C.EVP_PKEY_EC
3131
// we only support this
3232
const openssl_ec_named_curve = C.OPENSSL_EC_NAMED_CURVE
3333

34+
// https://docs.openssl.org/3.0/man3/EVP_PKEY_fromdata/#selections
35+
const evp_pkey_keypair = C.EVP_PKEY_KEYPAIR
36+
37+
// POINT_CONVERSION FORAMT
38+
const point_conversion_uncompressed = 4
39+
3440
// Nid is an enumeration of the supported curves
3541
pub enum Nid {
36-
prime256v1
37-
secp384r1
38-
secp521r1
39-
secp256k1
42+
prime256v1 = C.NID_X9_62_prime256v1
43+
secp384r1 = C.NID_secp384r1
44+
secp521r1 = C.NID_secp521r1
45+
secp256k1 = C.NID_secp256k1
46+
}
47+
48+
// we need this group (cruve) name representation to pass them into needed routines
49+
fn (nid Nid) str() string {
50+
match nid {
51+
// TODO: maybe better relies on info from underlying C defined constants,
52+
// ie, #define SN_X9_62_prime256v1 "prime256v1" etc
53+
.prime256v1 { return 'prime256v1' }
54+
.secp384r1 { return 'secp384r1' }
55+
.secp521r1 { return 'secp521r1' }
56+
.secp256k1 { return 'secp256k1' }
57+
}
4058
}
4159

4260
@[params]
@@ -103,92 +121,32 @@ pub fn new_key_from_seed(seed []u8, opt CurveOptions) !PrivateKey {
103121
if seed.len == 0 {
104122
return error('Seed with null-length was not allowed')
105123
}
106-
// Create a new EC_KEY object with the specified curve
107-
ec_key := new_curve(opt)
108-
if ec_key == 0 {
109-
C.EC_KEY_free(ec_key)
110-
return error('Failed to create new EC_KEY')
111-
}
112-
// Retrieve the EC_GROUP object associated with the EC_KEY
113-
// Note: cast with voidptr() to allow -cstrict checks to pass
114-
group := voidptr(C.EC_KEY_get0_group(ec_key))
115-
if group == 0 {
116-
C.EC_KEY_free(ec_key)
117-
return error('Unable to load group')
118-
}
119-
// Adds early check for upper size, so, we dont hit unnecessary
120-
// call to math intensive calculation, conversion and checking routines.
121-
num_bits := C.EC_GROUP_get_degree(group)
124+
evpkey := evpkey_from_seed(seed, opt)!
125+
num_bits := C.EVP_PKEY_get_bits(evpkey)
122126
key_size := (num_bits + 7) / 8
123127
if seed.len > key_size {
124-
C.EC_KEY_free(ec_key)
128+
C.EVP_PKEY_free(evpkey)
125129
return error('Seed length exceeds key size')
126130
}
127131
// Check if its using fixed key size or flexible one
128132
if opt.fixed_size {
129133
if seed.len != key_size {
130-
C.EC_KEY_free(ec_key)
134+
C.EVP_PKEY_free(evpkey)
131135
return error('seed size doesnt match with curve key size')
132136
}
133137
}
134-
// Convert the seed bytes into a BIGNUM
135-
bn := C.BN_bin2bn(seed.data, seed.len, 0)
136-
if bn == 0 {
137-
C.EC_KEY_free(ec_key)
138-
return error('Failed to create BIGNUM from seed')
139-
}
140-
// Set the BIGNUM as the private key in the EC_KEY object
141-
mut res := C.EC_KEY_set_private_key(ec_key, bn)
142-
if res != 1 {
143-
C.BN_free(bn)
144-
C.EC_KEY_free(ec_key)
145-
return error('Failed to set private key')
146-
}
147-
// Now compute the public key
148-
//
149-
// Create a new EC_POINT object for the public key
150-
pub_key_point := C.EC_POINT_new(group)
151-
// Create a new BN_CTX object for efficient BIGNUM operations
152-
ctx := C.BN_CTX_new()
153-
if ctx == 0 {
154-
C.EC_POINT_free(pub_key_point)
155-
C.BN_free(bn)
156-
C.EC_KEY_free(ec_key)
157-
return error('Failed to create BN_CTX')
158-
}
159-
defer {
160-
C.BN_CTX_free(ctx)
161-
}
162-
// Perform the point multiplication to compute the public key: pub_key_point = bn * G
163-
res = C.EC_POINT_mul(group, pub_key_point, bn, 0, 0, ctx)
164-
if res != 1 {
165-
C.EC_POINT_free(pub_key_point)
166-
C.BN_free(bn)
167-
C.EC_KEY_free(ec_key)
168-
return error('Failed to compute public key')
169-
}
170-
// Set the computed public key in the EC_KEY object
171-
res = C.EC_KEY_set_public_key(ec_key, pub_key_point)
172-
if res != 1 {
173-
C.EC_POINT_free(pub_key_point)
174-
C.BN_free(bn)
175-
C.EC_KEY_free(ec_key)
176-
return error('Failed to set public key')
177-
}
178-
// Add key check
179-
// EC_KEY_check_key return 1 on success or 0 on error.
180-
chk := C.EC_KEY_check_key(ec_key)
181-
if chk == 0 {
182-
C.EC_KEY_free(ec_key)
183-
return error('EC_KEY_check_key failed')
138+
// TODO: remove this when its ready to go out
139+
eckey := C.EVP_PKEY_get1_EC_KEY(evpkey)
140+
if eckey == 0 {
141+
C.EC_KEY_free(eckey)
142+
C.EVP_PKEY_free(evpkey)
143+
return error('EVP_PKEY_get1_EC_KEY failed')
184144
}
185-
C.EC_POINT_free(pub_key_point)
186-
C.BN_free(bn)
187-
188145
mut pvkey := PrivateKey{
189-
key: ec_key
146+
key: eckey
147+
evpkey: evpkey
190148
}
191-
// we set the flag information on the key
149+
192150
if opt.fixed_size {
193151
// using fixed one
194152
pvkey.ks_flag = .fixed
@@ -435,6 +393,11 @@ pub fn (pv PrivateKey) public_key() !PublicKey {
435393
// - whether both of private keys lives under the same group (curve),
436394
// - compares if two private key bytes was equal.
437395
pub fn (priv_key PrivateKey) equal(other PrivateKey) bool {
396+
if priv_key.evpkey != unsafe { nil } && other.evpkey != unsafe { nil } {
397+
eq := C.EVP_PKEY_eq(voidptr(priv_key.evpkey), voidptr(other.evpkey))
398+
return eq == 1
399+
}
400+
// TODO: remove this when its ready
438401
group1 := voidptr(C.EC_KEY_get0_group(priv_key.key))
439402
group2 := voidptr(C.EC_KEY_get0_group(other.key))
440403
ctx := C.BN_CTX_new()
@@ -488,6 +451,10 @@ pub fn (pb PublicKey) verify(message []u8, sig []u8, opt SignerOpts) !bool {
488451

489452
// Compare two public keys
490453
pub fn (pub_key PublicKey) equal(other PublicKey) bool {
454+
if pub_key.evpkey != unsafe { nil } && other.evpkey != unsafe { nil } {
455+
eq := C.EVP_PKEY_eq(voidptr(pub_key.evpkey), voidptr(other.evpkey))
456+
return eq == 1
457+
}
491458
// TODO: check validity of the group
492459
group1 := voidptr(C.EC_KEY_get0_group(pub_key.key))
493460
group2 := voidptr(C.EC_KEY_get0_group(other.key))
@@ -759,7 +726,7 @@ fn calc_digest_with_md(msg []u8, md &C.EVP_MD) ![]u8 {
759726
upd := C.EVP_DigestUpdate(ctx, msg.data, msg.len)
760727
assert upd == 1
761728

762-
size := usize(C.EVP_MD_get_size(md))
729+
size := u32(C.EVP_MD_get_size(md))
763730
out := []u8{len: int(size)}
764731

765732
fin := C.EVP_DigestFinal(ctx, out.data, &size)
@@ -797,3 +764,138 @@ fn default_digest(key &C.EVP_PKEY) !&C.EVP_MD {
797764
}
798765
return error('should not here')
799766
}
767+
768+
// Build EVP_PKEY from raw seed of bytes and options.
769+
fn evpkey_from_seed(seed []u8, opt CurveOptions) !&C.EVP_PKEY {
770+
// This routine mostly comes from the official docs with adds some checking at
771+
// https://docs.openssl.org/3.0/man3/EVP_PKEY_fromdata/#creating-an-ecc-keypair-using-raw-key-data
772+
//
773+
// convert the seed bytes to BIGNUM.
774+
bn := C.BN_bin2bn(seed.data, seed.len, 0)
775+
if bn == 0 {
776+
C.BN_free(bn)
777+
return error('BN_bin2bn failed from seed')
778+
}
779+
// build the group (curve) from the options.
780+
group := C.EC_GROUP_new_by_curve_name(int(opt.nid))
781+
if group == 0 {
782+
C.EC_GROUP_free(group)
783+
C.BN_free(bn)
784+
return error('EC_GROUP_new_by_curve_name failed')
785+
}
786+
// Build EC_POINT from this BIGNUM and gets bytes represantion of this point
787+
// in uncompressed format.
788+
point := ec_point_mult(group, bn)!
789+
pub_bytes := point_2_buf(group, point, point_conversion_uncompressed)!
790+
791+
// Lets build params builder
792+
param_bld := C.OSSL_PARAM_BLD_new()
793+
assert param_bld != 0
794+
795+
// push the group, private and public key bytes infos into the builder
796+
n := C.OSSL_PARAM_BLD_push_utf8_string(param_bld, c'group', opt.nid.str().str, 0)
797+
m := C.OSSL_PARAM_BLD_push_BN(param_bld, c'priv', bn)
798+
o := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'pub', pub_bytes.data, pub_bytes.len)
799+
if n <= 0 || m <= 0 || o <= 0 {
800+
C.EC_POINT_free(point)
801+
C.BN_free(bn)
802+
C.EC_GROUP_free(group)
803+
C.OSSL_PARAM_BLD_free(param_bld)
804+
return error('OSSL_PARAM_BLD_push FAILED')
805+
}
806+
// Setup the new key
807+
mut pkey := C.EVP_PKEY_new()
808+
assert pkey != 0
809+
810+
// build parameter, initialize and build the key from params
811+
params := C.OSSL_PARAM_BLD_to_param(param_bld)
812+
pctx := C.EVP_PKEY_CTX_new_id(nid_evp_pkey_ec, 0)
813+
if params == 0 || pctx == 0 {
814+
C.EC_POINT_free(point)
815+
C.BN_free(bn)
816+
C.EC_GROUP_free(group)
817+
C.OSSL_PARAM_BLD_free(param_bld)
818+
C.OSSL_PARAM_free(params)
819+
C.EVP_PKEY_free(pkey)
820+
if pctx == 0 {
821+
C.EVP_PKEY_CTX_free(pctx)
822+
}
823+
return error('EVP_PKEY_CTX_new or OSSL_PARAM_BLD_to_param failed')
824+
}
825+
// initialize key and build the key from builded params context.
826+
p := C.EVP_PKEY_fromdata_init(pctx)
827+
q := C.EVP_PKEY_fromdata(pctx, &pkey, evp_pkey_keypair, params)
828+
if p <= 0 || q <= 0 {
829+
C.EC_POINT_free(point)
830+
C.BN_free(bn)
831+
C.EC_GROUP_free(group)
832+
C.OSSL_PARAM_BLD_free(param_bld)
833+
C.OSSL_PARAM_free(params)
834+
C.EVP_PKEY_free(pkey)
835+
C.EVP_PKEY_CTX_free(pctx)
836+
return error('EVP_PKEY_fromdata failed')
837+
}
838+
// After this step, we have build the key in pkey
839+
// TODO: right way to check the builded key
840+
841+
// Cleans up
842+
C.EC_POINT_free(point)
843+
C.BN_free(bn)
844+
C.EC_GROUP_free(group)
845+
C.OSSL_PARAM_BLD_free(param_bld)
846+
C.OSSL_PARAM_free(params)
847+
C.EVP_PKEY_CTX_free(pctx)
848+
849+
return pkey
850+
}
851+
852+
// ec_point_mult performs point multiplications, point = bn * generator
853+
fn ec_point_mult(group &C.EC_GROUP, bn &C.BIGNUM) !&C.EC_POINT {
854+
// Create a new EC_POINT object for the public key
855+
point := C.EC_POINT_new(group)
856+
// Create a new BN_CTX object for efficient BIGNUM operations
857+
ctx := C.BN_CTX_new()
858+
if ctx == 0 {
859+
C.EC_POINT_free(point)
860+
C.BN_CTX_free(ctx)
861+
return error('Failed to create BN_CTX')
862+
}
863+
864+
// Perform the point multiplication to compute the public key: point = bn * G
865+
res := C.EC_POINT_mul(group, point, bn, 0, 0, ctx)
866+
if res != 1 {
867+
C.EC_POINT_free(point)
868+
C.BN_CTX_free(ctx)
869+
return error('Failed to compute public key')
870+
}
871+
C.BN_CTX_free(ctx)
872+
return point
873+
}
874+
875+
// maximum key size we supported was 64 bytes.
876+
const default_point_bufsize = 160 // 2 * 64 + 1 + extra
877+
878+
// point_2_buf gets bytes representation of the EC_POINT
879+
fn point_2_buf(group &C.EC_GROUP, point &C.EC_POINT, fmt int) ![]u8 {
880+
ctx := C.BN_CTX_new()
881+
pbuf := []u8{len: default_point_bufsize}
882+
// Notes from the docs:
883+
// EC_POINT_point2buf() allocates a buffer of suitable length and writes an EC_POINT to it in octet format.
884+
// The allocated buffer is written to *pbuf and its length is returned.
885+
// The caller must free up the allocated buffer with a call to OPENSSL_free().
886+
// Since the allocated buffer value is written to *pbuf the pbuf parameter MUST NOT be NULL.
887+
// So, we explicitly call `.OPENSSL_free` on the allocated buffer.
888+
n := C.EC_POINT_point2buf(group, point, fmt, voidptr(&pbuf.data), ctx)
889+
if n <= 0 {
890+
C.BN_CTX_free(ctx)
891+
C.OPENSSL_free(voidptr(&pbuf.data))
892+
return error('Get null length of buf')
893+
}
894+
// Gets the copy of the result with the correct length
895+
result := pbuf[..n].clone()
896+
897+
C.OPENSSL_free(voidptr(pbuf.data))
898+
C.BN_CTX_free(ctx)
899+
900+
return result
901+
}

0 commit comments

Comments
 (0)