如何将 PKCS7_sign 结果转换为 char * 或 std::string
我尝试编写一个小的邮件过滤器来使用 S/MIME 对邮件进行签名.到目前为止,我已经完成了直到签署邮件的代码.我使用了 openssl 中的 demos/smime 代码示例来完成这项工作.不幸的是,这些示例演示了如何将输入消息写入输出文件,但我需要将结果作为字符串.
I try to write a little mail milter to sign mails with S/MIME. So far, I have completed to the code up to signing the mail. I used the demos/smime code examples in openssl to do the job. Unfortunately the examples demonstrate how to write a input message to an output file, but I need the result as a string.
这是我的 Smime 方法:
This is my Smime-method:
void Smime::sign() {
if (!isLoaded())
return;
// Null-mailer or unknown
if (mailFrom.empty())
return;
auto *client = util::mlfipriv(ctx);
bool signedOrEncrypted = false;
std::vector<std::string> contentType;
contentType.push_back("multipart/signed");
contentType.push_back("multipart/encrypted");
contentType.push_back("application/pkcs7-mime");
if (client->sessionData.count("Content-Type") == 1) {
std::string value {client->sessionData["Content-Type"]};
std::size_t found;
for (int i=0; i<contentType.size(); i++) {
found = value.find(contentType.at(i));
if (found != std::string::npos) {
signedOrEncrypted = true;
break;
}
}
}
if (signedOrEncrypted) {
const char logmsg[] = "Message already signed or encrypted";
syslog(LOG_NOTICE, "%s", logmsg);
return;
}
/*
* TODO:
* Catch more cases, where an email already could have been encrypted
* or signed elsewhere.
*/
mapfile::Map email {mailFrom};
auto cert = fs::path(email.getSmimeFilename<mapfile::Smime::CERT>());
auto key = fs::path(email.getSmimeFilename<mapfile::Smime::KEY>());
if (!fs::exists(cert) && !fs::is_regular(cert))
return;
if (!fs::exists(key) && !fs::is_regular(key))
return;
// Signing starts here
BIO *in = nullptr, *out = nullptr, *tbio = nullptr;
X509 *scert = nullptr;
EVP_PKEY *skey = nullptr;
PKCS7 *p7 = nullptr;
int flags = PKCS7_DETACHED | PKCS7_STREAM;
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
// S/MIME certificate
tbio = BIO_new_file(cert.string().c_str(), "r");
if (!tbio) {
std::cerr << "Error: BIO_new_file(Cert) failed" << std::endl;
return;
}
scert = PEM_read_bio_X509(tbio, nullptr, 0, nullptr);
// S/MIME key
tbio = BIO_new_file(key.string().c_str(), "r");
if (!tbio) {
std::cerr << "Error: BIO_new_file(Key) failed" << std::endl;
return;
}
skey = PEM_read_bio_PrivateKey(tbio, nullptr, 0, nullptr);
if (!scert || !skey) {
std::cerr << "Error: Neither cert or key was loaded" << std::endl;
return;
}
// Loading mail content from temp file
in = BIO_new_file(client->getTempFile().c_str(), "r");
if (!in) {
std::cerr << "Error: Unable to load content from temp file"
<< std::endl;
return;
}
// Signing
p7 = PKCS7_sign(scert, skey, nullptr, in, flags);
if (!p7) {
std::cerr << "Error: Message could not be signed" << std::endl;
return;
}
// Cleanup
PKCS7_free(p7);
X509_free(scert);
EVP_PKEY_free(skey);
BIO_free(in);
BIO_free(out);
BIO_free(tbio);
smimeSigned = true;
}
由于 openssl 有 1600 多个手册页,我不知道去哪里查找信息.
As there are more than 1600 man pages for openssl, I have no idea where to look for information.
我很想使用p7"并将其写入一个简单的 std::string(或 char *,如果需要).我编写的 milter 应用程序将获取此字符串并执行更改正文(尚未编写,但这是我的想法).
I would love to use the "p7" and write it to a simple std::string (or char *, if required). The milter application I write will pick up this string and does a change-body (Not yet written, but this is my idea).
有人可以向我指出例程/手册页,或者有可以帮助我的代码示例吗?
Can somebody point me to routines/man pages or does have a code example that may help me?
提前致谢
推荐答案
我很想使用p7"并将其写入一个简单的 std::string(或 char *,如果需要).我编写的 milter 应用程序将获取此字符串并执行更改正文(尚未编写,但这是我的想法).
I would love to use the "p7" and write it to a simple std::string (or char *, if required). The milter application I write will pick up this string and does a change-body (Not yet written, but this is my idea).
我不相信你可以把它放在 char*
中,因为可能有一个嵌入的 NULL
,它会截断结果.
I don't believe you can put it in a char*
because there may be an embedded NULL
, which would truncate the result.
使用 std::string
和 (1) i2d_PKCS7_bio
用于 ASN.1/DER 或 (2) PEM_write_bio_PKCS7
用于 PEM.这个想法是你像往常一样使用库,将输出写入 MEM_BIO
,然后使用 BUF_MEM
获取 bio 的内容.BUF_MEM
保存一个指向数据及其长度的指针.像……
Use a std::string
and either (1) i2d_PKCS7_bio
for ASN.1/DER or (2) PEM_write_bio_PKCS7
for PEM. The idea is you use the library as usual, write output to a MEM_BIO
and then get the contents of the bio using BUF_MEM
. The BUF_MEM
holds a pointer to the data and its length. Something like...
using BIO_MEM_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
using BIO_MEM_BUF_ptr = std::unique_ptr<BUF_MEM, decltype(&::BIO_free)>;
BIO_MEM_ptr bio(BIO_new(BIO_s_mem()), ::BIO_free);
int ret = i2d_PKCS7_bio(bio, p7);
ASSERT(ret == 1);
BIO_MEM_BUF_ptr buff;
BIO_get_mem_ptr(bio.get(), &buff.get());
const BUF_MEM& t = *buff.get();
std::string result((t.data ? t.data : ""), (t.data ? t.length : 0));
如果您使用PEM_write_bio_PKCS7
和char*
,那么PEM编码将缺少终止NULL
.一定要考虑到它,因为它不是 C 字符串.另请参阅生成随机 n 字节 Base64 字符串后的不可打印字符,其中讨论了如何在未编码的情况下写入 NULL.
If you use PEM_write_bio_PKCS7
and a char*
, then the PEM encoding will lack the terminating NULL
. Be sure to account for it because its not a C-string. Also see Non-printable character after generating random n-byte Base64 string, which discusses how to write a NULL without it being encoded.
由于 openssl 有 1600 多个手册页,我不知道去哪里查找信息...
As there are more than 1600 man pages for openssl, I have no idea where to look for information...
检查子命令的源代码.它向您展示了该库如何使用 API 执行操作.例如,当您使用 openssl pkcs7
时,它会使用 pkcs7
应用程序.
Checkout the source code for the subcommands. It shows you how the library does things with the API. For example, when you use openssl pkcs7
, it uses the pkcs7
app.
$ cd <openssl src dir>
$ cd apps
$ ls *.c
app_rand.c dsaparam.c openssl.c rehash.c speed.c
apps.c ec.c opt.c req.c spkac.c
asn1pars.c ecparam.c passwd.c rsa.c srp.c
ca.c enc.c pkcs12.c rsautl.c ts.c
ciphers.c engine.c pkcs7.c s_cb.c verify.c
cms.c errstr.c pkcs8.c s_client.c version.c
crl.c gendsa.c pkey.c s_server.c vms_decc_init.c
crl2p7.c genpkey.c pkeyparam.c s_socket.c x509.c
dgst.c genrsa.c pkeyutl.c s_time.c
dhparam.c nseq.c prime.c sess_id.c
dsa.c ocsp.c rand.c smime.c
<小时>
将 unique_ptr
与 dtor 函数一起使用可确保自动清理对象,并有助于保持代码清洁.我尝试在 OpenSSL 与 C++ 交叉路径时使用它(参见 How to generate RSA private key using openssl 另一个示例).
Using unique_ptr
with the dtor function ensures the objects are automatically cleaned up, and it helps keep the code clean. I try to use it whenever OpenSSL crosses paths with C++ (see How to generate RSA private key using openssl for another example).
这是我的一个使用 OpenSSL 的 C++ 项目的内容:
Here's something from one of my C++ projects which uses OpenSSL:
using EC_KEY_ptr = std::unique_ptr<EC_KEY, decltype(&::EC_KEY_free)>;
using EC_GROUP_ptr = std::unique_ptr<EC_GROUP, decltype(&::EC_GROUP_free)>;
using EC_POINT_ptr = std::unique_ptr<EC_POINT, decltype(&::EC_POINT_free)>;
using DH_ptr = std::unique_ptr<DH, decltype(&::DH_free)>;
using RSA_ptr = std::unique_ptr<RSA, decltype(&::RSA_free)>;
using DSA_ptr = std::unique_ptr<DSA, decltype(&::DSA_free)>;
using EVP_PKEY_ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
using BN_ptr = std::unique_ptr<BIGNUM, decltype(&::BN_free)>;
using FILE_ptr = std::unique_ptr<FILE, decltype(&::fclose)>;
using BIO_MEM_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
using BIO_FILE_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
using EVP_MD_CTX_ptr = std::unique_ptr<EVP_MD_CTX, decltype(&::EVP_MD_CTX_destroy)>;
using X509_ptr = std::unique_ptr<X509, decltype(&::X509_free)>;
using ASN1_INTEGER_ptr = std::unique_ptr<ASN1_INTEGER, decltype(&::ASN1_INTEGER_free)>;
using ASN1_TIME_ptr = std::unique_ptr<ASN1_TIME, decltype(&::ASN1_TIME_free)>;
using X509_EXTENSION_ptr = std::unique_ptr<X509_EXTENSION, decltype(&::X509_EXTENSION_free)>;
using X509_NAME_ptr = std::unique_ptr<X509_NAME, decltype(&::X509_NAME_free)>;
using X509_NAME_ENTRY_ptr = std::unique_ptr<X509_NAME_ENTRY, decltype(&::X509_NAME_ENTRY_free)>;
using X509_STORE_ptr = std::unique_ptr<X509_STORE, decltype(&::X509_STORE_free)>;
using X509_LOOKUP_ptr = std::unique_ptr<X509_LOOKUP, decltype(&::X509_LOOKUP_free)>;
using X509_STORE_CTX_ptr = std::unique_ptr<X509_STORE_CTX, decltype(&::X509_STORE_CTX_free)>;
相关文章