How to get PKCS7_sign result into a char * or std::string
Asked Answered
S

1

2

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.

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

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

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?

Thanks in advance

Selfsacrifice answered 28/6, 2016 at 13:10 Comment(2)
I don't believe you can put it in a char* because there may be an embedded NULL, which would truncate the result.Do
The example code calls out = BIO_new_file("smout.txt", "w"); ... SMIME_write_PKCS7(out, p7, in, flags) The result is base64 encoded. I am pretty sure, it are only one or two functions that would do the job. But which? :-)Hognut
D
3

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

I don't believe you can put it in a char* because there may be an embedded NULL, which would truncate the result.

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

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.


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

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

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

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)>;
Do answered 28/6, 2016 at 14:33 Comment(1)
Thank you so very much. This is so great. Especially the use of smart pointers. By reading your examples, I have learned wonderful things. I will follow your suggestions tomorrowHognut

© 2022 - 2024 — McMap. All rights reserved.