Skip to content

Commit

Permalink
crypto.ecdsa: migrate core routines for signing (and verifying)
Browse files Browse the repository at this point in the history
  • Loading branch information
blackshirt committed Feb 13, 2025
1 parent 92f436d commit 3e73061
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 18 deletions.
43 changes: 42 additions & 1 deletion vlib/crypto/ecdsa/ecdsa.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module ecdsa
#flag darwin -L/usr/local/opt/openssl/lib
#include <openssl/ecdsa.h>
#include <openssl/obj_mac.h>
#include <openssl/objects.h>
#include <openssl/types.h>
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <openssl/ec.h>
Expand All @@ -34,11 +34,37 @@ 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_size(key &C.EVP_PKEY) int

// no-prehash signing (verifying)
fn C.EVP_PKEY_sign(ctx &C.EVP_PKEY_CTX, sig &u8, siglen &usize, tbs &u8, tbslen int) int
fn C.EVP_PKEY_sign_init(ctx &C.EVP_PKEY_CTX) int
fn C.EVP_PKEY_verify_init(ctx &C.EVP_PKEY_CTX) int
fn C.EVP_PKEY_verify(ctx &C.EVP_PKEY_CTX, sig &u8, siglen int, tbs &u8, tbslen int) int

// single shoot digest signing (verifying) routine
fn C.EVP_DigestSign(ctx &C.EVP_MD_CTX, sig &u8, siglen &usize, tbs &u8, tbslen int) int
fn C.EVP_DigestVerify(ctx &C.EVP_MD_CTX, sig &u8, siglen int, tbs &u8, tbslen int) int

// 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

// 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
fn C.EVP_DigestSignUpdate(ctx &C.EVP_MD_CTX, d voidptr, cnt int) int
fn C.EVP_DigestSignFinal(ctx &C.EVP_MD_CTX, sig &u8, siglen &usize) int
fn C.EVP_DigestVerifyInit(ctx &C.EVP_MD_CTX, pctx &&C.EVP_PKEY_CTX, tipe &C.EVP_MD, e voidptr, pkey &C.EVP_PKEY) int
fn C.EVP_DigestVerifyUpdate(ctx &C.EVP_MD_CTX, d voidptr, cnt int) int
fn C.EVP_DigestVerifyFinal(ctx &C.EVP_MD_CTX, sig &u8, siglen int) int

// EVP_PKEY Context
@[typedef]
struct C.EVP_PKEY_CTX {}

fn C.EVP_PKEY_CTX_new(pkey &C.EVP_PKEY, e voidptr) &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
Expand Down Expand Up @@ -124,3 +150,18 @@ struct C.ECDSA_SIG {}
fn C.ECDSA_size(key &C.EC_KEY) u32
fn C.ECDSA_sign(type_ int, dgst &u8, dgstlen int, sig &u8, siglen &u32, eckey &C.EC_KEY) int
fn C.ECDSA_verify(type_ int, dgst &u8, dgstlen int, sig &u8, siglen int, eckey &C.EC_KEY) int

@[typedef]
struct C.EVP_MD_CTX {}

fn C.EVP_MD_CTX_new() &C.EVP_MD_CTX
fn C.EVP_MD_CTX_free(ctx &C.EVP_MD_CTX)

// Wrapper of digest and signing related of the C opaque and functions.
@[typedef]
struct C.EVP_MD {}

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
185 changes: 171 additions & 14 deletions vlib/crypto/ecdsa/ecdsa.v
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,12 @@ pub fn PrivateKey.new(opt CurveOptions) !PrivateKey {
// 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 {
digest := calc_digest(pv.key, message, opt)!
return pv.sign_message(digest)!
if pv.evpkey != unsafe { nil } {
digest := calc_digest_with_evpkey(pv.evpkey, message, opt)!
return sign_digest(pv.evpkey, digest)!
}
digest := calc_digest_with_eckey(pv.key, message, opt)!
return pv.sign_digest(digest)!
}

// sign_with_options signs message with the options. It will be deprecated,
Expand All @@ -307,18 +311,18 @@ pub fn (pv PrivateKey) sign_with_options(message []u8, opt SignerOpts) ![]u8 {
return pv.sign(message, opt)
}

