/* * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved. * * Licensed under the Apache License 2.0 (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy * in the file LICENSE in the source distribution or at * https://www.openssl.org/source/license.html */ #include #include #include #include #include #include #include #include #include "crypto/ml_kem.h" #include "prov/provider_ctx.h" #include "prov/implementations.h" #include "prov/securitycheck.h" #include "prov/providercommon.h" static OSSL_FUNC_kem_newctx_fn ml_kem_newctx; static OSSL_FUNC_kem_freectx_fn ml_kem_freectx; static OSSL_FUNC_kem_encapsulate_init_fn ml_kem_encapsulate_init; static OSSL_FUNC_kem_encapsulate_fn ml_kem_encapsulate; static OSSL_FUNC_kem_decapsulate_init_fn ml_kem_decapsulate_init; static OSSL_FUNC_kem_decapsulate_fn ml_kem_decapsulate; static OSSL_FUNC_kem_set_ctx_params_fn ml_kem_set_ctx_params; static OSSL_FUNC_kem_settable_ctx_params_fn ml_kem_settable_ctx_params; typedef struct { ML_KEM_KEY *key; uint8_t entropy_buf[ML_KEM_RANDOM_BYTES]; uint8_t *entropy; int op; } PROV_ML_KEM_CTX; static void *ml_kem_newctx(void *provctx) { PROV_ML_KEM_CTX *ctx; if ((ctx = OPENSSL_malloc(sizeof(*ctx))) == NULL) return NULL; ctx->key = NULL; ctx->entropy = NULL; ctx->op = 0; return ctx; } static void ml_kem_freectx(void *vctx) { PROV_ML_KEM_CTX *ctx = vctx; if (ctx->entropy != NULL) OPENSSL_cleanse(ctx->entropy, ML_KEM_RANDOM_BYTES); OPENSSL_free(ctx); } static int ml_kem_init(void *vctx, int op, void *key, const OSSL_PARAM params[]) { PROV_ML_KEM_CTX *ctx = vctx; if (!ossl_prov_is_running()) return 0; ctx->key = key; ctx->op = op; return ml_kem_set_ctx_params(vctx, params); } static int ml_kem_encapsulate_init(void *vctx, void *vkey, const OSSL_PARAM params[]) { ML_KEM_KEY *key = vkey; if (!ossl_ml_kem_have_pubkey(key)) { ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY); return 0; } return ml_kem_init(vctx, EVP_PKEY_OP_ENCAPSULATE, key, params); } static int ml_kem_decapsulate_init(void *vctx, void *vkey, const OSSL_PARAM params[]) { ML_KEM_KEY *key = vkey; if (!ossl_ml_kem_have_prvkey(key)) { ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY); return 0; } return ml_kem_init(vctx, EVP_PKEY_OP_DECAPSULATE, key, params); } static int ml_kem_set_ctx_params(void *vctx, const OSSL_PARAM params[]) { PROV_ML_KEM_CTX *ctx = vctx; const OSSL_PARAM *p; if (ctx == NULL) return 0; if (ctx->op == EVP_PKEY_OP_DECAPSULATE && ctx->entropy != NULL) { /* Decapsulation is deterministic */ OPENSSL_cleanse(ctx->entropy, ML_KEM_RANDOM_BYTES); ctx->entropy = NULL; } if (ossl_param_is_empty(params)) return 1; /* Encapsulation ephemeral input key material "ikmE" */ if (ctx->op == EVP_PKEY_OP_ENCAPSULATE && (p = OSSL_PARAM_locate_const(params, OSSL_KEM_PARAM_IKME)) != NULL) { size_t len = ML_KEM_RANDOM_BYTES; ctx->entropy = ctx->entropy_buf; if (OSSL_PARAM_get_octet_string(p, (void **)&ctx->entropy, len, &len) && len == ML_KEM_RANDOM_BYTES) return 1; /* Possibly, but much less likely wrong type */ ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_SEED_LENGTH); ctx->entropy = NULL; return 0; } return 1; } static const OSSL_PARAM *ml_kem_settable_ctx_params(ossl_unused void *vctx, ossl_unused void *provctx) { static const OSSL_PARAM params[] = { OSSL_PARAM_octet_string(OSSL_KEM_PARAM_IKME, NULL, 0), OSSL_PARAM_END }; return params; } static int ml_kem_encapsulate(void *vctx, unsigned char *ctext, size_t *clen, unsigned char *shsec, size_t *slen) { PROV_ML_KEM_CTX *ctx = vctx; ML_KEM_KEY *key = ctx->key; const ML_KEM_VINFO *v; size_t encap_clen; size_t encap_slen; int ret = 0; if (!ossl_ml_kem_have_pubkey(key)) { ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY); goto end; } v = ossl_ml_kem_key_vinfo(key); encap_clen = v->ctext_bytes; encap_slen = ML_KEM_SHARED_SECRET_BYTES; if (ctext == NULL) { if (clen == NULL && slen == NULL) return 0; if (clen != NULL) *clen = encap_clen; if (slen != NULL) *slen = encap_slen; return 1; } if (shsec == NULL) { ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL, "NULL shared-secret buffer"); goto end; } if (clen == NULL) { ERR_raise_data(ERR_LIB_PROV, PROV_R_NULL_LENGTH_POINTER, "null ciphertext input/output length pointer"); goto end; } else if (*clen < encap_clen) { ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL, "ciphertext buffer too small"); goto end; } else { *clen = encap_clen; } if (slen == NULL) { ERR_raise_data(ERR_LIB_PROV, PROV_R_NULL_LENGTH_POINTER, "null shared secret input/output length pointer"); goto end; } else if (*slen < encap_slen) { ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL, "shared-secret buffer too small"); goto end; } else { *slen = encap_slen; } if (ctx->entropy != NULL) ret = ossl_ml_kem_encap_seed(ctext, encap_clen, shsec, encap_slen, ctx->entropy, ML_KEM_RANDOM_BYTES, key); else ret = ossl_ml_kem_encap_rand(ctext, encap_clen, shsec, encap_slen, key); end: /* * One shot entropy, each encapsulate call must either provide a new * "ikmE", or else will use a random value. If a caller sets an explicit * ikmE once for testing, and later performs multiple encapsulations * without again calling encapsulate_init(), these should not share the * original entropy. */ if (ctx->entropy != NULL) { OPENSSL_cleanse(ctx->entropy, ML_KEM_RANDOM_BYTES); ctx->entropy = NULL; } return ret; } static int ml_kem_decapsulate(void *vctx, uint8_t *shsec, size_t *slen, const uint8_t *ctext, size_t clen) { PROV_ML_KEM_CTX *ctx = vctx; ML_KEM_KEY *key = ctx->key; size_t decap_slen = ML_KEM_SHARED_SECRET_BYTES; if (!ossl_ml_kem_have_prvkey(key)) { ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY); return 0; } if (shsec == NULL) { if (slen == NULL) return 0; *slen = ML_KEM_SHARED_SECRET_BYTES; return 1; } /* For now tolerate newly-deprecated NULL length pointers. */ if (slen == NULL) { slen = &decap_slen; } else if (*slen < decap_slen) { ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL, "shared-secret buffer too small"); return 0; } else { *slen = decap_slen; } /* ML-KEM decap handles incorrect ciphertext lengths internally */ return ossl_ml_kem_decap(shsec, decap_slen, ctext, clen, key); } const OSSL_DISPATCH ossl_ml_kem_asym_kem_functions[] = { { OSSL_FUNC_KEM_NEWCTX, (OSSL_FUNC) ml_kem_newctx }, { OSSL_FUNC_KEM_ENCAPSULATE_INIT, (OSSL_FUNC) ml_kem_encapsulate_init }, { OSSL_FUNC_KEM_ENCAPSULATE, (OSSL_FUNC) ml_kem_encapsulate }, { OSSL_FUNC_KEM_DECAPSULATE_INIT, (OSSL_FUNC) ml_kem_decapsulate_init }, { OSSL_FUNC_KEM_DECAPSULATE, (OSSL_FUNC) ml_kem_decapsulate }, { OSSL_FUNC_KEM_FREECTX, (OSSL_FUNC) ml_kem_freectx }, { OSSL_FUNC_KEM_SET_CTX_PARAMS, (OSSL_FUNC) ml_kem_set_ctx_params }, { OSSL_FUNC_KEM_SETTABLE_CTX_PARAMS, (OSSL_FUNC) ml_kem_settable_ctx_params }, OSSL_DISPATCH_END };