Skip to content

Commit 132e3ba

Browse files
committed
crypto.ecdsa: migrate new_key_from_seed to use high opaque
1 parent b85c9e0 commit 132e3ba

File tree

2 files changed

+203
-80
lines changed

2 files changed

+203
-80
lines changed

vlib/crypto/ecdsa/ecdsa.c.v

+29-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,8 @@ 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
141+
fn C.EC_GROUP_check(group &C.EC_GROUP, ctx &C.BN_CTX) int
132142

133143
// Elliptic BIGNUM related declarations.
134144
@[typedef]
@@ -170,3 +180,20 @@ fn C.EVP_sha256() &C.EVP_MD
170180
fn C.EVP_sha384() &C.EVP_MD
171181
fn C.EVP_sha512() &C.EVP_MD
172182
fn C.EVP_MD_get_size(md &C.EVP_MD) int // -1 failure
183+
184+
fn C.OPENSSL_free(addr voidptr)
185+
186+
@[typedef]
187+
struct C.OSSL_PARAM {}
188+
189+
@[typedef]
190+
struct C.OSSL_PARAM_BLD {}
191+
192+
fn C.OSSL_PARAM_free(params &C.OSSL_PARAM)
193+
fn C.OSSL_PARAM_BLD_free(param_bld &C.OSSL_PARAM_BLD)
194+
fn C.OSSL_PARAM_BLD_new() &C.OSSL_PARAM_BLD
195+
fn C.OSSL_PARAM_BLD_push_utf8_string(bld &C.OSSL_PARAM_BLD, key &u8, buf &u8, bsize int) int
196+
fn C.OSSL_PARAM_BLD_push_BN(bld &C.OSSL_PARAM_BLD, key &u8, bn &C.BIGNUM) int
197+
fn C.OSSL_PARAM_BLD_push_octet_string(bld &C.OSSL_PARAM_BLD, key &u8, buf voidptr, bsize int) int
198+
fn C.OSSL_PARAM_BLD_to_param(bld &C.OSSL_PARAM_BLD) &C.OSSL_PARAM
199+
fn C.OSSL_PARAM_BLD_push_int(bld &C.OSSL_PARAM_BLD, key &u8, val int) int

vlib/crypto/ecdsa/ecdsa.v

+174-78
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,22 @@ 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_key_parameters = C.EVP_PKEY_KEY_PARAMETERS
36+
const evp_pkey_public_key = C.EVP_PKEY_PUBLIC_KEY
37+
const evp_pkey_keypair = C.EVP_PKEY_KEYPAIR
38+
39+
// POINT_CONVERSION FORAMT
40+
const point_conversion_compressed = 2
41+
const point_conversion_uncompressed = 4
42+
const point_conversion_hybrid = 6
43+
3444
// Nid is an enumeration of the supported curves
3545
pub enum Nid {
36-
prime256v1
37-
secp384r1
38-
secp521r1
39-
secp256k1
46+
prime256v1 = C.NID_X9_62_prime256v1
47+
secp384r1 = C.NID_secp384r1
48+
secp521r1 = C.NID_secp521r1
49+
secp256k1 = C.NID_secp256k1
4050
}
4151

4252
@[params]
@@ -103,92 +113,32 @@ pub fn new_key_from_seed(seed []u8, opt CurveOptions) !PrivateKey {
103113
if seed.len == 0 {
104114
return error('Seed with null-length was not allowed')
105115
}
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)
116+
evpkey := evpkey_from_seed_with(seed, opt)!
117+
num_bits := C.EVP_PKEY_get_bits(evpkey)
122118
key_size := (num_bits + 7) / 8
123119
if seed.len > key_size {
124-
C.EC_KEY_free(ec_key)
120+
C.EVP_PKEY_free(evpkey)
125121
return error('Seed length exceeds key size')
126122
}
127123
// Check if its using fixed key size or flexible one
128124
if opt.fixed_size {
129125
if seed.len != key_size {
130-
C.EC_KEY_free(ec_key)
126+
C.EVP_PKEY_free(evpkey)
131127
return error('seed size doesnt match with curve key size')
132128
}
133129
}
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')
130+
// TODO: remove this when its ready to go out
131+
eckey := C.EVP_PKEY_get1_EC_KEY(evpkey)
132+
if eckey == 0 {
133+
C.EC_KEY_free(eckey)
134+
C.EVP_PKEY_free(evpkey)
135+
return error('EVP_PKEY_get1_EC_KEY failed')
184136
}
185-
C.EC_POINT_free(pub_key_point)
186-
C.BN_free(bn)
187-
188137
mut pvkey := PrivateKey{
189-
key: ec_key
138+
key: eckey
139+
evpkey: evpkey
190140
}
191-
// we set the flag information on the key
141+
192142
if opt.fixed_size {
193143
// using fixed one
194144
pvkey.ks_flag = .fixed
@@ -435,6 +385,11 @@ pub fn (pv PrivateKey) public_key() !PublicKey {
435385
// - whether both of private keys lives under the same group (curve),
436386
// - compares if two private key bytes was equal.
437387
pub fn (priv_key PrivateKey) equal(other PrivateKey) bool {
388+
if priv_key.evpkey != unsafe { nil } && other.evpkey != unsafe { nil } {
389+
eq := C.EVP_PKEY_eq(voidptr(priv_key.evpkey), voidptr(other.evpkey))
390+
return eq == 1
391+
}
392+
// TODO: remove this when its ready
438393
group1 := voidptr(C.EC_KEY_get0_group(priv_key.key))
439394
group2 := voidptr(C.EC_KEY_get0_group(other.key))
440395
ctx := C.BN_CTX_new()
@@ -488,6 +443,10 @@ pub fn (pb PublicKey) verify(message []u8, sig []u8, opt SignerOpts) !bool {
488443

489444
// Compare two public keys
490445
pub fn (pub_key PublicKey) equal(other PublicKey) bool {
446+
if pub_key.evpkey != unsafe { nil } && other.evpkey != unsafe { nil } {
447+
eq := C.EVP_PKEY_eq(voidptr(pub_key.evpkey), voidptr(other.evpkey))
448+
return eq == 1
449+
}
491450
// TODO: check validity of the group
492451
group1 := voidptr(C.EC_KEY_get0_group(pub_key.key))
493452
group2 := voidptr(C.EC_KEY_get0_group(other.key))
@@ -759,7 +718,7 @@ fn calc_digest_with_md(msg []u8, md &C.EVP_MD) ![]u8 {
759718
upd := C.EVP_DigestUpdate(ctx, msg.data, msg.len)
760719
assert upd == 1
761720

762-
size := usize(C.EVP_MD_get_size(md))
721+
size := u32(C.EVP_MD_get_size(md))
763722
out := []u8{len: int(size)}
764723

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

0 commit comments

Comments
 (0)