mirror of
https://github.com/vlang/v.git
synced 2025-09-15 23:42:28 +03:00
380 lines
10 KiB
V
380 lines
10 KiB
V
// Copyright (c) blackshirt. All rights reserved.
|
|
// Use of this source code is governed by an MIT license
|
|
// that can be found in the LICENSE file.
|
|
module slhdsa
|
|
|
|
// PrivateKey represents SLH-DSA keypair.
|
|
pub struct PrivateKey {
|
|
key &C.EVP_PKEY
|
|
}
|
|
|
|
// new creates a new SLH-DSA PrivateKey based on the supplied options.
|
|
// By default, it will create a `SLH-DSA-SHA2-128s` key with random generator.
|
|
// See `enum Kind` for all of availables choice's type (kind) of key.
|
|
// Its also support to generate keys based on the seed or private bytes through
|
|
// provided options. See available options in `KeyOpts`.
|
|
// Usage example:
|
|
// ```v
|
|
// pk1 := slhdsa.PrivateKey.new()!; println(pk1)
|
|
// pk2 := slhdsa.PrivateKey.new(kind: .sha2_128s)!; println(pk2)
|
|
// ```
|
|
pub fn PrivateKey.new(opt KeyOpts) !PrivateKey {
|
|
match opt.flag {
|
|
// default random generated key, its recommended way
|
|
0 {
|
|
kind := opt.kind.long_name()
|
|
key := C.EVP_PKEY_Q_keygen(0, 0, kind)
|
|
if key == 0 {
|
|
return error('EVP_PKEY_Q_keygen failed')
|
|
}
|
|
return PrivateKey{
|
|
key: key
|
|
}
|
|
}
|
|
// use seed bytes, usually for testing purposes
|
|
1 {
|
|
// if you dont provides seed bytes, it will discarded, and use random one
|
|
return PrivateKey.from_seed(opt.seed, opt.kind)!
|
|
}
|
|
// use private bytes, usually for testing purposes
|
|
2 {
|
|
// if you dont provides private bytes, it will discarded, and use random one
|
|
return PrivateKey.from_bytes(opt.priv, opt.kind)!
|
|
}
|
|
else {
|
|
return error('Unsupported flag')
|
|
}
|
|
}
|
|
}
|
|
|
|
// sign signs the message using this key under desired options in opt.
|
|
pub fn (pv PrivateKey) sign(msg []u8, opt SignerOpts) ![]u8 {
|
|
if msg.len == 0 {
|
|
return error('Zero length of messages')
|
|
}
|
|
if opt.context.len > 255 {
|
|
return error('The context string size was over than 255')
|
|
}
|
|
out := slhdsa_do_sign(pv.key, msg, opt)!
|
|
return out
|
|
}
|
|
|
|
// verify verifies signature for the message msg under provided options.
|
|
// Its possible because of under the hood, private key is a key pair.
|
|
pub fn (pv PrivateKey) verify(sig []u8, msg []u8, opt SignerOpts) !bool {
|
|
if opt.context.len > 255 {
|
|
return error('The context string size was over than 255')
|
|
}
|
|
return slhdsa_do_verify(pv.key, sig, msg, opt)!
|
|
}
|
|
|
|
const default_bioread_bufsize = 4096
|
|
|
|
// dump_key represents PrivateKey in human readable string.
|
|
pub fn (pv PrivateKey) dump_key() !string {
|
|
bo := C.BIO_new(C.BIO_s_mem())
|
|
if bo == 0 {
|
|
C.BIO_free_all(bo)
|
|
return error('BIO_new failed')
|
|
}
|
|
n := C.EVP_PKEY_print_private(bo, pv.key, 2, 0)
|
|
if n <= 0 {
|
|
C.BIO_free_all(bo)
|
|
return error('print private failed')
|
|
}
|
|
size := usize(0)
|
|
buf := []u8{len: default_bioread_bufsize}
|
|
m := C.BIO_read_ex(bo, buf.data, buf.len, &size)
|
|
|
|
if m <= 0 {
|
|
unsafe { buf.free() }
|
|
C.BIO_free_all(bo)
|
|
return error('BIO_read_ex failed')
|
|
}
|
|
output := buf[..size].bytestr()
|
|
|
|
// Cleans up and return the result
|
|
unsafe { buf.free() }
|
|
C.BIO_free_all(bo)
|
|
|
|
return output
|
|
}
|
|
|
|
// public_key gets the public part of this private key as a PublicKey.
|
|
pub fn (pv PrivateKey) public_key() !PublicKey {
|
|
pbkey := C.EVP_PKEY_dup(pv.key)
|
|
// we clears out the private bits from the key
|
|
// by setting it into null
|
|
n := C.EVP_PKEY_set_octet_string_param(pbkey, c'priv', 0, 0)
|
|
if n <= 0 {
|
|
C.EVP_PKEY_free(pbkey)
|
|
return error('EVP_PKEY_set_octet_string_param')
|
|
}
|
|
|
|
return PublicKey{
|
|
key: pbkey
|
|
}
|
|
}
|
|
|
|
// free releases memory occupied by this key.
|
|
pub fn (mut pv PrivateKey) free() {
|
|
C.EVP_PKEY_free(pv.key)
|
|
}
|
|
|
|
// PublicKey represents public key part from the key.
|
|
pub struct PublicKey {
|
|
key &C.EVP_PKEY
|
|
}
|
|
|
|
// verify verifies signature sig for messages msg under options provided.
|
|
pub fn (pb PublicKey) verify(sig []u8, msg []u8, opt SignerOpts) !bool {
|
|
if opt.context.len > 255 {
|
|
return error('The context string size was over than 255')
|
|
}
|
|
return slhdsa_do_verify(pb.key, sig, msg, opt)!
|
|
}
|
|
|
|
// dump_key dumps this public key as a human readable string.
|
|
pub fn (pb PublicKey) dump_key() !string {
|
|
bo := C.BIO_new(C.BIO_s_mem())
|
|
n := C.EVP_PKEY_print_public(bo, pb.key, 2, 0)
|
|
if n <= 0 {
|
|
C.BIO_free_all(bo)
|
|
return error('EVP_PKEY_print_public failed')
|
|
}
|
|
size := usize(0)
|
|
mut m := C.BIO_read_ex(bo, 0, default_bioread_bufsize, &size)
|
|
mut buf := []u8{len: int(size)}
|
|
m = C.BIO_read_ex(bo, buf.data, buf.len, &size)
|
|
if m <= 0 {
|
|
C.BIO_free_all(bo)
|
|
return error('BIO_read_ex failed')
|
|
}
|
|
|
|
output := buf[..size].bytestr()
|
|
// Cleans up
|
|
unsafe { buf.free() }
|
|
C.BIO_free_all(bo)
|
|
|
|
return output
|
|
}
|
|
|
|
// free releases memory occupied by this public key
|
|
pub fn (mut pb PublicKey) free() {
|
|
C.EVP_PKEY_free(pb.key)
|
|
}
|
|
|
|
// the biggest supported size of seed was 32 bytes length, where private key contains 4*32 bytes
|
|
const default_privkey_buffer = 128
|
|
|
|
fn (pv PrivateKey) bytes() ![]u8 {
|
|
priv_len := usize(0)
|
|
priv := []u8{len: default_privkey_buffer}
|
|
|
|
n := C.EVP_PKEY_get_octet_string_param(pv.key, c'priv', priv.data, priv.len, &priv_len)
|
|
if n <= 0 {
|
|
return error('EVP_PKEY_get_octet_string_param failed')
|
|
}
|
|
out := priv[..int(priv_len)].clone()
|
|
unsafe { priv.free() }
|
|
return out
|
|
}
|
|
|
|
// the biggest public key size was 64 bytes length, where public key
|
|
// was contains 2*n, so we relaxed it to 2*72 bytes
|
|
const default_pubkey_buffer = 144
|
|
|
|
fn (pv PrivateKey) public_bytes() ![]u8 {
|
|
pblen := usize(0)
|
|
buf := []u8{len: default_pubkey_buffer}
|
|
n := C.EVP_PKEY_get_octet_string_param(pv.key, c'pub', buf.data, buf.len, &pblen)
|
|
if n <= 0 {
|
|
return error('EVP_PKEY_get_octet_string_param failed')
|
|
}
|
|
|
|
out := buf[..pblen].clone()
|
|
unsafe { buf.free() }
|
|
|
|
return out
|
|
}
|
|
|
|
// Signing and verifying Helpers
|
|
//
|
|
// slhdsa_do_sign performs signing msg with SLH-DSA key.
|
|
@[inline]
|
|
fn slhdsa_do_sign(key &C.EVP_PKEY, msg []u8, opt SignerOpts) ![]u8 {
|
|
sctx := C.EVP_PKEY_CTX_new_from_pkey(0, key, 0)
|
|
if sctx == 0 {
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
return error('EVP_PKEY_CTX_new_from_pkey failed')
|
|
}
|
|
|
|
// gets algo name from key
|
|
algo_name := key_type_name(key)
|
|
sig_alg := C.EVP_SIGNATURE_fetch(0, algo_name, 0)
|
|
if sig_alg == 0 {
|
|
C.EVP_SIGNATURE_free(sig_alg)
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
return error('EVP_SIGNATURE_fetch failed')
|
|
}
|
|
|
|
param_bld := C.OSSL_PARAM_BLD_new()
|
|
if param_bld == 0 {
|
|
C.OSSL_PARAM_BLD_free(param_bld)
|
|
C.EVP_SIGNATURE_free(sig_alg)
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
return error('OSSL_PARAM_BLD_new failed')
|
|
}
|
|
// if context string was set into non-null string, then we set
|
|
// `context-string` params into context key generator.
|
|
if opt.context.len > 0 {
|
|
// OSSL_PARAM_octet_string("context-string", (unsigned char *)"A context string", 33),
|
|
o := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'context-string', opt.context.str,
|
|
opt.context.len)
|
|
if o <= 0 {
|
|
C.OSSL_PARAM_BLD_free(param_bld)
|
|
C.EVP_SIGNATURE_free(sig_alg)
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
return error('OSSL_PARAM_BLD_push_octet_string FAILED')
|
|
}
|
|
}
|
|
// handle entropy testing
|
|
if opt.entropy.len > 0 {
|
|
if opt.encoding != 0 {
|
|
C.OSSL_PARAM_BLD_free(param_bld)
|
|
C.EVP_SIGNATURE_free(sig_alg)
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
return error('encoding need 0 for testing')
|
|
}
|
|
o := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'test-entropy', opt.entropy.data,
|
|
opt.entropy.len)
|
|
if o <= 0 {
|
|
C.OSSL_PARAM_BLD_free(param_bld)
|
|
C.EVP_SIGNATURE_free(sig_alg)
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
return error('OSSL_PARAM_BLD_push_octet_string failed')
|
|
}
|
|
oo := C.OSSL_PARAM_BLD_push_int(param_bld, c'message-encoding', opt.encoding)
|
|
if oo <= 0 {
|
|
C.OSSL_PARAM_BLD_free(param_bld)
|
|
C.EVP_SIGNATURE_free(sig_alg)
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
return error('OSSL_PARAM_BLD_push_int FAILED')
|
|
}
|
|
}
|
|
if opt.encoding == 0 {
|
|
oo := C.OSSL_PARAM_BLD_push_int(param_bld, c'message-encoding', opt.encoding)
|
|
if oo <= 0 {
|
|
C.OSSL_PARAM_BLD_free(param_bld)
|
|
C.EVP_SIGNATURE_free(sig_alg)
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
return error('OSSL_PARAM_BLD_push_int FAILED')
|
|
}
|
|
}
|
|
// build params
|
|
params := C.OSSL_PARAM_BLD_to_param(param_bld)
|
|
|
|
// Then init the context with this params
|
|
x := C.EVP_PKEY_sign_message_init(sctx, sig_alg, params)
|
|
if x <= 0 {
|
|
C.OSSL_PARAM_BLD_free(param_bld)
|
|
C.OSSL_PARAM_free(params)
|
|
C.EVP_SIGNATURE_free(sig_alg)
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
return error('EVP_PKEY_sign_message_init failed')
|
|
}
|
|
|
|
sig_len := usize(C.EVP_PKEY_size(key))
|
|
buf := []u8{len: int(sig_len)}
|
|
// Do signing the msg and updates the sig_len.
|
|
m := C.EVP_PKEY_sign(sctx, buf.data, &sig_len, msg.data, msg.len)
|
|
if m <= 0 {
|
|
C.OSSL_PARAM_BLD_free(param_bld)
|
|
C.OSSL_PARAM_free(params)
|
|
C.EVP_SIGNATURE_free(sig_alg)
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
return error('EVP_PKEY_sign_message_init failed')
|
|
}
|
|
|
|
// return the copy of the sig
|
|
sig := buf[..sig_len].clone()
|
|
|
|
// cleans up
|
|
unsafe { buf.free() }
|
|
C.OSSL_PARAM_BLD_free(param_bld)
|
|
C.OSSL_PARAM_free(params)
|
|
C.EVP_SIGNATURE_free(sig_alg)
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
|
|
return sig
|
|
}
|
|
|
|
// slhdsa_do_verify performs verifying of provided signature in sign for the msg message.
|
|
@[inline]
|
|
fn slhdsa_do_verify(key &C.EVP_PKEY, sign []u8, msg []u8, opt SignerOpts) !bool {
|
|
sctx := C.EVP_PKEY_CTX_new_from_pkey(0, key, 0)
|
|
if sctx == 0 {
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
return error('EVP_PKEY_CTX_new_from_pkey failed')
|
|
}
|
|
|
|
// get the algo signature name from the key
|
|
algo_name := key_type_name(key)
|
|
sig_alg := C.EVP_SIGNATURE_fetch(0, algo_name, 0)
|
|
if sig_alg == 0 {
|
|
C.EVP_SIGNATURE_free(sig_alg)
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
return error('EVP_SIGNATURE_fetch failed')
|
|
}
|
|
|
|
param_bld := C.OSSL_PARAM_BLD_new()
|
|
if param_bld == 0 {
|
|
C.OSSL_PARAM_BLD_free(param_bld)
|
|
C.EVP_SIGNATURE_free(sig_alg)
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
return error('OSSL_PARAM_BLD_new failed')
|
|
}
|
|
|
|
// handle context option
|
|
if opt.context.len > 0 {
|
|
o := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'context-string', opt.context.str,
|
|
opt.context.len)
|
|
if o <= 0 {
|
|
C.OSSL_PARAM_BLD_free(param_bld)
|
|
C.EVP_SIGNATURE_free(sig_alg)
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
return error('OSSL_PARAM_BLD_push failed')
|
|
}
|
|
}
|
|
// for testing
|
|
if opt.encoding == 0 {
|
|
oo := C.OSSL_PARAM_BLD_push_int(param_bld, c'message-encoding', opt.encoding)
|
|
if oo <= 0 {
|
|
C.OSSL_PARAM_BLD_free(param_bld)
|
|
C.EVP_SIGNATURE_free(sig_alg)
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
return error('OSSL_PARAM_BLD_push FAILED')
|
|
}
|
|
}
|
|
// build params
|
|
params := C.OSSL_PARAM_BLD_to_param(param_bld)
|
|
n := C.EVP_PKEY_verify_message_init(sctx, sig_alg, params)
|
|
if n <= 0 {
|
|
C.OSSL_PARAM_BLD_free(param_bld)
|
|
C.OSSL_PARAM_free(params)
|
|
C.EVP_SIGNATURE_free(sig_alg)
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
return error('OSSL_PARAM_BLD_push FAILED')
|
|
}
|
|
|
|
// do verifying of the signature.
|
|
m := C.EVP_PKEY_verify(sctx, sign.data, sign.len, msg.data, msg.len)
|
|
|
|
// Cleans up
|
|
C.EVP_SIGNATURE_free(sig_alg)
|
|
C.EVP_PKEY_CTX_free(sctx)
|
|
C.OSSL_PARAM_BLD_free(param_bld)
|
|
C.OSSL_PARAM_free(params)
|
|
|
|
return m == 1
|
|
}
|