Coverage Report

Created: 2026-10-16 07:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openssl/providers/implementations/kem/mlx_kem.c
Line
Count
Source
1
/*
2
 * Copyright 2024-2026 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 <openssl/core_dispatch.h>
11
#include <openssl/core_names.h>
12
#include <openssl/crypto.h>
13
#include <openssl/err.h>
14
#include <openssl/evp.h>
15
#include <openssl/obj_mac.h>
16
#include <openssl/params.h>
17
#include <openssl/proverr.h>
18
#include <openssl/rand.h>
19
#include "prov/implementations.h"
20
#include "prov/names.h"
21
#include "prov/mlx_kem.h"
22
#include "prov/provider_ctx.h"
23
#include "prov/providercommon.h"
24
#include "providers/implementations/kem/mlx_kem.inc"
25
26
static OSSL_FUNC_kem_newctx_fn mlx_kem_newctx;
27
static OSSL_FUNC_kem_freectx_fn mlx_kem_freectx;
28
static OSSL_FUNC_kem_encapsulate_init_fn mlx_kem_encapsulate_init;
29
static OSSL_FUNC_kem_encapsulate_fn mlx_kem_encapsulate;
30
static OSSL_FUNC_kem_decapsulate_init_fn mlx_kem_decapsulate_init;
31
static OSSL_FUNC_kem_decapsulate_fn mlx_kem_decapsulate;
32
static OSSL_FUNC_kem_set_ctx_params_fn mlx_kem_set_ctx_params;
33
static OSSL_FUNC_kem_settable_ctx_params_fn mlx_kem_settable_ctx_params;
34
#ifdef FIPS_MODULE
35
static OSSL_FUNC_kem_get_ctx_params_fn mlx_kem_get_ctx_params;
36
static OSSL_FUNC_kem_gettable_ctx_params_fn mlx_kem_gettable_ctx_params;
37
#endif
38
39
typedef struct {
40
    OSSL_LIB_CTX *libctx;
41
    MLX_KEY *key;
42
    int op;
43
} PROV_MLX_KEM_CTX;
44
45
static void *mlx_kem_newctx(void *provctx)
46
0
{
47
0
    PROV_MLX_KEM_CTX *ctx;
48
49
0
    if ((ctx = OPENSSL_malloc(sizeof(*ctx))) == NULL)
50
0
        return NULL;
51
52
0
    ctx->libctx = PROV_LIBCTX_OF(provctx);
53
0
    ctx->key = NULL;
54
0
    ctx->op = 0;
55
0
    return ctx;
56
0
}
57
58
static void mlx_kem_freectx(void *vctx)
59
0
{
60
0
    OPENSSL_free(vctx);
61
0
}
62
63
static int mlx_kem_init(void *vctx, int op, void *key,
64
    ossl_unused const OSSL_PARAM params[])
65
0
{
66
0
    PROV_MLX_KEM_CTX *ctx = vctx;
67
68
0
    if (!ossl_prov_is_running())
69
0
        return 0;
70
0
    ctx->key = key;
71
0
    ctx->op = op;
72
0
    return 1;
73
0
}
74
75
static int
76
mlx_kem_encapsulate_init(void *vctx, void *vkey, const OSSL_PARAM params[])
77
0
{
78
0
    MLX_KEY *key = vkey;
79
80
0
    if (!mlx_kem_have_pubkey(key)) {
81
0
        ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
82
0
        return 0;
83
0
    }
84
0
    return mlx_kem_init(vctx, EVP_PKEY_OP_ENCAPSULATE, key, params);
85
0
}
86
87
static int
88
mlx_kem_decapsulate_init(void *vctx, void *vkey, const OSSL_PARAM params[])
89
0
{
90
0
    MLX_KEY *key = vkey;
91
92
0
    if (!mlx_kem_have_prvkey(key)) {
93
0
        ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
94
0
        return 0;
95
0
    }
96
0
    return mlx_kem_init(vctx, EVP_PKEY_OP_DECAPSULATE, key, params);
97
0
}
98
99
static const OSSL_PARAM *mlx_kem_settable_ctx_params(ossl_unused void *vctx,
100
    ossl_unused void *provctx)
101
0
{
102
0
    static const OSSL_PARAM params[] = { OSSL_PARAM_END };
103
104
0
    return params;
105
0
}
106
107
static int
108
mlx_kem_set_ctx_params(void *vctx, const OSSL_PARAM params[])
109
0
{
110
0
    return 1;
111
0
}
112
113
#ifdef FIPS_MODULE
114
static const OSSL_PARAM *mlx_kem_gettable_ctx_params(ossl_unused void *vctx,
115
    ossl_unused void *provctx)
116
{
117
    return mlx_kem_get_ctx_params_list;
118
}
119
120
static int mlx_kem_get_ctx_params(void *vctx, OSSL_PARAM params[])
121
{
122
    PROV_MLX_KEM_CTX *ctx = vctx;
123
    struct mlx_kem_get_ctx_params_st p;
124
    int approved;
125
126
    if (ctx == NULL || !mlx_kem_get_ctx_params_decoder(params, &p))
127
        return 0;
128
129
    if (p.ind != NULL) {
130
        if (ctx->key == NULL || ctx->key->xinfo == NULL)
131
            return 0;
132
        approved = strcmp(ctx->key->xinfo->algorithm_name, SN_X448) != 0;
133
        if (!OSSL_PARAM_set_int(p.ind, approved))
134
            return 0;
135
    }
136
    return 1;
137
}
138
#endif
139
140
static int mlx_kem_encapsulate(void *vctx, unsigned char *ctext, size_t *clen,
141
    unsigned char *shsec, size_t *slen)
142
0
{
143
0
    MLX_KEY *key = ((PROV_MLX_KEM_CTX *)vctx)->key;
144
0
    EVP_PKEY_CTX *ctx = NULL;
145
0
    EVP_PKEY *xkey = NULL;
146
0
    size_t encap_clen;
147
0
    size_t encap_slen;
148
0
    uint8_t *cbuf;
149
0
    uint8_t *sbuf;
150
0
    int ml_kem_slot = key->xinfo->ml_kem_slot;
151
0
    int ret = 0;
152
153
0
    if (!mlx_kem_have_pubkey(key)) {
154
0
        ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
155
0
        return 0;
156
0
    }
157
0
    encap_clen = key->minfo->ctext_bytes + key->xinfo->pubkey_bytes;
158
0
    encap_slen = ML_KEM_SHARED_SECRET_BYTES + key->xinfo->shsec_bytes;
159
160
0
    if (ctext == NULL) {
161
0
        if (clen == NULL && slen == NULL)
162
0
            return 0;
163
0
        if (clen != NULL)
164
0
            *clen = encap_clen;
165
0
        if (slen != NULL)
166
0
            *slen = encap_slen;
167
0
        return 1;
168
0
    }
169
0
    if (shsec == NULL) {
170
0
        ERR_raise_data(ERR_LIB_PROV, PROV_R_NULL_OUTPUT_BUFFER,
171
0
            "null shared-secret output buffer");
172
0
        return 0;
173
0
    }
174
175
0
    if (clen == NULL) {
176
0
        ERR_raise_data(ERR_LIB_PROV, PROV_R_NULL_LENGTH_POINTER,
177
0
            "null ciphertext input/output length pointer");
178
0
        return 0;
179
0
    } else if (*clen < encap_clen) {
180
0
        ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL,
181
0
            "ciphertext buffer too small");
182
0
        return 0;
183
0
    } else {
184
0
        *clen = encap_clen;
185
0
    }
186
187
0
    if (slen == NULL) {
188
0
        ERR_raise_data(ERR_LIB_PROV, PROV_R_NULL_LENGTH_POINTER,
189
0
            "null shared secret input/output length pointer");
190
0
        return 0;
191
0
    } else if (*slen < encap_slen) {
192
0
        ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL,
193
0
            "shared-secret buffer too small");
194
0
        return 0;
195
0
    } else {
196
0
        *slen = encap_slen;
197
0
    }
198
199
    /* ML-KEM encapsulation */
