【问题标题】:C library for CMS/X.509 manipulation用于 CMS/X.509 操作的 C 库
【发布时间】:2023-03-06 23:11:01
【问题描述】:

一些上下文:我有一个符合 PKCS #11 的加密引擎。该引擎将用于处理签名/封装数据,即验证数据的 ECDSA/SHA1 签名,使用 RSAES-OAEP 解包对称密钥,并解密此数据。这意味着对称密钥将使用我的引擎的公钥进行包装:因此我希望此公钥的证书实际读取“主题公钥算法:RSAES-OAEP”

我正在寻找一个 C 库,它可以让我通过以下方式操作符合加密消息语法 (CMS) 和 X.509 标准的对象:

  1. 创建 X.509 证书签名请求 (CSR),将主题公钥算法设置为 RSAES-OAEP
  • 让我处理签名部分:我的私钥只能通过 PKCS #11 句柄访问,所以我需要库给我要签名的字节,然后 让我使用我的加密引擎计算的内容设置 CSR 的 ProofOfPossession 字段

  • 将完整的 CSR 导出到某个东西(DER 或 PEM)

  1. 创建 CMS 结构来保存类似 SignedData(EnvelopedData( stuff )) 的内容。该库可以处理实际的加密/密钥包装/签名,或者它可以让一些其他软件引擎来做它,只允许我设置八位字节字符串

  2. 让我轻松解析回消息并恢复那些八位字节字符串

  • 意思是我想打开一个包含此 CMS 消息的 DER/PEM 文件,并获取签名的字节、封装的密钥和加密的内容,以便我可以将它们提供给我的 PKCS #11 接口

在有人建议 OpenSSL 的 libcrypto 之前,我已经看过它(看起来,“上周花了很多时间试图了解结构如何工作,ASN.1 表示如何工作,我如何恢复字节我对 OpenSSL 的结构很感兴趣...”),但我有一些问题(截至 1.0.1f):

  • (cf 1.) 我无法将主题公钥算法设置为 RSAE-OAEP。从demos/x509/mkreq.c开始,一直回到x509t.h的怪异#define IMPLEMENT_ASN1_ENCODE_FUNCTIONS_fname(stname, itname, fname)宏的深处,我想我可以肯定X509_REQ_set_pubkey()不能处理OAEP。

  • (参见 2。)就此而言,加密库的 CMS 部分也不能。 cms_RecipientInfo_ktri_encrypt() 调用 EVP_PKEY_CTX_ctrl(),我猜它解析为 crypto/rsa/rsa_pmeth.c:pkey_rsa_ctrl():当 p2 为 EVP_PKEY_CTRL_CMS_ENCRYPT 时,p2 未解析,因此未设置 RSA 填充(如果事实证明我错了,我就是不能正确阅读代码,请告诉我)。

因此,虽然我很高兴 this guy 设法让它“像魅力一样工作”,但我无法分享他的热情。

我猜是 2。我可以使用 OpenSSL 创建 CMS 空白结构,计算未来的 EnvelopedData 内容(即使用对称密钥加密的内容 + 使用 RSA OAEP 包装的对称密钥),然后填充这些内容将内容放入结构中,完全绕过CMS_encrypt(),然后封装到SignedData(我假设CMS_sign() 将处理ECDSA/SHA1)。只要我不想为 OAEP 使用花哨的参数(尽管this other guy 设法修补了 lib 以使用 SHA-256 的 OAEP)。

但这最终可能需要对 OpenSSL 的 ASN.1 API 进行过多的摆弄。因此我的疑问是:有谁知道一个 C 库来构建 CMS 结构为它们提供由其他引擎计算的八位字节字符串还有如何构建“此密钥应与 RSAES-OAEP 一起使用”的证书。

我查看了 libksba 和 cryptlib,虽然我猜它们可以工作,但我还不知道如何使用它们(可能与我因盯着 OpenSSL 的代码而流血的眼睛有关——我不是故意的说 OpenSSL 的代码不好或什么的,只是我一直在看它hard,而且文档有点缺乏)。

