Coverage Report

Created: 2026-09-12 06:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openssl36/crypto/cms/cms_kemri.c
Line
Count
Source
1
/*
2
 * Copyright 2025-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/cms.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/kdf.h>
16
#include <openssl/x509.h>
17
#include "cms_local.h"
18
#include "crypto/evp.h"
19
#include "internal/sizes.h"
20
21
/* KEM Recipient Info (KEMRI) routines */
22
23
int ossl_cms_RecipientInfo_kemri_get0_alg(CMS_RecipientInfo *ri,
24
    uint32_t **pkekLength,
25
    X509_ALGOR **pwrap)
26
0
{
27
0
    if (ri->type != CMS_RECIPINFO_KEM) {
28
0
        ERR_raise(ERR_LIB_CMS, CMS_R_NOT_KEM);
29
0
        return 0;
30
0
    }
31
0
    if (pkekLength)
32
0
        *pkekLength = &ri->d.ori->d.kemri->kekLength;
33
0
    if (pwrap)
34
0
        *pwrap = ri->d.ori->d.kemri->wrap;
35
0
    return 1;
36
0
}
37
38
int CMS_RecipientInfo_kemri_cert_cmp(CMS_RecipientInfo *ri, X509 *cert)
39
0
{
40
0
    if (ri->type != CMS_RECIPINFO_KEM) {
41
0
        ERR_raise(ERR_LIB_CMS, CMS_R_NOT_KEM);
42
0
        return -2;
43
0
    }
44
0
    return ossl_cms_SignerIdentifier_cert_cmp(ri->d.ori->d.kemri->rid, cert);
45
0
}
46
47
int CMS_RecipientInfo_kemri_set0_pkey(CMS_RecipientInfo *ri, EVP_PKEY *pk)
48
0
{
49
0
    EVP_PKEY_CTX *pctx = NULL;
50
0
    CMS_KEMRecipientInfo *kemri;
51
52
0
    if (ri->type != CMS_RECIPINFO_KEM) {
53
0
        ERR_raise(ERR_LIB_CMS, CMS_R_NOT_KEM);
54
0
        return 0;
55
0
    }
56
57
0
    kemri = ri->d.ori->d.kemri;
58
59
0
    EVP_PKEY_CTX_free(kemri->pctx);
60
0
    kemri->pctx = NULL;
61
62
0
    if (pk != NULL) {
63
0
        pctx = EVP_PKEY_CTX_new_from_pkey(ossl_cms_ctx_get0_libctx(kemri->cms_ctx), pk,
64
0
            ossl_cms_ctx_get0_propq(kemri->cms_ctx));
65
0
        if (pctx == NULL || EVP_PKEY_decapsulate_init(pctx, NULL) <= 0)
66
0
            goto err;
67
68
0
        kemri->pctx = pctx;
69
0
    }
70
71
0
    return 1;
72
0
err:
73
0
    EVP_PKEY_CTX_free(pctx);
74
0
    return 0;
75
0
}
76
77
/* Initialise a kemri based on passed certificate and key */
78
79
int ossl_cms_RecipientInfo_kemri_init(CMS_RecipientInfo *ri, X509 *recip,
80
    EVP_PKEY *recipPubKey, unsigned int flags,
81
    const CMS_CTX *ctx)
