mirror of https://github.com/openssl/openssl.git
410 lines
12 KiB
C
410 lines
12 KiB
C
/*
|
|
* 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 <openssl/cms.h>
|
|
#include <openssl/core_names.h>
|
|
#include <openssl/crypto.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/evp.h>
|
|
#include <openssl/kdf.h>
|
|
#include <openssl/x509.h>
|
|
#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;
|
|
|
|
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;
|
|
|
|
ASN1_OCTET_STRING_free(kemri->ukm);
|
|
kemri->ukm = ASN1_OCTET_STRING_new();
|
|
if (kemri->ukm == NULL)
|
|
return 0;
|
|
ASN1_OCTET_STRING_set(kemri->ukm, ukm, ukmLength);
|
|
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);
|
|
goto err;
|
|
}
|
|
|
|
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, keklen);
|
|
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;
|
|
}
|