Coverage Report

Created: 2026-10-28 06:28

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