v2 / vlib / x / crypto / slhdsa / slhdsa.v
376 lines · 332 sloc · 10.49 KB · 794bd47ac81613ed2543af251fee39fb5eacebdd
Raw
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.
4module slhdsa
5
6// PrivateKey represents SLH-DSA keypair.
7pub struct PrivateKey {
8 key &C.EVP_PKEY
9}
10
11// new creates a new SLH-DSA PrivateKey based on the supplied options.
12// By default, it will create a `SLH-DSA-SHA2-128s` key with random generator.
13// See `enum Kind` for all of availables choice's type (kind) of key.
14// Its also support to generate keys based on the seed or private bytes through
15// provided options. See available options in `KeyOpts`.
16// Usage example:
17// ```v
18// pk1 := slhdsa.PrivateKey.new()!; println(pk1)
19// pk2 := slhdsa.PrivateKey.new(kind: .sha2_128s)!; println(pk2)
20// ```
21pub fn PrivateKey.new(opt KeyOpts) !PrivateKey {
22 match opt.flag {
23 // default random generated key, its recommended way
24 0 {
25 kind := opt.kind.long_name()
26 key := C.EVP_PKEY_Q_keygen(0, 0, kind)
27 if key == 0 {
28 return error('EVP_PKEY_Q_keygen failed')
29 }
30 return PrivateKey{
31 key: key
32 }
33 }
34 // use seed bytes, usually for testing purposes
35 1 {
36 // if you dont provides seed bytes, it will discarded, and use random one
37 return PrivateKey.from_seed(opt.seed, opt.kind)!
38 }
39 // use private bytes, usually for testing purposes
40 2 {
41 // if you dont provides private bytes, it will discarded, and use random one
42 return PrivateKey.from_bytes(opt.priv, opt.kind)!
43 }
44 else {
45 return error('Unsupported flag')
46 }
47 }
48}
49
50// sign signs the message using this key under desired options in opt.
51pub fn (pv PrivateKey) sign(msg []u8, opt SignerOpts) ![]u8 {
52 if msg.len == 0 {
53 return error('Zero length of messages')
54 }
55 if opt.context.len > 255 {
56 return error('The context string size was over than 255')
57 }
58 out := slhdsa_do_sign(pv.key, msg, opt)!
59 return out
60}
61
62// verify verifies signature for the message msg under provided options.
63// Its possible because of under the hood, private key is a key pair.
64pub fn (pv PrivateKey) verify(sig []u8, msg []u8, opt SignerOpts) !bool {
65 if opt.context.len > 255 {
66 return error('The context string size was over than 255')
67 }
68 return slhdsa_do_verify(pv.key, sig, msg, opt)!
69}
70
71const default_bioread_bufsize = 4096
72
73// dump_key represents PrivateKey in human readable string.
74pub fn (pv PrivateKey) dump_key() !string {
75 bo := C.BIO_new(C.BIO_s_mem())
76 if bo == 0 {
77 C.BIO_free_all(bo)
78 return error('BIO_new failed')
79 }
80 n := C.EVP_PKEY_print_private(bo, pv.key, 2, 0)
81 if n <= 0 {
82 C.BIO_free_all(bo)
83 return error('print private failed')
84 }
85 size := usize(0)
86 buf := []u8{len: default_bioread_bufsize}
87 m := C.BIO_read_ex(bo, buf.data, buf.len, &size)
88
89 if m <= 0 {
90 unsafe { buf.free() }
91 C.BIO_free_all(bo)
92 return error('BIO_read_ex failed')
93 }
94 output := buf[..size].bytestr()
95
96 // Cleans up and return the result
97 unsafe { buf.free() }
98 C.BIO_free_all(bo)
99
100 return output
101}
102
103// public_key gets the public part of this private key as a PublicKey.
104pub fn (pv PrivateKey) public_key() !PublicKey {
105 pbkey := C.EVP_PKEY_dup(pv.key)
106 // we clears out the private bits from the key
107 // by setting it into null
108 n := C.EVP_PKEY_set_octet_string_param(pbkey, c'priv', 0, 0)
109 if n <= 0 {
110 C.EVP_PKEY_free(pbkey)
111 return error('EVP_PKEY_set_octet_string_param')
112 }
113
114 return PublicKey{
115 key: pbkey
116 }
117}
118
119// free releases memory occupied by this key.
120pub fn (mut pv PrivateKey) free() {
121 C.EVP_PKEY_free(pv.key)
122}
123
124// PublicKey represents public key part from the key.
125pub struct PublicKey {
126 key &C.EVP_PKEY
127}
128
129// verify verifies signature sig for messages msg under options provided.
130pub fn (pb PublicKey) verify(sig []u8, msg []u8, opt SignerOpts) !bool {
131 if opt.context.len > 255 {
132 return error('The context string size was over than 255')
133 }
134 return slhdsa_do_verify(pb.key, sig, msg, opt)!
135}
136
137// dump_key dumps this public key as a human readable string.
138pub fn (pb PublicKey) dump_key() !string {
139 bo := C.BIO_new(C.BIO_s_mem())
140 n := C.EVP_PKEY_print_public(bo, pb.key, 2, 0)
141 if n <= 0 {
142 C.BIO_free_all(bo)
143 return error('EVP_PKEY_print_public failed')
144 }
145 size := usize(0)
146 mut m := C.BIO_read_ex(bo, 0, default_bioread_bufsize, &size)
147 mut buf := []u8{len: int(size)}
148 m = C.BIO_read_ex(bo, buf.data, buf.len, &size)
149 if m <= 0 {
150 C.BIO_free_all(bo)
151 return error('BIO_read_ex failed')
152 }
153
154 output := buf[..size].bytestr()
155 // Cleans up
156 unsafe { buf.free() }
157 C.BIO_free_all(bo)
158
159 return output
160}
161
162// free releases memory occupied by this public key
163pub fn (mut pb PublicKey) free() {
164 C.EVP_PKEY_free(pb.key)
165}
166
167// the biggest supported size of seed was 32 bytes length, where private key contains 4*32 bytes
168const default_privkey_buffer = 128
169
170fn (pv PrivateKey) bytes() ![]u8 {
171 priv_len := usize(0)
172 priv := []u8{len: default_privkey_buffer}
173
174 n := C.EVP_PKEY_get_octet_string_param(pv.key, c'priv', priv.data, priv.len, &priv_len)
175 if n <= 0 {
176 return error('EVP_PKEY_get_octet_string_param failed')
177 }
178 out := priv[..int(priv_len)].clone()
179 unsafe { priv.free() }
180 return out
181}
182
183// the biggest public key size was 64 bytes length, where public key
184// was contains 2*n, so we relaxed it to 2*72 bytes
185const default_pubkey_buffer = 144
186
187fn (pv PrivateKey) public_bytes() ![]u8 {
188 pblen := usize(0)
189 buf := []u8{len: default_pubkey_buffer}
190 n := C.EVP_PKEY_get_octet_string_param(pv.key, c'pub', buf.data, buf.len, &pblen)
191 if n <= 0 {
192 return error('EVP_PKEY_get_octet_string_param failed')
193 }
194
195 out := buf[..pblen].clone()
196 unsafe { buf.free() }
197
198 return out
199}
200
201// Signing and verifying Helpers
202//
203// slhdsa_do_sign performs signing msg with SLH-DSA key.
204@[inline]
205fn slhdsa_do_sign(key &C.EVP_PKEY, msg []u8, opt SignerOpts) ![]u8 {
206 sctx := C.EVP_PKEY_CTX_new_from_pkey(0, key, 0)
207 if sctx == 0 {
208 C.EVP_PKEY_CTX_free(sctx)
209 return error('EVP_PKEY_CTX_new_from_pkey failed')
210 }
211
212 // gets algo name from key
213 algo_name := key_type_name(key)
214 sig_alg := C.EVP_SIGNATURE_fetch(0, algo_name, 0)
215 if sig_alg == 0 {
216 C.EVP_SIGNATURE_free(sig_alg)
217 C.EVP_PKEY_CTX_free(sctx)
218 return error('EVP_SIGNATURE_fetch failed')
219 }
220
221 param_bld := C.OSSL_PARAM_BLD_new()
222 if param_bld == 0 {
223 C.OSSL_PARAM_BLD_free(param_bld)
224 C.EVP_SIGNATURE_free(sig_alg)
225 C.EVP_PKEY_CTX_free(sctx)
226 return error('OSSL_PARAM_BLD_new failed')
227 }
228
229 // writes `context-string` params into context key generator.
230 // OSSL_PARAM_octet_string("context-string", (unsigned char *)"A context string", 33),
231 cs := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'context-string', opt.context.str,
232 opt.context.len)
233 if cs <= 0 {
234 C.OSSL_PARAM_BLD_free(param_bld)
235 C.EVP_SIGNATURE_free(sig_alg)
236 C.EVP_PKEY_CTX_free(sctx)
237 return error('OSSL_PARAM_BLD_push_octet_string context-string flag FAILED')
238 }
239
240 // write `message-encoding` flag
241 me := C.OSSL_PARAM_BLD_push_int(param_bld, c'message-encoding', opt.encoding)
242 if me <= 0 {
243 C.OSSL_PARAM_BLD_free(param_bld)
244 C.EVP_SIGNATURE_free(sig_alg)
245 C.EVP_PKEY_CTX_free(sctx)
246 return error('OSSL_PARAM_BLD_push_int message-encoding flag FAILED')
247 }
248
249 // handle entropy testing
250 // `test-entropy flag only handled when `encoding` flag was set into 0 value
251 if opt.encoding == 0 {
252 te := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'test-entropy', opt.entropy.data,
253 opt.entropy.len)
254 if te <= 0 {
255 C.OSSL_PARAM_BLD_free(param_bld)
256 C.EVP_SIGNATURE_free(sig_alg)
257 C.EVP_PKEY_CTX_free(sctx)
258 return error('OSSL_PARAM_BLD_push_octet_string test-entropy flag failed')
259 }
260 }
261
262 // write `deterministic` flag, its getting ignored if "test-entropy" is set.
263 dt := C.OSSL_PARAM_BLD_push_int(param_bld, c'deterministic', opt.deterministic)
264 if dt <= 0 {
265 C.OSSL_PARAM_BLD_free(param_bld)
266 C.EVP_SIGNATURE_free(sig_alg)
267 C.EVP_PKEY_CTX_free(sctx)
268 return error('OSSL_PARAM_BLD_push_int deterministic flag FAILED')
269 }
270 // build params
271 params := C.OSSL_PARAM_BLD_to_param(param_bld)
272
273 // Then init the context with this params
274 x := C.EVP_PKEY_sign_message_init(sctx, sig_alg, params)
275 if x <= 0 {
276 C.OSSL_PARAM_BLD_free(param_bld)
277 C.OSSL_PARAM_free(params)
278 C.EVP_SIGNATURE_free(sig_alg)
279 C.EVP_PKEY_CTX_free(sctx)
280 return error('EVP_PKEY_sign_message_init failed')
281 }
282
283 sig_len := usize(C.EVP_PKEY_size(key))
284 buf := []u8{len: int(sig_len)}
285 // Do signing the msg and updates the sig_len.
286 m := C.EVP_PKEY_sign(sctx, buf.data, &sig_len, msg.data, msg.len)
287 if m <= 0 {
288 C.OSSL_PARAM_BLD_free(param_bld)
289 C.OSSL_PARAM_free(params)
290 C.EVP_SIGNATURE_free(sig_alg)
291 C.EVP_PKEY_CTX_free(sctx)
292 return error('EVP_PKEY_sign failed')
293 }
294
295 // return the copy of the sig
296 sig := buf[..sig_len].clone()
297
298 // cleans up
299 unsafe { buf.free() }
300 C.OSSL_PARAM_BLD_free(param_bld)
301 C.OSSL_PARAM_free(params)
302 C.EVP_SIGNATURE_free(sig_alg)
303 C.EVP_PKEY_CTX_free(sctx)
304
305 return sig
306}
307
308// slhdsa_do_verify performs verifying of provided signature in sign for the msg message.
309@[inline]
310fn slhdsa_do_verify(key &C.EVP_PKEY, sign []u8, msg []u8, opt SignerOpts) !bool {
311 sctx := C.EVP_PKEY_CTX_new_from_pkey(0, key, 0)
312 if sctx == 0 {
313 C.EVP_PKEY_CTX_free(sctx)
314 return error('EVP_PKEY_CTX_new_from_pkey failed')
315 }
316
317 // get the algo signature name from the key
318 algo_name := key_type_name(key)
319 sig_alg := C.EVP_SIGNATURE_fetch(0, algo_name, 0)
320 if sig_alg == 0 {
321 C.EVP_SIGNATURE_free(sig_alg)
322 C.EVP_PKEY_CTX_free(sctx)
323 return error('EVP_SIGNATURE_fetch failed')
324 }
325
326 param_bld := C.OSSL_PARAM_BLD_new()
327 if param_bld == 0 {
328 C.OSSL_PARAM_BLD_free(param_bld)
329 C.EVP_SIGNATURE_free(sig_alg)
330 C.EVP_PKEY_CTX_free(sctx)
331 return error('OSSL_PARAM_BLD_new failed')
332 }
333
334 // handle context option
335 if opt.context.len > 0 {
336 o := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'context-string', opt.context.str,
337 opt.context.len)
338 if o <= 0 {
339 C.OSSL_PARAM_BLD_free(param_bld)
340 C.EVP_SIGNATURE_free(sig_alg)
341 C.EVP_PKEY_CTX_free(sctx)
342 return error('OSSL_PARAM_BLD_push failed')
343 }
344 }
345 // for testing
346 if opt.encoding == 0 {
347 oo := C.OSSL_PARAM_BLD_push_int(param_bld, c'message-encoding', opt.encoding)
348 if oo <= 0 {
349 C.OSSL_PARAM_BLD_free(param_bld)
350 C.EVP_SIGNATURE_free(sig_alg)
351 C.EVP_PKEY_CTX_free(sctx)
352 return error('OSSL_PARAM_BLD_push FAILED')
353 }
354 }
355 // build params
356 params := C.OSSL_PARAM_BLD_to_param(param_bld)
357 n := C.EVP_PKEY_verify_message_init(sctx, sig_alg, params)
358 if n <= 0 {
359 C.OSSL_PARAM_BLD_free(param_bld)
360 C.OSSL_PARAM_free(params)
361 C.EVP_SIGNATURE_free(sig_alg)
362 C.EVP_PKEY_CTX_free(sctx)
363 return error('OSSL_PARAM_BLD_push FAILED')
364 }
365
366 // do verifying of the signature.
367 m := C.EVP_PKEY_verify(sctx, sign.data, sign.len, msg.data, msg.len)
368
369 // Cleans up
370 C.EVP_SIGNATURE_free(sig_alg)
371 C.EVP_PKEY_CTX_free(sctx)
372 C.OSSL_PARAM_BLD_free(param_bld)
373 C.OSSL_PARAM_free(params)
374
375 return m == 1
376}
377