Path: blob/main/crypto/openssl/providers/implementations/encode_decode/ml_kem_codecs.c
104576 views
/*1* Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.2*3* Licensed under the Apache License 2.0 (the "License"). You may not use4* this file except in compliance with the License. You can obtain a copy5* in the file LICENSE in the source distribution or at6* https://www.openssl.org/source/license.html7*/89#include <string.h>10#include <openssl/byteorder.h>11#include <openssl/proverr.h>12#include <openssl/x509.h>13#include <openssl/core_names.h>14#include "internal/encoder.h"15#include "prov/ml_kem.h"16#include "ml_kem_codecs.h"1718/* Tables describing supported ASN.1 input/output formats. */1920/*-21* ML-KEM-512:22* Public key bytes: 800 (0x0320)23* Private key bytes: 1632 (0x0660)24*/25static const ML_COMMON_SPKI_FMT ml_kem_512_spkifmt = {26{270x30,280x82,290x03,300x32,310x30,320x0b,330x06,340x09,350x60,360x86,370x48,380x01,390x65,400x03,410x04,420x04,430x01,440x03,450x82,460x03,470x21,480x00,49}50};51static const ML_COMMON_PKCS8_FMT ml_kem_512_p8fmt[NUM_PKCS8_FORMATS] = {52{ "seed-priv", 0x06aa, 0, 0x308206a6, 0x0440, 6, 0x40, 0x04820660, 0x4a, 0x0660, 0, 0 },53{ "priv-only", 0x0664, 0, 0x04820660, 0, 0, 0, 0, 0x04, 0x0660, 0, 0 },54{ "oqskeypair", 0x0984, 0, 0x04820980, 0, 0, 0, 0, 0x04, 0x0660, 0x0664, 0x0320 },55{ "seed-only", 0x0042, 2, 0x8040, 0, 2, 0x40, 0, 0, 0, 0, 0 },56{ "bare-priv", 0x0660, 4, 0, 0, 0, 0, 0, 0, 0x0660, 0, 0 },57{ "bare-seed", 0x0040, 4, 0, 0, 0, 0x40, 0, 0, 0, 0, 0 },58};5960/*-61* ML-KEM-768:62* Public key bytes: 1184 (0x04a0)63* Private key bytes: 2400 (0x0960)64*/65static const ML_COMMON_SPKI_FMT ml_kem_768_spkifmt = {66{670x30,680x82,690x04,700xb2,710x30,720x0b,730x06,740x09,750x60,760x86,770x48,780x01,790x65,800x03,810x04,820x04,830x02,840x03,850x82,860x04,870xa1,880x00,89}90};91static const ML_COMMON_PKCS8_FMT ml_kem_768_p8fmt[NUM_PKCS8_FORMATS] = {92{93"seed-priv",940x09aa,950,960x308209a6,970x0440,986,990x40,1000x04820960,1010x4a,1020x0960,1030,1040,105},106{107"priv-only",1080x0964,1090,1100x04820960,1110,1120,1130,1140,1150x04,1160x0960,1170,1180,119},120{ "oqskeypair", 0x0e04, 0, 0x04820e00, 0, 0, 0, 0, 0x04, 0x0960, 0x0964, 0x04a0 },121{122"seed-only",1230x0042,1242,1250x8040,1260,1272,1280x40,1290,1300,1310,1320,1330,134},135{136"bare-priv",1370x0960,1384,1390,1400,1410,1420,1430,1440,1450x0960,1460,1470,148},149{150"bare-seed",1510x0040,1524,1530,1540,1550,1560x40,1570,1580,1590,1600,1610,162},163};164165/*-166* ML-KEM-1024:167* Private key bytes: 3168 (0x0c60)168* Public key bytes: 1568 (0x0620)169*/170static const ML_COMMON_SPKI_FMT ml_kem_1024_spkifmt = {171{1720x30,1730x82,1740x06,1750x32,1760x30,1770x0b,1780x06,1790x09,1800x60,1810x86,1820x48,1830x01,1840x65,1850x03,1860x04,1870x04,1880x03,1890x03,1900x82,1910x06,1920x21,1930x00,194}195};196static const ML_COMMON_PKCS8_FMT ml_kem_1024_p8fmt[NUM_PKCS8_FORMATS] = {197{ "seed-priv", 0x0caa, 0, 0x30820ca6, 0x0440, 6, 0x40, 0x04820c60, 0x4a, 0x0c60, 0, 0 },198{ "priv-only", 0x0c64, 0, 0x04820c60, 0, 0, 0, 0, 0x04, 0x0c60, 0, 0 },199{ "oqskeypair", 0x1284, 0, 0x04821280, 0, 0, 0, 0, 0x04, 0x0c60, 0x0c64, 0x0620 },200{ "seed-only", 0x0042, 2, 0x8040, 0, 2, 0x40, 0, 0, 0, 0, 0 },201{ "bare-priv", 0x0c60, 4, 0, 0, 0, 0, 0, 0, 0x0c60, 0, 0 },202{ "bare-seed", 0x0040, 4, 0, 0, 0, 0x40, 0, 0, 0, 0, 0 },203};204205/* Indices of slots in the `codecs` table below */206#define ML_KEM_512_CODEC 0207#define ML_KEM_768_CODEC 1208#define ML_KEM_1024_CODEC 2209210/*211* Per-variant fixed parameters212*/213static const ML_COMMON_CODEC codecs[3] = {214{ &ml_kem_512_spkifmt, ml_kem_512_p8fmt },215{ &ml_kem_768_spkifmt, ml_kem_768_p8fmt },216{ &ml_kem_1024_spkifmt, ml_kem_1024_p8fmt }217};218219/* Retrieve the parameters of one of the ML-KEM variants */220static const ML_COMMON_CODEC *ml_kem_get_codec(int evp_type)221{222switch (evp_type) {223case EVP_PKEY_ML_KEM_512:224return &codecs[ML_KEM_512_CODEC];225case EVP_PKEY_ML_KEM_768:226return &codecs[ML_KEM_768_CODEC];227case EVP_PKEY_ML_KEM_1024:228return &codecs[ML_KEM_1024_CODEC];229}230return NULL;231}232233ML_KEM_KEY *234ossl_ml_kem_d2i_PUBKEY(const uint8_t *pubenc, int publen, int evp_type,235PROV_CTX *provctx, const char *propq)236{237OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(provctx);238const ML_KEM_VINFO *v;239const ML_COMMON_CODEC *codec;240const ML_COMMON_SPKI_FMT *vspki;241ML_KEM_KEY *ret;242243if ((v = ossl_ml_kem_get_vinfo(evp_type)) == NULL244|| (codec = ml_kem_get_codec(evp_type)) == NULL)245return NULL;246vspki = codec->spkifmt;247if (publen != ML_COMMON_SPKI_OVERHEAD + (ossl_ssize_t)v->pubkey_bytes248|| memcmp(pubenc, vspki->asn1_prefix, ML_COMMON_SPKI_OVERHEAD) != 0)249return NULL;250publen -= ML_COMMON_SPKI_OVERHEAD;251pubenc += ML_COMMON_SPKI_OVERHEAD;252253if ((ret = ossl_ml_kem_key_new(libctx, propq, evp_type)) == NULL)254return NULL;255256if (!ossl_ml_kem_parse_public_key(pubenc, (size_t)publen, ret)) {257ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING,258"error parsing %s public key from input SPKI",259v->algorithm_name);260ossl_ml_kem_key_free(ret);261return NULL;262}263264return ret;265}266267ML_KEM_KEY *268ossl_ml_kem_d2i_PKCS8(const uint8_t *prvenc, int prvlen,269int evp_type, PROV_CTX *provctx,270const char *propq)271{272const ML_KEM_VINFO *v;273const ML_COMMON_CODEC *codec;274ML_COMMON_PKCS8_FMT_PREF *fmt_slots = NULL, *slot;275const ML_COMMON_PKCS8_FMT *p8fmt;276ML_KEM_KEY *key = NULL, *ret = NULL;277PKCS8_PRIV_KEY_INFO *p8inf = NULL;278const uint8_t *buf, *pos;279const X509_ALGOR *alg = NULL;280const char *formats;281int len, ptype;282uint32_t magic;283uint16_t seed_magic;284285/* Which ML-KEM variant? */286if ((v = ossl_ml_kem_get_vinfo(evp_type)) == NULL287|| (codec = ml_kem_get_codec(evp_type)) == NULL)288return 0;289290/* Extract the key OID and any parameters. */291if ((p8inf = d2i_PKCS8_PRIV_KEY_INFO(NULL, &prvenc, prvlen)) == NULL)292return 0;293/* Shortest prefix is 4 bytes: seq tag/len + octet string tag/len */294if (!PKCS8_pkey_get0(NULL, &buf, &len, &alg, p8inf))295goto end;296/* Bail out early if this is some other key type. */297if (OBJ_obj2nid(alg->algorithm) != evp_type)298goto end;299300/* Get the list of enabled decoders. Their order is not important here. */301formats = ossl_prov_ctx_get_param(302provctx, OSSL_PKEY_PARAM_ML_KEM_INPUT_FORMATS, NULL);303fmt_slots = ossl_ml_common_pkcs8_fmt_order(v->algorithm_name, codec->p8fmt,304"input", formats);305if (fmt_slots == NULL)306goto end;307308/* Parameters must be absent. */309X509_ALGOR_get0(NULL, &ptype, NULL, alg);310if (ptype != V_ASN1_UNDEF) {311ERR_raise_data(ERR_LIB_PROV, PROV_R_UNEXPECTED_KEY_PARAMETERS,312"unexpected parameters with a PKCS#8 %s private key",313v->algorithm_name);314goto end;315}316if ((ossl_ssize_t)len < (ossl_ssize_t)sizeof(magic))317goto end;318319/* Find the matching p8 info slot, that also has the expected length. */320pos = OPENSSL_load_u32_be(&magic, buf);321for (slot = fmt_slots; (p8fmt = slot->fmt) != NULL; ++slot) {322if (len != (ossl_ssize_t)p8fmt->p8_bytes)323continue;324if (p8fmt->p8_shift == sizeof(magic)325|| (magic >> (p8fmt->p8_shift * 8)) == p8fmt->p8_magic) {326pos -= p8fmt->p8_shift;327break;328}329}330if (p8fmt == NULL331|| (p8fmt->seed_length > 0 && p8fmt->seed_length != ML_KEM_SEED_BYTES)332|| (p8fmt->priv_length > 0 && p8fmt->priv_length != v->prvkey_bytes)333|| (p8fmt->pub_length > 0 && p8fmt->pub_length != v->pubkey_bytes)) {334ERR_raise_data(ERR_LIB_PROV, PROV_R_ML_KEM_NO_FORMAT,335"no matching enabled %s private key input formats",336v->algorithm_name);337goto end;338}339340if (p8fmt->seed_length > 0) {341/* Check |seed| tag/len, if not subsumed by |magic|. */342if (pos + sizeof(uint16_t) == buf + p8fmt->seed_offset) {343pos = OPENSSL_load_u16_be(&seed_magic, pos);344if (seed_magic != p8fmt->seed_magic)345goto end;346} else if (pos != buf + p8fmt->seed_offset) {347goto end;348}349pos += ML_KEM_SEED_BYTES;350}351if (p8fmt->priv_length > 0) {352/* Check |priv| tag/len */353if (pos + sizeof(uint32_t) == buf + p8fmt->priv_offset) {354pos = OPENSSL_load_u32_be(&magic, pos);355if (magic != p8fmt->priv_magic)356goto end;357} else if (pos != buf + p8fmt->priv_offset) {358goto end;359}360pos += v->prvkey_bytes;361}362if (p8fmt->pub_length > 0) {363if (pos != buf + p8fmt->pub_offset)364goto end;365pos += v->pubkey_bytes;366}367if (pos != buf + len)368goto end;369370/*371* Collect the seed and/or key into a "decoded" private key object,372* to be turned into a real key on provider "load" or "import".373*/374if ((key = ossl_prov_ml_kem_new(provctx, propq, evp_type)) == NULL)375goto end;376377if (p8fmt->seed_length > 0) {378if (!ossl_ml_kem_set_seed(buf + p8fmt->seed_offset,379ML_KEM_SEED_BYTES, key)) {380ERR_raise_data(ERR_LIB_OSSL_DECODER, ERR_R_INTERNAL_ERROR,381"error storing %s private key seed",382v->algorithm_name);383goto end;384}385}386if (p8fmt->priv_length > 0) {387if ((key->encoded_dk = OPENSSL_malloc(p8fmt->priv_length)) == NULL) {388ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY,389"error parsing %s private key",390v->algorithm_name);391goto end;392}393memcpy(key->encoded_dk, buf + p8fmt->priv_offset, p8fmt->priv_length);394}395/* Any OQS public key content is ignored */396ret = key;397398end:399OPENSSL_free(fmt_slots);400PKCS8_PRIV_KEY_INFO_free(p8inf);401if (ret == NULL)402ossl_ml_kem_key_free(key);403return ret;404}405406/* Same as ossl_ml_kem_encode_pubkey, but allocates the output buffer. */407int ossl_ml_kem_i2d_pubkey(const ML_KEM_KEY *key, unsigned char **out)408{409size_t publen;410411if (!ossl_ml_kem_have_pubkey(key)) {412ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_A_PUBLIC_KEY,413"no %s public key data available",414key->vinfo->algorithm_name);415return 0;416}417publen = key->vinfo->pubkey_bytes;418419if (out != NULL420&& (*out = OPENSSL_malloc(publen)) == NULL)421return 0;422if (!ossl_ml_kem_encode_public_key(*out, publen, key)) {423ERR_raise_data(ERR_LIB_OSSL_ENCODER, ERR_R_INTERNAL_ERROR,424"error encoding %s public key",425key->vinfo->algorithm_name);426OPENSSL_free(*out);427return 0;428}429430return (int)publen;431}432433/* Allocate and encode PKCS#8 private key payload. */434int ossl_ml_kem_i2d_prvkey(const ML_KEM_KEY *key, uint8_t **out,435PROV_CTX *provctx)436{437const ML_KEM_VINFO *v = key->vinfo;438const ML_COMMON_CODEC *codec;439ML_COMMON_PKCS8_FMT_PREF *fmt_slots, *slot;440const ML_COMMON_PKCS8_FMT *p8fmt;441uint8_t *buf = NULL, *pos;442const char *formats;443int len = ML_KEM_SEED_BYTES;444int ret = 0;445446/* Not ours to handle */447if ((codec = ml_kem_get_codec(v->evp_type)) == NULL)448return 0;449450if (!ossl_ml_kem_have_prvkey(key)) {451ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_A_PRIVATE_KEY,452"no %s private key data available",453key->vinfo->algorithm_name);454return 0;455}456457formats = ossl_prov_ctx_get_param(458provctx, OSSL_PKEY_PARAM_ML_KEM_OUTPUT_FORMATS, NULL);459fmt_slots = ossl_ml_common_pkcs8_fmt_order(v->algorithm_name, codec->p8fmt,460"output", formats);461if (fmt_slots == NULL)462return 0;463464/* If we don't have a seed, skip seedful entries */465for (slot = fmt_slots; (p8fmt = slot->fmt) != NULL; ++slot)466if (ossl_ml_kem_have_seed(key) || p8fmt->seed_length == 0)467break;468/* No matching table entries, give up */469if (p8fmt == NULL470|| (p8fmt->seed_length > 0 && p8fmt->seed_length != ML_KEM_SEED_BYTES)471|| (p8fmt->priv_length > 0 && p8fmt->priv_length != v->prvkey_bytes)472|| (p8fmt->pub_length > 0 && p8fmt->pub_length != v->pubkey_bytes)) {473ERR_raise_data(ERR_LIB_PROV, PROV_R_ML_KEM_NO_FORMAT,474"no matching enabled %s private key output formats",475v->algorithm_name);476goto end;477}478len = p8fmt->p8_bytes;479480if (out == NULL) {481ret = len;482goto end;483}484485if ((pos = buf = OPENSSL_malloc((size_t)len)) == NULL)486goto end;487488switch (p8fmt->p8_shift) {489case 0:490pos = OPENSSL_store_u32_be(pos, p8fmt->p8_magic);491break;492case 2:493pos = OPENSSL_store_u16_be(pos, (uint16_t)p8fmt->p8_magic);494break;495case 4:496break;497default:498ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,499"error encoding %s private key",500v->algorithm_name);501goto end;502}503504if (p8fmt->seed_length != 0) {505/*506* Either the tag/len were already included in |magic| or they require507* us to write two bytes now.508*/509if (pos + sizeof(uint16_t) == buf + p8fmt->seed_offset)510pos = OPENSSL_store_u16_be(pos, p8fmt->seed_magic);511if (pos != buf + p8fmt->seed_offset512|| !ossl_ml_kem_encode_seed(pos, ML_KEM_SEED_BYTES, key)) {513ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,514"error encoding %s private key",515v->algorithm_name);516goto end;517}518pos += ML_KEM_SEED_BYTES;519}520if (p8fmt->priv_length != 0) {521if (pos + sizeof(uint32_t) == buf + p8fmt->priv_offset)522pos = OPENSSL_store_u32_be(pos, p8fmt->priv_magic);523if (pos != buf + p8fmt->priv_offset524|| !ossl_ml_kem_encode_private_key(pos, v->prvkey_bytes, key)) {525ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,526"error encoding %s private key",527v->algorithm_name);528goto end;529}530pos += v->prvkey_bytes;531}532/* OQS form output with tacked-on public key */533if (p8fmt->pub_length != 0) {534/* The OQS pubkey is never separately DER-wrapped */535if (pos != buf + p8fmt->pub_offset536|| !ossl_ml_kem_encode_public_key(pos, v->pubkey_bytes, key)) {537ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,538"error encoding %s private key",539v->algorithm_name);540goto end;541}542pos += v->pubkey_bytes;543}544545if (pos == buf + len) {546*out = buf;547ret = len;548}549550end:551OPENSSL_free(fmt_slots);552if (ret == 0)553OPENSSL_free(buf);554return ret;555}556557int ossl_ml_kem_key_to_text(BIO *out, const ML_KEM_KEY *key, int selection)558{559uint8_t seed[ML_KEM_SEED_BYTES], *prvenc = NULL, *pubenc = NULL;560size_t publen, prvlen;561const char *type_label = NULL;562int ret = 0;563564if (out == NULL || key == NULL) {565ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_PASSED_NULL_PARAMETER);566return 0;567}568type_label = key->vinfo->algorithm_name;569publen = key->vinfo->pubkey_bytes;570prvlen = key->vinfo->prvkey_bytes;571572if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0573&& (ossl_ml_kem_have_prvkey(key)574|| ossl_ml_kem_have_seed(key))) {575if (BIO_printf(out, "%s Private-Key:\n", type_label) <= 0)576return 0;577578if (ossl_ml_kem_have_seed(key)) {579if (!ossl_ml_kem_encode_seed(seed, sizeof(seed), key))580goto end;581if (!ossl_bio_print_labeled_buf(out, "seed:", seed, sizeof(seed)))582goto end;583}584if (ossl_ml_kem_have_prvkey(key)) {585if ((prvenc = OPENSSL_malloc(prvlen)) == NULL)586return 0;587if (!ossl_ml_kem_encode_private_key(prvenc, prvlen, key))588goto end;589if (!ossl_bio_print_labeled_buf(out, "dk:", prvenc, prvlen))590goto end;591}592ret = 1;593}594595/* The public key is output regardless of the selection */596if (ossl_ml_kem_have_pubkey(key)) {597/* If we did not output private key bits, this is a public key */598if (ret == 0 && BIO_printf(out, "%s Public-Key:\n", type_label) <= 0)599goto end;600601if ((pubenc = OPENSSL_malloc(key->vinfo->pubkey_bytes)) == NULL602|| !ossl_ml_kem_encode_public_key(pubenc, publen, key)603|| !ossl_bio_print_labeled_buf(out, "ek:", pubenc, publen))604goto end;605ret = 1;606}607608/* If we got here, and ret == 0, there was no key material */609if (ret == 0)610ERR_raise_data(ERR_LIB_PROV, PROV_R_MISSING_KEY,611"no %s key material available",612type_label);613614end:615OPENSSL_free(pubenc);616OPENSSL_free(prvenc);617return ret;618}619620621