xref: /freebsd/crypto/openssl/providers/implementations/encode_decode/ml_kem_codecs.c (revision a689bfa4e25af8307709dc12f75b0e02a65abf18)
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