| 1 | // Copyright (c) blackshirt. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module slhdsa |
| 5 | |
| 6 | // Configuration options used in SLH-DSA key generation. |
| 7 | @[params] |
| 8 | pub struct KeyOpts { |
| 9 | pub mut: |
| 10 | // An opaque represents the kind of SLH-DSA keys want to built. |
| 11 | // See `enum Kind` for available options. |
| 12 | kind Kind = .sha2_128s |
| 13 | |
| 14 | // flag, 0=random (default), 1= use seed bytes, 2 = use priv bytes, otherwise error |
| 15 | flag int |
| 16 | // when you set flag=1, builder will use seed bytes as a params, |
| 17 | // so, make sure to pass seed bytes length != 0 |
| 18 | seed []u8 |
| 19 | // when flag=2, you should ensure private bytes length != 0 |
| 20 | priv []u8 |
| 21 | |
| 22 | // This option below was not supported yet. |
| 23 | // |
| 24 | // Sets properties to be used when fetching algorithm implementations |
| 25 | // used for SLH-DSA hashing operations. |
| 26 | propq string |
| 27 | } |
| 28 | |
| 29 | // Configurations parameters used for signing (and or verifying) |
| 30 | @[params] |
| 31 | pub struct SignerOpts { |
| 32 | pub mut: |
| 33 | // optional context string up to 255 length, used in signing (verifying) |
| 34 | context string |
| 35 | |
| 36 | // "message-encoding" |
| 37 | // The default value of 1 uses 'Pure SLH-DSA Signature Generation'. |
| 38 | // Setting it to 0 does not encode the message, which is used for testing, |
| 39 | // but can also be used for 'Pre Hash SLH-DSA Signature Generation'. |
| 40 | // If you set encoding to 0, you should provide the entropy. |
| 41 | encoding int = 1 |
| 42 | |
| 43 | // "test-entropy" used for testing to pass a optional random value. |
| 44 | entropy []u8 // octet-string |
| 45 | |
| 46 | // "deterministic" integer option. |
| 47 | // The default value of 0 generates a random value (using a DRBG) this is used when |
| 48 | // processing the message. Setting this to 1 causes the private key seed to be used instead. |
| 49 | // This value is ignored if "test-entropy" is set. |
| 50 | deterministic int |
| 51 | } |
| 52 | |
| 53 | // from_seed creates a new SLH-DSA PrivateKey from seed bytes and kind options. |
| 54 | // If the seed length was zero, it will create a key based on randomly generated seed. |
| 55 | // You should make sure, the seed bytes comes from trusted cryptographic secure source. |
| 56 | // The seed size was 3 times of `𝑛` parameter defined in the standard. |
| 57 | // The `𝑛` size maybe 16, 24 or 32 bytes length, depend on the chosen type. |
| 58 | fn PrivateKey.from_seed(seed []u8, kind Kind) !PrivateKey { |
| 59 | // when seed bytes length was zero, build the key based on the random ones |
| 60 | if seed.len == 0 { |
| 61 | return PrivateKey.new(kind: kind)! |
| 62 | } |
| 63 | // Get the n size parameter set from the options |
| 64 | nsize := kind.nsize() |
| 65 | // The length of the seed bytes supplied must be 3 * nsize. |
| 66 | if seed.len != 3 * nsize { |
| 67 | return error('Unmatching seed length with kind supplied, need ${3 * nsize} bytes') |
| 68 | } |
| 69 | |
| 70 | param_bld := C.OSSL_PARAM_BLD_new() |
| 71 | assert param_bld != 0 |
| 72 | |
| 73 | m := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'seed', seed.data, seed.len) |
| 74 | if m <= 0 { |
| 75 | C.OSSL_PARAM_BLD_free(param_bld) |
| 76 | return error('OSSL_PARAM_BLD_push failed') |
| 77 | } |
| 78 | |
| 79 | // build params |
| 80 | params := C.OSSL_PARAM_BLD_to_param(param_bld) |
| 81 | // create a desired context |
| 82 | pctx := C.EVP_PKEY_CTX_new_from_name(0, kind.long_name(), 0) |
| 83 | if params == 0 || pctx == 0 { |
| 84 | C.OSSL_PARAM_BLD_free(param_bld) |
| 85 | C.OSSL_PARAM_free(params) |
| 86 | C.EVP_PKEY_CTX_free(pctx) |
| 87 | return error('EVP_PKEY_CTX_new or OSSL_PARAM_BLD_to_param failed') |
| 88 | } |
| 89 | pkey := C.EVP_PKEY_new() |
| 90 | assert pkey != 0 |
| 91 | ke := C.EVP_PKEY_keygen_init(pctx) |
| 92 | if ke <= 0 { |
| 93 | C.OSSL_PARAM_BLD_free(param_bld) |
| 94 | C.OSSL_PARAM_free(params) |
| 95 | C.EVP_PKEY_CTX_free(pctx) |
| 96 | C.EVP_PKEY_free(pkey) |
| 97 | return error('EVP_PKEY_keygen_init failed') |
| 98 | } |
| 99 | // Use EVP_PKEY_CTX_set_params() after calling EVP_PKEY_keygen_init(). |
| 100 | s := C.EVP_PKEY_CTX_set_params(pctx, params) |
| 101 | if s != 1 { |
| 102 | C.OSSL_PARAM_BLD_free(param_bld) |
| 103 | C.OSSL_PARAM_free(params) |
| 104 | C.EVP_PKEY_CTX_free(pctx) |
| 105 | C.EVP_PKEY_free(pkey) |
| 106 | return error('EVP_PKEY_CTX_set_params failed') |
| 107 | } |
| 108 | |
| 109 | ss := C.EVP_PKEY_keygen(pctx, &pkey) |
| 110 | if ss <= 0 { |
| 111 | C.OSSL_PARAM_BLD_free(param_bld) |
| 112 | C.OSSL_PARAM_free(params) |
| 113 | C.EVP_PKEY_CTX_free(pctx) |
| 114 | C.EVP_PKEY_free(pkey) |
| 115 | return error('EVP_PKEY_keygen failed') |
| 116 | } |
| 117 | // TODO: right way to check the key |
| 118 | pvkey := PrivateKey{ |
| 119 | key: pkey |
| 120 | } |
| 121 | // Cleans up |
| 122 | C.OSSL_PARAM_BLD_free(param_bld) |
| 123 | C.OSSL_PARAM_free(params) |
| 124 | C.EVP_PKEY_CTX_free(pctx) |
| 125 | |
| 126 | return pvkey |
| 127 | } |
| 128 | |
| 129 | fn PrivateKey.from_bytes(bytes []u8, kind Kind) !PrivateKey { |
| 130 | // when bytes length was zero, build the key based on the random ones |
| 131 | if bytes.len == 0 { |
| 132 | return PrivateKey.new(kind: kind)! |
| 133 | } |
| 134 | // Get the n size parameter set from the options |
| 135 | nsize := kind.nsize() |
| 136 | // The private key has a size of 4 * n bytes. |
| 137 | if bytes.len != 4 * nsize { |
| 138 | return error('Unmatching private bytes length with kind supplied, need ${4 * nsize} bytes') |
| 139 | } |
| 140 | |
| 141 | param_bld := C.OSSL_PARAM_BLD_new() |
| 142 | assert param_bld != 0 |
| 143 | |
| 144 | m := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'priv', bytes.data, bytes.len) |
| 145 | if m <= 0 { |
| 146 | C.OSSL_PARAM_BLD_free(param_bld) |
| 147 | return error('OSSL_PARAM_BLD_push FAILED') |
| 148 | } |
| 149 | |
| 150 | // build params |
| 151 | params := C.OSSL_PARAM_BLD_to_param(param_bld) |
| 152 | // create a desired context |
| 153 | pctx := C.EVP_PKEY_CTX_new_from_name(0, kind.long_name(), 0) |
| 154 | if params == 0 || pctx == 0 { |
| 155 | C.OSSL_PARAM_BLD_free(param_bld) |
| 156 | C.OSSL_PARAM_free(params) |
| 157 | C.EVP_PKEY_CTX_free(pctx) |
| 158 | return error('EVP_PKEY_CTX_new or OSSL_PARAM_BLD_to_param failed') |
| 159 | } |
| 160 | |
| 161 | pkey := C.EVP_PKEY_new() |
| 162 | assert pkey != 0 |
| 163 | |
| 164 | s := C.EVP_PKEY_fromdata_init(pctx) |
| 165 | if s <= 0 { |
| 166 | C.OSSL_PARAM_BLD_free(param_bld) |
| 167 | C.OSSL_PARAM_free(params) |
| 168 | C.EVP_PKEY_CTX_free(pctx) |
| 169 | C.EVP_PKEY_free(pkey) |
| 170 | return error('EVP_PKEY_fromdata_init failed') |
| 171 | } |
| 172 | |
| 173 | ss := C.EVP_PKEY_fromdata(pctx, &pkey, evp_pkey_keypair, params) |
| 174 | if ss <= 0 { |
| 175 | C.OSSL_PARAM_BLD_free(param_bld) |
| 176 | C.OSSL_PARAM_free(params) |
| 177 | C.EVP_PKEY_CTX_free(pctx) |
| 178 | C.EVP_PKEY_free(pkey) |
| 179 | return error('EVP_PKEY_fromdata failed') |
| 180 | } |
| 181 | |
| 182 | pvkey := PrivateKey{ |
| 183 | key: pkey |
| 184 | } |
| 185 | // Cleans up |
| 186 | C.OSSL_PARAM_BLD_free(param_bld) |
| 187 | C.OSSL_PARAM_free(params) |
| 188 | C.EVP_PKEY_CTX_free(pctx) |
| 189 | |
| 190 | return pvkey |
| 191 | } |
| 192 | |
| 193 | // from_bytes creates a new PublicKey with type of supported key and bytes array. |
| 194 | // If you dont provide the bytes, ie, supplied with zero-length bytes, |
| 195 | // it will be generated with random bytes for you. |
| 196 | pub fn PublicKey.from_bytes(bytes []u8, kind Kind) !PublicKey { |
| 197 | // when bytes length was zero, build the key based on the random ones |
| 198 | if bytes.len == 0 { |
| 199 | pv := PrivateKey.new(kind: kind)! |
| 200 | pbk := pv.public_key()! |
| 201 | return pbk |
| 202 | } |
| 203 | // Get the n size parameter set from the options |
| 204 | nsize := kind.nsize() |
| 205 | // The public key has a size of 2 * n bytes. |
| 206 | if bytes.len != 2 * nsize { |
| 207 | return error('Unmatching public bytes length with kind supplied, need ${2 * nsize} bytes') |
| 208 | } |
| 209 | |
| 210 | param_bld := C.OSSL_PARAM_BLD_new() |
| 211 | assert param_bld != 0 |
| 212 | |
| 213 | m := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'pub', bytes.data, bytes.len) |
| 214 | if m <= 0 { |
| 215 | C.OSSL_PARAM_BLD_free(param_bld) |
| 216 | return error('OSSL_PARAM_BLD_push FAILED') |
| 217 | } |
| 218 | |
| 219 | // build params |
| 220 | params := C.OSSL_PARAM_BLD_to_param(param_bld) |
| 221 | // create a desired context |
| 222 | pctx := C.EVP_PKEY_CTX_new_from_name(0, kind.long_name(), 0) |
| 223 | if params == 0 || pctx == 0 { |
| 224 | C.OSSL_PARAM_BLD_free(param_bld) |
| 225 | C.OSSL_PARAM_free(params) |
| 226 | C.EVP_PKEY_CTX_free(pctx) |
| 227 | return error('EVP_PKEY_CTX_new or OSSL_PARAM_BLD_to_param failed') |
| 228 | } |
| 229 | |
| 230 | pkey := C.EVP_PKEY_new() |
| 231 | assert pkey != 0 |
| 232 | |
| 233 | s := C.EVP_PKEY_fromdata_init(pctx) |
| 234 | if s <= 0 { |
| 235 | C.OSSL_PARAM_BLD_free(param_bld) |
| 236 | C.OSSL_PARAM_free(params) |
| 237 | C.EVP_PKEY_CTX_free(pctx) |
| 238 | C.EVP_PKEY_free(pkey) |
| 239 | return error('EVP_PKEY_fromdata failed') |
| 240 | } |
| 241 | |
| 242 | ss := C.EVP_PKEY_fromdata(pctx, &pkey, evp_pkey_public_key, params) |
| 243 | if ss <= 0 { |
| 244 | C.OSSL_PARAM_BLD_free(param_bld) |
| 245 | C.OSSL_PARAM_free(params) |
| 246 | C.EVP_PKEY_CTX_free(pctx) |
| 247 | C.EVP_PKEY_free(pkey) |
| 248 | return error('EVP_PKEY_fromdata failed') |
| 249 | } |
| 250 | |
| 251 | pbkey := PublicKey{ |
| 252 | key: pkey |
| 253 | } |
| 254 | // Cleans up |
| 255 | C.OSSL_PARAM_BLD_free(param_bld) |
| 256 | C.OSSL_PARAM_free(params) |
| 257 | C.EVP_PKEY_CTX_free(pctx) |
| 258 | |
| 259 | return pbkey |
| 260 | } |
| 261 | |
| 262 | // The enumeration of NID of SLHDSA parameters set. <br> |
| 263 | // See Table 2. SLH-DSA parameter sets of the Chapter 11. Parameter Sets<br> |
| 264 | // Each sets name indicates: |
| 265 | // |
| 266 | // - the hash function family (SHA2 or SHAKE) that is used to instantiate the hash functions. |
| 267 | // - the length in bits of the security parameter, in the 128, 192, and 256 respectives number. |
| 268 | // - the mnemonic name indicates parameter to create relatively small signatures (`s`) |
| 269 | // or to have relatively fast signature generation (`f`). |
| 270 | pub enum Kind { |
| 271 | // SHA2-based family |
| 272 | sha2_128s = C.NID_SLH_DSA_SHA2_128s |
| 273 | sha2_128f = C.NID_SLH_DSA_SHA2_128f |
| 274 | sha2_192s = C.NID_SLH_DSA_SHA2_192s |
| 275 | sha2_192f = C.NID_SLH_DSA_SHA2_192f |
| 276 | sha2_256s = C.NID_SLH_DSA_SHA2_256s |
| 277 | sha2_256f = C.NID_SLH_DSA_SHA2_256f |
| 278 | // SHAKE-based family |
| 279 | shake_128s = C.NID_SLH_DSA_SHAKE_128s |
| 280 | shake_128f = C.NID_SLH_DSA_SHAKE_128f |
| 281 | shake_192s = C.NID_SLH_DSA_SHAKE_192s |
| 282 | shake_192f = C.NID_SLH_DSA_SHAKE_192f |
| 283 | shake_256s = C.NID_SLH_DSA_SHAKE_256s |
| 284 | shake_256f = C.NID_SLH_DSA_SHAKE_256f |
| 285 | } |
| 286 | |
| 287 | // nsize returns the size of underlying n parameter from current type. |
| 288 | @[inline] |
| 289 | fn (n Kind) nsize() int { |
| 290 | match n { |
| 291 | .sha2_128s, .sha2_128f, .shake_128s, .shake_128f { return 16 } |
| 292 | .sha2_192s, .sha2_192f, .shake_192s, .shake_192f { return 24 } |
| 293 | .sha2_256s, .sha2_256f, .shake_256s, .shake_256f { return 32 } |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | fn (n Kind) str() string { |
| 298 | match n { |
| 299 | // vfmt off |
| 300 | // SHA2-based family |
| 301 | .sha2_128s { return "sha2_128s" } |
| 302 | .sha2_128f { return "sha2_128f" } |
| 303 | .sha2_192s { return "sha2_192s" } |
| 304 | .sha2_192f { return "sha2_192f" } |
| 305 | .sha2_256s { return "sha2_256s" } |
| 306 | .sha2_256f { return "sha2_256f" } |
| 307 | // SHAKE-based family |
| 308 | .shake_128s { return "shake_128s" } |
| 309 | .shake_128f { return "shake_128f" } |
| 310 | .shake_192s { return "shake_192s" } |
| 311 | .shake_192f { return "shake_192f" } |
| 312 | .shake_256s { return "shake_256s" } |
| 313 | .shake_256f { return "shake_256f" } |
| 314 | // vfmt on |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | // Kind long name as v string |
| 319 | @[inline] |
| 320 | fn (n Kind) ln_to_vstr() string { |
| 321 | out := unsafe { n.long_name().vstring() } |
| 322 | return out |
| 323 | } |
| 324 | |
| 325 | // Kind long name as c-style string |
| 326 | @[inline] |
| 327 | fn (n Kind) long_name() &char { |
| 328 | match n { |
| 329 | // vfmt off |
| 330 | // SHA2-based family |
| 331 | .sha2_128s { return ln_slhdsa_sha2_128s } |
| 332 | .sha2_128f { return ln_slhdsa_sha2_128f } |
| 333 | .sha2_192s { return ln_slhdsa_sha2_192s } |
| 334 | .sha2_192f { return ln_slhdsa_sha2_192f } |
| 335 | .sha2_256s { return ln_slhdsa_sha2_256s } |
| 336 | .sha2_256f { return ln_slhdsa_sha2_256f } |
| 337 | // SHAKE-based family |
| 338 | .shake_128s { return ln_slhdsa_shake_128s } |
| 339 | .shake_128f { return ln_slhdsa_shake_128f } |
| 340 | .shake_192s { return ln_slhdsa_shake_192s } |
| 341 | .shake_192f { return ln_slhdsa_shake_192f } |
| 342 | .shake_256s { return ln_slhdsa_shake_256s } |
| 343 | .shake_256f { return ln_slhdsa_shake_256f } |
| 344 | // vfmt on |
| 345 | } |
| 346 | } |
| 347 | |
| 348 | // Chapter 11. Parameters Set |
| 349 | struct ParamSet { |
| 350 | // Algorithm name |
| 351 | id Kind |
| 352 | n int |
| 353 | h int |
| 354 | d int |
| 355 | hp int |
| 356 | a int |
| 357 | k int |
| 358 | lgw int = 4 |
| 359 | m int |
| 360 | sc int |
| 361 | pkb int |
| 362 | sig int |
| 363 | } |
| 364 | |
| 365 | // Table 2. SLH-DSA parameter sets |
| 366 | const paramset = { |
| 367 | // id 𝑛 ℎ 𝑑 ℎ′ 𝑎 𝑘 𝑙𝑔𝑤 𝑚 sc pkb sig |
| 368 | 'sha2_128s': ParamSet{.sha2_128s, 16, 63, 7, 9, 12, 14, 4, 30, 1, 32, 7856} |
| 369 | 'sha2_128f': ParamSet{.sha2_128f, 16, 66, 22, 3, 6, 33, 4, 34, 1, 32, 17088} |
| 370 | 'sha2_192s': ParamSet{.sha2_192s, 24, 63, 7, 9, 14, 17, 4, 39, 3, 48, 16224} |
| 371 | 'sha2_192f': ParamSet{.sha2_192f, 24, 66, 22, 3, 8, 33, 4, 42, 3, 48, 35664} |
| 372 | 'sha2_256s': ParamSet{.sha2_256s, 32, 64, 8, 8, 14, 22, 4, 47, 5, 64, 29792} |
| 373 | 'sha2_256f': ParamSet{.sha2_256f, 32, 68, 17, 4, 9, 35, 4, 49, 5, 64, 49856} |
| 374 | // SHAKE family |
| 375 | 'shake_128s': ParamSet{.shake_128s, 16, 63, 7, 9, 12, 14, 4, 30, 1, 32, 7856} |
| 376 | 'shake_128f': ParamSet{.shake_128f, 16, 66, 22, 3, 6, 33, 4, 34, 1, 32, 17088} |
| 377 | 'shake_192s': ParamSet{.shake_192s, 24, 63, 7, 9, 14, 17, 4, 39, 3, 48, 16224} |
| 378 | 'shake_192f': ParamSet{.shake_192f, 24, 66, 22, 3, 8, 33, 4, 42, 3, 48, 35664} |
| 379 | 'shake_256s': ParamSet{.shake_256s, 32, 64, 8, 8, 14, 22, 4, 47, 5, 64, 29792} |
| 380 | 'shake_256f': ParamSet{.shake_256f, 32, 68, 17, 4, 9, 35, 4, 49, 5, 64, 49856} |
| 381 | } |
| 382 | |
| 383 | fn ParamSet.from_kind(k Kind) ParamSet { |
| 384 | return paramset[k.str()] |
| 385 | } |
| 386 | |
| 387 | // Some helpers |
| 388 | // |
| 389 | fn key_algo_name(key &C.EVP_PKEY) &char { |
| 390 | name := voidptr(C.EVP_PKEY_get0_type_name(key)) |
| 391 | assert name != 0 |
| 392 | return name |
| 393 | } |
| 394 | |
| 395 | fn key_type_name(key &C.EVP_PKEY) &char { |
| 396 | kn := voidptr(C.EVP_PKEY_get0_type_name(key)) |
| 397 | assert kn != 0 |
| 398 | return kn |
| 399 | } |
| 400 | |
| 401 | fn key_description(key &C.EVP_PKEY) &char { |
| 402 | kd := voidptr(C.EVP_PKEY_get0_description(key)) |
| 403 | assert kd != 0 |
| 404 | return kd |
| 405 | } |
| 406 | |