用于CMS / X.509操作的C库

时间:2014-04-17 21:08:42

标签: c openssl x509 pkcs#7

某些上下文:我有一个符合PKCS#11标准的加密引擎。该引擎将用于处理签名/封装数据,即验证数据的ECDSA / SHA1签名,用RSAES-OAEP打开对称密钥,并解密该数据。这意味着对称密钥将被我的引擎的公钥包裹:因此我希望该公钥的证书能够实际读取"主题公钥算法:RSAES-OAEP&# 34;

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

  1. 创建X.509证书签名请求(CSR),将主题公钥算法设置为RSAES-OAEP

    • 让我处理签名部分:我的私钥只能通过PKCS#11句柄访问,所以我需要库给我签名的字节,然后让我用我的加密引擎计算出的CSR的ProofOfPossession字段

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

  2. 创建CMS结构以保存SignedData(EnvelopedData(stuff))之类的东西。该库可以处理实际的加密/密钥包装/签名,或者它可以让其他软件引擎执行它并且只允许我设置八位字节串

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

    • 意思是我想打开包含此CMS消息的DER / PEM文件,并获取签名,包装密钥和加密内容的字节,以便我可以将它们提供给我的PKCS#11接口
  4. 在任何人建议使用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。

    • (cf 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包装的对称密钥),并填写此内容在封装到SignedData之前完全绕过CMS_encrypt()的结构内容(我假设CMS_sign()将处理ECDSA / SHA1)。只要我不想为OAEP使用花哨的参数(尽管this other guy设法修补lib以使用OAEP和SHA-256)。

    但这可能最终需要过多地摆弄OpenSSL的ASN.1 API。因此我的审讯:有没有人知道一个C库来建立CMS结构并且为它们提供由其他一些引擎计算的八位字符串此外,如何建立阅读"这个密钥与RSAES-OAEP一起使用的证书"。

    我看过libksba和cryptlib,虽然我猜他们可以工作,但是我还看不到如何使用它们(可能与我的眼睛因为盯着OpenSSL的代码而流血有关 - 我并不是说OpenSSL的代码是坏的或者其他什么,只是因为我一直在看它,而且文档略显缺乏)。

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

    1. 构建CSR

      1. ...特色" RSAES-OAEP"作为主题公钥算法

      2. 给我签名持有证明部分的字节

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

    2. 解析CMS结构

      1. (SignedData)给我对应于signedInfo->签名和encapsulatedContentInfo字段的字节,以便我可以用其他引擎验证它们

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

    3. 构建CMS结构,或者......

      1. 让一些外部引擎设置上面提到的字段,并让我指定算法

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

    4. 现在我正在使用"全OpenSSL"方法,因为我觉得我太深了,不应该开始在别的地方徘徊。如果有人有更好的想法,我会全力以赴。

      至于设置该主题的公钥算法......好吧,我或者只是留下常规的RSA并让我的应用程序"知道"包装是RSAES-OAEP,或者......我不知道。至于签署请求...... POP是否仍然有用? (这不是一个严肃的问题)

      NB:已修改以删除整个"我希望我的证书可以阅读OAEP",因为我刚刚发现了RFC 5756(也找到了{{3}从2006年开始,这个RFC还没有出来。)

1 个答案:

答案 0 :(得分: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到C结构”编译器在RFC中编译ASN.1模块,但没有运气(他们一直在语法上窒息)。