Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/crypto/openssl/providers/implementations/encode_decode/ml_dsa_codecs.c
103954 views
1
/*
2
* Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
3
*
4
* Licensed under the Apache License 2.0 (the "License"). You may not use
5
* this file except in compliance with the License. You can obtain a copy
6
* in the file LICENSE in the source distribution or at
7
* https://www.openssl.org/source/license.html
8
*/
9
10
#include <string.h>
11
#include <openssl/byteorder.h>
12
#include <openssl/err.h>
13
#include <openssl/proverr.h>
14
#include <openssl/x509.h>
15
#include <openssl/core_names.h>
16
#include "internal/encoder.h"
17
#include "prov/ml_dsa.h"
18
#include "ml_dsa_codecs.h"
19
20
/*-
21
* Tables describing supported ASN.1 input/output formats.
22
*/
23
24
/*-
25
* ML-DSA-44:
26
* Public key bytes: 1312 (0x0520)
27
* Private key bytes: 2560 (0x0a00)
28
*/
29
static const ML_COMMON_SPKI_FMT ml_dsa_44_spkifmt = {
30
{
31
0x30,
32
0x82,
33
0x05,
34
0x32,
35
0x30,
36
0x0b,
37
0x06,
38
0x09,
39
0x60,
40
0x86,
41
0x48,
42
0x01,
43
0x65,
44
0x03,
45
0x04,
46
0x03,
47
0x11,
48
0x03,
49
0x82,
50
0x05,
51
0x21,
52
0x00,
53
}
54
};
55
static const ML_COMMON_PKCS8_FMT ml_dsa_44_p8fmt[NUM_PKCS8_FORMATS] = {
56
{
57
"seed-priv",
58
0x0a2a,
59
0,
60
0x30820a26,
61
0x0420,
62
6,
63
0x20,
64
0x04820a00,
65
0x2a,
66
0x0a00,
67
0,
68
0,
69
},
70
{
71
"priv-only",
72
0x0a04,
73
0,
74
0x04820a00,
75
0,
76
0,
77
0,
78
0,
79
0x04,
80
0x0a00,
81
0,
82
0,
83
},
84
{ "oqskeypair", 0x0f24, 0, 0x04820f20, 0, 0, 0, 0, 0x04, 0x0a00, 0x0a04, 0x0520 },
85
{
86
"seed-only",
87
0x0022,
88
2,
89
0x8020,
90
0,
91
2,
92
0x20,
93
0,
94
0,
95
0,
96
0,
97
0,
98
},
99
{
100
"bare-priv",
101
0x0a00,
102
4,
103
0,
104
0,
105
0,
106
0,
107
0,
108
0,
109
0x0a00,
110
0,
111
0,
112
},
113
{
114
"bare-seed",
115
0x0020,
116
4,
117
0,
118
0,
119
0,
120
0x20,
121
0,
122
0,
123
0,
124
0,
125
0,
126
},
127
};
128
129
/*
130
* ML-DSA-65:
131
* Public key bytes: 1952 (0x07a0)
132
* Private key bytes: 4032 (0x0fc0)
133
*/
134
static const ML_COMMON_SPKI_FMT ml_dsa_65_spkifmt = {
135
{
136
0x30,
137
0x82,
138
0x07,
139
0xb2,
140
0x30,
141
0x0b,
142
0x06,
143
0x09,
144
0x60,
145
0x86,
146
0x48,
147
0x01,
148
0x65,
149
0x03,
150
0x04,
151
0x03,
152
0x12,
153
0x03,
154
0x82,
155
0x07,
156
0xa1,
157
0x00,
158
}
159
};
160
static const ML_COMMON_PKCS8_FMT ml_dsa_65_p8fmt[NUM_PKCS8_FORMATS] = {
161
{
162
"seed-priv",
163
0x0fea,
164
0,
165
0x30820fe6,
166
0x0420,
167
6,
168
0x20,
169
0x04820fc0,
170
0x2a,
171
0x0fc0,
172
0,
173
0,
174
},
175
{
176
"priv-only",
177
0x0fc4,
178
0,
179
0x04820fc0,
180
0,
181
0,
182
0,
183
0,
184
0x04,
185
0x0fc0,
186
0,
187
0,
188
},
189
{ "oqskeypair", 0x1764, 0, 0x04821760, 0, 0, 0, 0, 0x04, 0x0fc0, 0x0fc4, 0x07a0 },
190
{
191
"seed-only",
192
0x0022,
193
2,
194
0x8020,
195
0,
196
2,
197
0x20,
198
0,
199
0,
200
0,
201
0,
202
0,
203
},
204
{
205
"bare-priv",
206
0x0fc0,
207
4,
208
0,
209
0,
210
0,
211
0,
212
0,
213
0,
214
0x0fc0,
215
0,
216
0,
217
},
218
{
219
"bare-seed",
220
0x0020,
221
4,
222
0,
223
0,
224
0,
225
0x20,
226
0,
227
0,
228
0,
229
0,
230
0,
231
},
232
};
233
234
/*-
235
* ML-DSA-87:
236
* Public key bytes: 2592 (0x0a20)
237
* Private key bytes: 4896 (0x1320)
238
*/
239
static const ML_COMMON_SPKI_FMT ml_dsa_87_spkifmt = {
240
{
241
0x30,
242
0x82,
243
0x0a,
244
0x32,
245
0x30,
246
0x0b,
247
0x06,
248
0x09,
249
0x60,
250
0x86,
251
0x48,
252
0x01,
253
0x65,
254
0x03,
255
0x04,
256
0x03,
257
0x13,
258
0x03,
259
0x82,
260
0x0a,
261
0x21,
262
0x00,
263
}
264
};
265
static const ML_COMMON_PKCS8_FMT ml_dsa_87_p8fmt[NUM_PKCS8_FORMATS] = {
266
{
267
"seed-priv",
268
0x134a,
269
0,
270
0x30821346,
271
0x0420,
272
6,
273
0x20,
274
0x04821320,
275
0x2a,
276
0x1320,
277
0,
278
0,
279
},
280
{
281
"priv-only",
282
0x1324,
283
0,
284
0x04821320,
285
0,
286
0,
287
0,
288
0,
289
0x04,
290
0x1320,
291
0,
292
0,
293
},
294
{ "oqskeypair", 0x1d44, 0, 0x04821d40, 0, 0, 0, 0, 0x04, 0x1320, 0x1324, 0x0a20 },
295
{
296
"seed-only",
297
0x0022,
298
2,
299
0x8020,
300
0,
301
2,
302
0x20,
303
0,
304
0,
305
0,
306
0,
307
0,
308
},
309
{
310
"bare-priv",
311
0x1320,
312
4,
313
0,
314
0,
315
0,
316
0,
317
0,
318
0,
319
0x1320,
320
0,
321
0,
322
},
323
{
324
"bare-seed",
325
0x0020,
326
4,
327
0,
328
0,
329
0,
330
0x20,
331
0,
332
0,
333
0,
334
0,
335
0,
336
},
337
};
338
339
/* Indices of slots in the codec table below */
340
#define ML_DSA_44_CODEC 0
341
#define ML_DSA_65_CODEC 1
342
#define ML_DSA_87_CODEC 2
343
344
/*
345
* Per-variant fixed parameters
346
*/
347
static const ML_COMMON_CODEC codecs[3] = {
348
{ &ml_dsa_44_spkifmt, ml_dsa_44_p8fmt },
349
{ &ml_dsa_65_spkifmt, ml_dsa_65_p8fmt },
350
{ &ml_dsa_87_spkifmt, ml_dsa_87_p8fmt }
351
};
352
353
/* Retrieve the parameters of one of the ML-DSA variants */
354
static const ML_COMMON_CODEC *ml_dsa_get_codec(int evp_type)
355
{
356
switch (evp_type) {
357
case EVP_PKEY_ML_DSA_44:
358
return &codecs[ML_DSA_44_CODEC];
359
case EVP_PKEY_ML_DSA_65:
360
return &codecs[ML_DSA_65_CODEC];
361
case EVP_PKEY_ML_DSA_87:
362
return &codecs[ML_DSA_87_CODEC];
363
}
364
return NULL;
365
}
366
367
ML_DSA_KEY *
368
ossl_ml_dsa_d2i_PUBKEY(const uint8_t *pk, int pk_len, int evp_type,
369
PROV_CTX *provctx, const char *propq)
370
{
371
OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(provctx);
372
const ML_COMMON_CODEC *codec;
373
const ML_DSA_PARAMS *params;
374
ML_DSA_KEY *ret;
375
376
if ((params = ossl_ml_dsa_params_get(evp_type)) == NULL
377
|| (codec = ml_dsa_get_codec(evp_type)) == NULL)
378
return NULL;
379
if (pk_len != ML_COMMON_SPKI_OVERHEAD + (ossl_ssize_t)params->pk_len
380
|| memcmp(pk, codec->spkifmt->asn1_prefix, ML_COMMON_SPKI_OVERHEAD) != 0)
381
return NULL;
382
pk_len -= ML_COMMON_SPKI_OVERHEAD;
383
pk += ML_COMMON_SPKI_OVERHEAD;
384
385
if ((ret = ossl_ml_dsa_key_new(libctx, propq, evp_type)) == NULL)
386
return NULL;
387
388
if (!ossl_ml_dsa_pk_decode(ret, pk, (size_t)pk_len)) {
389
ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING,
390
"error parsing %s public key from input SPKI",
391
params->alg);
392
ossl_ml_dsa_key_free(ret);
393
return NULL;
394
}
395
396
return ret;
397
}
398
399
ML_DSA_KEY *
400
ossl_ml_dsa_d2i_PKCS8(const uint8_t *prvenc, int prvlen,
401
int evp_type, PROV_CTX *provctx,
402
const char *propq)
403
{
404
const ML_DSA_PARAMS *v;
405
const ML_COMMON_CODEC *codec;
406
ML_COMMON_PKCS8_FMT_PREF *fmt_slots = NULL, *slot;
407
const ML_COMMON_PKCS8_FMT *p8fmt;
408
ML_DSA_KEY *key = NULL, *ret = NULL;
409
PKCS8_PRIV_KEY_INFO *p8inf = NULL;
410
const uint8_t *buf, *pos;
411
const X509_ALGOR *alg = NULL;
412
const char *formats;
413
int len, ptype;
414
uint32_t magic;
415
uint16_t seed_magic;
416
const uint8_t *seed = NULL;
417
const uint8_t *priv = NULL;
418
419
/* Which ML-DSA variant? */
420
if ((v = ossl_ml_dsa_params_get(evp_type)) == NULL
421
|| (codec = ml_dsa_get_codec(evp_type)) == NULL)
422
return 0;
423
424
/* Extract the key OID and any parameters. */
425
if ((p8inf = d2i_PKCS8_PRIV_KEY_INFO(NULL, &prvenc, prvlen)) == NULL)
426
return 0;
427
/* Shortest prefix is 4 bytes: seq tag/len + octet string tag/len */
428
if (!PKCS8_pkey_get0(NULL, &buf, &len, &alg, p8inf))
429
goto end;
430
/* Bail out early if this is some other key type. */
431
if (OBJ_obj2nid(alg->algorithm) != evp_type)
432
goto end;
433
434
/* Get the list of enabled decoders. Their order is not important here. */
435
formats = ossl_prov_ctx_get_param(
436
provctx, OSSL_PKEY_PARAM_ML_DSA_INPUT_FORMATS, NULL);
437
fmt_slots = ossl_ml_common_pkcs8_fmt_order(v->alg, codec->p8fmt,
438
"input", formats);
439
if (fmt_slots == NULL)
440
goto end;
441
442
/* Parameters must be absent. */
443
X509_ALGOR_get0(NULL, &ptype, NULL, alg);
444
if (ptype != V_ASN1_UNDEF) {
445
ERR_raise_data(ERR_LIB_PROV, PROV_R_UNEXPECTED_KEY_PARAMETERS,
446
"unexpected parameters with a PKCS#8 %s private key",
447
v->alg);
448
goto end;
449
}
450
if ((ossl_ssize_t)len < (ossl_ssize_t)sizeof(magic))
451
goto end;
452
453
/* Find the matching p8 info slot, that also has the expected length. */
454
pos = OPENSSL_load_u32_be(&magic, buf);
455
for (slot = fmt_slots; (p8fmt = slot->fmt) != NULL; ++slot) {
456
if (len != (ossl_ssize_t)p8fmt->p8_bytes)
457
continue;
458
if (p8fmt->p8_shift == sizeof(magic)
459
|| (magic >> (p8fmt->p8_shift * 8)) == p8fmt->p8_magic) {
460
pos -= p8fmt->p8_shift;
461
break;
462
}
463
}
464
if (p8fmt == NULL
465
|| (p8fmt->seed_length > 0 && p8fmt->seed_length != ML_DSA_SEED_BYTES)
466
|| (p8fmt->priv_length > 0 && p8fmt->priv_length != v->sk_len)
467
|| (p8fmt->pub_length > 0 && p8fmt->pub_length != v->pk_len)) {
468
ERR_raise_data(ERR_LIB_PROV, PROV_R_ML_DSA_NO_FORMAT,
469
"no matching enabled %s private key input formats",
470
v->alg);
471
goto end;
472
}
473
474
if (p8fmt->seed_length > 0) {
475
/* Check |seed| tag/len, if not subsumed by |magic|. */
476
if (pos + sizeof(uint16_t) == buf + p8fmt->seed_offset) {
477
pos = OPENSSL_load_u16_be(&seed_magic, pos);
478
if (seed_magic != p8fmt->seed_magic)
479
goto end;
480
} else if (pos != buf + p8fmt->seed_offset) {
481
goto end;
482
}
483
pos += ML_DSA_SEED_BYTES;
484
}
485
if (p8fmt->priv_length > 0) {
486
/* Check |priv| tag/len */
487
if (pos + sizeof(uint32_t) == buf + p8fmt->priv_offset) {
488
pos = OPENSSL_load_u32_be(&magic, pos);
489
if (magic != p8fmt->priv_magic)
490
goto end;
491
} else if (pos != buf + p8fmt->priv_offset) {
492
goto end;
493
}
494
pos += v->sk_len;
495
}
496
if (p8fmt->pub_length > 0) {
497
if (pos != buf + p8fmt->pub_offset)
498
goto end;
499
pos += v->pk_len;
500
}
501
if (pos != buf + len)
502
goto end;
503
504
/*
505
* Collect the seed and/or key into a "decoded" private key object,
506
* to be turned into a real key on provider "load" or "import".
507
*/
508
if ((key = ossl_prov_ml_dsa_new(provctx, propq, evp_type)) == NULL)
509
goto end;
510
if (p8fmt->seed_length > 0)
511
seed = buf + p8fmt->seed_offset;
512
if (p8fmt->priv_length > 0)
513
priv = buf + p8fmt->priv_offset;
514
/* Any OQS public key content is ignored */
515
516
if (ossl_ml_dsa_set_prekey(key, 0, 0,
517
seed, ML_DSA_SEED_BYTES, priv, v->sk_len))
518
ret = key;
519
520
end:
521
OPENSSL_free(fmt_slots);
522
PKCS8_PRIV_KEY_INFO_free(p8inf);
523
if (ret == NULL)
524
ossl_ml_dsa_key_free(key);
525
return ret;
526
}
527
528
/* Same as ossl_ml_dsa_encode_pubkey, but allocates the output buffer. */
529
int ossl_ml_dsa_i2d_pubkey(const ML_DSA_KEY *key, unsigned char **out)
530
{
531
const ML_DSA_PARAMS *params = ossl_ml_dsa_key_params(key);
532
const uint8_t *pk = ossl_ml_dsa_key_get_pub(key);
533
534
if (pk == NULL) {
535
ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_A_PUBLIC_KEY,
536
"no %s public key data available", params->alg);
537
return 0;
538
}
539
if (out != NULL
540
&& (*out = OPENSSL_memdup(pk, params->pk_len)) == NULL)
541
return 0;
542
return (int)params->pk_len;
543
}
544
545
/* Allocate and encode PKCS#8 private key payload. */
546
int ossl_ml_dsa_i2d_prvkey(const ML_DSA_KEY *key, uint8_t **out,
547
PROV_CTX *provctx)
548
{
549
const ML_DSA_PARAMS *params = ossl_ml_dsa_key_params(key);
550
const ML_COMMON_CODEC *codec;
551
ML_COMMON_PKCS8_FMT_PREF *fmt_slots, *slot;
552
const ML_COMMON_PKCS8_FMT *p8fmt;
553
uint8_t *buf = NULL, *pos;
554
const char *formats;
555
int len = ML_DSA_SEED_BYTES;
556
int ret = 0;
557
const uint8_t *seed = ossl_ml_dsa_key_get_seed(key);
558
const uint8_t *sk = ossl_ml_dsa_key_get_priv(key);
559
560
/* Not ours to handle */
561
if ((codec = ml_dsa_get_codec(params->evp_type)) == NULL)
562
return 0;
563
564
if (sk == NULL) {
565
ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_A_PRIVATE_KEY,
566
"no %s private key data available",
567
params->alg);
568
return 0;
569
}
570
571
formats = ossl_prov_ctx_get_param(
572
provctx, OSSL_PKEY_PARAM_ML_DSA_OUTPUT_FORMATS, NULL);
573
fmt_slots = ossl_ml_common_pkcs8_fmt_order(params->alg, codec->p8fmt,
574
"output", formats);
575
if (fmt_slots == NULL)
576
return 0;
577
578
/* If we don't have a seed, skip seedful entries */
579
for (slot = fmt_slots; (p8fmt = slot->fmt) != NULL; ++slot)
580
if (seed != NULL || p8fmt->seed_length == 0)
581
break;
582
/* No matching table entries, give up */
583
if (p8fmt == NULL
584
|| (p8fmt->seed_length > 0 && p8fmt->seed_length != ML_DSA_SEED_BYTES)
585
|| (p8fmt->priv_length > 0 && p8fmt->priv_length != params->sk_len)
586
|| (p8fmt->pub_length > 0 && p8fmt->pub_length != params->pk_len)) {
587
ERR_raise_data(ERR_LIB_PROV, PROV_R_ML_DSA_NO_FORMAT,
588
"no matching enabled %s private key output formats",
589
params->alg);
590
goto end;
591
}
592
len = p8fmt->p8_bytes;
593
594
if (out == NULL) {
595
ret = len;
596
goto end;
597
}
598
599
if ((pos = buf = OPENSSL_malloc((size_t)len)) == NULL)
600
goto end;
601
602
switch (p8fmt->p8_shift) {
603
case 0:
604
pos = OPENSSL_store_u32_be(pos, p8fmt->p8_magic);
605
break;
606
case 2:
607
pos = OPENSSL_store_u16_be(pos, (uint16_t)p8fmt->p8_magic);
608
break;
609
case 4:
610
break;
611
default:
612
ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
613
"error encoding %s private key", params->alg);
614
goto end;
615
}
616
617
if (p8fmt->seed_length != 0) {
618
/*
619
* Either the tag/len were already included in |magic| or they require
620
* us to write two bytes now.
621
*/
622
if (pos + sizeof(uint16_t) == buf + p8fmt->seed_offset)
623
pos = OPENSSL_store_u16_be(pos, p8fmt->seed_magic);
624
if (pos != buf + p8fmt->seed_offset) {
625
ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
626
"error encoding %s private key", params->alg);
627
goto end;
628
}
629
memcpy(pos, seed, ML_DSA_SEED_BYTES);
630
pos += ML_DSA_SEED_BYTES;
631
}
632
if (p8fmt->priv_length != 0) {
633
if (pos + sizeof(uint32_t) == buf + p8fmt->priv_offset)
634
pos = OPENSSL_store_u32_be(pos, p8fmt->priv_magic);
635
if (pos != buf + p8fmt->priv_offset) {
636
ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
637
"error encoding %s private key", params->alg);
638
goto end;
639
}
640
memcpy(pos, sk, params->sk_len);
641
pos += params->sk_len;
642
}
643
/* OQS form output with tacked-on public key */
644
if (p8fmt->pub_length != 0) {
645
/* The OQS pubkey is never separately DER-wrapped */
646
if (pos != buf + p8fmt->pub_offset) {
647
ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
648
"error encoding %s private key", params->alg);
649
goto end;
650
}
651
memcpy(pos, ossl_ml_dsa_key_get_pub(key), params->pk_len);
652
pos += params->pk_len;
653
}
654
655
if (pos == buf + len) {
656
*out = buf;
657
ret = len;
658
}
659
660
end:
661
OPENSSL_free(fmt_slots);
662
if (ret == 0)
663
OPENSSL_free(buf);
664
return ret;
665
}
666
667
int ossl_ml_dsa_key_to_text(BIO *out, const ML_DSA_KEY *key, int selection)
668
{
669
const ML_DSA_PARAMS *params;
670
const uint8_t *seed, *sk, *pk;
671
672
if (out == NULL || key == NULL) {
673
ERR_raise(ERR_LIB_PROV, ERR_R_PASSED_NULL_PARAMETER);
674
return 0;
675
}
676
params = ossl_ml_dsa_key_params(key);
677
pk = ossl_ml_dsa_key_get_pub(key);
678
sk = ossl_ml_dsa_key_get_priv(key);
679
seed = ossl_ml_dsa_key_get_seed(key);
680
681
if (pk == NULL) {
682
/* Regardless of the |selection|, there must be a public key */
683
ERR_raise_data(ERR_LIB_PROV, PROV_R_MISSING_KEY,
684
"no %s key material available", params->alg);
685
return 0;
686
}
687
688
if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0) {
689
if (sk == NULL) {
690
ERR_raise_data(ERR_LIB_PROV, PROV_R_MISSING_KEY,
691
"no %s key material available", params->alg);
692
return 0;
693
}
694
if (BIO_printf(out, "%s Private-Key:\n", params->alg) <= 0)
695
return 0;
696
if (seed != NULL && !ossl_bio_print_labeled_buf(out, "seed:", seed, ML_DSA_SEED_BYTES))
697
return 0;
698
if (!ossl_bio_print_labeled_buf(out, "priv:", sk, params->sk_len))
699
return 0;
700
} else if ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0) {
701
if (BIO_printf(out, "%s Public-Key:\n", params->alg) <= 0)
702
return 0;
703
}
704
705
if (!ossl_bio_print_labeled_buf(out, "pub:", pk, params->pk_len))
706
return 0;
707
708
return 1;
709
}
710
711