c++ - 如何将 PKCS7_sign 结果转换为 char * 或 std::string

标签 c++ openssl smime

我尝试编写一个小邮件过滤器来使用 S/MIME 签署邮件。到目前为止,我已经完成了签署邮件的代码。我使用 openssl 中的 demos/smime 代码示例来完成这项工作。不幸的是,这些示例演示了如何将输入消息写入输出文件,但我需要结果作为字符串。

这是我的 Smime 方法:

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 个手册页,我不知道在哪里寻找信息。

我很想使用“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,这会截断结果。

使用 std::string 以及 (1) i2d_PKCS7_bio(对于 ASN.1/DER)或 (2) PEM_write_bio_PKCS7(对于 PEM)。这个想法是您照常使用该库,将输出写入 MEM_BIO,然后使用 BUF_MEM 获取 Bio 的内容。 BUF_MEM 保存指向数据及其长度的指针。类似...

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_PKCS7char*,那么PEM编码将缺少终止。请务必考虑到它,因为它不是 C 字符串。另请参阅Non-printable character after generating random n-byte Base64 string ,其中讨论了如何在不进行编码的情况下写入 NULL。


As there are more than 1600 man pages for openssl, I have no idea where to look for information...

查看子命令的源代码。它向您展示了库如何使用 API 进行操作。例如,当您使用 openssl pkcs7 时,它会使用 pkcs7 应用程序。

$ 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)。

以下是我的一个使用 OpenSSL 的 C++ 项目中的一些内容:

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)>;

关于c++ - 如何将 PKCS7_sign 结果转换为 char * 或 std::string,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38077226/

相关文章:

c++ - 如何将各种类型的函数指针存储在一起?

C++ 成员函数与自由函数

python - Python 中的 openssl_seal()

ios - 适用于 iOS 的 S/MIME 电子邮件加密库

java - 使用 JavaMail 创建多部分

c++ - 调用 GetLine() 没有匹配函数

c++ - 为什么未初始化的 const 成员在 C 中的处理方式与在 C++ 中的处理方式不同?

node.js - 允许对 NodeJ 进行遗留重新协商

c - 头文件 dh.h 中缺少定义(openssl 1.1.0f)

java - S/Mime 附件 java