v2 / vlib / crypto / ecdsa / ecdsa.v
592 lines · 532 sloc · 17.89 KB · 07c796b670d9e498ccb25605af189617f61ec295
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. 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 ecdsa
5
6import hash
7
8// NID constants
9//
10// NIST P-256 is referred to as secp256r1 and prime256v1, defined as #define NID_X9_62_prime256v1 415
11// Different names, but they are all the same.
12// https://www.rfc-editor.org/rfc/rfc4492.html#appendix-A
13const nid_prime256v1 = C.NID_X9_62_prime256v1
14
15// NIST P-384, ie, secp384r1 curve, defined as #define NID_secp384r1 715
16const nid_secp384r1 = C.NID_secp384r1
17
18// NIST P-521, ie, secp521r1 curve, defined as #define NID_secp521r1 716
19const nid_secp521r1 = C.NID_secp521r1
20
21// Bitcoin curve, defined as #define NID_secp256k1 714
22const nid_secp256k1 = C.NID_secp256k1
23
24// #define NID_X9_62_id_ecPublicKey 408
25const nid_ec_publickey = C.NID_X9_62_id_ecPublicKey
26// C.EVP_PKEY_EC = NID_X9_62_id_ecPublicKey
27const nid_evp_pkey_ec = C.EVP_PKEY_EC
28// we only support this
29const openssl_ec_named_curve = C.OPENSSL_EC_NAMED_CURVE
30
31// https://docs.openssl.org/3.0/man3/EVP_PKEY_fromdata/#selections
32const evp_pkey_keypair = C.EVP_PKEY_KEYPAIR
33
34// POINT_CONVERSION FORAMT
35const point_conversion_uncompressed = 4
36
37// Nid is an enumeration of the supported curves
38pub enum Nid {
39 prime256v1 = C.NID_X9_62_prime256v1
40 secp384r1 = C.NID_secp384r1
41 secp521r1 = C.NID_secp521r1
42 secp256k1 = C.NID_secp256k1
43}
44
45// we need this group (cruve) name representation to pass them into needed routines
46fn (nid Nid) str() string {
47 match nid {
48 // TODO: maybe better relies on info from underlying C defined constants,
49 // ie, #define SN_X9_62_prime256v1 "prime256v1" etc
50 .prime256v1 { return 'prime256v1' }
51 .secp384r1 { return 'secp384r1' }
52 .secp521r1 { return 'secp521r1' }
53 .secp256k1 { return 'secp256k1' }
54 }
55}
56
57// CurveOptions represents configuration options to drive keypair generation.
58@[params]
59pub struct CurveOptions {
60pub mut:
61 // default to NIST P-256 curve
62 nid Nid = .prime256v1
63 // by default, allow arbitrary size of seed bytes as key.
64 // Set it to `true` when you need fixed size, using the curve key size.
65 // Its main purposes is to support the `.new_key_from_seed` call.
66 fixed_size bool
67}
68
69// HashConfig is an enumeration of the possible options for key signing (verifying).
70pub enum HashConfig {
71 with_recommended_hash
72 with_no_hash
73 with_custom_hash
74}
75
76// SignerOpts represents configuration options to drive signing and verifying process.
77@[params]
78pub struct SignerOpts {
79pub mut:
80 // default to .with_recommended_hash
81 hash_config HashConfig = .with_recommended_hash
82 // make sense when HashConfig != with_recommended_hash
83 allow_smaller_size bool
84 allow_custom_hash bool
85 // set to non-nil if allow_custom_hash was true
86 custom_hash &hash.Hash = unsafe { nil }
87}
88
89// KeyFlag is an enumeration of possible options to support flexible of PrivateKey key size.
90enum KeyFlag {
91 // flexible flag to allow flexible-size of seed bytes
92 flexible
93 // fixed flag for using underlying curve key size
94 fixed
95}
96
97// generate_key generates a new key pair. If opt was not provided, its default to prime256v1 curve.
98// If you want another curve, use `pubkey, pivkey := ecdsa.generate_key(nid: .secp384r1)!` instead.
99pub fn generate_key(opt CurveOptions) !(PublicKey, PrivateKey) {
100 // This can be simplified to just more simpler one
101 pv := PrivateKey.new(opt)!
102 pb := pv.public_key()!
103
104 return pb, pv
105}
106
107// new_key_from_seed creates a new private key from the seed bytes. If opt was not provided,
108// its default to prime256v1 curve.
109//
110// Notes on the seed:
111//
112// You should make sure, the seed bytes come from a cryptographically secure random generator,
113// likes the `crypto.rand` or other trusted sources.
114// Internally, the seed size's would be checked to not exceed the key size of underlying curve,
115// ie, 32 bytes length for p-256 and secp256k1, 48 bytes length for p-384 and 66 bytes length for p-521.
116// Its recommended to use seed with bytes length matching with underlying curve key size.
117pub fn new_key_from_seed(seed []u8, opt CurveOptions) !PrivateKey {
118 // Early exit check
119 if seed.len == 0 {
120 return error('Seed with null-length was not allowed')
121 }
122 evpkey := evpkey_from_seed(seed, opt) or { return err }
123 num_bits := C.EVP_PKEY_get_bits(evpkey)
124 key_size := (num_bits + 7) / 8
125 if seed.len > key_size {
126 C.EVP_PKEY_free(evpkey)
127 return error('Seed length exceeds key size')
128 }
129 // Check if its using fixed key size or flexible one
130 if opt.fixed_size {
131 if seed.len != key_size {
132 C.EVP_PKEY_free(evpkey)
133 return error('seed size doesnt match with curve key size')
134 }
135 }
136 mut pvkey := PrivateKey{
137 evpkey: evpkey
138 }
139
140 if opt.fixed_size {
141 // using fixed one
142 pvkey.ks_flag = .fixed
143 pvkey.ks_size = key_size
144 } else {
145 pvkey.ks_size = seed.len
146 }
147
148 return pvkey
149}
150
151// PrivateKey represents ECDSA private key. Actually its a key pair,
152// contains private key and public key parts.
153pub struct PrivateKey {
154 // The new high level of keypair opaque
155 evpkey &C.EVP_PKEY
156mut:
157 // ks_flag with .flexible value allowing
158 // flexible-size seed bytes as key.
159 // When it is `.fixed`, it will use the underlying key size.
160 ks_flag KeyFlag = .flexible
161 // ks_size stores size of the seed bytes when ks_flag was .flexible.
162 // You should set it to a non zero value
163 ks_size int
164}
165
166// PrivateKey.new creates a new key pair. By default, it would create a prime256v1 based key.
167// Dont forget to call `.free()` after finish with your key.
168pub fn PrivateKey.new(opt CurveOptions) !PrivateKey {
169 evpkey := C.EVP_PKEY_new()
170 pctx := C.EVP_PKEY_CTX_new_id(nid_evp_pkey_ec, 0)
171 if pctx == 0 {
172 C.EVP_PKEY_free(evpkey)
173 C.EVP_PKEY_CTX_free(pctx)
174 return error('C.EVP_PKEY_CTX_new_id failed')
175 }
176 nt := C.EVP_PKEY_keygen_init(pctx)
177 if nt <= 0 {
178 C.EVP_PKEY_free(evpkey)
179 C.EVP_PKEY_CTX_free(pctx)
180 return error('EVP_PKEY_keygen_init failed')
181 }
182 // set the group (curve)
183 cn := C.EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, int(opt.nid))
184 if cn <= 0 {
185 C.EVP_PKEY_free(evpkey)
186 C.EVP_PKEY_CTX_free(pctx)
187 return error('EVP_PKEY_CTX_set_ec_paramgen_curve_nid')
188 }
189 // explicitly only allowing named curve, likely its the default on 3.0.
190 pn := C.EVP_PKEY_CTX_set_ec_param_enc(pctx, openssl_ec_named_curve)
191 if pn <= 0 {
192 C.EVP_PKEY_free(evpkey)
193 C.EVP_PKEY_CTX_free(pctx)
194 return error('EVP_PKEY_CTX_set_ec_param_enc failed')
195 }
196 // generates keypair
197 nr := C.EVP_PKEY_keygen(pctx, &evpkey)
198 if nr <= 0 {
199 C.EVP_PKEY_free(evpkey)
200 C.EVP_PKEY_CTX_free(pctx)
201 return error('EVP_PKEY_keygen failed')
202 }
203
204 // Cleans up the context
205 C.EVP_PKEY_CTX_free(pctx)
206 // when using default this function, its using underlying curve key size
207 // and discarded opt.fixed_size flag when its not set.
208 priv_key := PrivateKey{
209 evpkey: evpkey
210 ks_flag: .fixed
211 }
212 return priv_key
213}
214
215// sign performs signing the message with the options. By default options,
216// it will perform hashing before signing the message.
217pub fn (pv PrivateKey) sign(message []u8, opt SignerOpts) ![]u8 {
218 digest := calc_digest_with_evpkey(pv.evpkey, message, opt)!
219 return sign_digest(pv.evpkey, digest)!
220}
221
222// sign_with_options signs message with the options. It will be deprecated,
223// Use `PrivateKey.sign()` instead.
224@[deprecated: 'use PrivateKey.sign() instead']
225pub fn (pv PrivateKey) sign_with_options(message []u8, opt SignerOpts) ![]u8 {
226 return pv.sign(message, opt)
227}
228
229// bytes represent private key as bytes.
230pub fn (pv PrivateKey) bytes() ![]u8 {
231 mut bn := &C.BIGNUM(unsafe { nil })
232 // retrieves a BIGNUM value associated with a 'priv' key name
233 n := C.EVP_PKEY_get_bn_param(pv.evpkey, c'priv', &bn)
234 if n <= 0 {
235 C.BN_free(bn)
236 return error('EVP_PKEY_get_bn_param failed')
237 }
238 num_bytes := (C.BN_num_bits(bn) + 7) / 8
239 // Get the buffer size to store the seed.
240 size := if pv.ks_flag == .flexible {
241 // should be non zero
242 pv.ks_size
243 } else {
244 num_bytes
245 }
246 mut buf := []u8{len: int(size)}
247 res := C.BN_bn2binpad(bn, buf.data, i32(size))
248 if res <= 0 {
249 C.BN_free(bn)
250 return error('Failed to convert BIGNUM to bytes')
251 }
252 C.BN_free(bn)
253 return buf
254}
255
256// seed gets the seed (private key bytes). It will be deprecated.
257// Use `PrivateKey.bytes()` instead.
258@[deprecated: 'use PrivateKey.bytes() instead']
259pub fn (pv PrivateKey) seed() ![]u8 {
260 return pv.bytes()
261}
262
263// public_key gets the PublicKey from private key.
264pub fn (pv PrivateKey) public_key() !PublicKey {
265 // Using duplicate key and removes (clears out) priv key
266 pbkey := C.EVP_PKEY_dup(pv.evpkey)
267 bn := C.BN_new()
268 n := C.EVP_PKEY_set_bn_param(pbkey, c'priv', bn)
269 assert n == 1
270 // cleansup
271 C.BN_free(bn)
272 return PublicKey{
273 evpkey: pbkey
274 }
275}
276
277// equal compares two private keys was equal.
278pub fn (priv_key PrivateKey) equal(other PrivateKey) bool {
279 eq := C.EVP_PKEY_eq(voidptr(priv_key.evpkey), voidptr(other.evpkey))
280 return eq == 1
281}
282
283// free clears out allocated memory for PrivateKey. Dont use PrivateKey after calling `.free()`
284pub fn (pv &PrivateKey) free() {
285 C.EVP_PKEY_free(pv.evpkey)
286}
287
288// PublicKey represents ECDSA public key for verifying message.
289pub struct PublicKey {
290 // The new high level of keypair opaque
291 evpkey &C.EVP_PKEY
292}
293
294// verify verifies a message with the signature are valid with public key provided .
295// You should provide it with the same SignerOpts used with the `.sign()` call.
296// or verify would fail (false).
297pub fn (pb PublicKey) verify(message []u8, sig []u8, opt SignerOpts) !bool {
298 digest := calc_digest_with_evpkey(pb.evpkey, message, opt)!
299 return verify_signature(pb.evpkey, sig, digest)
300}
301
302// equal compares two public keys was equal.
303pub fn (pub_key PublicKey) equal(other PublicKey) bool {
304 eq := C.EVP_PKEY_eq(voidptr(pub_key.evpkey), voidptr(other.evpkey))
305 return eq == 1
306}
307
308// free clears out allocated memory for PublicKey. Dont use PublicKey after calling `.free()`
309pub fn (pb &PublicKey) free() {
310 C.EVP_PKEY_free(pb.evpkey)
311}
312
313// Helpers
314//
315// calc_digest_with_evpkey get the digest of the messages under the EVP_PKEY and options
316fn calc_digest_with_evpkey(key &C.EVP_PKEY, message []u8, opt SignerOpts) ![]u8 {
317 if message.len == 0 {
318 return error('null-length messages')
319 }
320 match opt.hash_config {
321 .with_no_hash, .with_recommended_hash {
322 md := default_digest(key)!
323 return calc_digest_with_md(message, md)!
324 }
325 .with_custom_hash {
326 mut cfg := opt
327 if !cfg.allow_custom_hash {
328 return error('custom hash was not allowed, set it into true')
329 }
330 if cfg.custom_hash == unsafe { nil } {
331 return error('Custom hasher was not defined')
332 }
333 key_size := evp_key_size(key)!
334 if key_size > cfg.custom_hash.size() {
335 if !cfg.allow_smaller_size {
336 return error('Hash into smaller size than current key size was not allowed')
337 }
338 }
339 // we need to reset the custom hash before writes message
340 cfg.custom_hash.reset()
341 _ := cfg.custom_hash.write(message)!
342 digest := cfg.custom_hash.sum([]u8{})
343
344 return digest
345 }
346 }
347
348 return error('Not should be here')
349}
350
351// sign_digest signs the digest with the key. Under the hood, EVP_PKEY_sign() does not
352// hash the data to be signed, and therefore is normally used to sign digests.
353fn sign_digest(key &C.EVP_PKEY, digest []u8) ![]u8 {
354 ctx := C.EVP_PKEY_CTX_new(key, 0)
355 if ctx == 0 {
356 C.EVP_PKEY_CTX_free(ctx)
357 return error('EVP_PKEY_CTX_new failed')
358 }
359 sin := C.EVP_PKEY_sign_init(ctx)
360 if sin != 1 {
361 C.EVP_PKEY_CTX_free(ctx)
362 return error('EVP_PKEY_sign_init failed')
363 }
364 // siglen was used to store the size of the signature output. When EVP_PKEY_sign
365 // was called with NULL signature buffer, siglen will tell maximum size of signature.
366 siglen := usize(C.EVP_PKEY_size(key))
367 sig := []u8{len: int(siglen)}
368
369 // calls directly with sign
370 do := C.EVP_PKEY_sign(ctx, sig.data, &siglen, digest.data, digest.len)
371 if do <= 0 {
372 unsafe { sig.free() }
373 C.EVP_PKEY_CTX_free(ctx)
374 return error('EVP_PKEY_sign fails to sign message')
375 }
376 // siglen now contains actual length of the signature buffer.
377 signed := sig[..int(siglen)].clone()
378
379 // Cleans up
380 unsafe { sig.free() }
381 C.EVP_PKEY_CTX_free(ctx)
382
383 return signed
384}
385
386// verify_signature verifies the signature for the digest under the provided key.
387fn verify_signature(key &C.EVP_PKEY, sig []u8, digest []u8) bool {
388 ctx := C.EVP_PKEY_CTX_new(key, 0)
389 if ctx == 0 {
390 C.EVP_PKEY_CTX_free(ctx)
391 return false
392 }
393 vinit := C.EVP_PKEY_verify_init(ctx)
394 if vinit != 1 {
395 C.EVP_PKEY_CTX_free(ctx)
396 return false
397 }
398 res := C.EVP_PKEY_verify(ctx, sig.data, sig.len, digest.data, digest.len)
399 if res <= 0 {
400 C.EVP_PKEY_CTX_free(ctx)
401 return false
402 }
403 C.EVP_PKEY_CTX_free(ctx)
404 return res == 1
405}
406
407// calc_digest_with_md get the digest of the msg using md digest algorithm
408fn calc_digest_with_md(msg []u8, md &C.EVP_MD) ![]u8 {
409 ctx := C.EVP_MD_CTX_new()
410 if ctx == 0 {
411 C.EVP_MD_CTX_free(ctx)
412 return error('EVP_MD_CTX_new failed')
413 }
414 nt := C.EVP_DigestInit(ctx, md)
415 assert nt == 1
416 upd := C.EVP_DigestUpdate(ctx, msg.data, msg.len)
417 assert upd == 1
418
419 size := u32(C.EVP_MD_get_size(md))
420 out := []u8{len: int(size)}
421
422 fin := C.EVP_DigestFinal(ctx, out.data, &size)
423 assert fin == 1
424
425 digest := out[..size].clone()
426 // cleans up
427 unsafe { out.free() }
428 C.EVP_MD_CTX_free(ctx)
429
430 return digest
431}
432
433// default_digest gets the default digest (hash) algorithm for this key.
434fn default_digest(key &C.EVP_PKEY) !&C.EVP_MD {
435 // get bits size of this key
436 bits_size := C.EVP_PKEY_get_bits(key)
437 if bits_size <= 0 {
438 return error(' this size isnt available.')
439 }
440 // based on this bits_size, choose appropriate digest algorithm
441 match true {
442 bits_size <= 256 {
443 return voidptr(C.EVP_sha256())
444 }
445 bits_size > 256 && bits_size <= 384 {
446 return voidptr(C.EVP_sha384())
447 }
448 bits_size > 384 {
449 return voidptr(C.EVP_sha512())
450 }
451 else {
452 return error('Unsupported bits size')
453 }
454 }
455
456 return error('should not here')
457}
458
459// Build EVP_PKEY from raw seed of bytes and options.
460fn evpkey_from_seed(seed []u8, opt CurveOptions) !&C.EVP_PKEY {
461 // This routine mostly comes from the official docs with adds some checking at
462 // https://docs.openssl.org/3.0/man3/EVP_PKEY_fromdata/#creating-an-ecc-keypair-using-raw-key-data
463 //
464 // convert the seed bytes to BIGNUM.
465 bn := C.BN_bin2bn(seed.data, seed.len, 0)
466 if bn == 0 {
467 C.BN_free(bn)
468 return error('BN_bin2bn failed from seed')
469 }
470 // build the group (curve) from the options.
471 group := C.EC_GROUP_new_by_curve_name(int(opt.nid))
472 if group == 0 {
473 C.EC_GROUP_free(group)
474 C.BN_free(bn)
475 return error('EC_GROUP_new_by_curve_name failed')
476 }
477 // Build EC_POINT from this BIGNUM and gets bytes represantion of this point
478 // in uncompressed format.
479 point := ec_point_mult(group, bn) or { return err }
480 pub_bytes := point_2_buf(group, point, point_conversion_uncompressed)!
481
482 // Lets build params builder
483 param_bld := C.OSSL_PARAM_BLD_new()
484 assert param_bld != 0
485
486 // push the group, private and public key bytes infos into the builder
487 n := C.OSSL_PARAM_BLD_push_utf8_string(param_bld, c'group', voidptr(opt.nid.str().str), 0)
488 m := C.OSSL_PARAM_BLD_push_BN(param_bld, c'priv', bn)
489 o := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'pub', pub_bytes.data, usize(pub_bytes.len))
490 if n <= 0 || m <= 0 || o <= 0 {
491 C.EC_POINT_free(point)
492 C.BN_free(bn)
493 C.EC_GROUP_free(group)
494 C.OSSL_PARAM_BLD_free(param_bld)
495 return error('OSSL_PARAM_BLD_push FAILED')
496 }
497 // Setup the new key
498 mut pkey := C.EVP_PKEY_new()
499 assert pkey != 0
500
501 // build parameter, initialize and build the key from params
502 params := C.OSSL_PARAM_BLD_to_param(param_bld)
503 pctx := C.EVP_PKEY_CTX_new_id(nid_evp_pkey_ec, 0)
504 if params == 0 || pctx == 0 {
505 C.EC_POINT_free(point)
506 C.BN_free(bn)
507 C.EC_GROUP_free(group)
508 C.OSSL_PARAM_BLD_free(param_bld)
509 C.OSSL_PARAM_free(params)
510 C.EVP_PKEY_free(pkey)
511 if pctx == 0 {
512 C.EVP_PKEY_CTX_free(pctx)
513 }
514 return error('EVP_PKEY_CTX_new or OSSL_PARAM_BLD_to_param failed')
515 }
516 // initialize key and build the key from builded params context.
517 p := C.EVP_PKEY_fromdata_init(pctx)
518 q := C.EVP_PKEY_fromdata(pctx, &pkey, evp_pkey_keypair, params)
519 if p <= 0 || q <= 0 {
520 C.EC_POINT_free(point)
521 C.BN_free(bn)
522 C.EC_GROUP_free(group)
523 C.OSSL_PARAM_BLD_free(param_bld)
524 C.OSSL_PARAM_free(params)
525 C.EVP_PKEY_free(pkey)
526 C.EVP_PKEY_CTX_free(pctx)
527 return error('EVP_PKEY_fromdata failed')
528 }
529 // After this step, we have build the key in pkey
530 // TODO: right way to check the builded key
531
532 // Cleans up
533 C.EC_POINT_free(point)
534 C.BN_free(bn)
535 C.EC_GROUP_free(group)
536 C.OSSL_PARAM_BLD_free(param_bld)
537 C.OSSL_PARAM_free(params)
538 C.EVP_PKEY_CTX_free(pctx)
539
540 return pkey
541}
542
543// ec_point_mult performs point multiplications, point = bn * generator
544fn ec_point_mult(group &C.EC_GROUP, bn &C.BIGNUM) !&C.EC_POINT {
545 // Create a new EC_POINT object for the public key
546 point := C.EC_POINT_new(group)
547 // Create a new BN_CTX object for efficient BIGNUM operations
548 ctx := C.BN_CTX_new()
549 if ctx == 0 {
550 C.EC_POINT_free(point)
551 C.BN_CTX_free(ctx)
552 return error('Failed to create BN_CTX')
553 }
554
555 // Perform the point multiplication to compute the public key: point = bn * G
556 res := C.EC_POINT_mul(group, point, bn, 0, 0, ctx)
557 if res != 1 {
558 C.EC_POINT_free(point)
559 C.BN_CTX_free(ctx)
560 return error('Failed to compute public key')
561 }
562 C.BN_CTX_free(ctx)
563 return point
564}
565
566// maximum key size we supported was 64 bytes.
567const default_point_bufsize = 160 // 2 * 64 + 1 + extra
568
569// point_2_buf gets bytes representation of the EC_POINT
570fn point_2_buf(group &C.EC_GROUP, point &C.EC_POINT, fmt int) ![]u8 {
571 ctx := C.BN_CTX_new()
572 mut pbuf := &u8(unsafe { nil })
573 // Notes from the docs:
574 // EC_POINT_point2buf() allocates a buffer of suitable length and writes an EC_POINT to it in octet format.
575 // The allocated buffer is written to *pbuf and its length is returned.
576 // The caller must free up the allocated buffer with a call to OPENSSL_free().
577 // Since the allocated buffer value is written to *pbuf the pbuf parameter MUST NOT be NULL.
578 // So, we explicitly call `.OPENSSL_free` on the allocated buffer.
579 n := C.EC_POINT_point2buf(group, point, fmt, &pbuf, ctx)
580 if n <= 0 {
581 C.BN_CTX_free(ctx)
582 C.OPENSSL_free(voidptr(pbuf))
583 return error('Get null length of buf')
584 }
585 // Gets the copy of the result with the correct length
586 result := unsafe { pbuf.vbytes(int(n)).clone() }
587
588 C.OPENSSL_free(voidptr(pbuf))
589 C.BN_CTX_free(ctx)
590
591 return result
592}
593