crypto.ecdsa: migrate ecdsa.PrivateKey.new() to use a high level API (#23640)

This commit is contained in:
blackshirt 2025-02-04 16:13:56 +07:00 committed by GitHub
parent 4f85b35bb9
commit d30598bbca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 165 additions and 100 deletions

View File

@ -31,6 +31,17 @@ 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
// EVP_PKEY Context
@[typedef]
struct C.EVP_PKEY_CTX {}
fn C.EVP_PKEY_CTX_new_id(id int, e voidptr) &C.EVP_PKEY_CTX
fn C.EVP_PKEY_keygen_init(ctx &C.EVP_PKEY_CTX) int
fn C.EVP_PKEY_keygen(ctx &C.EVP_PKEY_CTX, ppkey &&C.EVP_PKEY) int
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)
// Elliptic curve keypair declarations
@[typedef]
struct C.EC_KEY {}

View File

@ -26,8 +26,12 @@ const nid_secp256k1 = C.NID_secp256k1
// #define NID_X9_62_id_ecPublicKey 408
const nid_ec_publickey = C.NID_X9_62_id_ecPublicKey
// C.EVP_PKEY_EC = NID_X9_62_id_ecPublicKey
const nid_evp_pkey_ec = C.EVP_PKEY_EC
// we only support this
const openssl_ec_named_curve = C.OPENSSL_EC_NAMED_CURVE
// The list of supported curve(s)
// Nid is an enumeration of the supported curves
pub enum Nid {
prime256v1
secp384r1
@ -46,7 +50,27 @@ pub mut:
fixed_size bool
}
// enum flag to allow flexible PrivateKey size
// HashConfig is an enumeration of the possible options for key signing (verifying).
pub enum HashConfig {
with_recommended_hash
with_no_hash
with_custom_hash
}
// SignerOpts represents configuration options to drive signing and verifying process.
@[params]
pub struct SignerOpts {
pub mut:
// default to .with_recommended_hash
hash_config HashConfig = .with_recommended_hash
// make sense when HashConfig != with_recommended_hash
allow_smaller_size bool
allow_custom_hash bool
// set to non-nil if allow_custom_hash was true
custom_hash &hash.Hash = unsafe { nil }
}
// KeyFlag is an enumeration of possible options to support flexible of PrivateKey key size.
enum KeyFlag {
// flexible flag to allow flexible-size of seed bytes
flexible
@ -54,61 +78,6 @@ enum KeyFlag {
fixed
}
// PrivateKey represents ECDSA private key. Actually its a key pair,
// contains private key and public key parts.
pub struct PrivateKey {
// The new high level of keypair opaque, set to nil now.
evpkey &C.EVP_PKEY = unsafe { nil }
// TODO: when all has been migrated to the new one,
// removes this low level declarations.
key &C.EC_KEY
mut:
// ks_flag with .flexible value allowing
// flexible-size seed bytes as key.
// When it is `.fixed`, it will use the underlying key size.
ks_flag KeyFlag = .flexible
// ks_size stores size of the seed bytes when ks_flag was .flexible.
// You should set it to a non zero value
ks_size int
}
// PublicKey represents ECDSA public key for verifying message.
pub struct PublicKey {
// The new high level of keypair opaque, set to nil now.
evpkey &C.EVP_PKEY = unsafe { nil }
// Remove this when its fully obsoleted by the new one.
key &C.EC_KEY
}
// PrivateKey.new creates a new key pair. By default, it would create a prime256v1 based key.
pub fn PrivateKey.new(opt CurveOptions) !PrivateKey {
// creates new empty key
ec_key := new_curve(opt)
if ec_key == 0 {
C.EC_KEY_free(ec_key)
return error('Failed to create new EC_KEY')
}
// Generates new public and private key for the supplied ec_key object.
res := C.EC_KEY_generate_key(ec_key)
if res != 1 {
C.EC_KEY_free(ec_key)
return error('Failed to generate EC_KEY')
}
// performs explicit check
chk := C.EC_KEY_check_key(ec_key)
if chk == 0 {
C.EC_KEY_free(ec_key)
return error('EC_KEY_check_key failed')
}
// when using default EC_KEY_generate_key, its using underlying curve key size
// and discarded opt.fixed_size flag when its not set.
priv_key := PrivateKey{
key: ec_key
ks_flag: .fixed
}
return priv_key
}
// generate_key generates a new key pair. If opt was not provided, its default to prime256v1 curve.
// If you want another curve, use in the following manner: `pubkey, pivkey := ecdsa.generate_key(nid: .secp384r1)!`
pub fn generate_key(opt CurveOptions) !(PublicKey, PrivateKey) {
@ -250,7 +219,7 @@ pub fn new_key_from_seed(seed []u8, opt CurveOptions) !PrivateKey {
// EC_KEY_check_key return 1 on success or 0 on error.
chk := C.EC_KEY_check_key(ec_key)
if chk == 0 {
key_free(ec_key)
C.EC_KEY_free(ec_key)
return error('EC_KEY_check_key failed')
}
C.EC_POINT_free(pub_key_point)
@ -271,6 +240,99 @@ pub fn new_key_from_seed(seed []u8, opt CurveOptions) !PrivateKey {
return pvkey
}
// PrivateKey represents ECDSA private key. Actually its a key pair,
// contains private key and public key parts.
pub struct PrivateKey {
// The new high level of keypair opaque, set to nil now.
evpkey &C.EVP_PKEY = unsafe { nil }
// TODO: when all has been migrated to the new one,
// removes this low level declarations.
key &C.EC_KEY
mut:
// ks_flag with .flexible value allowing
// flexible-size seed bytes as key.
// When it is `.fixed`, it will use the underlying key size.
ks_flag KeyFlag = .flexible
// ks_size stores size of the seed bytes when ks_flag was .flexible.
// You should set it to a non zero value
ks_size int
}
// PrivateKey.new creates a new key pair. By default, it would create a prime256v1 based key.
// Dont forget to call `.free()` after finish with your key.
pub fn PrivateKey.new(opt CurveOptions) !PrivateKey {
// Default to prime256v1 based key
mut group_nid := nid_prime256v1
match opt.nid {
.prime256v1 {}
.secp384r1 {
group_nid = nid_secp384r1
}
.secp521r1 {
group_nid = nid_secp521r1
}
.secp256k1 {
group_nid = nid_secp256k1
}
}
// New high level keypair generator
evpkey := C.EVP_PKEY_new()
pctx := C.EVP_PKEY_CTX_new_id(nid_evp_pkey_ec, 0)
if pctx == 0 {
C.EVP_PKEY_free(evpkey)
C.EVP_PKEY_CTX_free(pctx)
return error('C.EVP_PKEY_CTX_new_id failed')
}
nt := C.EVP_PKEY_keygen_init(pctx)
if nt <= 0 {
C.EVP_PKEY_free(evpkey)
C.EVP_PKEY_CTX_free(pctx)
return error('EVP_PKEY_keygen_init failed')
}
// set the group (curve)
cn := C.EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, group_nid)
if cn <= 0 {
C.EVP_PKEY_free(evpkey)
C.EVP_PKEY_CTX_free(pctx)
return error('EVP_PKEY_CTX_set_ec_paramgen_curve_nid')
}
// explicitly only allowing named curve, likely its the default on 3.0.
pn := C.EVP_PKEY_CTX_set_ec_param_enc(pctx, openssl_ec_named_curve)
if pn <= 0 {
C.EVP_PKEY_free(evpkey)
C.EVP_PKEY_CTX_free(pctx)
return error('EVP_PKEY_CTX_set_ec_param_enc failed')
}
// generates keypair
nr := C.EVP_PKEY_keygen(pctx, &evpkey)
if nr <= 0 {
C.EVP_PKEY_free(evpkey)
C.EVP_PKEY_CTX_free(pctx)
return error('EVP_PKEY_keygen failed')
}
// EVP_PKEY_get1_EC_KEY was deprecated in 3.0. Its used here for compatibility purposes
// to support the old key function.
// TODO: removes this when its ready to obsolete.
eckey := C.EVP_PKEY_get1_EC_KEY(evpkey)
if eckey == 0 {
C.EVP_PKEY_CTX_free(pctx)
C.EC_KEY_free(eckey)
C.EVP_PKEY_free(evpkey)
return error('EVP_PKEY_get1_EC_KEY failed')
}
// Cleans up the context
C.EVP_PKEY_CTX_free(pctx)
// when using default this function, its using underlying curve key size
// and discarded opt.fixed_size flag when its not set.
priv_key := PrivateKey{
evpkey: evpkey
key: eckey
ks_flag: .fixed
}
return priv_key
}
// sign performs signing the message with the options. By default options,
// it will perform hashing before signing the message.
pub fn (pv PrivateKey) sign(message []u8, opt SignerOpts) ![]u8 {
@ -303,18 +365,6 @@ fn (priv_key PrivateKey) sign_message(message []u8) ![]u8 {
return signed_data.clone()
}
// verify verifies a message with the signature are valid with public key provided .
// You should provide it with the same SignerOpts used with the `.sign()` call.
// or verify would fail (false).
pub fn (pub_key PublicKey) verify(message []u8, sig []u8, opt SignerOpts) !bool {
digest := calc_digest(pub_key.key, message, opt)!
res := C.ECDSA_verify(0, digest.data, digest.len, sig.data, sig.len, pub_key.key)
if res == -1 {
return error('Failed to verify signature')
}
return res == 1
}
// bytes represent private key as bytes.
pub fn (priv_key PrivateKey) bytes() ![]u8 {
bn := voidptr(C.EC_KEY_get0_private_key(priv_key.key))
@ -413,6 +463,33 @@ pub fn (priv_key PrivateKey) equal(other PrivateKey) bool {
return false
}
// free clears out allocated memory for PrivateKey
// Dont use PrivateKey after calling `.free()`
pub fn (pv &PrivateKey) free() {
C.EC_KEY_free(pv.key)
C.EVP_PKEY_free(pv.evpkey)
}
// PublicKey represents ECDSA public key for verifying message.
pub struct PublicKey {
// The new high level of keypair opaque, set to nil now.
evpkey &C.EVP_PKEY = unsafe { nil }
// Remove this when its fully obsoleted by the new one.
key &C.EC_KEY
}
// verify verifies a message with the signature are valid with public key provided .
// You should provide it with the same SignerOpts used with the `.sign()` call.
// or verify would fail (false).
pub fn (pub_key PublicKey) verify(message []u8, sig []u8, opt SignerOpts) !bool {
digest := calc_digest(pub_key.key, message, opt)!
res := C.ECDSA_verify(0, digest.data, digest.len, sig.data, sig.len, pub_key.key)
if res == -1 {
return error('Failed to verify signature')
}
return res == 1
}
// Compare two public keys
pub fn (pub_key PublicKey) equal(other PublicKey) bool {
// TODO: check validity of the group
@ -443,6 +520,13 @@ pub fn (pub_key PublicKey) equal(other PublicKey) bool {
return false
}
// free clears out allocated memory for PublicKey.
// Dont use PublicKey after calling `.free()`
pub fn (pb &PublicKey) free() {
C.EC_KEY_free(pb.key)
C.EVP_PKEY_free(pb.evpkey)
}
// Helpers
//
// new_curve creates a new empty curve based on curve NID, default to prime256v1 (or secp256r1).
@ -492,24 +576,6 @@ fn recommended_hash(key &C.EC_KEY) !crypto.Hash {
}
}
pub enum HashConfig {
with_recommended_hash
with_no_hash
with_custom_hash
}
@[params]
pub struct SignerOpts {
pub mut:
// default to .with_recommended_hash
hash_config HashConfig = .with_recommended_hash
// make sense when HashConfig != with_recommended_hash
allow_smaller_size bool
allow_custom_hash bool
// set to non-nil if allow_custom_hash was true
custom_hash &hash.Hash = unsafe { nil }
}
fn calc_digest_with_recommended_hash(key &C.EC_KEY, msg []u8) ![]u8 {
h := recommended_hash(key)!
match h {
@ -591,15 +657,3 @@ fn calc_digest(key &C.EC_KEY, message []u8, opt SignerOpts) ![]u8 {
fn key_free(ec_key &C.EC_KEY) {
C.EC_KEY_free(ec_key)
}
// free clears out allocated memory for PublicKey.
// Dont use PublicKey after calling `.free()`
pub fn (pb &PublicKey) free() {
C.EC_KEY_free(pb.key)
}
// free clears out allocated memory for PrivateKey
// Dont use PrivateKey after calling `.free()`
pub fn (pv &PrivateKey) free() {
C.EC_KEY_free(pv.key)
}