crypto.ecdsa: migrate new_key_from_seed to use high opaque, simplify the logic (#23876)

This commit is contained in:
blackshirt 2025-03-07 22:31:58 +07:00 committed by GitHub
parent d59f21776b
commit bc3fc1218b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 207 additions and 80 deletions

View file

@ -27,6 +27,7 @@ module ecdsa
#include <openssl/x509.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/param_build.h>
// The following header is available on OpenSSL 3.0, but not in OpenSSL 1.1.1f
//#include <openssl/core.h>
@ -39,8 +40,12 @@ fn C.EVP_PKEY_new() &C.EVP_PKEY
fn C.EVP_PKEY_free(key &C.EVP_PKEY)
fn C.EVP_PKEY_get1_EC_KEY(pkey &C.EVP_PKEY) &C.EC_KEY
fn C.EVP_PKEY_base_id(key &C.EVP_PKEY) int
fn C.EVP_PKEY_get_bits(pkey &C.EVP_PKEY) int
fn C.EVP_PKEY_bits(pkey &C.EVP_PKEY) int
fn C.EVP_PKEY_size(key &C.EVP_PKEY) int
fn C.EVP_PKEY_eq(a &C.EVP_PKEY, b &C.EVP_PKEY) int
fn C.EVP_PKEY_fromdata_init(ctx &C.EVP_PKEY_CTX) int
fn C.EVP_PKEY_fromdata(ctx &C.EVP_PKEY_CTX, ppkey &&C.EVP_PKEY, selection int, params &C.OSSL_PARAM) int
// no-prehash signing (verifying)
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
// Message digest routines
fn C.EVP_DigestInit(ctx &C.EVP_MD_CTX, md &C.EVP_MD) int
fn C.EVP_DigestUpdate(ctx &C.EVP_MD_CTX, d voidptr, cnt int) int
fn C.EVP_DigestFinal(ctx &C.EVP_MD_CTX, md &u8, s &usize) int
fn C.EVP_DigestFinal(ctx &C.EVP_MD_CTX, md &u8, s &u32) int
// Recommended hashed signing/verifying routines
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
fn C.EVP_PKEY_CTX_set_ec_param_enc(ctx &C.EVP_PKEY_CTX, param_enc int) int
fn C.EVP_PKEY_CTX_free(ctx &C.EVP_PKEY_CTX)
fn C.EVP_PKEY_get_bits(pkey &C.EVP_PKEY) int
// Elliptic curve keypair declarations
@[typedef]
struct C.EC_KEY {}
@ -118,6 +125,7 @@ struct C.EC_POINT {}
fn C.EC_POINT_new(group &C.EC_GROUP) &C.EC_POINT
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
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
fn C.EC_POINT_point2buf(group &C.EC_GROUP, point &C.EC_POINT, form int, pbuf &&u8, ctx &C.BN_CTX) int
fn C.EC_POINT_cmp(group &C.EC_GROUP, a &C.EC_POINT, b &C.EC_POINT, ctx &C.BN_CTX) int
fn C.EC_POINT_free(point &C.EC_POINT)
@ -129,6 +137,7 @@ fn C.EC_GROUP_free(group &C.EC_GROUP)
fn C.EC_GROUP_get_degree(g &C.EC_GROUP) int
fn C.EC_GROUP_get_curve_name(g &C.EC_GROUP) int
fn C.EC_GROUP_cmp(a &C.EC_GROUP, b &C.EC_GROUP, ctx &C.BN_CTX) int
fn C.EC_GROUP_new_by_curve_name(nid int) &C.EC_GROUP
// Elliptic BIGNUM related declarations.
@[typedef]
@ -170,3 +179,19 @@ fn C.EVP_sha256() &C.EVP_MD
fn C.EVP_sha384() &C.EVP_MD
fn C.EVP_sha512() &C.EVP_MD
fn C.EVP_MD_get_size(md &C.EVP_MD) int // -1 failure
fn C.OPENSSL_free(addr voidptr)
@[typedef]
struct C.OSSL_PARAM {}
@[typedef]
struct C.OSSL_PARAM_BLD {}
fn C.OSSL_PARAM_free(params &C.OSSL_PARAM)
fn C.OSSL_PARAM_BLD_free(param_bld &C.OSSL_PARAM_BLD)
fn C.OSSL_PARAM_BLD_new() &C.OSSL_PARAM_BLD
fn C.OSSL_PARAM_BLD_push_utf8_string(bld &C.OSSL_PARAM_BLD, key &char, buf &char, bsize int) int
fn C.OSSL_PARAM_BLD_push_BN(bld &C.OSSL_PARAM_BLD, key &u8, bn &C.BIGNUM) int
fn C.OSSL_PARAM_BLD_push_octet_string(bld &C.OSSL_PARAM_BLD, key &u8, buf voidptr, bsize int) int
fn C.OSSL_PARAM_BLD_to_param(bld &C.OSSL_PARAM_BLD) &C.OSSL_PARAM

View file

@ -31,12 +31,30 @@ const nid_evp_pkey_ec = C.EVP_PKEY_EC
// we only support this
const openssl_ec_named_curve = C.OPENSSL_EC_NAMED_CURVE
// https://docs.openssl.org/3.0/man3/EVP_PKEY_fromdata/#selections
const evp_pkey_keypair = C.EVP_PKEY_KEYPAIR
// POINT_CONVERSION FORAMT
const point_conversion_uncompressed = 4
// Nid is an enumeration of the supported curves
pub enum Nid {
prime256v1
secp384r1
secp521r1
secp256k1
prime256v1 = C.NID_X9_62_prime256v1
secp384r1 = C.NID_secp384r1
secp521r1 = C.NID_secp521r1
secp256k1 = C.NID_secp256k1
}
// we need this group (cruve) name representation to pass them into needed routines
fn (nid Nid) str() string {
match nid {
// TODO: maybe better relies on info from underlying C defined constants,
// ie, #define SN_X9_62_prime256v1 "prime256v1" etc
.prime256v1 { return 'prime256v1' }
.secp384r1 { return 'secp384r1' }
.secp521r1 { return 'secp521r1' }
.secp256k1 { return 'secp256k1' }
}
}
@[params]
@ -103,92 +121,32 @@ pub fn new_key_from_seed(seed []u8, opt CurveOptions) !PrivateKey {
if seed.len == 0 {
return error('Seed with null-length was not allowed')
}
// Create a new EC_KEY object with the specified curve
ec_key := new_curve(opt)
if ec_key == 0 {
C.EC_KEY_free(ec_key)
return error('Failed to create new EC_KEY')
}
// Retrieve the EC_GROUP object associated with the EC_KEY
// Note: cast with voidptr() to allow -cstrict checks to pass
group := voidptr(C.EC_KEY_get0_group(ec_key))
if group == 0 {
C.EC_KEY_free(ec_key)
return error('Unable to load group')
}
// Adds early check for upper size, so, we dont hit unnecessary
// call to math intensive calculation, conversion and checking routines.
num_bits := C.EC_GROUP_get_degree(group)
evpkey := evpkey_from_seed(seed, opt)!
num_bits := C.EVP_PKEY_get_bits(evpkey)
key_size := (num_bits + 7) / 8
if seed.len > key_size {
C.EC_KEY_free(ec_key)
C.EVP_PKEY_free(evpkey)
return error('Seed length exceeds key size')
}
// Check if its using fixed key size or flexible one
if opt.fixed_size {
if seed.len != key_size {
C.EC_KEY_free(ec_key)
C.EVP_PKEY_free(evpkey)
return error('seed size doesnt match with curve key size')
}
}
// Convert the seed bytes into a BIGNUM
bn := C.BN_bin2bn(seed.data, seed.len, 0)
if bn == 0 {
C.EC_KEY_free(ec_key)
return error('Failed to create BIGNUM from seed')
// TODO: remove this when its ready to go out
eckey := C.EVP_PKEY_get1_EC_KEY(evpkey)
if eckey == 0 {
C.EC_KEY_free(eckey)
C.EVP_PKEY_free(evpkey)
return error('EVP_PKEY_get1_EC_KEY failed')
}
// Set the BIGNUM as the private key in the EC_KEY object
mut res := C.EC_KEY_set_private_key(ec_key, bn)
if res != 1 {
C.BN_free(bn)
C.EC_KEY_free(ec_key)
return error('Failed to set private key')
}
// Now compute the public key
//
// Create a new EC_POINT object for the public key
pub_key_point := C.EC_POINT_new(group)
// Create a new BN_CTX object for efficient BIGNUM operations
ctx := C.BN_CTX_new()
if ctx == 0 {
C.EC_POINT_free(pub_key_point)
C.BN_free(bn)
C.EC_KEY_free(ec_key)
return error('Failed to create BN_CTX')
}
defer {
C.BN_CTX_free(ctx)
}
// Perform the point multiplication to compute the public key: pub_key_point = bn * G
res = C.EC_POINT_mul(group, pub_key_point, bn, 0, 0, ctx)
if res != 1 {
C.EC_POINT_free(pub_key_point)
C.BN_free(bn)
C.EC_KEY_free(ec_key)
return error('Failed to compute public key')
}
// Set the computed public key in the EC_KEY object
res = C.EC_KEY_set_public_key(ec_key, pub_key_point)
if res != 1 {
C.EC_POINT_free(pub_key_point)
C.BN_free(bn)
C.EC_KEY_free(ec_key)
return error('Failed to set public key')
}
// Add key check
// EC_KEY_check_key return 1 on success or 0 on error.
chk := C.EC_KEY_check_key(ec_key)
if chk == 0 {
C.EC_KEY_free(ec_key)
return error('EC_KEY_check_key failed')
}
C.EC_POINT_free(pub_key_point)
C.BN_free(bn)
mut pvkey := PrivateKey{
key: ec_key
key: eckey
evpkey: evpkey
}
// we set the flag information on the key
if opt.fixed_size {
// using fixed one
pvkey.ks_flag = .fixed
@ -435,6 +393,11 @@ pub fn (pv PrivateKey) public_key() !PublicKey {
// - whether both of private keys lives under the same group (curve),
// - compares if two private key bytes was equal.
pub fn (priv_key PrivateKey) equal(other PrivateKey) bool {
if priv_key.evpkey != unsafe { nil } && other.evpkey != unsafe { nil } {
eq := C.EVP_PKEY_eq(voidptr(priv_key.evpkey), voidptr(other.evpkey))
return eq == 1
}
// TODO: remove this when its ready
group1 := voidptr(C.EC_KEY_get0_group(priv_key.key))
group2 := voidptr(C.EC_KEY_get0_group(other.key))
ctx := C.BN_CTX_new()
@ -488,6 +451,10 @@ pub fn (pb PublicKey) verify(message []u8, sig []u8, opt SignerOpts) !bool {
// Compare two public keys
pub fn (pub_key PublicKey) equal(other PublicKey) bool {
if pub_key.evpkey != unsafe { nil } && other.evpkey != unsafe { nil } {
eq := C.EVP_PKEY_eq(voidptr(pub_key.evpkey), voidptr(other.evpkey))
return eq == 1
}
// TODO: check validity of the group
group1 := voidptr(C.EC_KEY_get0_group(pub_key.key))
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 {
upd := C.EVP_DigestUpdate(ctx, msg.data, msg.len)
assert upd == 1
size := usize(C.EVP_MD_get_size(md))
size := u32(C.EVP_MD_get_size(md))
out := []u8{len: int(size)}
fin := C.EVP_DigestFinal(ctx, out.data, &size)
@ -797,3 +764,138 @@ fn default_digest(key &C.EVP_PKEY) !&C.EVP_MD {
}
return error('should not here')
}
// Build EVP_PKEY from raw seed of bytes and options.
fn evpkey_from_seed(seed []u8, opt CurveOptions) !&C.EVP_PKEY {
// This routine mostly comes from the official docs with adds some checking at
// https://docs.openssl.org/3.0/man3/EVP_PKEY_fromdata/#creating-an-ecc-keypair-using-raw-key-data
//
// convert the seed bytes to BIGNUM.
bn := C.BN_bin2bn(seed.data, seed.len, 0)
if bn == 0 {
C.BN_free(bn)
return error('BN_bin2bn failed from seed')
}
// build the group (curve) from the options.
group := C.EC_GROUP_new_by_curve_name(int(opt.nid))
if group == 0 {
C.EC_GROUP_free(group)
C.BN_free(bn)
return error('EC_GROUP_new_by_curve_name failed')
}
// Build EC_POINT from this BIGNUM and gets bytes represantion of this point
// in uncompressed format.
point := ec_point_mult(group, bn)!
pub_bytes := point_2_buf(group, point, point_conversion_uncompressed)!
// Lets build params builder
param_bld := C.OSSL_PARAM_BLD_new()
assert param_bld != 0
// push the group, private and public key bytes infos into the builder
n := C.OSSL_PARAM_BLD_push_utf8_string(param_bld, c'group', opt.nid.str().str, 0)
m := C.OSSL_PARAM_BLD_push_BN(param_bld, c'priv', bn)
o := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'pub', pub_bytes.data, pub_bytes.len)
if n <= 0 || m <= 0 || o <= 0 {
C.EC_POINT_free(point)
C.BN_free(bn)
C.EC_GROUP_free(group)
C.OSSL_PARAM_BLD_free(param_bld)
return error('OSSL_PARAM_BLD_push FAILED')
}
// Setup the new key
mut pkey := C.EVP_PKEY_new()
assert pkey != 0
// build parameter, initialize and build the key from params
params := C.OSSL_PARAM_BLD_to_param(param_bld)
pctx := C.EVP_PKEY_CTX_new_id(nid_evp_pkey_ec, 0)
if params == 0 || pctx == 0 {
C.EC_POINT_free(point)
C.BN_free(bn)
C.EC_GROUP_free(group)
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_PKEY_free(pkey)
if pctx == 0 {
C.EVP_PKEY_CTX_free(pctx)
}
return error('EVP_PKEY_CTX_new or OSSL_PARAM_BLD_to_param failed')
}
// initialize key and build the key from builded params context.
p := C.EVP_PKEY_fromdata_init(pctx)
q := C.EVP_PKEY_fromdata(pctx, &pkey, evp_pkey_keypair, params)
if p <= 0 || q <= 0 {
C.EC_POINT_free(point)
C.BN_free(bn)
C.EC_GROUP_free(group)
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_PKEY_free(pkey)
C.EVP_PKEY_CTX_free(pctx)
return error('EVP_PKEY_fromdata failed')
}
// After this step, we have build the key in pkey
// TODO: right way to check the builded key
// Cleans up
C.EC_POINT_free(point)
C.BN_free(bn)
C.EC_GROUP_free(group)
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_PKEY_CTX_free(pctx)
return pkey
}
// ec_point_mult performs point multiplications, point = bn * generator
fn ec_point_mult(group &C.EC_GROUP, bn &C.BIGNUM) !&C.EC_POINT {
// Create a new EC_POINT object for the public key
point := C.EC_POINT_new(group)
// Create a new BN_CTX object for efficient BIGNUM operations
ctx := C.BN_CTX_new()
if ctx == 0 {
C.EC_POINT_free(point)
C.BN_CTX_free(ctx)
return error('Failed to create BN_CTX')
}
// Perform the point multiplication to compute the public key: point = bn * G
res := C.EC_POINT_mul(group, point, bn, 0, 0, ctx)
if res != 1 {
C.EC_POINT_free(point)
C.BN_CTX_free(ctx)
return error('Failed to compute public key')
}
C.BN_CTX_free(ctx)
return point
}
// maximum key size we supported was 64 bytes.
const default_point_bufsize = 160 // 2 * 64 + 1 + extra
// point_2_buf gets bytes representation of the EC_POINT
fn point_2_buf(group &C.EC_GROUP, point &C.EC_POINT, fmt int) ![]u8 {
ctx := C.BN_CTX_new()
pbuf := []u8{len: default_point_bufsize}
// Notes from the docs:
// EC_POINT_point2buf() allocates a buffer of suitable length and writes an EC_POINT to it in octet format.
// The allocated buffer is written to *pbuf and its length is returned.
// The caller must free up the allocated buffer with a call to OPENSSL_free().
// Since the allocated buffer value is written to *pbuf the pbuf parameter MUST NOT be NULL.
// So, we explicitly call `.OPENSSL_free` on the allocated buffer.
n := C.EC_POINT_point2buf(group, point, fmt, voidptr(&pbuf.data), ctx)
if n <= 0 {
C.BN_CTX_free(ctx)
C.OPENSSL_free(voidptr(&pbuf.data))
return error('Get null length of buf')
}
// Gets the copy of the result with the correct length
result := pbuf[..n].clone()
C.OPENSSL_free(voidptr(pbuf.data))
C.BN_CTX_free(ctx)
return result
}