82
0
{
83
0
    CMS_OtherRecipientInfo *ori;
84
0
    CMS_KEMRecipientInfo *kemri;
85
0
    int idtype;
86
0
    X509_PUBKEY *x_pubkey;
87
0
    X509_ALGOR *x_alg;
88
89
0
    ri->d.ori = M_ASN1_new_of(CMS_OtherRecipientInfo);
90
0
    if (ri->d.ori == NULL)
91
0
        return 0;
92
0
    ri->encoded_type = CMS_RECIPINFO_OTHER;
93
0
    ri->type = CMS_RECIPINFO_KEM;
94
95
0
    ori = ri->d.ori;
96
0
    ori->oriType = OBJ_nid2obj(NID_id_smime_ori_kem);
97
0
    if (ori->oriType == NULL)
98
0
        return 0;
99
0
    ori->d.kemri = M_ASN1_new_of(CMS_KEMRecipientInfo);
100
0
    if (ori->d.kemri == NULL)
101
0
        return 0;
102
103
0
    kemri = ori->d.kemri;
104
0
    kemri->version = 0;
105
0
    kemri->cms_ctx = ctx;
106
107
    /*
108
     * Not a typo: RecipientIdentifier and SignerIdentifier are the same
109
     * structure.
110
     */
111
112
0
    idtype = (flags & CMS_USE_KEYID) ? CMS_RECIPINFO_KEYIDENTIFIER : CMS_RECIPINFO_ISSUER_SERIAL;
113
0
    if (!ossl_cms_set1_SignerIdentifier(kemri->rid, recip, idtype, ctx))
114
0
        return 0;
115
116
0
    x_pubkey = X509_get_X509_PUBKEY(recip);
117
0
    if (x_pubkey == NULL)
118
0
        return 0;
119
0
    if (!X509_PUBKEY_get0_param(NULL, NULL, NULL, &x_alg, x_pubkey))
120
0
        return 0;
121
0
    if (!X509_ALGOR_copy(kemri->kem, x_alg))
122
0
        return 0;
123
124
0
    kemri->pctx = EVP_PKEY_CTX_new_from_pkey(ossl_cms_ctx_get0_libctx(ctx),
125
0
        recipPubKey,
126
0
        ossl_cms_ctx_get0_propq(ctx));
127
0
    if (kemri->pctx == NULL)
128
0
        return 0;
129
0
    if (EVP_PKEY_encapsulate_init(kemri->pctx, NULL) <= 0)
130
0
        return 0;
131
132
0
    return 1;
133
0
}
134
135
EVP_CIPHER_CTX *CMS_RecipientInfo_kemri_get0_ctx(CMS_RecipientInfo *ri)
136
0
{
137
0
    if (ri->type == CMS_RECIPINFO_KEM)
138
0
        return ri->d.ori->d.kemri->ctx;
139
0
    return NULL;
140
0
}
141
142
X509_ALGOR *CMS_RecipientInfo_kemri_get0_kdf_alg(CMS_RecipientInfo *ri)
143
0
{
144
0
    if (ri->type == CMS_RECIPINFO_KEM)
145
0
        return ri->d.ori->d.kemri->kdf;
146
0
    return NULL;
147
0
}
148
149
int CMS_RecipientInfo_kemri_set_ukm(CMS_RecipientInfo *ri,
150
    const unsigned char *ukm,
151
    int ukmLength)
152
0
{
153
0
    CMS_KEMRecipientInfo *kemri;
154
0
    ASN1_OCTET_STRING *ukm_str;
155
156
0
    if (ri->type != CMS_RECIPINFO_KEM) {
157
0
        ERR_raise(ERR_LIB_CMS, CMS_R_NOT_KEM);
158
0
        return 0;
159
0
    }
160
161
0
    if (ukm == NULL && ukmLength != 0) {
162
0
        ERR_raise(ERR_LIB_CMS, ERR_R_PASSED_INVALID_ARGUMENT);
163
0
        return 0;
164
0
    }
165
166
0
    kemri = ri->d.ori->d.kemri;
167
168
0
    ukm_str = ASN1_OCTET_STRING_new();
169
0
    if (ukm_str == NULL)
170
0
        return 0;
171
0
    if (!ASN1_OCTET_STRING_set(ukm_str, ukm, ukmLength)) {
172
0
        ASN1_OCTET_STRING_free(ukm_str);
173
0
        return 0;
174
0
    }
175
0
    ASN1_OCTET_STRING_free(kemri->ukm);
176
0
    kemri->ukm = ukm_str;
177
0
    return 1;
178
0
}
179
180
static EVP_KDF_CTX *create_kdf_ctx(CMS_KEMRecipientInfo *kemri)
181
0
{
182
0
    const ASN1_OBJECT *kdf_oid;
183
0
    int ptype;
184
0
    char kdf_alg[OSSL_MAX_NAME_SIZE];
185
0
    EVP_KDF *kdf = NULL;
186
0
    EVP_KDF_CTX *kctx = NULL;
187
188
    /*
189
     * KDFs with algorithm identifier parameters are not supported yet. To
190
     * support this, EVP_KDF_CTX_set_algor_params from
191
     * `doc/designs/passing-algorithmidentifier-parameters.md` needs to be
192
     * implemented.
193
     */
194
0
    X509_ALGOR_get0(&kdf_oid, &ptype, NULL, kemri->kdf);
195
0
    if (ptype != V_ASN1_UNDEF && ptype != V_ASN1_NULL) {
196
0
        ERR_raise(ERR_LIB_CMS, CMS_R_UNSUPPORTED_KDF_ALGORITHM);
197
0
        goto err;
198
0
    }
199
0
    if (OBJ_obj2txt(kdf_alg, sizeof(kdf_alg), kdf_oid, 1) < 0)
200
0
        goto err;
201
202
0
    kdf = EVP_KDF_fetch(ossl_cms_ctx_get0_libctx(kemri->cms_ctx), kdf_alg,
203
0
        ossl_cms_ctx_get0_propq(kemri->cms_ctx));
204
0
    if (kdf == NULL)
205
0
        goto err;
206
207
0
    kctx = EVP_KDF_CTX_new(kdf);
208
0
err:
209
0
    EVP_KDF_free(kdf);
210
0
    return kctx;
211
0
}
212
213
static int kdf_derive(unsigned char *kek, size_t keklen,
214
    const unsigned char *ss, size_t sslen,
215
    CMS_KEMRecipientInfo *kemri)
