Coverage Report

Created: 2026-10-28 06:28

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