200
0
    encap_clen = key->minfo->ctext_bytes;
201
0
    encap_slen = ML_KEM_SHARED_SECRET_BYTES;
202
0
    cbuf = ctext + ml_kem_slot * key->xinfo->pubkey_bytes;
203
0
    sbuf = shsec + ml_kem_slot * key->xinfo->shsec_bytes;
204
0
    ctx = EVP_PKEY_CTX_new_from_pkey(key->libctx, key->mkey, key->propq);
205
0
    if (ctx == NULL
206
0
        || EVP_PKEY_encapsulate_init(ctx, NULL) <= 0
207
0
        || EVP_PKEY_encapsulate(ctx, cbuf, &encap_clen, sbuf, &encap_slen) <= 0)
208
0
        goto end;
209
0
    if (encap_clen != key->minfo->ctext_bytes) {
210
0
        ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
211
0
            "unexpected %s ciphertext output size: %lu",
212
0
            key->minfo->algorithm_name, (unsigned long)encap_clen);
213
0
        goto end;
214
0
    }
215
0
    if (encap_slen != ML_KEM_SHARED_SECRET_BYTES) {
216
0
        ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
217
0
            "unexpected %s shared secret output size: %lu",
218
0
            key->minfo->algorithm_name, (unsigned long)encap_slen);