216
0
{
217
0
    EVP_KDF_CTX *kctx = NULL;
218
0
    OSSL_PARAM params[3];
219
0
    unsigned char *infoder = NULL;
220
0
    int infolen = 0;
221
0
    int rv = 0;
222
223
0
    infolen = CMS_CMSORIforKEMOtherInfo_encode(&infoder, kemri->wrap, kemri->ukm,
224
0
        kemri->kekLength);
225
0
    if (infolen <= 0)
226
0
        goto err;
227
228
0
    kctx = create_kdf_ctx(kemri);
229
0
    if (kctx == NULL)
230
0
        goto err;
231
232
0
    params[0] = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY,
233
0
        (unsigned char *)ss, sslen);
234
0
    params[1] = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO,
235
0
        (char *)infoder, infolen);
236
0
    params[2] = OSSL_PARAM_construct_end();
237
238
0
    if (EVP_KDF_derive(kctx, kek, keklen, params) <= 0)
239
0
        goto err;
240
241
0
    rv = 1;
242
0
err:
243
0
    OPENSSL_free(infoder);
244
0
    EVP_KDF_CTX_free(kctx);
245
246
0
    return rv;
247
0
}
248
249
/*
250
 * Derive KEK and decrypt/encrypt with it to produce either the original CEK
251
 * or the encrypted CEK.
252
 */
253
254
static int cms_kek_cipher(unsigned char **pout, size_t *poutlen,
255
    const unsigned char *ss, size_t sslen,
256
    const unsigned char *in, size_t inlen,
257
    CMS_KEMRecipientInfo *kemri, int enc)
258
0
{
259
    /* Key encryption key */
260
0
    unsigned char kek[EVP_MAX_KEY_LENGTH];
261
0
    size_t keklen = kemri->kekLength;
262
0
    unsigned char *out = NULL;
263
0
    int outlen = 0;
264
0
    int rv = 0;
265
0
    size_t outsize;
266
267
0
    if (keklen > sizeof(kek)) {
268
0
        ERR_raise(ERR_LIB_CMS, CMS_R_INVALID_KEY_LENGTH);
269
0
        return 0;
270
0
    }
271
272
0
    if (inlen > INT_MAX)
273
0
        return 0;
274
275
0
    if (!kdf_derive(kek, keklen, ss, sslen, kemri))
276
0
        goto err;
277
278
    /* Set KEK in context */
279
0
    if (!EVP_CipherInit_ex(kemri->ctx, NULL, NULL, kek, NULL, enc))
280
0
        goto err;
281
    /* obtain output length of ciphered key */
282
0
    if (!EVP_CipherUpdate(kemri->ctx, NULL, &outlen, in, (int)inlen))
283
0
        goto err;
284
    /*
285
     * On its integrity-failure paths that primitive writes and cleanses up to
286
     * inlen bytes of the output buffer. Size the buffer for that worst case so
287
     * a failed unwrap cannot write past the allocation.
288
     */
289
0
    outsize = (size_t)outlen < inlen ? inlen : (size_t)outlen;
290
0
    out = OPENSSL_malloc(outsize);
291
0
    if (out == NULL)
292
0
        goto err;
293
0
    if (!EVP_CipherUpdate(kemri->ctx, out, &outlen, in, (int)inlen))
294
0
        goto err;
295
0
    *pout = out;
296
0
    out = NULL;
297
0
    *poutlen = (size_t)outlen;
298
299
0
    rv = 1;
300
0
err:
301
0
    OPENSSL_free(out);
302
0
    OPENSSL_cleanse(kek, sizeof(kek));
303
0
    EVP_CIPHER_CTX_reset(kemri->ctx);
304
0
    EVP_PKEY_CTX_free(kemri->pctx);
305
0
    kemri->pctx = NULL;
306
0
    return rv;
307
0
}
308
309
/* Encrypt content key in KEM recipient info */
310
311
int ossl_cms_RecipientInfo_kemri_encrypt(const CMS_ContentInfo *cms,
312
    CMS_RecipientInfo *ri)
