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_kem_codecs.c
104576 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/proverr.h>
13
#include <openssl/x509.h>
14
#include <openssl/core_names.h>
15
#include "internal/encoder.h"
16
#include "prov/ml_kem.h"
17
#include "ml_kem_codecs.h"
18
19
/* Tables describing supported ASN.1 input/output formats. */
20
21
/*-
22
* ML-KEM-512:
23
* Public key bytes: 800 (0x0320)
24
* Private key bytes: 1632 (0x0660)
25
*/
26
static const ML_COMMON_SPKI_FMT ml_kem_512_spkifmt = {
27
{
28
0x30,
29
0x82,
30
0x03,
31
0x32,
32
0x30,
33
0x0b,
34
0x06,
35
0x09,
36
0x60,
37
0x86,
38
0x48,
39
0x01,
40
0x65,
41
0x03,
42
0x04,
43
0x04,
44
0x01,
45
0x03,
46
0x82,
47
0x03,
48
0x21,
49
0x00,
50
}
51
};
52
static const ML_COMMON_PKCS8_FMT ml_kem_512_p8fmt[NUM_PKCS8_FORMATS] = {
53
{ "seed-priv", 0x06aa, 0, 0x308206a6, 0x0440, 6, 0x40, 0x04820660, 0x4a, 0x0660, 0, 0 },
54
{ "priv-only", 0x0664, 0, 0x04820660, 0, 0, 0, 0, 0x04, 0x0660, 0, 0 },
55
{ "oqskeypair", 0x0984, 0, 0x04820980, 0, 0, 0, 0, 0x04, 0x0660, 0x0664, 0x0320 },
56
{ "seed-only", 0x0042, 2, 0x8040, 0, 2, 0x40, 0, 0, 0, 0, 0 },
57
{ "bare-priv", 0x0660, 4, 0, 0, 0, 0, 0, 0, 0x0660, 0, 0 },
58
{ "bare-seed", 0x0040, 4, 0, 0, 0, 0x40, 0, 0, 0, 0, 0 },
59
};
60
61
/*-
62
* ML-KEM-768:
63
* Public key bytes: 1184 (0x04a0)
64
* Private key bytes: 2400 (0x0960)
65
*/
66
static const ML_COMMON_SPKI_FMT ml_kem_768_spkifmt = {
67
{
68
0x30,
69
0x82,
70
0x04,
71
0xb2,
72
0x30,
73
0x0b,
74
0x06,
75
0x09,
76
0x60,
77
0x86,
78
0x48,
79
0x01,
80
0x65,
81
0x03,
82
0x04,
83
0x04,
84
0x02,
85
0x03,
86
0x82,
87
0x04,
88
0xa1,
89
0x00,
90
}
91
};
92
static const ML_COMMON_PKCS8_FMT ml_kem_768_p8fmt[NUM_PKCS8_FORMATS] = {
93
{
94
"seed-priv",
95
0x09aa,
96
0,
97
0x308209a6,
98
0x0440,
99
6,
100
0x40,
101
0x04820960,
102
0x4a,
103
0x0960,
104
0,
105
0,
106
},
107
{
108
"priv-only",
109
0x0964,
110
0,
111
0x04820960,
112
0,
113
0,
114
0,
115
0,
116
0x04,
117
0x0960,
118
0,
119
0,
120
},
121
{ "oqskeypair", 0x0e04, 0, 0x04820e00, 0, 0, 0, 0, 0x04, 0x0960, 0x0964, 0x04a0 },
122
{
123
"seed-only",
124
0x0042,
125
2,
126
0x8040,
127
0,
128
2,
129
0x40,
130
0,
131
0,
132
0,
133
0,
134
0,
135
},
136
{
137
"bare-priv",
138
0x0960,
139
4,
140
0,
141
0,
142
0,
143
0,
144
0,
145
0,
146
0x0960,
147
0,
148
0,
149
},
150
{
151
"bare-seed",
152
0x0040,
153
4,
154
0,
155
0,
156
0,
157
0x40,
158
0,
159
0,
160
0,
161
0,
162
0,
163
},
164
};
165
166
/*-
167
* ML-KEM-1024:
168
* Private key bytes: 3168 (0x0c60)
169
* Public key bytes: 1568 (0x0620)
170
*/
171
static const ML_COMMON_SPKI_FMT ml_kem_1024_spkifmt = {
172
{
173
0x30,
174
0x82,
175
0x06,
176
0x32,
177
0x30,
178
0x0b,
179
0x06,
180
0x09,
181
0x60,
182
0x86,
183
0x48,
184
0x01,
185
0x65,
186
0x03,
187
0x04,
188
0x04,
189
0x03,
190
0x03,
191
0x82,
192
0x06,
193
0x21,
194
0x00,
195
}
196
};
197
static const ML_COMMON_PKCS8_FMT ml_kem_1024_p8fmt[NUM_PKCS8_FORMATS] = {
198
{ "seed-priv", 0x0caa, 0, 0x30820ca6, 0x0440, 6, 0x40, 0x04820c60, 0x4a, 0x0c60, 0, 0 },
199
{ "priv-only", 0x0c64, 0, 0x04820c60, 0, 0, 0, 0, 0x04, 0x0c60, 0, 0 },
200
{ "oqskeypair", 0x1284, 0, 0x04821280, 0, 0, 0, 0, 0x04, 0x0c60, 0x0c64, 0x0620 },
201
{ "seed-only", 0x0042, 2, 0x8040, 0, 2, 0x40, 0, 0, 0, 0, 0 },
202
{ "bare-priv", 0x0c60, 4, 0, 0, 0, 0, 0, 0, 0x0c60, 0, 0 },
203
{ "bare-seed", 0x0040, 4, 0, 0, 0, 0x40, 0, 0, 0, 0, 0 },
204
};
205
206
/* Indices of slots in the `codecs` table below */
207
#define ML_KEM_512_CODEC 0
208
#define ML_KEM_768_CODEC 1
209
#define ML_KEM_1024_CODEC 2
210
211
/*
212
* Per-variant fixed parameters
213
*/
214
static const ML_COMMON_CODEC codecs[3] = {
215
{ &ml_kem_512_spkifmt, ml_kem_512_p8fmt },
216
{ &ml_kem_768_spkifmt, ml_kem_768_p8fmt },
217
{ &ml_kem_1024_spkifmt, ml_kem_1024_p8fmt }
218
};
219
220
/* Retrieve the parameters of one of the ML-KEM variants */
221
static const ML_COMMON_CODEC *ml_kem_get_codec(int evp_type)
222
{
223
switch (evp_type) {
224
case EVP_PKEY_ML_KEM_512:
225
return &codecs[ML_KEM_512_CODEC];
226
case EVP_PKEY_ML_KEM_768:
227
return &codecs[ML_KEM_768_CODEC];
228
case EVP_PKEY_ML_KEM_1024:
229
return &codecs[ML_KEM_1024_CODEC];
230
}
231
return NULL;
232
}
233
234
ML_KEM_KEY *
235
ossl_ml_kem_d2i_PUBKEY(const uint8_t *pubenc, int publen, int evp_type,
236
PROV_CTX *provctx, const char *propq)
237
{
238
OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(provctx);
239
const ML_KEM_VINFO *v;
240
const ML_COMMON_CODEC *codec;
241
const ML_COMMON_SPKI_FMT *vspki;
242
ML_KEM_KEY *ret;
243
244
if ((v = ossl_ml_kem_get_vinfo(evp_type)) == NULL
245
|| (codec = ml_kem_get_codec(evp_type)) == NULL)
246
return NULL;
247
vspki = codec->spkifmt;
248
if (publen != ML_COMMON_SPKI_OVERHEAD + (ossl_ssize_t)v->pubkey_bytes
249
|| memcmp(pubenc, vspki->asn1_prefix, ML_COMMON_SPKI_OVERHEAD) != 0)
250
return NULL;
251
publen -= ML_COMMON_SPKI_OVERHEAD;
252
pubenc += ML_COMMON_SPKI_OVERHEAD;
253
254
if ((ret = ossl_ml_kem_key_new(libctx, propq, evp_type)) == NULL)
255
return NULL;
256
257
if (!ossl_ml_kem_parse_public_key(pubenc, (size_t)publen, ret)) {
258
ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING,
259
"error parsing %s public key from input SPKI",
260
v->algorithm_name);
261
ossl_ml_kem_key_free(ret);
262
return NULL;
263
}
264
265
return ret;
266
}
267
268
ML_KEM_KEY *
269
ossl_ml_kem_d2i_PKCS8(const uint8_t *prvenc, int prvlen,
270
int evp_type, PROV_CTX *provctx,
271
const char *propq)
272
{
273
const ML_KEM_VINFO *v;
274
const ML_COMMON_CODEC *codec;
275
ML_COMMON_PKCS8_FMT_PREF *fmt_slots = NULL, *slot;
276
const ML_COMMON_PKCS8_FMT *p8fmt;
277
ML_KEM_KEY *key = NULL, *ret = NULL;
278
PKCS8_PRIV_KEY_INFO *p8inf = NULL;
279
const uint8_t *buf, *pos;
280
const X509_ALGOR *alg = NULL;
281
const char *formats;
282
int len, ptype;
283
uint32_t magic;
284
uint16_t seed_magic;
285
286
/* Which ML-KEM variant? */
287
if ((v = ossl_ml_kem_get_vinfo(evp_type)) == NULL
288
|| (codec = ml_kem_get_codec(evp_type)) == NULL)
289
return 0;
290
291
/* Extract the key OID and any parameters. */
292
if ((p8inf = d2i_PKCS8_PRIV_KEY_INFO(NULL, &prvenc, prvlen)) == NULL)
293
return 0;
294
/* Shortest prefix is 4 bytes: seq tag/len + octet string tag/len */
295
if (!PKCS8_pkey_get0(NULL, &buf, &len, &alg, p8inf))
296
goto end;
297
/* Bail out early if this is some other key type. */
298
if (OBJ_obj2nid(alg->algorithm) != evp_type)
299
goto end;
300
301
/* Get the list of enabled decoders. Their order is not important here. */
302
formats = ossl_prov_ctx_get_param(
303
provctx, OSSL_PKEY_PARAM_ML_KEM_INPUT_FORMATS, NULL);
304
fmt_slots = ossl_ml_common_pkcs8_fmt_order(v->algorithm_name, codec->p8fmt,
305
"input", formats);
306
if (fmt_slots == NULL)
307
goto end;
308
309
/* Parameters must be absent. */
310
X509_ALGOR_get0(NULL, &ptype, NULL, alg);
311
if (ptype != V_ASN1_UNDEF) {
312
ERR_raise_data(ERR_LIB_PROV, PROV_R_UNEXPECTED_KEY_PARAMETERS,
313
"unexpected parameters with a PKCS#8 %s private key",
314
v->algorithm_name);
315
goto end;
316
}
317
if ((ossl_ssize_t)len < (ossl_ssize_t)sizeof(magic))
318
goto end;
319
320
/* Find the matching p8 info slot, that also has the expected length. */
321
pos = OPENSSL_load_u32_be(&magic, buf);
322
for (slot = fmt_slots; (p8fmt = slot->fmt) != NULL; ++slot) {
323
if (len != (ossl_ssize_t)p8fmt->p8_bytes)
324
continue;
325
if (p8fmt->p8_shift == sizeof(magic)
326
|| (magic >> (p8fmt->p8_shift * 8)) == p8fmt->p8_magic) {
327
pos -= p8fmt->p8_shift;
328
break;
329
}
330
}
331
if (p8fmt == NULL
332
|| (p8fmt->seed_length > 0 && p8fmt->seed_length != ML_KEM_SEED_BYTES)
333
|| (p8fmt->priv_length > 0 && p8fmt->priv_length != v->prvkey_bytes)
334
|| (p8fmt->pub_length > 0 && p8fmt->pub_length != v->pubkey_bytes)) {
335
ERR_raise_data(ERR_LIB_PROV, PROV_R_ML_KEM_NO_FORMAT,
336
"no matching enabled %s private key input formats",
337
v->algorithm_name);
338
goto end;
339
}
340
341
if (p8fmt->seed_length > 0) {
342
/* Check |seed| tag/len, if not subsumed by |magic|. */
343
if (pos + sizeof(uint16_t) == buf + p8fmt->seed_offset) {
344
pos = OPENSSL_load_u16_be(&seed_magic, pos);
345
if (seed_magic != p8fmt->seed_magic)
346
goto end;
347
} else if (pos != buf + p8fmt->seed_offset) {
348
goto end;
349
}
350
pos += ML_KEM_SEED_BYTES;
351
}
352
if (p8fmt->priv_length > 0) {
353
/* Check |priv| tag/len */
354
if (pos + sizeof(uint32_t) == buf + p8fmt->priv_offset) {
355
pos = OPENSSL_load_u32_be(&magic, pos);
356
if (magic != p8fmt->priv_magic)
357
goto end;
358
} else if (pos != buf + p8fmt->priv_offset) {
359
goto end;
360
}
361
pos += v->prvkey_bytes;
362
}
363
if (p8fmt->pub_length > 0) {
364
if (pos != buf + p8fmt->pub_offset)
365
goto end;
366
pos += v->pubkey_bytes;
367
}
368
if (pos != buf + len)
369
goto end;
370
371
/*
372
* Collect the seed and/or key into a "decoded" private key object,
373
* to be turned into a real key on provider "load" or "import".
374
*/
375
if ((key = ossl_prov_ml_kem_new(provctx, propq, evp_type)) == NULL)
376
goto end;
377
378
if (p8fmt->seed_length > 0) {
379
if (!ossl_ml_kem_set_seed(buf + p8fmt->seed_offset,
380
ML_KEM_SEED_BYTES, key)) {
381
ERR_raise_data(ERR_LIB_OSSL_DECODER, ERR_R_INTERNAL_ERROR,
382
"error storing %s private key seed",
383
v->algorithm_name);
384
goto end;
385
}
386
}
387
if (p8fmt->priv_length > 0) {
388
if ((key->encoded_dk = OPENSSL_malloc(p8fmt->priv_length)) == NULL) {
389
ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY,
390
"error parsing %s private key",
391
v->algorithm_name);
392
goto end;
393
}
394
memcpy(key->encoded_dk, buf + p8fmt->priv_offset, p8fmt->priv_length);
395
}
396
/* Any OQS public key content is ignored */
397
ret = key;
398
399
end:
400
OPENSSL_free(fmt_slots);
401
PKCS8_PRIV_KEY_INFO_free(p8inf);
402
if (ret == NULL)
403
ossl_ml_kem_key_free(key);
404
return ret;
405
}
406
407
/* Same as ossl_ml_kem_encode_pubkey, but allocates the output buffer. */
408
int ossl_ml_kem_i2d_pubkey(const ML_KEM_KEY *key, unsigned char **out)
409
{
410
size_t publen;
411
412
if (!ossl_ml_kem_have_pubkey(key)) {
413
ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_A_PUBLIC_KEY,
414
"no %s public key data available",
415
key->vinfo->algorithm_name);
416
return 0;
417
}
418
publen = key->vinfo->pubkey_bytes;
419
420
if (out != NULL
421
&& (*out = OPENSSL_malloc(publen)) == NULL)
422
return 0;
423
if (!ossl_ml_kem_encode_public_key(*out, publen, key)) {
424
ERR_raise_data(ERR_LIB_OSSL_ENCODER, ERR_R_INTERNAL_ERROR,
425
"error encoding %s public key",
426
key->vinfo->algorithm_name);
427
OPENSSL_free(*out);
428
return 0;
429
}
430
431
return (int)publen;
432
}
433
434
/* Allocate and encode PKCS#8 private key payload. */
435
int ossl_ml_kem_i2d_prvkey(const ML_KEM_KEY *key, uint8_t **out,
436
PROV_CTX *provctx)
437
{
438
const ML_KEM_VINFO *v = key->vinfo;
439
const ML_COMMON_CODEC *codec;
440
ML_COMMON_PKCS8_FMT_PREF *fmt_slots, *slot;
441
const ML_COMMON_PKCS8_FMT *p8fmt;
442
uint8_t *buf = NULL, *pos;
443
const char *formats;
444
int len = ML_KEM_SEED_BYTES;
445
int ret = 0;
446
447
/* Not ours to handle */
448
if ((codec = ml_kem_get_codec(v->evp_type)) == NULL)
449
return 0;
450
451
if (!ossl_ml_kem_have_prvkey(key)) {
452
ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_A_PRIVATE_KEY,
453
"no %s private key data available",
454
key->vinfo->algorithm_name);
455
return 0;
456
}
457
458
formats = ossl_prov_ctx_get_param(
459
provctx, OSSL_PKEY_PARAM_ML_KEM_OUTPUT_FORMATS, NULL);
460
fmt_slots = ossl_ml_common_pkcs8_fmt_order(v->algorithm_name, codec->p8fmt,
461
"output", formats);
462
if (fmt_slots == NULL)
463
return 0;
464
465
/* If we don't have a seed, skip seedful entries */
466
for (slot = fmt_slots; (p8fmt = slot->fmt) != NULL; ++slot)
467
if (ossl_ml_kem_have_seed(key) || p8fmt->seed_length == 0)
468
break;
469
/* No matching table entries, give up */
470
if (p8fmt == NULL
471
|| (p8fmt->seed_length > 0 && p8fmt->seed_length != ML_KEM_SEED_BYTES)
472
|| (p8fmt->priv_length > 0 && p8fmt->priv_length != v->prvkey_bytes)
473
|| (p8fmt->pub_length > 0 && p8fmt->pub_length != v->pubkey_bytes)) {
474
ERR_raise_data(ERR_LIB_PROV, PROV_R_ML_KEM_NO_FORMAT,
475
"no matching enabled %s private key output formats",
476
v->algorithm_name);
477
goto end;
478
}
479
len = p8fmt->p8_bytes;
480
481
if (out == NULL) {
482
ret = len;
483
goto end;
484
}
485
486
if ((pos = buf = OPENSSL_malloc((size_t)len)) == NULL)
487
goto end;
488
489
switch (p8fmt->p8_shift) {
490
case 0:
491
pos = OPENSSL_store_u32_be(pos, p8fmt->p8_magic);
492
break;
493
case 2:
494
pos = OPENSSL_store_u16_be(pos, (uint16_t)p8fmt->p8_magic);
495
break;
496
case 4:
497
break;
498
default:
499
ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
500
"error encoding %s private key",
501
v->algorithm_name);
502
goto end;
503
}
504
505
if (p8fmt->seed_length != 0) {
506
/*
507
* Either the tag/len were already included in |magic| or they require
508
* us to write two bytes now.
509
*/
510
if (pos + sizeof(uint16_t) == buf + p8fmt->seed_offset)
511
pos = OPENSSL_store_u16_be(pos, p8fmt->seed_magic);
512
if (pos != buf + p8fmt->seed_offset
513
|| !ossl_ml_kem_encode_seed(pos, ML_KEM_SEED_BYTES, key)) {
514
ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
515
"error encoding %s private key",
516
v->algorithm_name);
517
goto end;
518
}
519
pos += ML_KEM_SEED_BYTES;
520
}
521
if (p8fmt->priv_length != 0) {
522
if (pos + sizeof(uint32_t) == buf + p8fmt->priv_offset)
523
pos = OPENSSL_store_u32_be(pos, p8fmt->priv_magic);
524
if (pos != buf + p8fmt->priv_offset
525
|| !ossl_ml_kem_encode_private_key(pos, v->prvkey_bytes, key)) {
526
ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
527
"error encoding %s private key",
528
v->algorithm_name);
529
goto end;
530
}
531
pos += v->prvkey_bytes;
532
}
533
/* OQS form output with tacked-on public key */
534
if (p8fmt->pub_length != 0) {
535
/* The OQS pubkey is never separately DER-wrapped */
536
if (pos != buf + p8fmt->pub_offset
537
|| !ossl_ml_kem_encode_public_key(pos, v->pubkey_bytes, key)) {
538
ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
539
"error encoding %s private key",
540
v->algorithm_name);
541
goto end;
542
}
543
pos += v->pubkey_bytes;
544
}
545
546
if (pos == buf + len) {
547
*out = buf;
548
ret = len;
549
}
550
551
end:
552
OPENSSL_free(fmt_slots);
553
if (ret == 0)
554
OPENSSL_free(buf);
555
return ret;
556
}
557
558
int ossl_ml_kem_key_to_text(BIO *out, const ML_KEM_KEY *key, int selection)
559
{
560
uint8_t seed[ML_KEM_SEED_BYTES], *prvenc = NULL, *pubenc = NULL;
561
size_t publen, prvlen;
562
const char *type_label = NULL;
563
int ret = 0;
564
565
if (out == NULL || key == NULL) {
566
ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_PASSED_NULL_PARAMETER);
567
return 0;
568
}
569
type_label = key->vinfo->algorithm_name;
570
publen = key->vinfo->pubkey_bytes;
571
prvlen = key->vinfo->prvkey_bytes;
572
573
if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0
574
&& (ossl_ml_kem_have_prvkey(key)
575
|| ossl_ml_kem_have_seed(key))) {
576
if (BIO_printf(out, "%s Private-Key:\n", type_label) <= 0)
577
return 0;
578
579
if (ossl_ml_kem_have_seed(key)) {
580
if (!ossl_ml_kem_encode_seed(seed, sizeof(seed), key))
581
goto end;
582
if (!ossl_bio_print_labeled_buf(out, "seed:", seed, sizeof(seed)))
583
goto end;
584
}
585
if (ossl_ml_kem_have_prvkey(key)) {
586
if ((prvenc = OPENSSL_malloc(prvlen)) == NULL)
587
return 0;
588
if (!ossl_ml_kem_encode_private_key(prvenc, prvlen, key))
589
goto end;
590
if (!ossl_bio_print_labeled_buf(out, "dk:", prvenc, prvlen))
591
goto end;
592
}
593
ret = 1;
594
}
595
596
/* The public key is output regardless of the selection */
597
if (ossl_ml_kem_have_pubkey(key)) {
598
/* If we did not output private key bits, this is a public key */
599
if (ret == 0 && BIO_printf(out, "%s Public-Key:\n", type_label) <= 0)
600
goto end;
601
602
if ((pubenc = OPENSSL_malloc(key->vinfo->pubkey_bytes)) == NULL
603
|| !ossl_ml_kem_encode_public_key(pubenc, publen, key)
604
|| !ossl_bio_print_labeled_buf(out, "ek:", pubenc, publen))
605
goto end;
606
ret = 1;
607
}
608
609
/* If we got here, and ret == 0, there was no key material */
610
if (ret == 0)
611
ERR_raise_data(ERR_LIB_PROV, PROV_R_MISSING_KEY,
612
"no %s key material available",
613
type_label);
614
615
end:
616
OPENSSL_free(pubenc);
617
OPENSSL_free(prvenc);
618
return ret;
619
}
620
621