// sign_message sign a message with private key.
fn (priv_key PrivateKey) sign_message(message []u8) ![]u8 {
if message.len == 0 {
return error('Message cannot be null or empty')
// sign_digest sign a digest with private key.
fn (pv PrivateKey) sign_digest(digest []u8) ![]u8 {
if digest.len == 0 {
return error('Digest cannot be null or empty')
}
mut sig_len := u32(0)
sig_size := C.ECDSA_size(priv_key.key)
sig_size := C.ECDSA_size(pv.key)
sig := unsafe { malloc(int(sig_size)) }
res := C.ECDSA_sign(0, message.data, message.len, sig, &sig_len, priv_key.key)
res := C.ECDSA_sign(0, digest.data, digest.len, sig, &sig_len, pv.key)
if res != 1 {
unsafe { free(sig) }
return error('Failed to sign message')
return error('Failed to sign digest')
}
signed_data := unsafe { sig.vbytes(int(sig_len)) }
unsafe { free(sig) }
Expand Down Expand Up @@ -469,9 +473,13 @@ pub struct PublicKey {
// 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)
pub fn (pb PublicKey) verify(message []u8, sig []u8, opt SignerOpts) !bool {
if pb.evpkey != unsafe { nil } {
digest := calc_digest_with_evpkey(pb.evpkey, message, opt)!
return verify_signature(pb.evpkey, sig, digest)
}
digest := calc_digest_with_eckey(pb.key, message, opt)!
res := C.ECDSA_verify(0, digest.data, digest.len, sig.data, sig.len, pb.key)
if res == -1 {
return error('Failed to verify signature')
}
Expand Down Expand Up @@ -582,9 +590,9 @@ fn calc_digest_with_recommended_hash(key &C.EC_KEY, msg []u8) ![]u8 {
}
}

// calc_digest tries to calculates digest (hash) of the message based on options provided.
// calc_digest_with_eckey tries to calculates digest (hash) of the message based on options provided.
// If the options was .with_no_hash, its has the same behaviour with .with_recommended_hash.
fn calc_digest(key &C.EC_KEY, message []u8, opt SignerOpts) ![]u8 {
fn calc_digest_with_eckey(key &C.EC_KEY, message []u8, opt SignerOpts) ![]u8 {
if message.len == 0 {
return error('null-length messages')
}
Expand Down Expand Up @@ -640,3 +648,152 @@ fn calc_digest(key &C.EC_KEY, message []u8, opt SignerOpts) ![]u8 {
}
return error('Not should be here')
}

// calc_digest_with_evpkey get the digest of the messages under the EVP_PKEY and options
fn calc_digest_with_evpkey(key &C.EVP_PKEY, message []u8, opt SignerOpts) ![]u8 {
if message.len == 0 {
return error('null-length messages')
}
bits_size := C.EVP_PKEY_get_bits(key)
if bits_size <= 0 {
return error(' bits_size was invalid')
}
key_size := (bits_size + 7) / 8

match opt.hash_config {
.with_no_hash, .with_recommended_hash {
md := default_digest(key)!
return calc_digest_with_md(message, md)!
}
.with_custom_hash {
mut cfg := opt
if !cfg.allow_custom_hash {
return error('custom hash was not allowed, set it into true')
}
if cfg.custom_hash == unsafe { nil } {
return error('Custom hasher was not defined')
}
if key_size > cfg.custom_hash.size() {
if !cfg.allow_smaller_size {
return error('Hash into smaller size than current key size was not allowed')
}
}
// we need to reset the custom hash before writes message
cfg.custom_hash.reset()
_ := cfg.custom_hash.write(message)!
digest := cfg.custom_hash.sum([]u8{})

return digest
}
}
return error('Not should be here')
}

// sign_digest signs the digest with the key. Under the hood, EVP_PKEY_sign() does not
// hash the data to be signed, and therefore is normally used to sign digests.
fn sign_digest(key &C.EVP_PKEY, digest []u8) ![]u8 {
ctx := C.EVP_PKEY_CTX_new(key, 0)
if ctx == 0 {
C.EVP_PKEY_CTX_free(ctx)
return error('EVP_PKEY_CTX_new failed')
}
sin := C.EVP_PKEY_sign_init(ctx)
if sin != 1 {
C.EVP_PKEY_CTX_free(ctx)
return error('EVP_PKEY_sign_init failed')
}
// siglen was used to store the size of the signature output. When EVP_PKEY_sign
// was called with NULL signature buffer, siglen will tell maximum size of signature.
siglen := usize(C.EVP_PKEY_size(key))
st := C.EVP_PKEY_sign(ctx, 0, &siglen, digest.data, digest.len)
if st <= 0 {
C.EVP_PKEY_CTX_free(ctx)
return error('Get null buffer length on EVP_PKEY_sign')
}
sig := []u8{len: int(siglen)}
do := C.EVP_PKEY_sign(ctx, sig.data, &siglen, digest.data, digest.len)
if do <= 0 {
C.EVP_PKEY_CTX_free(ctx)
return error('EVP_PKEY_sign fails to sign message')
}
// siglen now contains actual length of the signature buffer.
signed := sig[..siglen].clone()

// Cleans up
unsafe { sig.free() }
C.EVP_PKEY_CTX_free(ctx)

return signed
}

// verify_signature verifies the signature for the digest under the provided key.
fn verify_signature(key &C.EVP_PKEY, sig []u8, digest []u8) bool {
ctx := C.EVP_PKEY_CTX_new(key, 0)
if ctx == 0 {
C.EVP_PKEY_CTX_free(ctx)
return false
}
vinit := C.EVP_PKEY_verify_init(ctx)
if vinit != 1 {
C.EVP_PKEY_CTX_free(ctx)
return false
}
res := C.EVP_PKEY_verify(ctx, sig.data, sig.len, digest.data, digest.len)
if res <= 0 {
C.EVP_PKEY_CTX_free(ctx)
return false
}
C.EVP_PKEY_CTX_free(ctx)
return res == 1
}

// calc_digest_with_md get the digest of the msg using md digest algorithm
fn calc_digest_with_md(msg []u8, md &C.EVP_MD) ![]u8 {
ctx := C.EVP_MD_CTX_new()
if ctx == 0 {
C.EVP_MD_CTX_free(ctx)
return error('EVP_MD_CTX_new failed')
}
nt := C.EVP_DigestInit(ctx, md)
assert nt == 1
upd := C.EVP_DigestUpdate(ctx, msg.data, msg.len)
assert upd == 1

size := usize(C.EVP_MD_get_size(md))
out := []u8{len: int(size)}

fin := C.EVP_DigestFinal(ctx, out.data, &size)
assert fin == 1

digest := out[..size].clone()
// cleans up
unsafe { out.free() }
C.EVP_MD_CTX_free(ctx)

return digest
}

// default_digest gets the default digest (hash) algorithm for this key.
fn default_digest(key &C.EVP_PKEY) !&C.EVP_MD {
// get bits size of this key
bits_size := C.EVP_PKEY_get_bits(key)
if bits_size <= 0 {
return error(' this size isnt available.')
}
// based on this bits_size, choose appropriate digest algorithm
match true {
bits_size <= 256 {
return voidptr(C.EVP_sha256())
}
bits_size > 256 && bits_size <= 384 {
return voidptr(C.EVP_sha384())
}
bits_size > 384 {
return voidptr(C.EVP_sha512())
}
else {
return error('Unsupported bits size')
}
}
return error('should not here')
}
2 changes: 1 addition & 1 deletion vlib/crypto/ecdsa/example/ecdsa_seed_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fn test_new_key_from_seed_with_random_size_and_data() ! {
}
continue
}
ret_seed := pvkey.seed()!
ret_seed := pvkey.bytes()!
assert random_bytes == ret_seed
pvkey.free()
}
Expand Down
4 changes: 2 additions & 2 deletions vlib/crypto/ecdsa/util_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ fn test_for_pubkey_bytes() ! {
pb := '0421af184ac64c8a13e66c65d4f1ad31677edeaa97af791aef73b66ea26d1623a411f67b6c4d842ba22fa39d1216bd64acef00a1b924ac11a10af679ac3a7eb2fd'
pvkey := new_key_from_seed(hex.decode(pv)!)!

assert pvkey.seed()!.hex() == pv
assert pvkey.bytes()!.hex() == pv
pbkey := pvkey.public_key()!
assert pbkey.bytes()!.hex() == pb
pbkey.free()
Expand Down Expand Up @@ -87,7 +87,7 @@ fn test_for_pubkey_bytes() ! {
fn test_load_privkey_from_string_sign_and_verify() ! {
pvkey := privkey_from_string(privatekey_sample)!
expected_pvkey_bytes := '30ce3da288965ac6093f0ba9a9a15b2476bea3eda925e1b3c1f094674f52795cd6cb3cafe235dfc15bec542448ffa715'
assert pvkey.seed()!.hex() == expected_pvkey_bytes
assert pvkey.bytes()!.hex() == expected_pvkey_bytes

// public key part
pbkey := pvkey.public_key()!
Expand Down

0 comments on commit 3e73061

Please sign in to comment.