313
0
{
314
0
    CMS_KEMRecipientInfo *kemri;
315
0
    CMS_EncryptedContentInfo *ec;
316
0
    unsigned char *kem_ct = NULL;
317
0
    size_t kem_ct_len;
318
0
    unsigned char *kem_secret = NULL;
319
0
    size_t kem_secret_len = 0;
320
0
    unsigned char *enckey;
321
0
    size_t enckeylen;
322
0
    int rv = 0;
323
324
0
    if (ri->type != CMS_RECIPINFO_KEM) {
325
0
        ERR_raise(ERR_LIB_CMS, CMS_R_NOT_KEM);
326
0
        return 0;
327
0
    }
328
329
0
    kemri = ri->d.ori->d.kemri;
330
331
0
    ec = ossl_cms_get0_env_enc_content(cms);
332
    /* Initialise wrap algorithm parameters */
333
0
    if (!ossl_cms_RecipientInfo_wrap_init(ri, ec->cipher))
334
0
        return 0;
335
336
    /* Initialise KDF algorithm */
337
0
    if (!ossl_cms_env_asn1_ctrl(ri, 0))
338
0
        return 0;
339
340
0
    if (EVP_PKEY_encapsulate(kemri->pctx, NULL, &kem_ct_len, NULL, &kem_secret_len) <= 0)
341
0
        return 0;
342
0
    kem_ct = OPENSSL_malloc(kem_ct_len);
343
0
    kem_secret = OPENSSL_malloc(kem_secret_len);
344
0
    if (kem_ct == NULL || kem_secret == NULL)
345
0
        goto err;
346
347
0
    if (EVP_PKEY_encapsulate(kemri->pctx, kem_ct, &kem_ct_len, kem_secret, &kem_secret_len) <= 0)
348
0
        goto err;
349
350
0
    ASN1_STRING_set0(kemri->kemct, kem_ct, (int)kem_ct_len);
351
0
    kem_ct = NULL;
352
353
0
    if (!cms_kek_cipher(&enckey, &enckeylen, kem_secret, kem_secret_len, ec->key, ec->keylen,
354
0
            kemri, 1))
355
0
        goto err;
356
0
    ASN1_STRING_set0(kemri->encryptedKey, enckey, (int)enckeylen);
357
358
0
    rv = 1;
359
0
err:
360
0
    OPENSSL_free(kem_ct);
361
0
    OPENSSL_clear_free(kem_secret, kem_secret_len);
362
0
    return rv;
363
0
}
364
365
int ossl_cms_RecipientInfo_kemri_decrypt(const CMS_ContentInfo *cms,
366
    CMS_RecipientInfo *ri)
367
0
{
368
0
    CMS_KEMRecipientInfo *kemri;
369
0
    CMS_EncryptedContentInfo *ec;
370
0
    const unsigned char *kem_ct = NULL;
371
0
    size_t kem_ct_len;
372
0
    unsigned char *kem_secret = NULL;
373
0
    size_t kem_secret_len = 0;
374
0
    unsigned char *enckey = NULL;
375
0
    size_t enckeylen;
376
0
    unsigned char *cek = NULL;
377
0
    size_t ceklen;
378
0
    int ret = 0;
379
380
0
    if (ri->type != CMS_RECIPINFO_KEM) {
381
0
        ERR_raise(ERR_LIB_CMS, CMS_R_NOT_KEM);
382
0
        return 0;
383
0
    }
384
385
0
    kemri = ri->d.ori->d.kemri;
386
387
0
    ec = ossl_cms_get0_env_enc_content(cms);
388
389
0
    if (kemri->pctx == NULL) {
390
0
        ERR_raise(ERR_LIB_CMS, CMS_R_NO_PRIVATE_KEY);
391
0
        return 0;
392
0
    }
393
394
    /* Setup all parameters to derive KEK */
395
0
    if (!ossl_cms_env_asn1_ctrl(ri, 1))
396
0
        goto err;
397
398
0
    kem_ct = ASN1_STRING_get0_data(kemri->kemct);
399
0
    kem_ct_len = ASN1_STRING_length(kemri->kemct);
400
401
0
    if (EVP_PKEY_decapsulate(kemri->pctx, NULL, &kem_secret_len, kem_ct, kem_ct_len) <= 0)
402
0
        return 0;
403
0
    kem_secret = OPENSSL_malloc(kem_secret_len);
404
0
    if (kem_secret == NULL)
405
0
        goto err;
406
407
0
    if (EVP_PKEY_decapsulate(kemri->pctx, kem_secret, &kem_secret_len, kem_ct, kem_ct_len) <= 0)
408
0
        goto err;
409
410
    /* Attempt to decrypt CEK */
411
0
    enckeylen = kemri->encryptedKey->length;
412
0
    enckey = kemri->encryptedKey->data;
413
0
    if (!cms_kek_cipher(&cek, &ceklen, kem_secret, kem_secret_len, enckey, enckeylen, kemri, 0))
414
0
        goto err;
415
0
    ec = ossl_cms_get0_env_enc_content(cms);
416
0
    OPENSSL_clear_free(ec->key, ec->keylen);
417
0
    ec->key = cek;
418
0
    ec->keylen = ceklen;
419
420
0
    ret = 1;
421
0
err:
422
0
    OPENSSL_clear_free(kem_secret, kem_secret_len);
423
0
    return ret;
424
0
}