219
0
        goto end;
220
0
    }
221
0
    EVP_PKEY_CTX_free(ctx);
222
223
    /*-
224
     * ECDHE encapsulation
225
     *
226
     * Generate own ephemeral private key and add its public key to ctext.
227
     *
228
     * Note, we could support a settable parameter that sets an extant ECDH
229
     * keypair as the keys to use in encap, making it possible to reuse the
230
     * same (TLS client) ECDHE keypair for both the classical EC keyshare and a
231
     * corresponding ECDHE + ML-KEM keypair.  But the TLS layer would then need
232
     * know that this is a hybrid, and that it can partly reuse the same keys
233
     * as another group for which a keyshare will be sent.  Deferred until we
234
     * support generating multiple keyshares, there's a workable keyshare
235
     * prediction specification, and the optimisation is justified.
236
     */
237
0
    cbuf = ctext + (1 - ml_kem_slot) * key->minfo->ctext_bytes;
238
0
    encap_clen = key->xinfo->pubkey_bytes;
239
0
    ctx = EVP_PKEY_CTX_new_from_pkey(key->libctx, key->xkey, key->propq);
240
0
    if (ctx == NULL
241
0
        || EVP_PKEY_keygen_init(ctx) <= 0
242
0
        || EVP_PKEY_keygen(ctx, &xkey) <= 0
243
0
        || EVP_PKEY_get_octet_string_param(xkey, OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY,
244
0
               cbuf, encap_clen, &encap_clen)
245
0
            <= 0)
246
0
        goto end;
247
0
    if (encap_clen != key->xinfo->pubkey_bytes) {
248
0
        ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
249
0
            "unexpected %s public key output size: %lu",
250
0
            key->xinfo->algorithm_name, (unsigned long)encap_clen);
251
0
        goto end;
252
0
    }
253
0
    EVP_PKEY_CTX_free(ctx);
254
255
    /* Derive the ECDH shared secret */
256
0
    encap_slen = key->xinfo->shsec_bytes;
257
0
    sbuf = shsec + (1 - ml_kem_slot) * ML_KEM_SHARED_SECRET_BYTES;
