Coverage Report

Created: 2025-06-13 06:57

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