mirror of
https://github.com/vlang/v.git
synced 2025-08-03 17:57:59 -04:00
crypto.ecdsa: expand ecdsa module, to support other curves like secp384r1, secp521r1, secp256k1 (#23407)
This commit is contained in:
parent
81b421bb5b
commit
68c2da2560
@ -250,6 +250,7 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession {
|
||||
skip_files << 'examples/websocket/client-server/server.v' // requires OpenSSL
|
||||
skip_files << 'vlib/v/tests/websocket_logger_interface_should_compile_test.v' // requires OpenSSL
|
||||
skip_files << 'vlib/crypto/ecdsa/ecdsa_test.v' // requires OpenSSL
|
||||
skip_files << 'vlib/crypto/ecdsa/util_test.v' // requires OpenSSL
|
||||
$if tinyc {
|
||||
skip_files << 'examples/database/orm.v' // try fix it
|
||||
}
|
||||
@ -280,13 +281,15 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession {
|
||||
}
|
||||
if github_job == 'docker-ubuntu-musl' {
|
||||
skip_files << 'vlib/net/openssl/openssl_compiles_test.c.v'
|
||||
skip_files << 'vlib/crypto/ecdsa/ecdsa_test.v'
|
||||
skip_files << 'vlib/crypto/ecdsa/ecdsa_test.v' // requires OpenSSL
|
||||
skip_files << 'vlib/crypto/ecdsa/util_test.v' // requires OpenSSL
|
||||
skip_files << 'vlib/x/ttf/ttf_test.v'
|
||||
skip_files << 'vlib/encoding/iconv/iconv_test.v' // needs libiconv to be installed
|
||||
}
|
||||
if github_job == 'tests-sanitize-memory-clang' {
|
||||
skip_files << 'vlib/net/openssl/openssl_compiles_test.c.v'
|
||||
skip_files << 'vlib/crypto/ecdsa/ecdsa_test.v'
|
||||
skip_files << 'vlib/crypto/ecdsa/ecdsa_test.v' // requires OpenSSL
|
||||
skip_files << 'vlib/crypto/ecdsa/util_test.v' // requires OpenSSL
|
||||
// Fails compilation with: `/usr/bin/ld: /lib/x86_64-linux-gnu/libpthread.so.0: error adding symbols: DSO missing from command line`
|
||||
skip_files << 'examples/sokol/sounds/simple_sin_tones.v'
|
||||
}
|
||||
|
8
vlib/crypto/ecdsa/README.md
Normal file
8
vlib/crypto/ecdsa/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
## 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
|
@ -3,6 +3,11 @@
|
||||
// that can be found in the LICENSE file.
|
||||
module ecdsa
|
||||
|
||||
import hash
|
||||
import crypto
|
||||
import crypto.sha256
|
||||
import crypto.sha512
|
||||
|
||||
#flag darwin -L /opt/homebrew/opt/openssl/lib -I /opt/homebrew/opt/openssl/include
|
||||
|
||||
#flag -I/usr/include/openssl
|
||||
@ -39,9 +44,39 @@ fn C.EC_POINT_cmp(group &C.EC_GROUP, a &C.EC_POINT, b &C.EC_POINT, ctx &C.BN_CTX
|
||||
fn C.BN_CTX_new() &C.BN_CTX
|
||||
fn C.BN_CTX_free(ctx &C.BN_CTX)
|
||||
|
||||
// for checking the key
|
||||
fn C.EC_KEY_check_key(key &C.EC_KEY) int
|
||||
|
||||
// NID constants
|
||||
//
|
||||
// NIST P-256 is refered 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
|
||||
|
||||
// The list of supported curve(s)
|
||||
pub enum Nid {
|
||||
prime256v1
|
||||
secp384r1
|
||||
secp521r1
|
||||
secp256k1
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct CurveOptions {
|
||||
pub mut:
|
||||
nid Nid = .prime256v1 // default to NIST P-256 curve
|
||||
}
|
||||
|
||||
@[typedef]
|
||||
struct C.EC_KEY {}
|
||||
|
||||
@ -68,10 +103,9 @@ pub struct PublicKey {
|
||||
key &C.EC_KEY
|
||||
}
|
||||
|
||||
// Generate a new key pair
|
||||
pub fn generate_key() !(PublicKey, PrivateKey) {
|
||||
nid := nid_prime256v1 // Using NIST P-256 curve
|
||||
ec_key := C.EC_KEY_new_by_curve_name(nid)
|
||||
// Generate a new key pair. If opt was not provided, its default to prime256v1 curve.
|
||||
pub fn generate_key(opt CurveOptions) !(PublicKey, PrivateKey) {
|
||||
ec_key := new_curve(opt)
|
||||
if ec_key == 0 {
|
||||
return error('Failed to create new EC_KEY')
|
||||
}
|
||||
@ -80,6 +114,7 @@ pub fn generate_key() !(PublicKey, PrivateKey) {
|
||||
C.EC_KEY_free(ec_key)
|
||||
return error('Failed to generate EC_KEY')
|
||||
}
|
||||
|
||||
priv_key := PrivateKey{
|
||||
key: ec_key
|
||||
}
|
||||
@ -89,11 +124,10 @@ pub fn generate_key() !(PublicKey, PrivateKey) {
|
||||
return pub_key, priv_key
|
||||
}
|
||||
|
||||
// Create a new private key from a seed
|
||||
pub fn new_key_from_seed(seed []u8) !PrivateKey {
|
||||
nid := nid_prime256v1
|
||||
// Create a new private key from a seed. If opt was not provided, its default to prime256v1 curve.
|
||||
pub fn new_key_from_seed(seed []u8, opt CurveOptions) !PrivateKey {
|
||||
// Create a new EC_KEY object with the specified curve
|
||||
ec_key := C.EC_KEY_new_by_curve_name(nid)
|
||||
ec_key := new_curve(opt)
|
||||
if ec_key == 0 {
|
||||
return error('Failed to create new EC_KEY')
|
||||
}
|
||||
@ -113,7 +147,16 @@ pub fn new_key_from_seed(seed []u8) !PrivateKey {
|
||||
// Now compute the public key
|
||||
//
|
||||
// Retrieve the EC_GROUP object associated with the EC_KEY
|
||||
group := C.EC_KEY_get0_group(ec_key)
|
||||
// Note:
|
||||
// Its cast-ed with voidptr() to workaround the strictness of the type system,
|
||||
// ie, cc backend with `-cstrict` option behaviour. Without this cast,
|
||||
// C.EC_KEY_get0_group expected to return `const EC_GROUP *`,
|
||||
// ie expected to return pointer into constant of EC_GROUP on C parts,
|
||||
// so, its make cgen not happy with this and would fail with error.
|
||||
group := voidptr(C.EC_KEY_get0_group(ec_key))
|
||||
if group == 0 {
|
||||
return error('failed to load group')
|
||||
}
|
||||
// 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
|
||||
@ -143,6 +186,13 @@ pub fn new_key_from_seed(seed []u8) !PrivateKey {
|
||||
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 {
|
||||
key_free(ec_key)
|
||||
return error('EC_KEY_check_key failed')
|
||||
}
|
||||
C.EC_POINT_free(pub_key_point)
|
||||
C.BN_free(bn)
|
||||
return PrivateKey{
|
||||
@ -151,6 +201,7 @@ pub fn new_key_from_seed(seed []u8) !PrivateKey {
|
||||
}
|
||||
|
||||
// Sign a message with private key
|
||||
// FIXME: should the message should be hashed?
|
||||
pub fn (priv_key PrivateKey) sign(message []u8) ![]u8 {
|
||||
if message.len == 0 {
|
||||
return error('Message cannot be null or empty')
|
||||
@ -179,7 +230,7 @@ pub fn (pub_key PublicKey) verify(message []u8, sig []u8) !bool {
|
||||
|
||||
// Get the seed (private key bytes)
|
||||
pub fn (priv_key PrivateKey) seed() ![]u8 {
|
||||
bn := C.EC_KEY_get0_private_key(priv_key.key)
|
||||
bn := voidptr(C.EC_KEY_get0_private_key(priv_key.key))
|
||||
if bn == 0 {
|
||||
return error('Failed to get private key BIGNUM')
|
||||
}
|
||||
@ -204,19 +255,16 @@ pub fn (priv_key PrivateKey) public_key() !PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
// Compare two private keys
|
||||
pub fn (priv_key PrivateKey) equal(other PrivateKey) bool {
|
||||
bn1 := C.EC_KEY_get0_private_key(priv_key.key)
|
||||
bn2 := C.EC_KEY_get0_private_key(other.key)
|
||||
res := C.BN_cmp(bn1, bn2)
|
||||
return res == 0
|
||||
}
|
||||
// EC_GROUP_cmp() for comparing two group (curve).
|
||||
// EC_GROUP_cmp returns 0 if the curves are equal, 1 if they are not equal, or -1 on error.
|
||||
fn C.EC_GROUP_cmp(a &C.EC_GROUP, b &C.EC_GROUP, ctx &C.BN_CTX) int
|
||||
|
||||
// Compare two public keys
|
||||
pub fn (pub_key PublicKey) equal(other PublicKey) bool {
|
||||
group := C.EC_KEY_get0_group(pub_key.key)
|
||||
point1 := C.EC_KEY_get0_public_key(pub_key.key)
|
||||
point2 := C.EC_KEY_get0_public_key(other.key)
|
||||
// equal compares two private keys was equal. Its checks for two things, ie:
|
||||
// - 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 {
|
||||
group1 := voidptr(C.EC_KEY_get0_group(priv_key.key))
|
||||
group2 := voidptr(C.EC_KEY_get0_group(other.key))
|
||||
ctx := C.BN_CTX_new()
|
||||
if ctx == 0 {
|
||||
return false
|
||||
@ -224,8 +272,172 @@ pub fn (pub_key PublicKey) equal(other PublicKey) bool {
|
||||
defer {
|
||||
C.BN_CTX_free(ctx)
|
||||
}
|
||||
res := C.EC_POINT_cmp(group, point1, point2, ctx)
|
||||
return res == 0
|
||||
gres := C.EC_GROUP_cmp(group1, group2, ctx)
|
||||
// Its lives on the same group
|
||||
if gres == 0 {
|
||||
bn1 := voidptr(C.EC_KEY_get0_private_key(priv_key.key))
|
||||
bn2 := voidptr(C.EC_KEY_get0_private_key(other.key))
|
||||
res := C.BN_cmp(bn1, bn2)
|
||||
return res == 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Compare two public keys
|
||||
pub fn (pub_key PublicKey) equal(other PublicKey) bool {
|
||||
// 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))
|
||||
if group1 == 0 || group2 == 0 {
|
||||
return false
|
||||
}
|
||||
ctx := C.BN_CTX_new()
|
||||
if ctx == 0 {
|
||||
return false
|
||||
}
|
||||
defer {
|
||||
C.BN_CTX_free(ctx)
|
||||
}
|
||||
gres := C.EC_GROUP_cmp(group1, group2, ctx)
|
||||
// Its lives on the same group
|
||||
if gres == 0 {
|
||||
point1 := voidptr(C.EC_KEY_get0_public_key(pub_key.key))
|
||||
point2 := voidptr(C.EC_KEY_get0_public_key(other.key))
|
||||
if point1 == 0 || point2 == 0 {
|
||||
return false
|
||||
}
|
||||
res := C.EC_POINT_cmp(group1, point1, point2, ctx)
|
||||
return res == 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Helpers
|
||||
//
|
||||
// new_curve creates a new empty curve based on curve NID, default to prime256v1 (or secp256r1).
|
||||
fn new_curve(opt CurveOptions) &C.EC_KEY {
|
||||
mut nid := nid_prime256v1
|
||||
match opt.nid {
|
||||
.prime256v1 {
|
||||
// do nothing
|
||||
}
|
||||
.secp384r1 {
|
||||
nid = nid_secp384r1
|
||||
}
|
||||
.secp521r1 {
|
||||
nid = nid_secp521r1
|
||||
}
|
||||
.secp256k1 {
|
||||
nid = nid_secp256k1
|
||||
}
|
||||
}
|
||||
return C.EC_KEY_new_by_curve_name(nid)
|
||||
}
|
||||
|
||||
// Gets recommended hash function of the current PrivateKey.
|
||||
// Its purposes for hashing message to be signed
|
||||
fn (pv PrivateKey) recommended_hash() !crypto.Hash {
|
||||
group := voidptr(C.EC_KEY_get0_group(pv.key))
|
||||
if group == 0 {
|
||||
return error('Unable to load group')
|
||||
}
|
||||
// gets the bits size of private key group
|
||||
num_bits := C.EC_GROUP_get_degree(group)
|
||||
match true {
|
||||
// use sha256
|
||||
num_bits <= 256 {
|
||||
return .sha256
|
||||
}
|
||||
num_bits > 256 && num_bits <= 384 {
|
||||
return .sha384
|
||||
}
|
||||
// TODO: what hash should be used if the size is over > 512 bits
|
||||
num_bits > 384 {
|
||||
return .sha512
|
||||
}
|
||||
else {
|
||||
return error('Unsupported bits size')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum HashConfig {
|
||||
with_recomended_hash
|
||||
with_no_hash
|
||||
with_custom_hash
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct SignerOpts {
|
||||
pub mut:
|
||||
hash_config HashConfig = .with_recomended_hash
|
||||
// make sense when HashConfig != with_recomended_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 }
|
||||
}
|
||||
|
||||
// sign_with_options sign the message with the options. By default, it would precompute
|
||||
// hash value from message, with recommended_hash function, and then sign the hash value.
|
||||
pub fn (pv PrivateKey) sign_with_options(message []u8, opts SignerOpts) ![]u8 {
|
||||
// we're working on mutable copy of SignerOpts, with some issues when make it as a mutable.
|
||||
// ie, declaring a mutable parameter that accepts a struct with the `@[params]` attribute is not allowed.
|
||||
mut cfg := opts
|
||||
match cfg.hash_config {
|
||||
.with_recomended_hash {
|
||||
h := pv.recommended_hash()!
|
||||
match h {
|
||||
.sha256 {
|
||||
digest := sha256.sum256(message)
|
||||
return pv.sign(digest)!
|
||||
}
|
||||
.sha384 {
|
||||
digest := sha512.sum384(message)
|
||||
return pv.sign(digest)!
|
||||
}
|
||||
.sha512 {
|
||||
digest := sha512.sum512(message)
|
||||
return pv.sign(digest)!
|
||||
}
|
||||
else {
|
||||
return error('Unsupported hash')
|
||||
}
|
||||
}
|
||||
}
|
||||
.with_no_hash {
|
||||
return pv.sign(message)!
|
||||
}
|
||||
.with_custom_hash {
|
||||
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')
|
||||
}
|
||||
// check key size bits
|
||||
group := voidptr(C.EC_KEY_get0_group(pv.key))
|
||||
if group == 0 {
|
||||
return error('fail to load group')
|
||||
}
|
||||
num_bits := C.EC_GROUP_get_degree(group)
|
||||
// check for key size matching
|
||||
key_size := (num_bits + 7) / 8
|
||||
// If current Private Key size is bigger then current hash output size,
|
||||
// by default its not allowed, until set the allow_smaller_size into true
|
||||
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')
|
||||
}
|
||||
}
|
||||
// otherwise, just hash the message and sign
|
||||
digest := cfg.custom_hash.sum(message)
|
||||
defer { unsafe { cfg.custom_hash.free() } }
|
||||
return pv.sign(digest)!
|
||||
}
|
||||
}
|
||||
return error('Not should be here')
|
||||
}
|
||||
|
||||
// Clear allocated memory for key
|
||||
|
@ -15,6 +15,25 @@ fn test_ecdsa() {
|
||||
key_free(priv_key.key)
|
||||
}
|
||||
|
||||
// This test should exactly has the same behaviour with default sign(message),
|
||||
// because we passed .with_no_hash flag as an option.
|
||||
fn test_ecdsa_signing_with_options() {
|
||||
// Generate key pair
|
||||
pub_key, priv_key := generate_key() or { panic(err) }
|
||||
|
||||
// Sign a message
|
||||
message := 'Hello, ECDSA!'.bytes()
|
||||
opts := SignerOpts{
|
||||
hash_config: .with_no_hash
|
||||
}
|
||||
signature := priv_key.sign_with_options(message, opts) or { panic(err) }
|
||||
|
||||
// Verify the signature
|
||||
is_valid := pub_key.verify(message, signature) or { panic(err) }
|
||||
println('Signature valid: ${is_valid}')
|
||||
assert is_valid
|
||||
}
|
||||
|
||||
fn test_generate_key() ! {
|
||||
// Test key generation
|
||||
pub_key, priv_key := generate_key() or { panic(err) }
|
||||
@ -71,6 +90,15 @@ fn test_private_key_equal() ! {
|
||||
key_free(priv_key2.key)
|
||||
}
|
||||
|
||||
fn test_private_key_equality_on_different_curve() ! {
|
||||
// default group
|
||||
_, priv_key1 := generate_key() or { panic(err) }
|
||||
seed := priv_key1.seed() or { panic(err) }
|
||||
// using different group
|
||||
priv_key2 := new_key_from_seed(seed, nid: .secp384r1) or { panic(err) }
|
||||
assert !priv_key1.equal(priv_key2)
|
||||
}
|
||||
|
||||
fn test_public_key_equal() ! {
|
||||
// Test public key equality
|
||||
_, priv_key := generate_key() or { panic(err) }
|
||||
|
139
vlib/crypto/ecdsa/util.v
Normal file
139
vlib/crypto/ecdsa/util.v
Normal file
@ -0,0 +1,139 @@
|
||||
module ecdsa
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
// #define NID_X9_62_id_ecPublicKey 408
|
||||
const nid_ec_publickey = C.NID_X9_62_id_ecPublicKey
|
||||
|
||||
@[typedef]
|
||||
struct C.EVP_PKEY {}
|
||||
|
||||
// EVP_PKEY *EVP_PKEY_new(void);
|
||||
fn C.EVP_PKEY_new() &C.EVP_PKEY
|
||||
|
||||
// EVP_PKEY_free(EVP_PKEY *key);
|
||||
fn C.EVP_PKEY_free(key &C.EVP_PKEY)
|
||||
|
||||
// EC_KEY *EVP_PKEY_get1_EC_KEY(EVP_PKEY *pkey);
|
||||
fn C.EVP_PKEY_get1_EC_KEY(pkey &C.EVP_PKEY) &C.EC_KEY
|
||||
|
||||
// EVP_PKEY *d2i_PUBKEY(EVP_PKEY **a, const unsigned char **pp, long length);
|
||||
fn C.d2i_PUBKEY(k &&C.EVP_PKEY, pp &&u8, length u32) &C.EVP_PKEY
|
||||
|
||||
// point_conversion_form_t EC_KEY_get_conv_form(const EC_KEY *key);
|
||||
fn C.EC_KEY_get_conv_form(k &C.EC_KEY) int
|
||||
|
||||
// EC_GROUP_get_degree
|
||||
fn C.EC_GROUP_get_degree(g &C.EC_GROUP) int
|
||||
|
||||
// const EC_POINT *EC_KEY_get0_public_key(const EC_KEY *key);
|
||||
fn C.EC_KEY_get0_public_key(key &C.EC_KEY) &C.EC_POINT
|
||||
|
||||
// size_t EC_POINT_point2oct(const EC_GROUP *group, const EC_POINT *point, point_conversion_form_t form, uint8_t *buf, size_t max_out, BN_CTX *ctx);
|
||||
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
|
||||
|
||||
// int EVP_PKEY_get_base_id(const EVP_PKEY *pkey);
|
||||
fn C.EVP_PKEY_base_id(key &C.EVP_PKEY) int
|
||||
|
||||
// int EC_GROUP_get_curve_name(const EC_GROUP *group);
|
||||
fn C.EC_GROUP_get_curve_name(g &C.EC_GROUP) int
|
||||
fn C.EC_GROUP_free(group &C.EC_GROUP)
|
||||
|
||||
// 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
|
||||
//
|
||||
// const pubkey_sample = '-----BEGIN PUBLIC KEY-----
|
||||
// MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+P3rhFkT1fXHYbY3CpcBdh6xTC74MQFx
|
||||
// cftNVD3zEPVzo//OalIVatY162ksg8uRWBdvFFuHZ9OMVXkbjwWwhcXP7qmI9rOS
|
||||
// LR3AGUldy+bBpV2nT306qCIwgUAMeOJP
|
||||
// -----END PUBLIC KEY-----'
|
||||
//
|
||||
// block, _ := pem.decode(pubkey_sample) or { panic(err) }
|
||||
// pubkey := 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')
|
||||
}
|
||||
|
||||
eckey := C.EVP_PKEY_get1_EC_KEY(pub_key)
|
||||
if eckey == 0 {
|
||||
key_free(eckey)
|
||||
return error('Failed to get ec key')
|
||||
}
|
||||
// check the group for the supported curve(s)
|
||||
group := voidptr(C.EC_KEY_get0_group(eckey))
|
||||
if group == 0 {
|
||||
C.EC_GROUP_free(group)
|
||||
return error('Failed to load group from key')
|
||||
}
|
||||
nidgroup := C.EC_GROUP_get_curve_name(group)
|
||||
if nidgroup != nid_prime256v1 && nidgroup != nid_secp384r1 && nidgroup != nid_secp521r1
|
||||
&& nidgroup != nid_secp256k1 {
|
||||
return error('Unsupported group')
|
||||
}
|
||||
// Its OK to return
|
||||
return PublicKey{
|
||||
key: eckey
|
||||
}
|
||||
}
|
||||
|
||||
// bytes gets the bytes of public key parts of this keypair.
|
||||
pub fn (pbk PublicKey) bytes() ![]u8 {
|
||||
point := voidptr(C.EC_KEY_get0_public_key(pbk.key))
|
||||
if point == 0 {
|
||||
C.EC_POINT_free(point)
|
||||
return error('Failed to get public key BIGNUM')
|
||||
}
|
||||
|
||||
group := voidptr(C.EC_KEY_get0_group(pbk.key))
|
||||
num_bits := C.EC_GROUP_get_degree(group)
|
||||
// 1 byte of conversion format || x || y of EC_POINT
|
||||
num_bytes := 1 + 2 * ((num_bits + 7) / 8)
|
||||
|
||||
ctx := C.BN_CTX_new()
|
||||
defer {
|
||||
C.BN_CTX_free(ctx)
|
||||
}
|
||||
|
||||
if ctx == 0 {
|
||||
C.EC_POINT_free(point)
|
||||
C.BN_CTX_free(ctx)
|
||||
return error('Failed to create BN_CTX')
|
||||
}
|
||||
mut buf := []u8{len: num_bytes}
|
||||
|
||||
// Get conversion format.
|
||||
// The uncompressed form is indicated by 0x04 and the compressed form is indicated
|
||||
// by either 0x02 or 0x03, hybrid 0x06
|
||||
// The public key MUST be rejected if any other value is included in the first octet.
|
||||
conv_form := C.EC_KEY_get_conv_form(pbk.key)
|
||||
if conv_form !in [2, 3, 4, 6] {
|
||||
return error('bad conversion format')
|
||||
}
|
||||
n := C.EC_POINT_point2oct(group, point, conv_form, buf.data, buf.len, ctx)
|
||||
|
||||
// returns the clone of the buffer[..n]
|
||||
return buf[..n].clone()
|
||||
}
|
48
vlib/crypto/ecdsa/util_test.v
Normal file
48
vlib/crypto/ecdsa/util_test.v
Normal file
@ -0,0 +1,48 @@
|
||||
module ecdsa
|
||||
|
||||
import encoding.hex
|
||||
import crypto.pem
|
||||
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'
|
||||
// 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)!
|
||||
|
||||
status_without_hashed := pbkey.verify(message_tobe_signed.bytes(), expected_signature)!
|
||||
assert status_without_hashed == false
|
||||
|
||||
hashed_msg := sha512.sum384(message_tobe_signed.bytes())
|
||||
status_with_hashed := pbkey.verify(hashed_msg, expected_signature)!
|
||||
assert status_with_hashed == true
|
||||
}
|
||||
|
||||
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.seed()!.hex() == pv
|
||||
pbkey := pvkey.public_key()!
|
||||
assert pbkey.bytes()!.hex() == pb
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user