mirror of
https://github.com/vlang/v.git
synced 2025-09-09 15:27:05 -04:00
x.crypto.slhdsa: add a SLH-DSA implementation, a stateless hash-based DSA, a post quantum cryptographic module (#24086)
This commit is contained in:
parent
12afd051bb
commit
a8a4507237
75
vlib/x/crypto/slhdsa/README.md
Normal file
75
vlib/x/crypto/slhdsa/README.md
Normal file
@ -0,0 +1,75 @@
|
||||
# slhdsa
|
||||
Experimental module of NIST FIPS-205 Stateless Hash-Based Digital Signature Standard (SLH-DSA) in V
|
||||
|
||||
## About
|
||||
SLH-DSA was a quantum resistent cryptographic digital signature standard that was approved and publicly published by NIST at August, 2024. Its availables on [NIST FIPS 205](https://csrc.nist.gov/pubs/fips/205/final). <br>
|
||||
SLH-DSA allow builds relatively big signaturue size with small key (`16 - 32` bytes key).
|
||||
The signatures range from `±8K - ±50K` depending on the type chosen.
|
||||
|
||||
> [!NOTE]
|
||||
> This module wraps and written on top of SLH-DSA functionality availables on latest release
|
||||
> of recent OpenSSL library. Based on the history, this functionality was added in OpenSSL 3.5.
|
||||
> So, make sure, you have required version of OpenSSL library installed. For simple guides how
|
||||
> to build and install latest OpenSSL library on unix-like box,
|
||||
> see [install-latest-ssl.md](https://github.com/vlang/v/blob/master/vlib/x/crypto/slhdsa/install-latest-ssl.md)
|
||||
|
||||
## Basic
|
||||
SLH-DSA signature scheme is constructed using other hash-based signature schemes as components.
|
||||
SLH-DSA was comes with set of predefined parameter that describes security categories, ie:<br>
|
||||
|
||||
- What is underlying hash algorithm used in the mean of process. <br>
|
||||
The standard defines two's hash algorithm family availables, `sha2` family and `shake` family
|
||||
- Security bits number of parameter set
|
||||
- Whether the parameter set was designed to create relatively small signatures ('s') or to have
|
||||
relatively fast signature generation ('f').<br>
|
||||
See Table. 2 SLH-DSA parameter sets in the standard document. <br>
|
||||
|
||||
This parameter set represented by this opaque on this module:
|
||||
```codeblock
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
Example
|
||||
-------
|
||||
```v cgen
|
||||
import x.crypto.slhdsa
|
||||
|
||||
fn main() {
|
||||
// you can choose and pass the kind of the SLH-DSA parameter to the constructor
|
||||
opt := slhdsa.KeyOpts{
|
||||
kind: .sha2_128s
|
||||
// other options was availables
|
||||
}
|
||||
mut pv := slhdsa.PrivateKey.new(opt)!
|
||||
|
||||
// Example message
|
||||
msg := 'SLH-DSA example message'.bytes()
|
||||
|
||||
// Sign a message using constructed key
|
||||
sig := pv.sign(msg)!
|
||||
|
||||
// Then the public key part can verify this signature
|
||||
mut pb := pv.public_key()!
|
||||
verified := pb.verify(sig, msg)!
|
||||
assert verified // true
|
||||
|
||||
// release the resource
|
||||
pv.free()
|
||||
pb.free()
|
||||
}
|
||||
```
|
405
vlib/x/crypto/slhdsa/base.v
Normal file
405
vlib/x/crypto/slhdsa/base.v
Normal file
@ -0,0 +1,405 @@
|
||||
// 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
|
||||
}
|
187
vlib/x/crypto/slhdsa/base_test.v
Normal file
187
vlib/x/crypto/slhdsa/base_test.v
Normal file
@ -0,0 +1,187 @@
|
||||
// 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.
|
||||
// vtest build: has_modern_openssl?
|
||||
module slhdsa
|
||||
|
||||
import rand
|
||||
import encoding.hex
|
||||
|
||||
fn test_privatekey_from_bytes() ! {
|
||||
// for sha2_128s
|
||||
priv := hex.decode('4329f96b412b5d5ca9c730c9d54e52595265f6fc3ea67c183aa7043d90e949b8fd320c8fc0fa6a23462483535f131b9ea265848e7bb5f4d3cfd99de2595382b3')!
|
||||
mut pv := PrivateKey.from_bytes(priv, .sha2_128s)!
|
||||
|
||||
sig := pv.sign('test'.bytes())!
|
||||
|
||||
mut pb := pv.public_key()!
|
||||
status := pb.verify(sig, 'test'.bytes())!
|
||||
assert status == true
|
||||
|
||||
pv.free()
|
||||
pb.free()
|
||||
}
|
||||
|
||||
fn test_pvkey_from_seed() ! {
|
||||
kind := Kind.sha2_128s
|
||||
seed := rand.bytes(3 * kind.nsize())!
|
||||
|
||||
mut pv := PrivateKey.from_seed(seed, kind)!
|
||||
|
||||
msg := 'Hello'.bytes()
|
||||
sig := pv.sign(msg)!
|
||||
|
||||
mut pb := pv.public_key()!
|
||||
|
||||
verified := pb.verify(sig, msg)!
|
||||
assert verified
|
||||
|
||||
pv.free()
|
||||
pb.free()
|
||||
}
|
||||
|
||||
fn test_privkey_signing() ! {
|
||||
mut pv := PrivateKey.new(kind: .shake_256f)!
|
||||
assert pv.bytes()!.len == 128
|
||||
assert pv.public_bytes()!.len == 64
|
||||
|
||||
// type name
|
||||
chr := key_type_name(pv.key)
|
||||
assert unsafe { chr.vstring() } == 'SLH-DSA-SHAKE-256f'
|
||||
|
||||
msg := 'Hello'.bytes()
|
||||
sig := slhdsa_do_sign(pv.key, msg)!
|
||||
assert sig.len == 49856
|
||||
|
||||
verified := slhdsa_do_verify(pv.key, sig, msg)!
|
||||
assert verified
|
||||
|
||||
mut pb := pv.public_key()!
|
||||
sig2 := pv.sign(msg)!
|
||||
|
||||
status := pb.verify(sig, msg)!
|
||||
status2 := pb.verify(sig2, msg)!
|
||||
|
||||
assert status
|
||||
assert status2
|
||||
|
||||
pv.free()
|
||||
pb.free()
|
||||
}
|
||||
|
||||
fn test_basic_properties() ! {
|
||||
mut pv := PrivateKey.new()!
|
||||
desc := key_description(pv.key)
|
||||
unsafe { desc.vstring() } == 'OpenSSL SLH-DSA-SHA2-128f implementation'
|
||||
|
||||
n := C.EVP_PKEY_is_a(pv.key, ln_slhdsa_sha2_128s)
|
||||
assert n == 1 // true
|
||||
assert C.EVP_PKEY_size(pv.key) == 7856
|
||||
assert C.EVP_PKEY_get_bits(pv.key) == 256
|
||||
assert C.EVP_PKEY_get_security_bits(pv.key) == 128
|
||||
|
||||
mut pv2 := PrivateKey.new(kind: .sha2_128f)!
|
||||
n2 := C.EVP_PKEY_is_a(pv2.key, ln_slhdsa_sha2_128f)
|
||||
assert n2 == 1 // true
|
||||
assert C.EVP_PKEY_size(pv2.key) == 17088
|
||||
assert C.EVP_PKEY_get_bits(pv2.key) == 256
|
||||
assert C.EVP_PKEY_get_security_bits(pv2.key) == 128
|
||||
|
||||
mut pv3 := PrivateKey.new(kind: .sha2_192s)!
|
||||
n3 := C.EVP_PKEY_is_a(pv3.key, ln_slhdsa_sha2_192s)
|
||||
assert n3 == 1 // true
|
||||
assert C.EVP_PKEY_size(pv3.key) == 16224
|
||||
assert C.EVP_PKEY_get_bits(pv3.key) == 384
|
||||
assert C.EVP_PKEY_get_security_bits(pv3.key) == 192
|
||||
|
||||
mut pv4 := PrivateKey.new(kind: .shake_256f)!
|
||||
n4 := C.EVP_PKEY_is_a(pv4.key, ln_slhdsa_shake_256f)
|
||||
assert n4 == 1 // true
|
||||
assert C.EVP_PKEY_size(pv4.key) == 49856
|
||||
assert C.EVP_PKEY_get_bits(pv4.key) == 512
|
||||
assert C.EVP_PKEY_get_security_bits(pv4.key) == 256
|
||||
|
||||
pv.free()
|
||||
pv2.free()
|
||||
pv3.free()
|
||||
pv4.free()
|
||||
}
|
||||
|
||||
struct Slh128sSeed {
|
||||
mut:
|
||||
seed string
|
||||
pubk string
|
||||
priv string
|
||||
}
|
||||
|
||||
// test SLH-DSA-SHA2_128s based on samples data
|
||||
fn test_privatekey_slhdsa_128s_from_seed_with_sample_data() ! {
|
||||
for obj in sample_slhdsa_128s_seed {
|
||||
seed := hex.decode(obj.seed)!
|
||||
k_opt := KeyOpts{
|
||||
kind: .sha2_128s
|
||||
flag: 1
|
||||
seed: seed
|
||||
}
|
||||
mut pv := PrivateKey.new(k_opt)!
|
||||
assert pv.bytes()! == hex.decode(obj.priv)!
|
||||
assert pv.public_bytes()! == hex.decode(obj.pubk)!
|
||||
pv.free()
|
||||
}
|
||||
}
|
||||
|
||||
// This sample data was copied and adapted from boringssl test data in slh_keygen.txt
|
||||
// at https://boringssl.googlesource.com/boringssl.git/+/chromium-stable/crypto/slhdsa/slhdsa_keygen.txt
|
||||
// Its likely only support for SLH-DSA-SHA2_128s
|
||||
const sample_slhdsa_128s_seed = [
|
||||
Slh128sSeed{
|
||||
seed: '2f896d61d9cd9038ca303394fadaa22a24ac5ec1d86a989ca2196c3c8632419c1a05a42fe300e87b16aee116cb2e2363'
|
||||
pubk: '1a05a42fe300e87b16aee116cb2e236358e2c3e62632c9de03d08a535a0eb7e7'
|
||||
priv: '2f896d61d9cd9038ca303394fadaa22a24ac5ec1d86a989ca2196c3c8632419c1a05a42fe300e87b16aee116cb2e236358e2c3e62632c9de03d08a535a0eb7e7'
|
||||
},
|
||||
Slh128sSeed{
|
||||
seed: '87dd614ea188940d93c7df9943caa283cbdf2ccc4940e7007b7b6c799e95e160bbaca7b93fec1a097e115f10d18e6611'
|
||||
pubk: 'bbaca7b93fec1a097e115f10d18e66112aa087f0eeed95be3ab301ebc812406f'
|
||||
priv: '87dd614ea188940d93c7df9943caa283cbdf2ccc4940e7007b7b6c799e95e160bbaca7b93fec1a097e115f10d18e66112aa087f0eeed95be3ab301ebc812406f'
|
||||
},
|
||||
Slh128sSeed{
|
||||
seed: '0436b480d04c4c47f5ecd1e4b94f103988c5f804913fac77a96572cdc26f6a92f18d59d50b6b13f71821526f3aa800a8'
|
||||
pubk: 'f18d59d50b6b13f71821526f3aa800a8cde5ccb604485df0c6f14cd64b60979c'
|
||||
priv: '0436b480d04c4c47f5ecd1e4b94f103988c5f804913fac77a96572cdc26f6a92f18d59d50b6b13f71821526f3aa800a8cde5ccb604485df0c6f14cd64b60979c'
|
||||
},
|
||||
Slh128sSeed{
|
||||
seed: 'b9341d201d49b337c84e6ac2c26361e1f5dcab582f404fe89739df4ec64eccdd04519d0d7c1ad543cd1b479602e41784'
|
||||
pubk: '04519d0d7c1ad543cd1b479602e417848aeb88695d05f531a8415a4365b62983'
|
||||
priv: 'b9341d201d49b337c84e6ac2c26361e1f5dcab582f404fe89739df4ec64eccdd04519d0d7c1ad543cd1b479602e417848aeb88695d05f531a8415a4365b62983'
|
||||
},
|
||||
Slh128sSeed{
|
||||
seed: 'cbcaec354573e8cee1373b7aced67cdc683ed4491e17ef1dff9a9163fe7bcf74d91e0df4756c799ada2a0cf1b1aada1d'
|
||||
pubk: 'd91e0df4756c799ada2a0cf1b1aada1d1fddc3692e8909f2185e11654dc84def'
|
||||
priv: 'cbcaec354573e8cee1373b7aced67cdc683ed4491e17ef1dff9a9163fe7bcf74d91e0df4756c799ada2a0cf1b1aada1d1fddc3692e8909f2185e11654dc84def'
|
||||
},
|
||||
Slh128sSeed{
|
||||
seed: '1d92b6cc53e5bb08841b2669f1fb481c62db8f494e1bac12a6c6163142842a33771c182ffb096207da6476874ebdaf15'
|
||||
pubk: '771c182ffb096207da6476874ebdaf158c0143adbd0f66057a5180c96467d31d'
|
||||
priv: '1d92b6cc53e5bb08841b2669f1fb481c62db8f494e1bac12a6c6163142842a33771c182ffb096207da6476874ebdaf158c0143adbd0f66057a5180c96467d31d'
|
||||
},
|
||||
Slh128sSeed{
|
||||
seed: '94b11b26e2ec430f413f353fae96c144915e7ec84e9c786c895593b859bdb0edd54cc085cb4f4265f8288a9d67ae5806'
|
||||
pubk: 'd54cc085cb4f4265f8288a9d67ae5806ac638ad976a1fc8e2c7c5b9db2ea0573'
|
||||
priv: '94b11b26e2ec430f413f353fae96c144915e7ec84e9c786c895593b859bdb0edd54cc085cb4f4265f8288a9d67ae5806ac638ad976a1fc8e2c7c5b9db2ea0573'
|
||||
},
|
||||
Slh128sSeed{
|
||||
seed: '9078f099c6420c6bb1303977029b1e1bac048a4375f3c48862f74a6ccedb5be4755e624791fbfd27cd195ce3aa32c62c'
|
||||
pubk: '755e624791fbfd27cd195ce3aa32c62cd9030c131ca25c8c442c4ac0b27ae548'
|
||||
priv: '9078f099c6420c6bb1303977029b1e1bac048a4375f3c48862f74a6ccedb5be4755e624791fbfd27cd195ce3aa32c62cd9030c131ca25c8c442c4ac0b27ae548'
|
||||
},
|
||||
Slh128sSeed{
|
||||
seed: 'e0c67d9fb2434e27165c861c0f1040ed58c13b7f5e9517d000770bd3e88fbdb48cdf5f1cc1aa770a8bafcf1b8c5c843b'
|
||||
pubk: '8cdf5f1cc1aa770a8bafcf1b8c5c843ba659b85d99ca18f555b8452f411b519c'
|
||||
priv: 'e0c67d9fb2434e27165c861c0f1040ed58c13b7f5e9517d000770bd3e88fbdb48cdf5f1cc1aa770a8bafcf1b8c5c843ba659b85d99ca18f555b8452f411b519c'
|
||||
},
|
||||
Slh128sSeed{
|
||||
seed: 'ccae2481cecc4a55aa2afe4874b0ce501984d48423fc2452f24f49b2a68ef9b8d66807ecbfd2dc999d274d0d6414afc3'
|
||||
pubk: 'd66807ecbfd2dc999d274d0d6414afc3dfba899a4f7799cf74e98dc1c82a26ab'
|
||||
priv: 'ccae2481cecc4a55aa2afe4874b0ce501984d48423fc2452f24f49b2a68ef9b8d66807ecbfd2dc999d274d0d6414afc3dfba899a4f7799cf74e98dc1c82a26ab'
|
||||
},
|
||||
]
|
75
vlib/x/crypto/slhdsa/const.c.v
Normal file
75
vlib/x/crypto/slhdsa/const.c.v
Normal file
@ -0,0 +1,75 @@
|
||||
// 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
|
||||
|
||||
// The SLH-DSA functionality was added in recent OpenSSL 3.5 version.
|
||||
// So, we would panic if this module run on unmeet version on the current stock.
|
||||
fn init() {
|
||||
if C.OpenSSL_version_num() < u64(0x030500000) {
|
||||
panic('Your OpenSSL version ${C.OpenSSL_version_num().hex()} does not support for SLH-DSA')
|
||||
}
|
||||
}
|
||||
|
||||
// #define LN_SLH_DSA_SHA2_128s "SLH-DSA-SHA2-128s"
|
||||
// #define NID_SLH_DSA_SHA2_128s 1460
|
||||
const nid_slhdsa_sha2_128s = C.NID_SLH_DSA_SHA2_128s
|
||||
const ln_slhdsa_sha2_128s = &char(C.LN_SLH_DSA_SHA2_128s)
|
||||
|
||||
// #define LN_SLH_DSA_SHA2_128f "SLH-DSA-SHA2-128f"
|
||||
// #define NID_SLH_DSA_SHA2_128f 1461
|
||||
const nid_slhdsa_sha2_128f = C.NID_SLH_DSA_SHA2_128f
|
||||
const ln_slhdsa_sha2_128f = &char(C.LN_SLH_DSA_SHA2_128f)
|
||||
|
||||
// #define LN_SLH_DSA_SHA2_192s "SLH-DSA-SHA2-192s"
|
||||
// #define NID_SLH_DSA_SHA2_192s 1462
|
||||
const ln_slhdsa_sha2_192s = &char(C.LN_SLH_DSA_SHA2_192s)
|
||||
const nid_slhdsa_sha2_192s = C.NID_SLH_DSA_SHA2_192s
|
||||
|
||||
// #define LN_SLH_DSA_SHA2_192f "SLH-DSA-SHA2-192f"
|
||||
// #define NID_SLH_DSA_SHA2_192f 1463
|
||||
const ln_slhdsa_sha2_192f = &char(C.LN_SLH_DSA_SHA2_192f)
|
||||
const nid_slhdsa_sha2_192f = C.NID_SLH_DSA_SHA2_192f
|
||||
|
||||
// #define LN_SLH_DSA_SHA2_256s "SLH-DSA-SHA2-256s"
|
||||
// #define NID_SLH_DSA_SHA2_256s 1464
|
||||
const ln_slhdsa_sha2_256s = &char(C.LN_SLH_DSA_SHA2_256s)
|
||||
const nid_slhdsa_sha2_256s = C.NID_SLH_DSA_SHA2_256s
|
||||
|
||||
// #define LN_SLH_DSA_SHA2_256f "SLH-DSA-SHA2-256f"
|
||||
// #define NID_SLH_DSA_SHA2_256f 1465
|
||||
const ln_slhdsa_sha2_256f = &char(C.LN_SLH_DSA_SHA2_256f)
|
||||
const nid_slhdsa_sha2_256f = C.NID_SLH_DSA_SHA2_256f
|
||||
|
||||
// #define LN_SLH_DSA_SHAKE_128s "SLH-DSA-SHAKE-128s"
|
||||
// #define NID_SLH_DSA_SHAKE_128s 1466
|
||||
const ln_slhdsa_shake_128s = &char(C.LN_SLH_DSA_SHAKE_128s)
|
||||
const nid_slhdsa_shake_128s = C.NID_SLH_DSA_SHAKE_128s
|
||||
|
||||
// #define LN_SLH_DSA_SHAKE_128f "SLH-DSA-SHAKE-128f"
|
||||
// #define NID_SLH_DSA_SHAKE_128f 1467
|
||||
const ln_slhdsa_shake_128f = &char(C.LN_SLH_DSA_SHAKE_128f)
|
||||
const nid_slhdsa_shake_128f = C.NID_SLH_DSA_SHAKE_128f
|
||||
|
||||
// #define LN_SLH_DSA_SHAKE_192s "SLH-DSA-SHAKE-192s"
|
||||
// #define NID_SLH_DSA_SHAKE_192s 1468
|
||||
const ln_slhdsa_shake_192s = &char(C.LN_SLH_DSA_SHAKE_192s)
|
||||
const nid_slhdsa_shake_192s = C.NID_SLH_DSA_SHAKE_192s
|
||||
|
||||
// #define LN_SLH_DSA_SHAKE_192f "SLH-DSA-SHAKE-192f"
|
||||
// #define NID_SLH_DSA_SHAKE_192f 1469
|
||||
const ln_slhdsa_shake_192f = &char(C.LN_SLH_DSA_SHAKE_192f)
|
||||
const nid_slhdsa_shake_192f = C.NID_SLH_DSA_SHAKE_192f
|
||||
|
||||
// #define LN_SLH_DSA_SHAKE_256s "SLH-DSA-SHAKE-256s"
|
||||
// #define NID_SLH_DSA_SHAKE_256s 1470
|
||||
const ln_slhdsa_shake_256s = &char(C.LN_SLH_DSA_SHAKE_256s)
|
||||
const nid_slhdsa_shake_256s = C.NID_SLH_DSA_SHAKE_256s
|
||||
|
||||
// #define LN_SLH_DSA_SHAKE_256f "SLH-DSA-SHAKE-256f"
|
||||
// #define NID_SLH_DSA_SHAKE_256f 1471
|
||||
const ln_slhdsa_shake_256f = &char(C.LN_SLH_DSA_SHAKE_256f)
|
||||
const nid_slhdsa_shake_256f = C.NID_SLH_DSA_SHAKE_256f
|
||||
|
||||
const evp_pkey_keypair = C.EVP_PKEY_KEYPAIR
|
||||
const evp_pkey_public_key = C.EVP_PKEY_PUBLIC_KEY
|
26
vlib/x/crypto/slhdsa/examples/example0.v
Normal file
26
vlib/x/crypto/slhdsa/examples/example0.v
Normal file
@ -0,0 +1,26 @@
|
||||
module main
|
||||
|
||||
import x.crypto.slhdsa
|
||||
|
||||
fn main() {
|
||||
// creates default key
|
||||
mut pvkey := slhdsa.PrivateKey.new()!
|
||||
|
||||
// example of message
|
||||
xmsg := 'Sample message'.bytes()
|
||||
|
||||
// signing the message with the key
|
||||
sig := pvkey.sign(xmsg)!
|
||||
|
||||
// example of public key
|
||||
mut pbkey := pvkey.public_key()!
|
||||
|
||||
// verify signature with the public key
|
||||
verified := pbkey.verify(sig, xmsg)!
|
||||
dump(verified) // true
|
||||
assert verified == true
|
||||
|
||||
// release the resources
|
||||
pvkey.free()
|
||||
pbkey.free()
|
||||
}
|
35
vlib/x/crypto/slhdsa/install-latest-ssl.md
Normal file
35
vlib/x/crypto/slhdsa/install-latest-ssl.md
Normal file
@ -0,0 +1,35 @@
|
||||
This `slhdsa` module requires latest OpenSSL library and development headers.
|
||||
It required minimum of OpenSLL 3.5 version.
|
||||
Likely most of the linux (unix) distros does not ships with this version on this time
|
||||
of writing, at March, 2025.
|
||||
|
||||
You can manually download and install OpenSSL library by your self. This documents acts as a simple
|
||||
guide on how to build and install latest OpenSSL library to your box.
|
||||
|
||||
This guide was for unix-like system, for others, see the guides more detail goes to
|
||||
[install.md](https://github.com/openssl/openssl/blob/master/INSTALL.md#building-openssl)<br>
|
||||
1. Go to https://openssl-library.org/source/ and download the latest version
|
||||
```bash
|
||||
$ wget -v https://github.com/openssl/openssl/releases/download/openssl-3.5.0-beta1/openssl-3.5.0-beta1.tar.gz
|
||||
```
|
||||
2. Extract your downloaded file
|
||||
```bash
|
||||
$ tar -xzvf openssl-3.5.0-beta1.tar.gz
|
||||
$ cd openssl-3.5.0-beta1
|
||||
```
|
||||
3. Configure the build flag, this module for optional build requires to use `/opt/ssl` prefix.
|
||||
```bash
|
||||
$ ./Configure --prefix=/opt/ssl --openssldir=/opt/ssl
|
||||
$ make
|
||||
$ sudo make install
|
||||
```
|
||||
By succesfully this step, the latest OpenSSL has been built and installed onto
|
||||
configured path. You need to tell the system to add this path. <br>
|
||||
|
||||
4. Add `/opt/ssl/lib64` entry onto `/etc/ld.so.conf.d/libcrypto.conf`
|
||||
|
||||
5. Run `ldconfig`
|
||||
```bash
|
||||
$sudo ldconfig
|
||||
```
|
||||
6. Verify if the modules works.
|
99
vlib/x/crypto/slhdsa/slhdsa.c.v
Normal file
99
vlib/x/crypto/slhdsa/slhdsa.c.v
Normal file
@ -0,0 +1,99 @@
|
||||
// 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
|
||||
|
||||
// TODO: remove this when the 3.5 version was commonly availables on the stock.
|
||||
#flag linux -I/opt/ssl/include/openssl
|
||||
#flag linux -I/opt/ssl/include/crypto
|
||||
#flag linux -I/opt/ssl/include
|
||||
#flag linux -L/opt/ssl/lib64
|
||||
|
||||
// Standard path
|
||||
#flag linux -I/usr/local/include/openssl
|
||||
#flag linux -I/usr/local/include/crypto
|
||||
#flag linux -L/usr/local/lib64
|
||||
|
||||
#flag darwin -L /opt/homebrew/opt/openssl/lib -I /opt/homebrew/opt/openssl/include
|
||||
|
||||
#flag -I/usr/include/openssl
|
||||
#flag -lcrypto
|
||||
|
||||
#flag darwin -I/usr/local/opt/openssl/include
|
||||
#flag darwin -L/usr/local/opt/openssl/lib
|
||||
|
||||
#include <openssl/obj_mac.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/param_build.h>
|
||||
|
||||
@[typedef]
|
||||
struct C.EVP_PKEY {}
|
||||
|
||||
fn C.EVP_PKEY_new() &C.EVP_PKEY
|
||||
fn C.EVP_PKEY_Q_keygen(ctx voidptr, propq &char, tipe &char) &C.EVP_PKEY
|
||||
fn C.EVP_PKEY_is_a(pkey &C.EVP_PKEY, name &char) int
|
||||
fn C.EVP_PKEY_size(key &C.EVP_PKEY) int
|
||||
fn C.EVP_PKEY_dup(key &C.EVP_PKEY) &C.EVP_PKEY
|
||||
fn C.EVP_PKEY_free(key &C.EVP_PKEY)
|
||||
fn C.EVP_PKEY_get_octet_string_param(pkey &C.EVP_PKEY, key_name &char, buf &u8, max_buf_sz int, out_len &usize) int
|
||||
fn C.EVP_PKEY_print_private(out &C.BIO, pkey &C.EVP_PKEY, indent int, pctx voidptr) int
|
||||
fn C.EVP_PKEY_print_public(out &C.BIO, pkey &C.EVP_PKEY, indent int, pctx voidptr) int
|
||||
fn C.EVP_PKEY_keygen_init(ctx &C.EVP_PKEY_CTX) int // 1 success
|
||||
fn C.EVP_PKEY_keygen(ctx &C.EVP_PKEY_CTX, ppkey &&C.EVP_PKEY) int
|
||||
fn C.EVP_PKEY_CTX_set_params(ctx &C.EVP_PKEY_CTX, params &C.OSSL_PARAM) int
|
||||
fn C.EVP_PKEY_get0_type_name(key &C.EVP_PKEY) &char
|
||||
fn C.EVP_PKEY_get0_description(key &C.EVP_PKEY) &char
|
||||
fn C.EVP_PKEY_get_bits(pkey &C.EVP_PKEY) int
|
||||
fn C.EVP_PKEY_get_security_bits(pkey &C.EVP_PKEY) int
|
||||
fn C.EVP_PKEY_sign_init_ex(ctx &C.EVP_PKEY_CTX, params &C.OSSL_PARAM) int
|
||||
fn C.EVP_PKEY_sign_message_init(ctx &C.EVP_PKEY_CTX, algo &C.EVP_SIGNATURE, params &C.OSSL_PARAM) int
|
||||
fn C.EVP_PKEY_sign(ctx &C.EVP_PKEY_CTX, sig &char, siglen &usize, tbs &char, tbslen int) int
|
||||
fn C.EVP_PKEY_verify_message_init(ctx &C.EVP_PKEY_CTX, algo &C.EVP_SIGNATURE, params &C.OSSL_PARAM) int
|
||||
fn C.EVP_PKEY_verify(ctx &C.EVP_PKEY_CTX, sig &char, siglen &int, tbs &char, tbslen int) int
|
||||
fn C.EVP_PKEY_set_octet_string_param(key &C.EVP_PKEY, key_name &u8, buf &u8, bsize int) int
|
||||
fn C.EVP_PKEY_fromdata_init(ctx &C.EVP_PKEY_CTX) int
|
||||
fn C.EVP_PKEY_fromdata(ctx &C.EVP_PKEY_CTX, ppkey &&C.EVP_PKEY, selection int, params &C.OSSL_PARAM) int
|
||||
fn C.EVP_PKEY_check(ctx &C.EVP_PKEY_CTX) int
|
||||
fn C.EVP_PKEY_private_check(ctx &C.EVP_PKEY_CTX) int
|
||||
fn C.EVP_PKEY_public_check(ctx &C.EVP_PKEY_CTX) int
|
||||
|
||||
@[typedef]
|
||||
struct C.EVP_PKEY_CTX {}
|
||||
|
||||
fn C.EVP_PKEY_CTX_free(ctx &C.EVP_PKEY_CTX)
|
||||
fn C.EVP_PKEY_CTX_new_from_name(libctx voidptr, name &char, pq voidptr) &C.EVP_PKEY_CTX
|
||||
fn C.EVP_PKEY_CTX_new_from_pkey(libctx voidptr, pkey &C.EVP_PKEY, pq voidptr) &C.EVP_PKEY_CTX
|
||||
|
||||
fn C.OpenSSL_version_num() u64
|
||||
|
||||
@[typedef]
|
||||
struct C.EVP_SIGNATURE {}
|
||||
|
||||
fn C.EVP_SIGNATURE_free(signature &C.EVP_SIGNATURE)
|
||||
fn C.EVP_SIGNATURE_fetch(ctx voidptr, algorithm &char, properties voidptr) &C.EVP_SIGNATURE
|
||||
fn C.EVP_SIGNATURE_get0_name(signature &C.EVP_SIGNATURE) &char
|
||||
|
||||
@[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_int(bld &C.OSSL_PARAM_BLD, key &u8, val int) int
|
||||
fn C.OSSL_PARAM_BLD_push_octet_string(bld &C.OSSL_PARAM_BLD, key &u8, buf voidptr, bsize int) int
|
||||
fn C.OSSL_PARAM_BLD_to_param(bld &C.OSSL_PARAM_BLD) &C.OSSL_PARAM
|
||||
|
||||
@[typedef]
|
||||
struct C.BIO_METHOD {}
|
||||
|
||||
@[typedef]
|
||||
struct C.BIO {}
|
||||
|
||||
fn C.BIO_new(t &C.BIO_METHOD) &C.BIO
|
||||
fn C.BIO_read_ex(b &C.BIO, data voidptr, dlen int, readbytes &usize) int
|
||||
fn C.BIO_free_all(a &C.BIO)
|
||||
fn C.BIO_s_mem() &C.BIO_METHOD
|
380
vlib/x/crypto/slhdsa/slhdsa.v
Normal file
380
vlib/x/crypto/slhdsa/slhdsa.v
Normal file
@ -0,0 +1,380 @@
|
||||
// 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`.
|
||||
// Example:
|
||||
// ```
|
||||
// PrivateKey.new()!
|
||||
// PrivateKey.new(kind: .sha2_128s)
|
||||
// ```
|
||||
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 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
|
||||
}
|
109
vlib/x/crypto/slhdsa/slhdsa_siggen_test.v
Normal file
109
vlib/x/crypto/slhdsa/slhdsa_siggen_test.v
Normal file
File diff suppressed because one or more lines are too long
94
vlib/x/crypto/slhdsa/slhdsa_sigver_test.v
Normal file
94
vlib/x/crypto/slhdsa/slhdsa_sigver_test.v
Normal file
File diff suppressed because one or more lines are too long
28
vlib/x/crypto/slhdsa/usage_test.v
Normal file
28
vlib/x/crypto/slhdsa/usage_test.v
Normal file
@ -0,0 +1,28 @@
|
||||
// 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.
|
||||
// vtest build: has_modern_openssl?
|
||||
import x.crypto.slhdsa
|
||||
|
||||
fn test_slhdsa_basic_default_functionality() ! {
|
||||
// creates default key
|
||||
mut pvkey := slhdsa.PrivateKey.new()!
|
||||
|
||||
// example of message
|
||||
xmsg := 'Sample message'.bytes()
|
||||
|
||||
// signing the message with the key
|
||||
sig := pvkey.sign(xmsg)!
|
||||
|
||||
// example of public key
|
||||
mut pbkey := pvkey.public_key()!
|
||||
|
||||
// verify signature with the public key
|
||||
verified := pbkey.verify(sig, xmsg)!
|
||||
println(verified)
|
||||
assert verified == true
|
||||
|
||||
// release
|
||||
pvkey.free()
|
||||
pbkey.free()
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user