Skip to content

Commit

Permalink
crypto.ecdsa: migrate ecdsa.PrivateKey.new() to use a high level API (
Browse files Browse the repository at this point in the history
  • Loading branch information
blackshirt authored Feb 4, 2025
1 parent 4f85b35 commit d30598b
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 100 deletions.
11 changes: 11 additions & 0 deletions vlib/crypto/ecdsa/ecdsa.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down
254 changes: 154 additions & 100 deletions vlib/crypto/ecdsa/ecdsa.v
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -46,69 +50,34 @@ 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
// fixed flag for using underlying curve key size
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) {
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}

0 comments on commit d30598b

Please sign in to comment.