实际上,我想,我可以放弃 C 要求(主要是因为与加密引擎的通信是在 PKCS #11/C 中完成的)。这是图书馆应该能够做的事情:

  1. 建立企业社会责任

  2. ...以“RSAES-OAEP”作为主题公钥算法

  3. 给我字节来签署所有权证明部分

  4. 获取签名并输出完整的 X.509 CSR

  5. 解析 CMS 结构

  6. (SignedData) 给我对应于 signedInfo->signature 和封装的ContentInfo 字段的字节,以便我可以使用其他引擎验证它们

  7. (EnvelopedData) 给我对应于 keyTransRecipientInfo->encryptedKey 和 encryptedContentInfo->encryptedContent 字段的字节,以便我可以使用其他引擎解包/解密

  8. 构建一个 CMS 结构,或者...

  9. 让一些外部引擎设置上述字段,并让我指定算法

  10. 实际实现算法,并仅从数据构建 CMS(...使用 RSAES-OAEP 进行密钥包装)

现在我将采用“全 OpenSSL”的方法,因为我觉得自己陷得太深了,不应该开始在其他地方徘徊。如果有人有更好的想法,我会全力以赴。

至于设置主题公钥算法...好吧,要么我将保留常规 RSA,让我的应用程序“知道”包装是 RSAES-OAEP,或者...我不知道. 至于签署请求... POP 真的有用吗? (这不是一个严肃的问题)

NB: 已编辑以删除整个“我希望我的证书读取 OAEP”,因为我刚刚发现了 RFC 5756(还发现了 2006 年的 interesting discussion,当时没有此 RFC还没出来)。

【问题讨论】:

  • 存在 CryptLib 库(在 C 中),并且存在我们的跨平台 SecureBlackbox(可以从 C++ 中使用)。 SecureBlackbox 可以满足您的需求并支持 PKCS#11 API,但如前所述,它不完全是 C。

标签: c openssl x509 pkcs#7


【解决方案1】:

这是我到目前为止设法完成的工作。

1。构建 CSR,使用其他引擎对其进行签名

我主要关注demos/x509/mqreq.c,但有些曲折。

(注意:错误检查、花哨的模数长度/标签/主题 DN 生成/处理为简洁起见并专注于实际流程)。

unsigned char* mod = NULL;
unsigned char* exp = NULL;
size_t mod_l = 0;
size_t exp_l = 0;

P11_handle h_key = P11_gen_rsa(&mod, &mod_l, &exp, &exp_l);

RSA* rsa = RSA_new();

rsa->n = BN_bin2bn(rsa_mod, rsa_mod_l, NULL);
rsa->e = BN_bin2bn(rsa_exp, rsa_exp_l, NULL);

EVP_PKEY* pkey = EVP_PKEY_new();

EVP_PKEY_assign_RSA(pkey, rsa);

X509_REQ* csr = X509_REQ_new();
X509_REQ_set_pubkey(csr, pkey);

/* Boring X509_NAME/X509_EXTENSION stuff */

X509_REQ_INFO* csr_req = csr->req_info;

unsigned char* pop_in = NULL;
size_t pop_in_l = ASN1_item_i2d((void*)csr_req, &pop_in,
                                ASN1_ITEM_rptr(X509_REQ_INFO));

unsigned char* sig = NULL;
size_t sig_l = 0;

P11_make_pop(SIGN_RSA_PKCS, DIGEST_SHA256,
             pop_in, pop_in_l, &sig, &sig_l,
             h_key);

/* Add signature to CSR (heavily inspired from ASN1_item_sign_ctx())
 * (please don't ask about the flags) */

if (csr->signature->data != NULL) OPENSSL_free(csr->signature->data);
csr->signature->data = sig;
csr->signature->length = sig_l;
csr->signature->flags&= ~(ASN1_STRING_FLAG_BITS_LEFT|0x07);
csr->signature->flags|= ASN1_STRING_FLAG_BITS_LEFT;

/* Add signature algorithm information to CSR */

int sig_algo_nid = 0;
OBJ_find_sigid_by_algs(&sig_algo_nid,
                       EVP_MD_nid(EVP_sha256()), EVP_PKEY_RSA);

X509_ALGOR_set0(csr->sig_alg, OBJ_nid2obj(sig_algo_nid),
                V_ASN1_NULL, NULL));

之后,X509_REQ 结构适用于 PEM 导出。 openssl req -verify 似乎验证了这个过程,所以就我而言,这是可行的。

2。构建嵌套的 CMS 结构(如 SignedData(EnvelopedData(Data)))

终于搞定了,使用 1.0.2(任何以前的版本都需要打补丁或 ASN.1 级别的解析)。非常感谢 Stephen Henson 博士和 Tom Francis 通过邮件列表帮助我解决这个问题。

/* Make EnvelopedData structure */
BIO* in = BIO_new_file(in_path, "rb");

int flags = CMS_BINARY | CMS_USE_KEYID | CMS_PARTIAL | CMS_KEY_PARAM;

CMS_ContentInfo* edata = CMS_encrypt(NULL, NULL, cipher, flags);

CMS_RecipientInfo* r_info = CMS_add1_recipient_cert(edata, r_cert, flags);
EVP_PKEY_CTX* wrap_ctx = CMS_RecipientInfo_get0_pkey_ctx(r_info);

EVP_PKEY_CTX_set_rsa_padding(wrap_ctx, RSA_PKCS1_OAEP_PADDING);
EVP_PKEY_CTX_set_rsa_oaep_md(wrap_ctx, EVP_sha256());
EVP_PKEY_CTX_set_rsa_mgf1_md(wrap_ctx, EVP_sha256());
EVP_PKEY_CTX_set0_rsa_oaep_label(wrap_ctx, oaep_label, oaep_label_l);
/* NB: oaep_label must be heap-allocated, and will be freed by OSSL */

CMS_final(edata, in, NULL, flags);

BIO* tmp = BIO_new(BIO_s_mem());
i2d_CMS_bio(tmp, edata);

/* Make SignedData structure */

flags|= CMS_NOSMIMECAP | CMS_NOCERTS;
flags&= ~(CMS_KEY_PARAM);

CMS_ContentInfo* sdata = CMS_sign(NULL, NULL, NULL, NULL, flags);

ASN1_OBJECT* ectype_edata = OBJ_nid2obj(NID_pkcs7_enveloped);
CMS_set1_eContentType(sdata, ectype_edata);

CMS_SignerInfo* s_info =
    CMS_add1_signer(sdata, s_cert, s_key, NULL, flags);

CMS_SignerInfo_sign(s_info);

CMS_final(sdata, tmp, NULL, flags);

BIO* out = BIO_new_file(out_path, "wb");
i2d_CMS_bio(out, sdata);
BIO_flush(out);

3。解析结构并获取我需要的字段。

我基本上编写了自己的 CMS 解析器。当您了解规范时,ASN.1 实际上很容易解析。我尝试使用一些“ASN.1 to C structs”编译器编译 RFC 中的 ASN.1 模块,但没有运气(他们一直在语法上窒息)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-30
    • 1970-01-01
    • 2019-02-22
    • 2018-03-03
    • 2010-09-07
    • 2011-05-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多