258
0
    ctx = EVP_PKEY_CTX_new_from_pkey(key->libctx, xkey, key->propq);
259
0
    if (ctx == NULL
260
0
        || EVP_PKEY_derive_init(ctx) <= 0
261
0
        || EVP_PKEY_derive_set_peer(ctx, key->xkey) <= 0
262
0
        || EVP_PKEY_derive(ctx, sbuf, &encap_slen) <= 0)
263
0
        goto end;
264
0
    if (encap_slen != key->xinfo->shsec_bytes) {
265
0
        ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
266
0
            "unexpected %s shared secret output size: %lu",
267
0
            key->xinfo->algorithm_name, (unsigned long)encap_slen);
268
0
        goto end;
269
0
    }
270
271
0
    ret = 1;
272
0
end:
273
    /* Erase any partial shared secret on failure */
274
0
    if (ret == 0)
275
0
        OPENSSL_cleanse(shsec,
276
0
            ML_KEM_SHARED_SECRET_BYTES + key->xinfo->shsec_bytes);
277
0
    EVP_PKEY_free(xkey);
278
0
    EVP_PKEY_CTX_free(ctx);
279
0
    return ret;
280
0
}
281
282
static int mlx_kem_decapsulate(void *vctx, uint8_t *shsec, size_t *slen,
283
    const uint8_t *ctext, size_t clen)
284
0
{
285
0
    MLX_KEY *key = ((PROV_MLX_KEM_CTX *)vctx)->key;
286
0
    EVP_PKEY_CTX *ctx = NULL;
287
0
    EVP_PKEY *xkey = NULL;
288
0
    const uint8_t *cbuf;
289
0
    uint8_t *sbuf;
290
0
    size_t decap_slen = ML_KEM_SHARED_SECRET_BYTES + key->xinfo->shsec_bytes;
291
0
    size_t decap_clen = key->minfo->ctext_bytes + key->xinfo->pubkey_bytes;
292
0
    int ml_kem_slot = key->xinfo->ml_kem_slot;
293
0
    int ret = 0;
294
295
0
    if (!mlx_kem_have_prvkey(key)) {
296
0
        ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
297
0
        return 0;
298
0
    }
299
300
0
    if (shsec == NULL) {
301
0
        if (slen == NULL)
302
0
            return 0;
303
0
        *slen = decap_slen;
304
0
        return 1;
305
0
    }
306
307
    /* For now tolerate newly-deprecated NULL length pointers. */
308
0
    if (slen == NULL) {
309
0
        slen = &decap_slen;
310
0
    } else if (*slen < decap_slen) {
311
0
        ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL,
312
0
            "shared-secret buffer too small");
313
0
        return 0;
314
0
    } else {
315
0
        *slen = decap_slen;
316
0
    }
317
0
    if (clen != decap_clen) {
318
0
        ERR_raise_data(ERR_LIB_PROV, PROV_R_WRONG_CIPHERTEXT_SIZE,
319
0
            "wrong decapsulation input ciphertext size: %lu",
320
0
            (unsigned long)clen);
321
0
        return 0;
322
0
    }
323
324
    /* ML-KEM decapsulation */
325
0
    decap_clen = key->minfo->ctext_bytes;
326
0
    decap_slen = ML_KEM_SHARED_SECRET_BYTES;
327
0
    cbuf = ctext + ml_kem_slot * key->xinfo->pubkey_bytes;
328
0
    sbuf = shsec + ml_kem_slot * key->xinfo->shsec_bytes;
329
0
    ctx = EVP_PKEY_CTX_new_from_pkey(key->libctx, key->mkey, key->propq);
330
0
    if (ctx == NULL
331
0
        || EVP_PKEY_decapsulate_init(ctx, NULL) <= 0
332
0
        || EVP_PKEY_decapsulate(ctx, sbuf, &decap_slen, cbuf, decap_clen) <= 0)
333
0
        goto end;
