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