/* * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved. * * Licensed under the Apache License 2.0 (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy * in the file LICENSE in the source distribution or at * https://www.openssl.org/source/license.html */ #include #include #include #include #include #include #include #include "cms_local.h" #include "crypto/evp.h" #include "internal/sizes.h" /* KEM Recipient Info (KEMRI) routines */ int ossl_cms_RecipientInfo_kemri_get0_alg(CMS_RecipientInfo *ri, uint32_t **pkekLength, X509_ALGOR **pwrap) { if (ri->type != CMS_RECIPINFO_KEM) { ERR_raise(ERR_LIB_CMS, CMS_R_NOT_KEM); return 0; } if (pkekLength) *pkekLength = &ri->d.ori->d.kemri->kekLength; if (pwrap) *pwrap = ri->d.ori->d.kemri->wrap; return 1; } int CMS_RecipientInfo_kemri_cert_cmp(CMS_RecipientInfo *ri, X509 *cert) { if (ri->type != CMS_RECIPINFO_KEM) { ERR_raise(ERR_LIB_CMS, CMS_R_NOT_KEM); return -2; } return ossl_cms_SignerIdentifier_cert_cmp(ri->d.ori->d.kemri->rid, cert); } int CMS_RecipientInfo_kemri_set0_pkey(CMS_RecipientInfo *ri, EVP_PKEY *pk) { EVP_PKEY_CTX *pctx = NULL; CMS_KEMRecipientInfo *kemri; if (ri->type != CMS_RECIPINFO_KEM) { ERR_raise(ERR_LIB_CMS, CMS_R_NOT_KEM); return 0; } kemri = ri->d.ori->d.kemri; EVP_PKEY_CTX_free(kemri->pctx); kemri->pctx = NULL; if (pk != NULL) { pctx = EVP_PKEY_CTX_new_from_pkey(ossl_cms_ctx_get0_libctx(kemri->cms_ctx), pk, ossl_cms_ctx_get0_propq(kemri->cms_ctx)); if (pctx == NULL || EVP_PKEY_decapsulate_init(pctx, NULL) <= 0) goto err; kemri->pctx = pctx; } return 1; err: EVP_PKEY_CTX_free(pctx); return 0; } /* Initialise a kemri based on passed certificate and key */ int ossl_cms_RecipientInfo_kemri_init(CMS_RecipientInfo *ri, X509 *recip, EVP_PKEY *recipPubKey, unsigned int flags, const CMS_CTX *ctx) { CMS_OtherRecipientInfo *ori; CMS_KEMRecipientInfo *kemri; int idtype; X509_PUBKEY *x_pubkey; X509_ALGOR *x_alg; ri->d.ori = M_ASN1_new_of(CMS_OtherRecipientInfo); if (ri->d.ori == NULL) return 0; ri->encoded_type = CMS_RECIPINFO_OTHER; ri->type = CMS_RECIPINFO_KEM; ori = ri->d.ori; ori->oriType = OBJ_nid2obj(NID_id_smime_ori_kem); if (ori->oriType == NULL) return 0; ori->d.kemri = M_ASN1_new_of(CMS_KEMRecipientInfo); if (ori->d.kemri == NULL) return 0; kemri = ori->d.kemri; kemri->version = 0; kemri->cms_ctx = ctx; /* * Not a typo: RecipientIdentifier and SignerIdentifier are the same * structure. */ idtype = (flags & CMS_USE_KEYID) ? CMS_RECIPINFO_KEYIDENTIFIER : CMS_RECIPINFO_ISSUER_SERIAL; if (!ossl_cms_set1_SignerIdentifier(kemri->rid, recip, idtype, ctx)) return 0; x_pubkey = X509_get_X509_PUBKEY(recip); if (x_pubkey == NULL) return 0; if (!X509_PUBKEY_get0_param(NULL, NULL, NULL, &x_alg, x_pubkey)) return 0; if (!X509_ALGOR_copy(kemri->kem, x_alg)) return 0; kemri->pctx = EVP_PKEY_CTX_new_from_pkey(ossl_cms_ctx_get0_libctx(ctx), recipPubKey, ossl_cms_ctx_get0_propq(ctx)); if (kemri->pctx == NULL) return 0; if (EVP_PKEY_encapsulate_init(kemri->pctx, NULL) <= 0) return 0; return 1; } EVP_CIPHER_CTX *CMS_RecipientInfo_kemri_get0_ctx(CMS_RecipientInfo *ri) { if (ri->type == CMS_RECIPINFO_KEM) return ri->d.ori->d.kemri->ctx; return NULL; } X509_ALGOR *CMS_RecipientInfo_kemri_get0_kdf_alg(CMS_RecipientInfo *ri) { if (ri->type == CMS_RECIPINFO_KEM) return ri->d.ori->d.kemri->kdf; return NULL; } int CMS_RecipientInfo_kemri_set_ukm(CMS_RecipientInfo *ri, const unsigned char *ukm, int ukmLength) { CMS_KEMRecipientInfo *kemri; ASN1_OCTET_STRING *ukm_str; if (ri->type != CMS_RECIPINFO_KEM) { ERR_raise(ERR_LIB_CMS, CMS_R_NOT_KEM); return 0; } if (ukm == NULL && ukmLength != 0) { ERR_raise(ERR_LIB_CMS, ERR_R_PASSED_INVALID_ARGUMENT); return 0; } kemri = ri->d.ori->d.kemri; ukm_str = ASN1_OCTET_STRING_new(); if (ukm_str == NULL) return 0; if (!ASN1_OCTET_STRING_set(ukm_str, ukm, ukmLength)) { ASN1_OCTET_STRING_free(ukm_str); return 0; } ASN1_OCTET_STRING_free(kemri->ukm); kemri->ukm = ukm_str; return 1; } static EVP_KDF_CTX *create_kdf_ctx(CMS_KEMRecipientInfo *kemri) { const ASN1_OBJECT *kdf_oid; int ptype; char kdf_alg[OSSL_MAX_NAME_SIZE]; EVP_KDF *kdf = NULL; EVP_KDF_CTX *kctx = NULL; /* * KDFs with algorithm identifier parameters are not supported yet. To * support this, EVP_KDF_CTX_set_algor_params from * `doc/designs/passing-algorithmidentifier-parameters.md` needs to be * implemented. */ X509_ALGOR_get0(&kdf_oid, &ptype, NULL, kemri->kdf); if (ptype != V_ASN1_UNDEF && ptype != V_ASN1_NULL) { ERR_raise(ERR_LIB_CMS, CMS_R_UNSUPPORTED_KDF_ALGORITHM); goto err; } if (OBJ_obj2txt(kdf_alg, sizeof(kdf_alg), kdf_oid, 1) < 0) goto err; kdf = EVP_KDF_fetch(ossl_cms_ctx_get0_libctx(kemri->cms_ctx), kdf_alg, ossl_cms_ctx_get0_propq(kemri->cms_ctx)); if (kdf == NULL) goto err; kctx = EVP_KDF_CTX_new(kdf); err: EVP_KDF_free(kdf); return kctx; } static int kdf_derive(unsigned char *kek, size_t keklen, const unsigned char *ss, size_t sslen, CMS_KEMRecipientInfo *kemri) { EVP_KDF_CTX *kctx = NULL; OSSL_PARAM params[3]; unsigned char *infoder = NULL; int infolen = 0; int rv = 0; infolen = CMS_CMSORIforKEMOtherInfo_encode(&infoder, kemri->wrap, kemri->ukm, kemri->kekLength); if (infolen <= 0) goto err; kctx = create_kdf_ctx(kemri); if (kctx == NULL) goto err; params[0] = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, (unsigned char *)ss, sslen); params[1] = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO, (char *)infoder, infolen); params[2] = OSSL_PARAM_construct_end(); if (EVP_KDF_derive(kctx, kek, keklen, params) <= 0) goto err; rv = 1; err: OPENSSL_free(infoder); EVP_KDF_CTX_free(kctx); return rv; } /* * Derive KEK and decrypt/encrypt with it to produce either the original CEK * or the encrypted CEK. */ static int cms_kek_cipher(unsigned char **pout, size_t *poutlen, const unsigned char *ss, size_t sslen, const unsigned char *in, size_t inlen, CMS_KEMRecipientInfo *kemri, int enc) { /* Key encryption key */ unsigned char kek[EVP_MAX_KEY_LENGTH]; size_t keklen = kemri->kekLength; unsigned char *out = NULL; int outlen = 0; int rv = 0; if (keklen > sizeof(kek)) { ERR_raise(ERR_LIB_CMS, CMS_R_INVALID_KEY_LENGTH); return 0; } if (!kdf_derive(kek, keklen, ss, sslen, kemri)) goto err; /* Set KEK in context */ if (!EVP_CipherInit_ex(kemri->ctx, NULL, NULL, kek, NULL, enc)) goto err; /* obtain output length of ciphered key */ if (!EVP_CipherUpdate(kemri->ctx, NULL, &outlen, in, (int)inlen)) goto err; out = OPENSSL_malloc(outlen); if (out == NULL) goto err; if (!EVP_CipherUpdate(kemri->ctx, out, &outlen, in, (int)inlen)) goto err; *pout = out; out = NULL; *poutlen = (size_t)outlen; rv = 1; err: OPENSSL_free(out); OPENSSL_cleanse(kek, sizeof(kek)); EVP_CIPHER_CTX_reset(kemri->ctx); EVP_PKEY_CTX_free(kemri->pctx); kemri->pctx = NULL; return rv; } /* Encrypt content key in KEM recipient info */ int ossl_cms_RecipientInfo_kemri_encrypt(const CMS_ContentInfo *cms, CMS_RecipientInfo *ri) { CMS_KEMRecipientInfo *kemri; CMS_EncryptedContentInfo *ec; unsigned char *kem_ct = NULL; size_t kem_ct_len; unsigned char *kem_secret = NULL; size_t kem_secret_len = 0; unsigned char *enckey; size_t enckeylen; int rv = 0; if (ri->type != CMS_RECIPINFO_KEM) { ERR_raise(ERR_LIB_CMS, CMS_R_NOT_KEM); return 0; } kemri = ri->d.ori->d.kemri; ec = ossl_cms_get0_env_enc_content(cms); /* Initialise wrap algorithm parameters */ if (!ossl_cms_RecipientInfo_wrap_init(ri, ec->cipher)) return 0; /* Initialise KDF algorithm */ if (!ossl_cms_env_asn1_ctrl(ri, 0)) return 0; if (EVP_PKEY_encapsulate(kemri->pctx, NULL, &kem_ct_len, NULL, &kem_secret_len) <= 0) return 0; kem_ct = OPENSSL_malloc(kem_ct_len); kem_secret = OPENSSL_malloc(kem_secret_len); if (kem_ct == NULL || kem_secret == NULL) goto err; if (EVP_PKEY_encapsulate(kemri->pctx, kem_ct, &kem_ct_len, kem_secret, &kem_secret_len) <= 0) goto err; ASN1_STRING_set0(kemri->kemct, kem_ct, (int)kem_ct_len); kem_ct = NULL; if (!cms_kek_cipher(&enckey, &enckeylen, kem_secret, kem_secret_len, ec->key, ec->keylen, kemri, 1)) goto err; ASN1_STRING_set0(kemri->encryptedKey, enckey, (int)enckeylen); rv = 1; err: OPENSSL_free(kem_ct); OPENSSL_clear_free(kem_secret, kem_secret_len); return rv; } int ossl_cms_RecipientInfo_kemri_decrypt(const CMS_ContentInfo *cms, CMS_RecipientInfo *ri) { CMS_KEMRecipientInfo *kemri; CMS_EncryptedContentInfo *ec; const unsigned char *kem_ct = NULL; size_t kem_ct_len; unsigned char *kem_secret = NULL; size_t kem_secret_len = 0; unsigned char *enckey = NULL; size_t enckeylen; unsigned char *cek = NULL; size_t ceklen; int ret = 0; if (ri->type != CMS_RECIPINFO_KEM) { ERR_raise(ERR_LIB_CMS, CMS_R_NOT_KEM); return 0; } kemri = ri->d.ori->d.kemri; ec = ossl_cms_get0_env_enc_content(cms); if (kemri->pctx == NULL) { ERR_raise(ERR_LIB_CMS, CMS_R_NO_PRIVATE_KEY); return 0; } /* Setup all parameters to derive KEK */ if (!ossl_cms_env_asn1_ctrl(ri, 1)) goto err; kem_ct = ASN1_STRING_get0_data(kemri->kemct); kem_ct_len = ASN1_STRING_length(kemri->kemct); if (EVP_PKEY_decapsulate(kemri->pctx, NULL, &kem_secret_len, kem_ct, kem_ct_len) <= 0) return 0; kem_secret = OPENSSL_malloc(kem_secret_len); if (kem_secret == NULL) goto err; if (EVP_PKEY_decapsulate(kemri->pctx, kem_secret, &kem_secret_len, kem_ct, kem_ct_len) <= 0) goto err; /* Attempt to decrypt CEK */ enckeylen = kemri->encryptedKey->length; enckey = kemri->encryptedKey->data; if (!cms_kek_cipher(&cek, &ceklen, kem_secret, kem_secret_len, enckey, enckeylen, kemri, 0)) goto err; ec = ossl_cms_get0_env_enc_content(cms); OPENSSL_clear_free(ec->key, ec->keylen); ec->key = cek; ec->keylen = ceklen; ret = 1; err: OPENSSL_clear_free(kem_secret, kem_secret_len); return ret; }