mirror of
https://github.com/vlang/v.git
synced 2025-09-15 15:32:27 +03:00
405 lines
12 KiB
V
405 lines
12 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
|
||
|
||
// Configuration options used in SLH-DSA key generation.
|
||
@[params]
|
||
pub struct KeyOpts {
|
||
pub mut:
|
||
// An opaque represents the kind of SLH-DSA keys want to built.
|
||
// See `enum Kind` for available options.
|
||
kind Kind = .sha2_128s
|
||
|
||
// flag, 0=random (default), 1= use seed bytes, 2 = use priv bytes, otherwise error
|
||
flag int
|
||
// when you set flag=1, builder will use seed bytes as a params,
|
||
// so, make sure to pass seed bytes length != 0
|
||
seed []u8
|
||
// when flag=2, you should ensure private bytes length != 0
|
||
priv []u8
|
||
|
||
// This option below was not supported yet.
|
||
//
|
||
// Sets properties to be used when fetching algorithm implementations
|
||
// used for SLH-DSA hashing operations.
|
||
propq string
|
||
}
|
||
|
||
// Configurations parameters used for signing (and or verifying)
|
||
@[params]
|
||
pub struct SignerOpts {
|
||
pub mut:
|
||
// optional context string up to 255 length, used in signing (verifying)
|
||
context string
|
||
|
||
// "message-encoding"
|
||
// The default value of 1 uses 'Pure SLH-DSA Signature Generation'.
|
||
// Setting it to 0 does not encode the message, which is used for testing,
|
||
// but can also be used for 'Pre Hash SLH-DSA Signature Generation'.
|
||
// If you set encoding to 0, you should provide the entropy.
|
||
encoding int = 1
|
||
|
||
// "test-entropy" used for testing to pass a optional random value.
|
||
entropy []u8 // octet-string
|
||
|
||
// "deterministic" integer option.
|
||
// The default value of 0 generates a random value (using a DRBG) this is used when
|
||
// processing the message. Setting this to 1 causes the private key seed to be used instead.
|
||
// This value is ignored if "test-entropy" is set.
|
||
deterministic int
|
||
}
|
||
|
||
// from_seed creates a new SLH-DSA PrivateKey from seed bytes and kind options.
|
||
// If the seed length was zero, it will create a key based on randomly generated seed.
|
||
// You should make sure, the seed bytes comes from trusted cryptographic secure source.
|
||
// The seed size was 3 times of `𝑛` parameter defined in the standard.
|
||
// The `𝑛` size maybe 16, 24 or 32 bytes length, depend on the chosen type.
|
||
fn PrivateKey.from_seed(seed []u8, kind Kind) !PrivateKey {
|
||
// when seed bytes length was zero, build the key based on the random ones
|
||
if seed.len == 0 {
|
||
return PrivateKey.new(kind: kind)!
|
||
}
|
||
// Get the n size parameter set from the options
|
||
nsize := kind.nsize()
|
||
// The length of the seed bytes supplied must be 3 * nsize.
|
||
if seed.len != 3 * nsize {
|
||
return error('Unmatching seed length with kind supplied, need ${3 * nsize} bytes')
|
||
}
|
||
|
||
param_bld := C.OSSL_PARAM_BLD_new()
|
||
assert param_bld != 0
|
||
|
||
m := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'seed', seed.data, seed.len)
|
||
if m <= 0 {
|
||
C.OSSL_PARAM_BLD_free(param_bld)
|
||
return error('OSSL_PARAM_BLD_push failed')
|
||
}
|
||
|
||
// build params
|
||
params := C.OSSL_PARAM_BLD_to_param(param_bld)
|
||
// create a desired context
|
||
pctx := C.EVP_PKEY_CTX_new_from_name(0, kind.long_name(), 0)
|
||
if params == 0 || pctx == 0 {
|
||
C.OSSL_PARAM_BLD_free(param_bld)
|
||
C.OSSL_PARAM_free(params)
|
||
C.EVP_PKEY_CTX_free(pctx)
|
||
return error('EVP_PKEY_CTX_new or OSSL_PARAM_BLD_to_param failed')
|
||
}
|
||
pkey := C.EVP_PKEY_new()
|
||
assert pkey != 0
|
||
ke := C.EVP_PKEY_keygen_init(pctx)
|
||
if ke <= 0 {
|
||
C.OSSL_PARAM_BLD_free(param_bld)
|
||
C.OSSL_PARAM_free(params)
|
||
C.EVP_PKEY_CTX_free(pctx)
|
||
C.EVP_PKEY_free(pkey)
|
||
return error('EVP_PKEY_keygen_init failed')
|
||
}
|
||
// Use EVP_PKEY_CTX_set_params() after calling EVP_PKEY_keygen_init().
|
||
s := C.EVP_PKEY_CTX_set_params(pctx, params)
|
||
if s != 1 {
|
||
C.OSSL_PARAM_BLD_free(param_bld)
|
||
C.OSSL_PARAM_free(params)
|
||
C.EVP_PKEY_CTX_free(pctx)
|
||
C.EVP_PKEY_free(pkey)
|
||
return error('EVP_PKEY_CTX_set_params failed')
|
||
}
|
||
|
||
ss := C.EVP_PKEY_keygen(pctx, &pkey)
|
||
if ss <= 0 {
|
||
C.OSSL_PARAM_BLD_free(param_bld)
|
||
C.OSSL_PARAM_free(params)
|
||
C.EVP_PKEY_CTX_free(pctx)
|
||
C.EVP_PKEY_free(pkey)
|
||
return error('EVP_PKEY_keygen failed')
|
||
}
|
||
// TODO: right way to check the key
|
||
pvkey := PrivateKey{
|
||
key: pkey
|
||
}
|
||
// Cleans up
|
||
C.OSSL_PARAM_BLD_free(param_bld)
|
||
C.OSSL_PARAM_free(params)
|
||
C.EVP_PKEY_CTX_free(pctx)
|
||
|
||
return pvkey
|
||
}
|
||
|
||
fn PrivateKey.from_bytes(bytes []u8, kind Kind) !PrivateKey {
|
||
// when bytes length was zero, build the key based on the random ones
|
||
if bytes.len == 0 {
|
||
return PrivateKey.new(kind: kind)!
|
||
}
|
||
// Get the n size parameter set from the options
|
||
nsize := kind.nsize()
|
||
// The private key has a size of 4 * n bytes.
|
||
if bytes.len != 4 * nsize {
|
||
return error('Unmatching private bytes length with kind supplied, need ${4 * nsize} bytes')
|
||
}
|
||
|
||
param_bld := C.OSSL_PARAM_BLD_new()
|
||
assert param_bld != 0
|
||
|
||
m := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'priv', bytes.data, bytes.len)
|
||
if m <= 0 {
|
||
C.OSSL_PARAM_BLD_free(param_bld)
|
||
return error('OSSL_PARAM_BLD_push FAILED')
|
||
}
|
||
|
||
// build params
|
||
params := C.OSSL_PARAM_BLD_to_param(param_bld)
|
||
// create a desired context
|
||
pctx := C.EVP_PKEY_CTX_new_from_name(0, kind.long_name(), 0)
|
||
if params == 0 || pctx == 0 {
|
||
C.OSSL_PARAM_BLD_free(param_bld)
|
||
C.OSSL_PARAM_free(params)
|
||
C.EVP_PKEY_CTX_free(pctx)
|
||
return error('EVP_PKEY_CTX_new or OSSL_PARAM_BLD_to_param failed')
|
||
}
|
||
|
||
pkey := C.EVP_PKEY_new()
|
||
assert pkey != 0
|
||
|
||
s := C.EVP_PKEY_fromdata_init(pctx)
|
||
if s <= 0 {
|
||
C.OSSL_PARAM_BLD_free(param_bld)
|
||
C.OSSL_PARAM_free(params)
|
||
C.EVP_PKEY_CTX_free(pctx)
|
||
C.EVP_PKEY_free(pkey)
|
||
return error('EVP_PKEY_fromdata_init failed')
|
||
}
|
||
|
||
ss := C.EVP_PKEY_fromdata(pctx, &pkey, evp_pkey_keypair, params)
|
||
if ss <= 0 {
|
||
C.OSSL_PARAM_BLD_free(param_bld)
|
||
C.OSSL_PARAM_free(params)
|
||
C.EVP_PKEY_CTX_free(pctx)
|
||
C.EVP_PKEY_free(pkey)
|
||
return error('EVP_PKEY_fromdata failed')
|
||
}
|
||
|
||
pvkey := PrivateKey{
|
||
key: pkey
|
||
}
|
||
// Cleans up
|
||
C.OSSL_PARAM_BLD_free(param_bld)
|
||
C.OSSL_PARAM_free(params)
|
||
C.EVP_PKEY_CTX_free(pctx)
|
||
|
||
return pvkey
|
||
}
|
||
|
||
// from_bytes creates a new PublicKey with type of supported key and bytes array.
|
||
// If you dont provide the bytes, ie, supplied with zero-length bytes,
|
||
// it will be generated with random bytes for you.
|
||
pub fn PublicKey.from_bytes(bytes []u8, kind Kind) !PublicKey {
|
||
// when bytes length was zero, build the key based on the random ones
|
||
if bytes.len == 0 {
|
||
pv := PrivateKey.new(kind: kind)!
|
||
pbk := pv.public_key()!
|
||
return pbk
|
||
}
|
||
// Get the n size parameter set from the options
|
||
nsize := kind.nsize()
|
||
// The public key has a size of 2 * n bytes.
|
||
if bytes.len != 2 * nsize {
|
||
return error('Unmatching public bytes length with kind supplied, need ${2 * nsize} bytes')
|
||
}
|
||
|
||
param_bld := C.OSSL_PARAM_BLD_new()
|
||
assert param_bld != 0
|
||
|
||
m := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'pub', bytes.data, bytes.len)
|
||
if m <= 0 {
|
||
C.OSSL_PARAM_BLD_free(param_bld)
|
||
return error('OSSL_PARAM_BLD_push FAILED')
|
||
}
|
||
|
||
// build params
|
||
params := C.OSSL_PARAM_BLD_to_param(param_bld)
|
||
// create a desired context
|
||
pctx := C.EVP_PKEY_CTX_new_from_name(0, kind.long_name(), 0)
|
||
if params == 0 || pctx == 0 {
|
||
C.OSSL_PARAM_BLD_free(param_bld)
|
||
C.OSSL_PARAM_free(params)
|
||
C.EVP_PKEY_CTX_free(pctx)
|
||
return error('EVP_PKEY_CTX_new or OSSL_PARAM_BLD_to_param failed')
|
||
}
|
||
|
||
pkey := C.EVP_PKEY_new()
|
||
assert pkey != 0
|
||
|
||
s := C.EVP_PKEY_fromdata_init(pctx)
|
||
if s <= 0 {
|
||
C.OSSL_PARAM_BLD_free(param_bld)
|
||
C.OSSL_PARAM_free(params)
|
||
C.EVP_PKEY_CTX_free(pctx)
|
||
C.EVP_PKEY_free(pkey)
|
||
return error('EVP_PKEY_fromdata failed')
|
||
}
|
||
|
||
ss := C.EVP_PKEY_fromdata(pctx, &pkey, evp_pkey_public_key, params)
|
||
if ss <= 0 {
|
||
C.OSSL_PARAM_BLD_free(param_bld)
|
||
C.OSSL_PARAM_free(params)
|
||
C.EVP_PKEY_CTX_free(pctx)
|
||
C.EVP_PKEY_free(pkey)
|
||
return error('EVP_PKEY_fromdata failed')
|
||
}
|
||
|
||
pbkey := PublicKey{
|
||
key: pkey
|
||
}
|
||
// Cleans up
|
||
C.OSSL_PARAM_BLD_free(param_bld)
|
||
C.OSSL_PARAM_free(params)
|
||
C.EVP_PKEY_CTX_free(pctx)
|
||
|
||
return pbkey
|
||
}
|
||
|
||
// The enumeration of NID of SLHDSA parameters set. <br>
|
||
// See Table 2. SLH-DSA parameter sets of the Chapter 11. Parameter Sets<br>
|
||
// Each sets name indicates:
|
||
//
|
||
// - the hash function family (SHA2 or SHAKE) that is used to instantiate the hash functions.
|
||
// - the length in bits of the security parameter, in the 128, 192, and 256 respectives number.
|
||
// - the mnemonic name indicates parameter to create relatively small signatures (`s`)
|
||
// or to have relatively fast signature generation (`f`).
|
||
pub enum Kind {
|
||
// SHA2-based family
|
||
sha2_128s = C.NID_SLH_DSA_SHA2_128s
|
||
sha2_128f = C.NID_SLH_DSA_SHA2_128f
|
||
sha2_192s = C.NID_SLH_DSA_SHA2_192s
|
||
sha2_192f = C.NID_SLH_DSA_SHA2_192f
|
||
sha2_256s = C.NID_SLH_DSA_SHA2_256s
|
||
sha2_256f = C.NID_SLH_DSA_SHA2_256f
|
||
// SHAKE-based family
|
||
shake_128s = C.NID_SLH_DSA_SHAKE_128s
|
||
shake_128f = C.NID_SLH_DSA_SHAKE_128f
|
||
shake_192s = C.NID_SLH_DSA_SHAKE_192s
|
||
shake_192f = C.NID_SLH_DSA_SHAKE_192f
|
||
shake_256s = C.NID_SLH_DSA_SHAKE_256s
|
||
shake_256f = C.NID_SLH_DSA_SHAKE_256f
|
||
}
|
||
|
||
// nsize returns the size of underlying n parameter from current type.
|
||
@[inline]
|
||
fn (n Kind) nsize() int {
|
||
match n {
|
||
.sha2_128s, .sha2_128f, .shake_128s, .shake_128f { return 16 }
|
||
.sha2_192s, .sha2_192f, .shake_192s, .shake_192f { return 24 }
|
||
.sha2_256s, .sha2_256f, .shake_256s, .shake_256f { return 32 }
|
||
}
|
||
}
|
||
|
||
fn (n Kind) str() string {
|
||
match n {
|
||
// vfmt off
|
||
// SHA2-based family
|
||
.sha2_128s { return "sha2_128s" }
|
||
.sha2_128f { return "sha2_128f" }
|
||
.sha2_192s { return "sha2_192s" }
|
||
.sha2_192f { return "sha2_192f" }
|
||
.sha2_256s { return "sha2_256s" }
|
||
.sha2_256f { return "sha2_256f" }
|
||
// SHAKE-based family
|
||
.shake_128s { return "shake_128s" }
|
||
.shake_128f { return "shake_128f" }
|
||
.shake_192s { return "shake_192s" }
|
||
.shake_192f { return "shake_192f" }
|
||
.shake_256s { return "shake_256s" }
|
||
.shake_256f { return "shake_256f" }
|
||
// vfmt on
|
||
}
|
||
}
|
||
|
||
// Kind long name as v string
|
||
@[inline]
|
||
fn (n Kind) ln_to_vstr() string {
|
||
out := unsafe { n.long_name().vstring() }
|
||
return out
|
||
}
|
||
|
||
// Kind long name as c-style string
|
||
@[inline]
|
||
fn (n Kind) long_name() &char {
|
||
match n {
|
||
// vfmt off
|
||
// SHA2-based family
|
||
.sha2_128s { return ln_slhdsa_sha2_128s }
|
||
.sha2_128f { return ln_slhdsa_sha2_128f }
|
||
.sha2_192s { return ln_slhdsa_sha2_192s }
|
||
.sha2_192f { return ln_slhdsa_sha2_192f }
|
||
.sha2_256s { return ln_slhdsa_sha2_256s }
|
||
.sha2_256f { return ln_slhdsa_sha2_256f }
|
||
// SHAKE-based family
|
||
.shake_128s { return ln_slhdsa_shake_128s }
|
||
.shake_128f { return ln_slhdsa_shake_128f }
|
||
.shake_192s { return ln_slhdsa_shake_192s }
|
||
.shake_192f { return ln_slhdsa_shake_192f }
|
||
.shake_256s { return ln_slhdsa_shake_256s }
|
||
.shake_256f { return ln_slhdsa_shake_256f }
|
||
// vfmt on
|
||
}
|
||
}
|
||
|
||
// Chapter 11. Parameters Set
|
||
struct ParamSet {
|
||
// Algorithm name
|
||
id Kind
|
||
n int
|
||
h int
|
||
d int
|
||
hp int
|
||
a int
|
||
k int
|
||
lgw int = 4
|
||
m int
|
||
sc int
|
||
pkb int
|
||
sig int
|
||
}
|
||
|
||
// Table 2. SLH-DSA parameter sets
|
||
const paramset = {
|
||
// id 𝑛 ℎ 𝑑 ℎ′ 𝑎 𝑘 𝑙𝑔𝑤 𝑚 sc pkb sig
|
||
'sha2_128s': ParamSet{.sha2_128s, 16, 63, 7, 9, 12, 14, 4, 30, 1, 32, 7856}
|
||
'sha2_128f': ParamSet{.sha2_128f, 16, 66, 22, 3, 6, 33, 4, 34, 1, 32, 17088}
|
||
'sha2_192s': ParamSet{.sha2_192s, 24, 63, 7, 9, 14, 17, 4, 39, 3, 48, 16224}
|
||
'sha2_192f': ParamSet{.sha2_192f, 24, 66, 22, 3, 8, 33, 4, 42, 3, 48, 35664}
|
||
'sha2_256s': ParamSet{.sha2_256s, 32, 64, 8, 8, 14, 22, 4, 47, 5, 64, 29792}
|
||
'sha2_256f': ParamSet{.sha2_256f, 32, 68, 17, 4, 9, 35, 4, 49, 5, 64, 49856}
|
||
// SHAKE family
|
||
'shake_128s': ParamSet{.shake_128s, 16, 63, 7, 9, 12, 14, 4, 30, 1, 32, 7856}
|
||
'shake_128f': ParamSet{.shake_128f, 16, 66, 22, 3, 6, 33, 4, 34, 1, 32, 17088}
|
||
'shake_192s': ParamSet{.shake_192s, 24, 63, 7, 9, 14, 17, 4, 39, 3, 48, 16224}
|
||
'shake_192f': ParamSet{.shake_192f, 24, 66, 22, 3, 8, 33, 4, 42, 3, 48, 35664}
|
||
'shake_256s': ParamSet{.shake_256s, 32, 64, 8, 8, 14, 22, 4, 47, 5, 64, 29792}
|
||
'shake_256f': ParamSet{.shake_256f, 32, 68, 17, 4, 9, 35, 4, 49, 5, 64, 49856}
|
||
}
|
||
|
||
fn ParamSet.from_kind(k Kind) ParamSet {
|
||
return paramset[k.str()]
|
||
}
|
||
|
||
// Some helpers
|
||
//
|
||
fn key_algo_name(key &C.EVP_PKEY) &char {
|
||
name := voidptr(C.EVP_PKEY_get0_type_name(key))
|
||
assert name != 0
|
||
return name
|
||
}
|
||
|
||
fn key_type_name(key &C.EVP_PKEY) &char {
|
||
kn := voidptr(C.EVP_PKEY_get0_type_name(key))
|
||
assert kn != 0
|
||
return kn
|
||
}
|
||
|
||
fn key_description(key &C.EVP_PKEY) &char {
|
||
kd := voidptr(C.EVP_PKEY_get0_description(key))
|
||
assert kd != 0
|
||
return kd
|
||
}
|