334
0
    if (decap_slen != ML_KEM_SHARED_SECRET_BYTES) {
335
0
        ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
336
0
            "unexpected %s shared secret output size: %lu",
337
0
            key->minfo->algorithm_name, (unsigned long)decap_slen);
338
0
        goto end;
339
0
    }
340
0
    EVP_PKEY_CTX_free(ctx);
341
342
    /* ECDH decapsulation */
343
0
    decap_clen = key->xinfo->pubkey_bytes;
344
0
    decap_slen = key->xinfo->shsec_bytes;
345
0
    cbuf = ctext + (1 - ml_kem_slot) * key->minfo->ctext_bytes;
346
0
    sbuf = shsec + (1 - ml_kem_slot) * ML_KEM_SHARED_SECRET_BYTES;
347
0
    ctx = EVP_PKEY_CTX_new_from_pkey(key->libctx, key->xkey, key->propq);
348
0
    if (ctx == NULL
349
0
        || (xkey = EVP_PKEY_new()) == NULL
350
0
        || EVP_PKEY_copy_parameters(xkey, key->xkey) <= 0
351
0
        || EVP_PKEY_set1_encoded_public_key(xkey, cbuf, decap_clen) <= 0
352
0
        || EVP_PKEY_derive_init(ctx) <= 0
353
0
        || EVP_PKEY_derive_set_peer(ctx, xkey) <= 0
354
0
        || EVP_PKEY_derive(ctx, sbuf, &decap_slen) <= 0)
355
0
        goto end;
356
0
    if (decap_slen != key->xinfo->shsec_bytes) {
357
0
        ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
358
0
            "unexpected %s shared secret output size: %lu",
359
0
            key->xinfo->algorithm_name, (unsigned long)decap_slen);
360
0
        goto end;
361
0
    }
362
363
0
    ret = 1;
364
0
end:
365
    /* Erase any partial shared secret on failure */
366
0
    if (ret == 0)
367
0
        OPENSSL_cleanse(shsec,
368
0
            ML_KEM_SHARED_SECRET_BYTES + key->xinfo->shsec_bytes);
369
0
    EVP_PKEY_CTX_free(ctx);
370
0
    EVP_PKEY_free(xkey);
371
0
    return ret;
372
0
}
373
374
const OSSL_DISPATCH ossl_mlx_kem_asym_kem_functions[] = {
375
    { OSSL_FUNC_KEM_NEWCTX, (OSSL_FUNC)mlx_kem_newctx },
376
    { OSSL_FUNC_KEM_ENCAPSULATE_INIT, (OSSL_FUNC)mlx_kem_encapsulate_init },
377
    { OSSL_FUNC_KEM_ENCAPSULATE, (OSSL_FUNC)mlx_kem_encapsulate },
378
    { OSSL_FUNC_KEM_DECAPSULATE_INIT, (OSSL_FUNC)mlx_kem_decapsulate_init },
379
    { OSSL_FUNC_KEM_DECAPSULATE, (OSSL_FUNC)mlx_kem_decapsulate },
380
    { OSSL_FUNC_KEM_FREECTX, (OSSL_FUNC)mlx_kem_freectx },
381
    { OSSL_FUNC_KEM_SET_CTX_PARAMS, (OSSL_FUNC)mlx_kem_set_ctx_params },
382
    { OSSL_FUNC_KEM_SETTABLE_CTX_PARAMS, (OSSL_FUNC)mlx_kem_settable_ctx_params },
383
#ifdef FIPS_MODULE
384
    { OSSL_FUNC_KEM_GET_CTX_PARAMS, (OSSL_FUNC)mlx_kem_get_ctx_params },
385
    { OSSL_FUNC_KEM_GETTABLE_CTX_PARAMS,
386
        (OSSL_FUNC)mlx_kem_gettable_ctx_params },
387
#endif
388
    OSSL_DISPATCH_END
389
};