Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+32
View File
@@ -0,0 +1,32 @@
## ecdsa
`ecdsa` module for V language. Its a wrapper on top of openssl ecdsa functionality.
Its currently (expanded) to support the following curves:
- NIST P-256 curve, commonly referred as prime256v1 or secp256r1
- NIST P-384 curve, commonly referred as secp384r1
- NIST P-521 curve, commonly referred as secp521r1
- A famous Bitcoin curve, commonly referred as secp256k1
# Example
```v
import crypto.ecdsa
fn main() {
// create default NIST P-256 secp256r1 curve key pair. If you wish to generate another curve,
// use: `pbkey, pvkey := ecdsa.generate_key(nid: .secp521r1)!` instead.
pbkey, pvkey := ecdsa.generate_key()!
message_tobe_signed := 'Hello ecdsa'.bytes()
// create a signature with the recommended hash
signature := pvkey.sign(message_tobe_signed)!
// verify the message with the signature
verified := pbkey.verify(message_tobe_signed, signature)!
dump(verified) // should be true
// free allocated keys when you have done with your work.
pbkey.free()
pvkey.free()
}
```
+192
View File
@@ -0,0 +1,192 @@
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module ecdsa
// See https://docs.openssl.org/master/man7/openssl_user_macros/#description
// should be 0x30000000L, but a lot of EC_KEY method was deprecated on version 3.0
// #define OPENSSL_API_COMPAT 0x10100000L
#flag darwin -L/opt/homebrew/opt/openssl/lib
#flag darwin -I/opt/homebrew/opt/openssl/include
#flag darwin -I/usr/local/opt/openssl/include
#flag darwin -L/usr/local/opt/openssl/lib
#flag linux -I/usr/local/include/openssl
#flag linux -L/usr/local/lib64/
#flag openbsd -I/usr/local/include/eopenssl35
#flag openbsd -L/usr/local/lib/eopenssl35 -Wl,-rpath,/usr/local/lib/eopenssl35
// Installed through choco:
#flag windows -IC:/Program Files/OpenSSL-Win64/include
#flag windows -LC:/Program Files/OpenSSL-Win64/lib/VC/x64/MD
// Installed on the CI:
#flag windows -IC:/Program Files/OpenSSL/include
#flag windows -LC:/Program Files/OpenSSL/lib/VC/x64/MD
#flag -I/usr/include/openssl
#flag -lcrypto
#include <openssl/ecdsa.h>
#include <openssl/obj_mac.h>
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <openssl/ec.h>
#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>
// NID constants for elliptic curves
pub const C.NID_X9_62_prime256v1 int
pub const C.NID_secp384r1 int
pub const C.NID_secp521r1 int
pub const C.NID_secp256k1 int
pub const C.NID_X9_62_id_ecPublicKey int // The new opaque of public key pair high level API
@[typedef]
struct C.EVP_PKEY {}
fn C.EVP_PKEY_new() &C.EVP_PKEY
fn C.EVP_PKEY_free(key &C.EVP_PKEY)
fn C.EVP_PKEY_base_id(key &C.EVP_PKEY) i32
fn C.EVP_PKEY_bits(pkey &C.EVP_PKEY) i32
fn C.EVP_PKEY_size(key &C.EVP_PKEY) i32
fn C.EVP_PKEY_eq(a &C.EVP_PKEY, b &C.EVP_PKEY) i32
fn C.EVP_PKEY_check(ctx &C.EVP_PKEY_CTX) i32
fn C.EVP_PKEY_public_check(ctx &C.EVP_PKEY_CTX) i32
fn C.EVP_PKEY_dup(key &C.EVP_PKEY) &C.EVP_PKEY
fn C.EVP_PKEY_set_bn_param(pkey &C.EVP_PKEY, key_name &char, bn &C.BIGNUM) i32
fn C.EVP_PKEY_get_group_name(pkey &C.EVP_PKEY, gname &u8, gname_sz u32, gname_len &usize) i32
fn C.EVP_PKEY_get1_encoded_public_key(pkey &C.EVP_PKEY, ppub &&u8) usize
fn C.EVP_PKEY_get_bn_param(pkey &C.EVP_PKEY, key_name &char, bn &&C.BIGNUM) i32
fn C.EVP_PKEY_fromdata_init(ctx &C.EVP_PKEY_CTX) i32
fn C.EVP_PKEY_fromdata(ctx &C.EVP_PKEY_CTX, ppkey &&C.EVP_PKEY, selection i32, params &C.OSSL_PARAM) i32
// no-prehash signing (verifying)
fn C.EVP_PKEY_sign(ctx &C.EVP_PKEY_CTX, sig &u8, siglen &usize, tbs &u8, tbslen i32) i32
fn C.EVP_PKEY_sign_init(ctx &C.EVP_PKEY_CTX) i32
fn C.EVP_PKEY_verify_init(ctx &C.EVP_PKEY_CTX) i32
fn C.EVP_PKEY_verify(ctx &C.EVP_PKEY_CTX, sig &u8, siglen i32, tbs &u8, tbslen i32) i32
// single shoot digest signing (verifying) routine
fn C.EVP_DigestSign(ctx &C.EVP_MD_CTX, sig &u8, siglen &usize, tbs &u8, tbslen i32) i32
fn C.EVP_DigestVerify(ctx &C.EVP_MD_CTX, sig &u8, siglen i32, tbs &u8, tbslen i32) i32
// Message digest routines
fn C.EVP_DigestInit(ctx &C.EVP_MD_CTX, md &C.EVP_MD) i32
fn C.EVP_DigestUpdate(ctx &C.EVP_MD_CTX, d voidptr, cnt i32) i32
fn C.EVP_DigestFinal(ctx &C.EVP_MD_CTX, md &u8, s &u32) i32
// 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) i32
fn C.EVP_DigestSignUpdate(ctx &C.EVP_MD_CTX, d voidptr, cnt i32) i32
fn C.EVP_DigestSignFinal(ctx &C.EVP_MD_CTX, sig &u8, siglen &usize) i32
fn C.EVP_DigestVerifyInit(ctx &C.EVP_MD_CTX, pctx &&C.EVP_PKEY_CTX, tipe &C.EVP_MD, e voidptr, pkey &C.EVP_PKEY) i32
fn C.EVP_DigestVerifyUpdate(ctx &C.EVP_MD_CTX, d voidptr, cnt i32) i32
fn C.EVP_DigestVerifyFinal(ctx &C.EVP_MD_CTX, sig &u8, siglen i32) i32
// 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 i32, e voidptr) &C.EVP_PKEY_CTX
fn C.EVP_PKEY_keygen_init(ctx &C.EVP_PKEY_CTX) i32
fn C.EVP_PKEY_keygen(ctx &C.EVP_PKEY_CTX, ppkey &&C.EVP_PKEY) i32
fn C.EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx &C.EVP_PKEY_CTX, nid i32) i32
fn C.EVP_PKEY_CTX_set_ec_param_enc(ctx &C.EVP_PKEY_CTX, param_enc i32) i32
fn C.EVP_PKEY_CTX_free(ctx &C.EVP_PKEY_CTX)
fn C.EVP_PKEY_get_bits(pkey &C.EVP_PKEY) i32
// BIO input output declarations.
@[typedef]
struct C.BIO_METHOD {}
@[typedef]
pub struct C.BIO {}
fn C.BIO_new(t &C.BIO_METHOD) &C.BIO
fn C.BIO_free_all(a &C.BIO)
fn C.BIO_s_mem() &C.BIO_METHOD
fn C.BIO_write(b &C.BIO, buf &u8, length i32) i32
fn C.PEM_read_bio_PrivateKey(bp &C.BIO, x &&C.EVP_PKEY, cb i32, u &voidptr) &C.EVP_PKEY
fn C.PEM_read_bio_PUBKEY(bp &C.BIO, x &&C.EVP_PKEY, cb i32, u &voidptr) &C.EVP_PKEY
fn C.PEM_write_bio_PUBKEY(bp &C.BIO, x &C.EVP_PKEY) i32
fn C.d2i_PUBKEY(k &&C.EVP_PKEY, pp &&u8, length u32) &C.EVP_PKEY
fn C.i2d_PUBKEY_bio(bo &C.BIO, pkey &C.EVP_PKEY) i32
fn C.d2i_PUBKEY_bio(bo &C.BIO, key &&C.EVP_PKEY) &C.EVP_PKEY
// Elliptic curve point related declarations.
@[typedef]
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) i32
fn C.EC_POINT_point2buf(group &C.EC_GROUP, point &C.EC_POINT, form i32, pbuf &&u8, ctx &C.BN_CTX) usize
fn C.EC_POINT_free(point &C.EC_POINT)
// Elliptic group (curve) related declarations.
@[typedef]
struct C.EC_GROUP {}
fn C.EC_GROUP_free(group &C.EC_GROUP)
fn C.EC_GROUP_new_by_curve_name(nid i32) &C.EC_GROUP
// Elliptic BIGNUM related declarations.
@[typedef]
struct C.BIGNUM {}
fn C.BN_new() &C.BIGNUM
fn C.BN_num_bits(a &C.BIGNUM) i32
fn C.BN_bn2bin(a &C.BIGNUM, to &u8) i32
fn C.BN_bn2binpad(a &C.BIGNUM, to &u8, tolen i32) i32
fn C.BN_cmp(a &C.BIGNUM, b &C.BIGNUM) i32
fn C.BN_bin2bn(s &u8, len i32, ret &C.BIGNUM) &C.BIGNUM
fn C.BN_free(a &C.BIGNUM)
// BIGNUM context
@[typedef]
struct C.BN_CTX {}
fn C.BN_CTX_new() &C.BN_CTX
fn C.BN_CTX_free(ctx &C.BN_CTX)
@[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) i32 // -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 usize) i32
fn C.OSSL_PARAM_BLD_push_BN(bld &C.OSSL_PARAM_BLD, key &char, bn &C.BIGNUM) i32
fn C.OSSL_PARAM_BLD_push_octet_string(bld &C.OSSL_PARAM_BLD, key &char, buf voidptr, bsize usize) i32
fn C.OSSL_PARAM_BLD_to_param(bld &C.OSSL_PARAM_BLD) &C.OSSL_PARAM
+592
View File
@@ -0,0 +1,592 @@
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module ecdsa
import hash
// NID constants
//
// NIST P-256 is referred to as secp256r1 and prime256v1, defined as #define NID_X9_62_prime256v1 415
// Different names, but they are all the same.
// https://www.rfc-editor.org/rfc/rfc4492.html#appendix-A
const nid_prime256v1 = C.NID_X9_62_prime256v1
// NIST P-384, ie, secp384r1 curve, defined as #define NID_secp384r1 715
const nid_secp384r1 = C.NID_secp384r1
// NIST P-521, ie, secp521r1 curve, defined as #define NID_secp521r1 716
const nid_secp521r1 = C.NID_secp521r1
// Bitcoin curve, defined as #define NID_secp256k1 714
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
// 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 = 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' }
}
}
// CurveOptions represents configuration options to drive keypair generation.
@[params]
pub struct CurveOptions {
pub mut:
// default to NIST P-256 curve
nid Nid = .prime256v1
// by default, allow arbitrary size of seed bytes as key.
// Set it to `true` when you need fixed size, using the curve key size.
// Its main purposes is to support the `.new_key_from_seed` call.
fixed_size bool
}
// 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
}
// generate_key generates a new key pair. If opt was not provided, its default to prime256v1 curve.
// If you want another curve, use `pubkey, pivkey := ecdsa.generate_key(nid: .secp384r1)!` instead.
pub fn generate_key(opt CurveOptions) !(PublicKey, PrivateKey) {
// This can be simplified to just more simpler one
pv := PrivateKey.new(opt)!
pb := pv.public_key()!
return pb, pv
}
// new_key_from_seed creates a new private key from the seed bytes. If opt was not provided,
// its default to prime256v1 curve.
//
// Notes on the seed:
//
// You should make sure, the seed bytes come from a cryptographically secure random generator,
// likes the `crypto.rand` or other trusted sources.
// Internally, the seed size's would be checked to not exceed the key size of underlying curve,
// ie, 32 bytes length for p-256 and secp256k1, 48 bytes length for p-384 and 66 bytes length for p-521.
// Its recommended to use seed with bytes length matching with underlying curve key size.
pub fn new_key_from_seed(seed []u8, opt CurveOptions) !PrivateKey {
// Early exit check
if seed.len == 0 {
return error('Seed with null-length was not allowed')
}
evpkey := evpkey_from_seed(seed, opt) or { return err }
num_bits := C.EVP_PKEY_get_bits(evpkey)
key_size := (num_bits + 7) / 8
if seed.len > key_size {
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.EVP_PKEY_free(evpkey)
return error('seed size doesnt match with curve key size')
}
}
mut pvkey := PrivateKey{
evpkey: evpkey
}
if opt.fixed_size {
// using fixed one
pvkey.ks_flag = .fixed
pvkey.ks_size = key_size
} else {
pvkey.ks_size = seed.len
}
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
evpkey &C.EVP_PKEY
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 {
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, int(opt.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')
}
// 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
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 {
digest := calc_digest_with_evpkey(pv.evpkey, message, opt)!
return sign_digest(pv.evpkey, digest)!
}
// sign_with_options signs message with the options. It will be deprecated,
// Use `PrivateKey.sign()` instead.
@[deprecated: 'use PrivateKey.sign() instead']
pub fn (pv PrivateKey) sign_with_options(message []u8, opt SignerOpts) ![]u8 {
return pv.sign(message, opt)
}
// bytes represent private key as bytes.
pub fn (pv PrivateKey) bytes() ![]u8 {
mut bn := &C.BIGNUM(unsafe { nil })
// retrieves a BIGNUM value associated with a 'priv' key name
n := C.EVP_PKEY_get_bn_param(pv.evpkey, c'priv', &bn)
if n <= 0 {
C.BN_free(bn)
return error('EVP_PKEY_get_bn_param failed')
}
num_bytes := (C.BN_num_bits(bn) + 7) / 8
// Get the buffer size to store the seed.
size := if pv.ks_flag == .flexible {
// should be non zero
pv.ks_size
} else {
num_bytes
}
mut buf := []u8{len: int(size)}
res := C.BN_bn2binpad(bn, buf.data, i32(size))
if res <= 0 {
C.BN_free(bn)
return error('Failed to convert BIGNUM to bytes')
}
C.BN_free(bn)
return buf
}
// seed gets the seed (private key bytes). It will be deprecated.
// Use `PrivateKey.bytes()` instead.
@[deprecated: 'use PrivateKey.bytes() instead']
pub fn (pv PrivateKey) seed() ![]u8 {
return pv.bytes()
}
// public_key gets the PublicKey from private key.
pub fn (pv PrivateKey) public_key() !PublicKey {
// Using duplicate key and removes (clears out) priv key
pbkey := C.EVP_PKEY_dup(pv.evpkey)
bn := C.BN_new()
n := C.EVP_PKEY_set_bn_param(pbkey, c'priv', bn)
assert n == 1
// cleansup
C.BN_free(bn)
return PublicKey{
evpkey: pbkey
}
}
// equal compares two private keys was equal.
pub fn (priv_key PrivateKey) equal(other PrivateKey) bool {
eq := C.EVP_PKEY_eq(voidptr(priv_key.evpkey), voidptr(other.evpkey))
return eq == 1
}
// free clears out allocated memory for PrivateKey. Dont use PrivateKey after calling `.free()`
pub fn (pv &PrivateKey) free() {
C.EVP_PKEY_free(pv.evpkey)
}
// PublicKey represents ECDSA public key for verifying message.
pub struct PublicKey {
// The new high level of keypair opaque
evpkey &C.EVP_PKEY
}
// 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 (pb PublicKey) verify(message []u8, sig []u8, opt SignerOpts) !bool {
digest := calc_digest_with_evpkey(pb.evpkey, message, opt)!
return verify_signature(pb.evpkey, sig, digest)
}
// equal compares two public keys was equal.
pub fn (pub_key PublicKey) equal(other PublicKey) bool {
eq := C.EVP_PKEY_eq(voidptr(pub_key.evpkey), voidptr(other.evpkey))
return eq == 1
}
// free clears out allocated memory for PublicKey. Dont use PublicKey after calling `.free()`
pub fn (pb &PublicKey) free() {
C.EVP_PKEY_free(pb.evpkey)
}
// Helpers
//
// 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')
}
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')
}
key_size := evp_key_size(key)!
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))
sig := []u8{len: int(siglen)}
// calls directly with sign
do := C.EVP_PKEY_sign(ctx, sig.data, &siglen, digest.data, digest.len)
if do <= 0 {
unsafe { sig.free() }
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[..int(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 := u32(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')
}
// 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) or { return err }
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', voidptr(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, usize(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()
mut pbuf := &u8(unsafe { nil })
// 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, &pbuf, ctx)
if n <= 0 {
C.BN_CTX_free(ctx)
C.OPENSSL_free(voidptr(pbuf))
return error('Get null length of buf')
}
// Gets the copy of the result with the correct length
result := unsafe { pbuf.vbytes(int(n)).clone() }
C.OPENSSL_free(voidptr(pbuf))
C.BN_CTX_free(ctx)
return result
}
+236
View File
@@ -0,0 +1,236 @@
// vtest build: present_openssl? && !(openbsd && gcc) && !(sanitize-memory-clang || docker-ubuntu-musl)
module ecdsa
fn test_ecdsa() {
// Generate key pair
pub_key, priv_key := generate_key() or { panic(err) }
// Sign a message
message := 'Hello, ECDSA!'.bytes()
signature := priv_key.sign(message) or { panic(err) }
// Verify the signature
is_valid := pub_key.verify(message, signature) or { panic(err) }
println('Signature valid: ${is_valid}')
assert is_valid
priv_key.free()
pub_key.free()
}
fn test_ecdsa_signing_with_recommended_hash_options() {
// Generate key pair
pub_key, priv_key := generate_key() or { panic(err) }
// Sign a message
message := 'Hello, ECDSA!'.bytes()
signature := priv_key.sign(message) or { panic(err) }
// Verify the signature
is_valid := pub_key.verify(message, signature) or { panic(err) }
println('Signature valid: ${is_valid}')
assert is_valid
pub_key.free()
priv_key.free()
}
fn test_generate_key() ! {
// Test key generation with high level opaque
pub_key, priv_key := generate_key() or { panic(err) }
assert pub_key.evpkey != unsafe { nil }
assert priv_key.evpkey != unsafe { nil }
priv_key.free()
pub_key.free()
}
fn test_new_key_from_seed() ! {
// Test generating a key from a seed
seed := [u8(1), 2, 3, 4, 5]
priv_key := new_key_from_seed(seed) or { panic(err) }
retrieved_seed := priv_key.bytes() or { panic(err) }
assert seed == retrieved_seed
priv_key.free()
}
fn test_new_key_from_seed_with_leading_zeros_bytes() ! {
// Test generating a key from a seed
seed := [u8(0), u8(1), 2, 3, 4, 5]
priv_key := new_key_from_seed(seed) or { panic(err) }
retrieved_seed := priv_key.bytes() or { panic(err) }
assert seed == retrieved_seed
priv_key.free()
}
fn test_sign_and_verify() ! {
// Test signing and verifying a message
pub_key, priv_key := generate_key() or { panic(err) }
message := 'Test message'.bytes()
signature := priv_key.sign(message) or { panic(err) }
is_valid := pub_key.verify(message, signature) or { panic(err) }
assert is_valid
priv_key.free()
pub_key.free()
}
fn test_seed() ! {
// Test retrieving the seed from a private key
pub_key, priv_key := generate_key() or { panic(err) }
seed := priv_key.bytes() or { panic(err) }
assert seed.len > 0
priv_key.free()
pub_key.free()
}
fn test_public_key() ! {
// Test getting the public key from a private key
pubkk, priv_key := generate_key() or { panic(err) }
pub_key1 := priv_key.public_key() or { panic(err) }
pub_key2, privkk := generate_key() or { panic(err) }
assert !pub_key1.equal(pub_key2)
pubkk.free()
privkk.free()
priv_key.free()
pub_key1.free()
pub_key2.free()
}
fn test_private_key_equal() ! {
// Test private key equality
pbk, priv_key1 := generate_key() or { panic(err) }
seed := priv_key1.bytes() or { panic(err) }
priv_key2 := new_key_from_seed(seed) or { panic(err) }
assert priv_key1.equal(priv_key2)
pbk.free()
priv_key1.free()
priv_key2.free()
}
fn test_private_key_equality_on_different_curve() ! {
// default group
pbk, priv_key1 := generate_key() or { panic(err) }
seed := priv_key1.bytes() or { panic(err) }
// using different group
priv_key2 := new_key_from_seed(seed, nid: .secp384r1) or { panic(err) }
assert !priv_key1.equal(priv_key2)
pbk.free()
priv_key1.free()
priv_key2.free()
}
fn test_public_key_equal() ! {
// Test public key equality
pbk, priv_key := generate_key() or { panic(err) }
pub_key1 := priv_key.public_key() or { panic(err) }
pub_key2 := priv_key.public_key() or { panic(err) }
assert pub_key1.equal(pub_key2)
pbk.free()
priv_key.free()
pub_key1.free()
pub_key2.free()
}
fn test_sign_with_new_key_from_seed() ! {
// Test signing with a key generated from a seed
seed := [u8(10), 20, 30, 40, 50]
priv_key := new_key_from_seed(seed) or { panic(err) }
message := 'Another test message'.bytes()
signature := priv_key.sign(message) or { panic(err) }
pub_key := priv_key.public_key() or { panic(err) }
is_valid := pub_key.verify(message, signature) or { panic(err) }
assert is_valid
priv_key.free()
pub_key.free()
}
fn test_invalid_signature() ! {
// Test verifying an invalid signature
pub_key, pvk := generate_key() or { panic(err) }
message := 'Test message'.bytes()
invalid_signature := [u8(1), 2, 3] // Deliberately invalid
result := pub_key.verify(message, invalid_signature) or {
// Expecting verification to fail
assert err.msg() == 'Failed to verify signature'
pub_key.free()
pvk.free()
return
}
assert !result
pub_key.free()
pvk.free()
}
fn test_different_keys_not_equal() ! {
// Test that different keys are not equal
pbk1, priv_key1 := generate_key() or { panic(err) }
pbk2, priv_key2 := generate_key() or { panic(err) }
assert !priv_key1.equal(priv_key2)
pbk1.free()
pbk2.free()
priv_key1.free()
priv_key2.free()
}
fn test_private_key_new() ! {
priv_key := PrivateKey.new()!
assert priv_key.ks_flag == .fixed
size := evp_key_size(priv_key.evpkey)!
assert size == 32
pubkey := priv_key.public_key()!
message := 'Another test message'.bytes()
signature := priv_key.sign(message)!
is_valid := pubkey.verify(message, signature)!
assert is_valid
// new private key
seed := priv_key.bytes()!
priv_key2 := new_key_from_seed(seed)!
pubkey2 := priv_key2.public_key()!
assert priv_key.equal(priv_key2)
assert pubkey.equal(pubkey2)
is_valid2 := pubkey2.verify(message, signature)!
assert is_valid2
// generates new key with different curve
priv_key3 := new_key_from_seed(seed, nid: .secp384r1)!
pubkey3 := priv_key3.public_key()!
assert !priv_key3.equal(priv_key2)
assert !pubkey3.equal(pubkey2)
is_valid3 := pubkey3.verify(message, signature)!
assert !is_valid3
priv_key.free()
priv_key2.free()
priv_key3.free()
pubkey.free()
pubkey2.free()
pubkey3.free()
}
// See https://discord.com/channels/592103645835821068/592114487759470596/1334319744098107423
fn test_key_with_msg_exceed_key_size() ! {
pv := PrivateKey.new()!
msg := 'a'.repeat(200).bytes()
opt := SignerOpts{
hash_config: .with_no_hash
}
signed := pv.sign(msg, opt)!
pb := pv.public_key()!
// should be verified
st := pb.verify(msg, signed, opt)!
assert st
// different msg should not be verified
other_msg := 'a'.repeat(392).bytes()
ds := pb.verify(other_msg, signed, opt)!
// This should assert to false.
assert !ds
pv.free()
pb.free()
}
@@ -0,0 +1,85 @@
// vtest build: present_openssl? && !(openbsd && gcc) && !(sanitize-memory-clang || docker-ubuntu-musl)
import rand
import crypto.ecdsa
import encoding.hex
// The test file placed on its own directory. Its for workaround for
// module lookup problem, because there are two rand module availables,
// between `crypto.rand` and `rand` module.
// See [the talk](https://discord.com/channels/592103645835821068/592294828432424960/1328198034806407311) on discord.
fn test_new_key_from_seed_with_random_size_and_data() ! {
num_iters := 100
// default prime256v1 curve key size was 32 bytes.
max_key_size := i32(48)
for i := 0; i <= num_iters; i++ {
m := rand.i32n(max_key_size)!
random_bytes := rand.bytes(m)!
pvkey := ecdsa.new_key_from_seed(random_bytes) or {
// With default size, would error on m > 32 or m == 0
// dump(m)
if m == 0 {
assert err == error('Seed with null-length was not allowed')
} else if m > 32 {
assert err == error('Seed length exceeds key size')
} else {
assert err == error('EC_KEY_check_key failed')
}
continue
}
ret_seed := pvkey.bytes()!
assert random_bytes == ret_seed
pvkey.free()
}
}
fn test_private_and_public_key_from_string() ! {
// See [this](https://github.com/vlang/v/blob/master/vlib/crypto/ecdsa/util_test.v) for detail
// of material used as a sample.
privkey_sample := '-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAwzj2iiJZaxgk/C6mp
oVskdr6j7akl4bPB8JRnT1J5XNbLPK/iNd/BW+xUJEj/pxWhZANiAAT4/euEWRPV
9cdhtjcKlwF2HrFMLvgxAXFx+01UPfMQ9XOj/85qUhVq1jXraSyDy5FYF28UW4dn
04xVeRuPBbCFxc/uqYj2s5ItHcAZSV3L5sGlXadPfTqoIjCBQAx44k8=
-----END PRIVATE KEY-----'
pubkey_sample := '-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+P3rhFkT1fXHYbY3CpcBdh6xTC74MQFx
cftNVD3zEPVzo//OalIVatY162ksg8uRWBdvFFuHZ9OMVXkbjwWwhcXP7qmI9rOS
LR3AGUldy+bBpV2nT306qCIwgUAMeOJP
-----END PUBLIC KEY-----'
// Message tobe signed and verified
message_tobe_signed := 'Example of ECDSA with P-384'.bytes()
// Message signature generated with SHA384 digest with associated key previously.
signature :=
hex.decode('3066023100b08f6ec77bb319fdb7bce55a2714d7e79cc645d834ee539d8903cfcc88c6fa90df1558856cb840b2dd82e82cd89d7046023100d9d482ca8a6545a3b081fbdd4bb9643a2b4eda4e21fd624833216596032471faae646891f8d2f0bbb86b796c36d3c390')!
// loads a Privatekey and PublicKey from above sample
privkey := ecdsa.privkey_from_string(privkey_sample)!
pubkey := ecdsa.pubkey_from_string(pubkey_sample)!
// get a public key from private key
pbkey_from_privkey := privkey.public_key()!
// two public key should be equal, its comes from the same source.
assert pubkey.equal(pbkey_from_privkey)
// lets create the signature
created_signature := privkey.sign(message_tobe_signed)!
verified1 := pubkey.verify(message_tobe_signed, signature)!
verified2 := pubkey.verify(message_tobe_signed, created_signature)!
assert verified1 == true
assert verified2 == true
// Its also should be verified with pbkey_from_privkey opaque
verified3 := pbkey_from_privkey.verify(message_tobe_signed, signature)!
verified4 := pbkey_from_privkey.verify(message_tobe_signed, created_signature)!
assert verified3 == true
assert verified4 == true
// release the key
privkey.free()
pubkey.free()
pbkey_from_privkey.free()
}
@@ -0,0 +1,15 @@
// vtest build: present_openssl? && !(openbsd && gcc) && !(sanitize-memory-clang || docker-ubuntu-musl)
import net.openssl
import crypto.ecdsa
fn test_openssl_and_crypto_ecdsa_are_compatible() {
pbkey, pvkey := ecdsa.generate_key()!
message_tobe_signed := 'Hello ecdsa'.bytes()
signature := pvkey.sign(message_tobe_signed)!
verified := pbkey.verify(message_tobe_signed, signature)!
assert verified
pbkey.free()
pvkey.free()
c := openssl.SSLConn{}
assert c.str().contains('in_memory_verification: false')
}
@@ -0,0 +1,16 @@
import crypto.ecdsa
fn main() {
// create secp256r1, NIST P-256 curve key pair
pbkey, pvkey := ecdsa.generate_key()!
message_tobe_signed := 'Hello ecdsa'.bytes()
// create signature with recommended hash
signature := pvkey.sign(message_tobe_signed, hash_config: .with_recommended_hash)!
// verified the message with signature
verified := pbkey.verify(message_tobe_signed, signature, hash_config: .with_recommended_hash)!
dump(verified) // should true
pbkey.free()
pvkey.free()
}
+247
View File
@@ -0,0 +1,247 @@
module ecdsa
// pubkey_from_bytes loads ECDSA Public Key from bytes array.
// The bytes of data should be a valid of ASN.1 DER serialized SubjectPublicKeyInfo structrue of RFC 5480.
// Otherwise, its should an error.
// Typically, you can load the bytes from pem formatted of ecdsa public key.
//
// Examples:
// ```codeblock
// import crypto.pem
// import crypto.ecdsa
//
// const pubkey_sample = '-----BEGIN PUBLIC KEY-----
// MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+P3rhFkT1fXHYbY3CpcBdh6xTC74MQFx
// cftNVD3zEPVzo//OalIVatY162ksg8uRWBdvFFuHZ9OMVXkbjwWwhcXP7qmI9rOS
// LR3AGUldy+bBpV2nT306qCIwgUAMeOJP
// -----END PUBLIC KEY-----'
//
// block, _ := pem.decode(pubkey_sample) or { panic(err) }
// pubkey := ecdsa.pubkey_from_bytes(block.data)!
// ```
pub fn pubkey_from_bytes(bytes []u8) !PublicKey {
if bytes.len == 0 {
return error('Invalid bytes')
}
mut pub_key := C.EVP_PKEY_new()
pub_key = C.d2i_PUBKEY(&pub_key, voidptr(&bytes.data), bytes.len)
if pub_key == 0 {
C.EVP_PKEY_free(pub_key)
return error('Error loading public key')
}
// Get the NID of this pubkey, and check if the pubkey object was
// have the correct NID of ec public key type, ie, NID_X9_62_id_ecPublicKey
nid := C.EVP_PKEY_base_id(pub_key)
if nid != nid_ec_publickey {
C.EVP_PKEY_free(pub_key)
return error('Get an nid of non ecPublicKey')
}
// check the key
pctx := C.EVP_PKEY_CTX_new(pub_key, 0)
if pctx == 0 {
C.EVP_PKEY_CTX_free(pctx)
C.EVP_PKEY_free(pub_key)
return error('EVP_PKEY_CTX_new failed')
}
// performs public-only evpkey check
nck := C.EVP_PKEY_public_check(pctx)
if nck != 1 {
C.EVP_PKEY_CTX_free(pctx)
C.EVP_PKEY_free(pub_key)
return error('EVP_PKEY_check failed')
}
// Matching the supported group
gn := key_group_name(pub_key)!
// TODO: using shortname constant
if gn != 'secp256k1' && gn != 'secp384r1' && gn != 'secp521r1' && gn != 'prime256v1' {
C.EVP_PKEY_CTX_free(pctx)
C.EVP_PKEY_free(pub_key)
return error('Unsupported group')
}
// Cleans up
C.EVP_PKEY_CTX_free(pctx)
// Its OK to return
return PublicKey{
evpkey: pub_key
}
}
// bytes gets the bytes of public key.
pub fn (pbk PublicKey) bytes() ![]u8 {
mut ppub := &u8(unsafe { nil })
n := C.EVP_PKEY_get1_encoded_public_key(pbk.evpkey, &ppub)
if n <= 0 {
C.OPENSSL_free(voidptr(ppub))
return error('EVP_PKEY_get1_encoded_public_key failed')
}
out := unsafe { ppub.vbytes(int(n)).clone() }
// ppub should be freed by calling `OPENSSL_free` or memleak happens.
C.OPENSSL_free(voidptr(ppub))
return out
}
// pubkey_from_string loads a PublicKey from valid PEM-formatted string in s.
pub fn pubkey_from_string(s string) !PublicKey {
if s.len == 0 {
return error('Null length string was not allowed')
}
mut evpkey := C.EVP_PKEY_new()
bo := C.BIO_new(C.BIO_s_mem())
if bo == 0 {
C.EVP_PKEY_free(evpkey)
C.BIO_free_all(bo)
return error('Failed to create BIO_new')
}
n := C.BIO_write(bo, s.str, s.len)
if n <= 0 {
C.EVP_PKEY_free(evpkey)
C.BIO_free_all(bo)
return error('BIO_write failed')
}
evpkey = C.PEM_read_bio_PUBKEY(bo, &evpkey, 0, 0)
if evpkey == 0 {
C.BIO_free_all(bo)
C.EVP_PKEY_free(evpkey)
return error('Error loading key')
}
// Get the NID of this key, and check if the key object was
// have the correct NID of ec public key type, ie, NID_X9_62_id_ecPublicKey
nid := C.EVP_PKEY_base_id(evpkey)
if nid != nid_ec_publickey {
C.BIO_free_all(bo)
C.EVP_PKEY_free(evpkey)
return error('Get an nid of non ecPublicKey')
}
// check the key
pctx := C.EVP_PKEY_CTX_new(evpkey, 0)
if pctx == 0 {
C.BIO_free_all(bo)
C.EVP_PKEY_CTX_free(pctx)
C.EVP_PKEY_free(evpkey)
return error('EVP_PKEY_CTX_new failed')
}
// performs public-only evpkey check
nck := C.EVP_PKEY_public_check(pctx)
if nck != 1 {
C.BIO_free_all(bo)
C.EVP_PKEY_CTX_free(pctx)
C.EVP_PKEY_free(evpkey)
return error('EVP_PKEY_check failed')
}
// Matching the supported group
gn := key_group_name(evpkey)!
// TODO: using shortname constant
if gn != 'secp256k1' && gn != 'secp384r1' && gn != 'secp521r1' && gn != 'prime256v1' {
C.BIO_free_all(bo)
C.EVP_PKEY_CTX_free(pctx)
C.EVP_PKEY_free(evpkey)
return error('Unsupported group')
}
// Cleans up
C.BIO_free_all(bo)
C.EVP_PKEY_CTX_free(pctx)
// Its OK to return
return PublicKey{
evpkey: evpkey
}
}
// privkey_from_string loads a PrivateKey from valid PEM-formatted string in s.
// Underlying wrapper support for old SECG and PKCS8 private key format, but this was not heavily tested.
// This routine does not support for the PKCS8 EncryptedPrivateKeyInfo format.
// See [ecdsa_seed_test.v](https://github.com/vlang/v/blob/master/vlib/crypto/ecdsa/example/ecdsa_seed_test.v) file
// for example of usage.
pub fn privkey_from_string(s string) !PrivateKey {
if s.len == 0 {
return error('null string was not allowed')
}
mut evpkey := C.EVP_PKEY_new()
bo := C.BIO_new(C.BIO_s_mem())
if bo == 0 {
C.BIO_free_all(bo)
C.EVP_PKEY_free(evpkey)
return error('Failed to create BIO_new')
}
n := C.BIO_write(bo, s.str, s.len)
if n <= 0 {
C.BIO_free_all(bo)
C.EVP_PKEY_free(evpkey)
return error('BIO_write failed')
}
evpkey = C.PEM_read_bio_PrivateKey(bo, &evpkey, 0, 0)
if evpkey == 0 {
C.BIO_free_all(bo)
C.EVP_PKEY_free(evpkey)
return error('Error loading key')
}
// Get the NID of this key, and check if the key object was
// have the correct NID of ec public key type, ie, NID_X9_62_id_ecPublicKey
nid := C.EVP_PKEY_base_id(evpkey)
if nid != nid_ec_publickey {
C.BIO_free_all(bo)
C.EVP_PKEY_free(evpkey)
return error('Get an nid of non ecPublicKey')
}
pctx := C.EVP_PKEY_CTX_new(evpkey, 0)
if pctx == 0 {
C.BIO_free_all(bo)
C.EVP_PKEY_CTX_free(pctx)
C.EVP_PKEY_free(evpkey)
return error('EVP_PKEY_CTX_new failed')
}
// performs evpkey check
nck := C.EVP_PKEY_check(pctx)
if nck != 1 {
C.BIO_free_all(bo)
C.EVP_PKEY_CTX_free(pctx)
C.EVP_PKEY_free(evpkey)
return error('EVP_PKEY_check failed')
}
// Matching the supported group
gn := key_group_name(evpkey)!
// TODO: using shortname constant
if gn != 'secp256k1' && gn != 'secp384r1' && gn != 'secp521r1' && gn != 'prime256v1' {
C.BIO_free_all(bo)
C.EVP_PKEY_CTX_free(pctx)
C.EVP_PKEY_free(evpkey)
return error('Unsupported group')
}
// Cleans up
C.BIO_free_all(bo)
C.EVP_PKEY_CTX_free(pctx)
// Its OK to return
return PrivateKey{
evpkey: evpkey
ks_flag: .fixed
}
}
// evp_key_size get the key size of this ec key
fn evp_key_size(key &C.EVP_PKEY) !int {
num_bits := C.EVP_PKEY_get_bits(key)
key_size := (num_bits + 7) / 8
return key_size
}
const default_groupname_size = 25 // short name commonly only take 10-15 length
// key_group_name returns underlying group name of the key as a string.
fn key_group_name(key &C.EVP_PKEY) !string {
gname := []u8{len: default_groupname_size}
gname_len := usize(0)
mut s := C.EVP_PKEY_get_group_name(key, gname.data, u32(gname.len), &gname_len)
if s == 0 {
unsafe { gname.free() }
return error('fail to get group name')
}
group := gname[..int(gname_len)].bytestr()
unsafe { gname.free() }
return group
}
+198
View File
@@ -0,0 +1,198 @@
// vtest build: present_openssl? && !(openbsd && gcc) && !(sanitize-memory-clang || docker-ubuntu-musl)
module ecdsa
import encoding.hex
import crypto.pem
import crypto.sha1
import crypto.sha512
// This material wss generated with https://emn178.github.io/online-tools/ecdsa/key-generator
// with curve SECG secp384r1 aka NIST P-384
const privatekey_sample = '-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAwzj2iiJZaxgk/C6mp
oVskdr6j7akl4bPB8JRnT1J5XNbLPK/iNd/BW+xUJEj/pxWhZANiAAT4/euEWRPV
9cdhtjcKlwF2HrFMLvgxAXFx+01UPfMQ9XOj/85qUhVq1jXraSyDy5FYF28UW4dn
04xVeRuPBbCFxc/uqYj2s5ItHcAZSV3L5sGlXadPfTqoIjCBQAx44k8=
-----END PRIVATE KEY-----'
const public_key_sample = '-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+P3rhFkT1fXHYbY3CpcBdh6xTC74MQFx
cftNVD3zEPVzo//OalIVatY162ksg8uRWBdvFFuHZ9OMVXkbjwWwhcXP7qmI9rOS
LR3AGUldy+bBpV2nT306qCIwgUAMeOJP
-----END PUBLIC KEY-----'
// Message tobe signed and verified
const message_tobe_signed = 'Example of ECDSA with P-384'.bytes()
// Message signature created with SHA384 digest with associated above key
const expected_signature = hex.decode('3066023100b08f6ec77bb319fdb7bce55a2714d7e79cc645d834ee539d8903cfcc88c6fa90df1558856cb840b2dd82e82cd89d7046023100d9d482ca8a6545a3b081fbdd4bb9643a2b4eda4e21fd624833216596032471faae646891f8d2f0bbb86b796c36d3c390')!
fn test_load_pubkey_from_der_serialized_bytes() ! {
block, _ := pem.decode(public_key_sample) or { panic(err) }
pbkey := pubkey_from_bytes(block.data)!
// .with_no_hash currently changed to have same behaviour with .with_recommended_hash
status_without_hashed := pbkey.verify(message_tobe_signed, expected_signature,
hash_config: .with_no_hash
)!
assert status_without_hashed == true
// expected signature was comes from hashed message with sha384
status_with_hashed := pbkey.verify(message_tobe_signed, expected_signature)!
assert status_with_hashed == true
pbkey.free()
}
fn test_for_pubkey_bytes() ! {
// material generated with online ecdsa generator https://emn178.github.io/online-tools/ecdsa/key-generator/
pv := '62e998bea8a15f52ff0b76cf3fe281cfcd8042ce4479b6e652ca7b5a36f6fb40'
pb := '0421af184ac64c8a13e66c65d4f1ad31677edeaa97af791aef73b66ea26d1623a411f67b6c4d842ba22fa39d1216bd64acef00a1b924ac11a10af679ac3a7eb2fd'
pvkey := new_key_from_seed(hex.decode(pv)!)!
assert pvkey.bytes()!.hex() == pv
pbkey := pvkey.public_key()!
assert pbkey.bytes()!.hex() == pb
pbkey.free()
pvkey.free()
}
// above pem-formatted private key read with
// `$openssl ec -in vlib/crypto/ecdsa/example.pem -text -param_out -check`
// produces following result:
// ```codeblock
// read EC key
// Private-Key: (384 bit)
// priv:
// 30:ce:3d:a2:88:96:5a:c6:09:3f:0b:a9:a9:a1:5b:
// 24:76:be:a3:ed:a9:25:e1:b3:c1:f0:94:67:4f:52:
// 79:5c:d6:cb:3c:af:e2:35:df:c1:5b:ec:54:24:48:
// ff:a7:15
// pub:
// 04:f8:fd:eb:84:59:13:d5:f5:c7:61:b6:37:0a:97:
// 01:76:1e:b1:4c:2e:f8:31:01:71:71:fb:4d:54:3d:
// f3:10:f5:73:a3:ff:ce:6a:52:15:6a:d6:35:eb:69:
// 2c:83:cb:91:58:17:6f:14:5b:87:67:d3:8c:55:79:
// 1b:8f:05:b0:85:c5:cf:ee:a9:88:f6:b3:92:2d:1d:
// c0:19:49:5d:cb:e6:c1:a5:5d:a7:4f:7d:3a:a8:22:
// 30:81:40:0c:78:e2:4f
// ASN1 OID: secp384r1
// NIST CURVE: P-384
// EC Key valid.
// writing EC key
// -----BEGIN EC PRIVATE KEY-----
// MIGkAgEBBDAwzj2iiJZaxgk/C6mpoVskdr6j7akl4bPB8JRnT1J5XNbLPK/iNd/B
// W+xUJEj/pxWgBwYFK4EEACKhZANiAAT4/euEWRPV9cdhtjcKlwF2HrFMLvgxAXFx
// +01UPfMQ9XOj/85qUhVq1jXraSyDy5FYF28UW4dn04xVeRuPBbCFxc/uqYj2s5It
// HcAZSV3L5sGlXadPfTqoIjCBQAx44k8=
// -----END EC PRIVATE KEY-----
// ```
fn test_load_privkey_from_string_sign_and_verify() ! {
pvkey := privkey_from_string(privatekey_sample)!
expected_pvkey_bytes := '30ce3da288965ac6093f0ba9a9a15b2476bea3eda925e1b3c1f094674f52795cd6cb3cafe235dfc15bec542448ffa715'
assert pvkey.bytes()!.hex() == expected_pvkey_bytes
// public key part
pbkey := pvkey.public_key()!
pbkey_bytes := pbkey.bytes()!
expected_pubkey_bytes := '04f8fdeb845913d5f5c761b6370a9701761eb14c2ef831017171fb4d543df310f573a3ffce6a52156ad635eb692c83cb9158176f145b8767d38c55791b8f05b085c5cfeea988f6b3922d1dc019495dcbe6c1a55da74f7d3aa8223081400c78e24f'
assert pbkey_bytes.hex() == expected_pubkey_bytes
// lets sign the message with default hash, ie, sha384
signature := pvkey.sign(message_tobe_signed)!
verified := pbkey.verify(message_tobe_signed, signature)!
assert verified == true
pvkey.free()
pbkey.free()
}
fn test_load_pubkey_from_string_and_used_for_verifying() ! {
pbkey := pubkey_from_string(public_key_sample)!
pbkey_bytes := pbkey.bytes()!
expected_pubkey_bytes := '04f8fdeb845913d5f5c761b6370a9701761eb14c2ef831017171fb4d543df310f573a3ffce6a52156ad635eb692c83cb9158176f145b8767d38c55791b8f05b085c5cfeea988f6b3922d1dc019495dcbe6c1a55da74f7d3aa8223081400c78e24f'
assert pbkey_bytes.hex() == expected_pubkey_bytes
// expected signature was comes from hashed message with sha384
status_with_hashed := pbkey.verify(message_tobe_signed, expected_signature)!
assert status_with_hashed == true
pbkey.free()
}
// test for loading privat key from unsupported curve should fail.
fn test_load_privkey_from_string_with_unsupported_curve() ! {
// generated with openssl ecparam -name secp192k1 -genkey -noout -out key.pem
key := '-----BEGIN EC PRIVATE KEY-----
MFwCAQEEGDHV+WhJL2UjUhgMLh52k0RJjRebtu4HvqAHBgUrgQQAH6E0AzIABFyF
UHhnmmVRraSwrVkPdYIeXhH/Ob4+8OLcwrQBMv4RXsD1GVFsgkvEYDTEb/vnMA==
-----END EC PRIVATE KEY-----'
_ := privkey_from_string(key) or {
assert err == error('Unsupported group')
return
}
}
fn test_key_signing_verifying_with_custom_hash() ! {
// privatekey_sample was P-384 key
pvkey := privkey_from_string(privatekey_sample)!
// public key part
pbkey := pvkey.public_key()!
pbk := pubkey_from_string(public_key_sample)!
// lets sign the message with default hash, ie, sha384
signature := pvkey.sign(message_tobe_signed)!
verified := pbkey.verify(message_tobe_signed, signature)!
assert verified == true
// Use the bigger custom hash
opt0 := SignerOpts{
hash_config: .with_custom_hash
allow_custom_hash: true
custom_hash: sha512.new()
}
// online-generated signature with sha512 digest with the same params from https://emn178.github.io/online-tools/ecdsa/sign/
online_sign0 :=
hex.decode('3066023100b54b479b64961481074c4200a9dec83fb8a42bb7db53cf97f1da131504a058ead85d0a9e4e32be14098bc9b4d1a5a8dd023100f9c7de178a286329103f684d1eab1ccfe359c65a41a1459d7f535b703c57048f25931b1670ab4ec7a812d94c69063522')!
// library generated signature
sign0 := pvkey.sign(message_tobe_signed, opt0)!
v00 := pbkey.verify(message_tobe_signed, sign0, opt0)!
// this own signature should assert into true
assert v00 == true
// verify online-generated signature
v01 := pbkey.verify(message_tobe_signed, online_sign0, opt0)!
assert v01 == true
// with public_key_sample key
v02 := pbk.verify(message_tobe_signed, sign0, opt0)!
assert v02 == true
v03 := pbk.verify(message_tobe_signed, online_sign0, opt0)!
assert v03 == true
// Use smaller custom hash
opt1 := SignerOpts{
hash_config: .with_custom_hash
allow_custom_hash: true
allow_smaller_size: true
custom_hash: sha1.new()
}
// online-generated signature with SHA1 digest
online_sign1 :=
hex.decode('306602310084299d8a70bf512c25cd2b79ae36509572f2bd6f198baeee074683578a70b4af8008e1cf451a2df1a887cf43daff4eea023100dceb267fe5037025c2af9f37911e05a36cbe666dd90fd6904020b5db056e86f25f9439a0ccb443d113b174cab6e2ad61')!
// library generated signature
sign1 := pvkey.sign(message_tobe_signed, opt1)!
verified1 := pbkey.verify(message_tobe_signed, sign1, opt1)!
// this own signature should assert into true
assert verified1 == true
// verify online-generated signature
verified11 := pbkey.verify(message_tobe_signed, online_sign1, opt1)!
assert verified11 == true
// verify with public_key_sample key
v11 := pbk.verify(message_tobe_signed, sign1, opt1)!
assert v11 == true
v12 := pbk.verify(message_tobe_signed, online_sign1, opt1)!
assert v12 == true
pvkey.free()
pbkey.free()
pbk.free()
}