Unique_Ptr and Openssl's Stack_Of(X509)*

unique_ptr and OpenSSL's STACK_OF(X509)*

I defined a regular function:

void stackOfX509Deleter(STACK_OF(X509) *ptr) {
sk_X509_free(ptr);
}

Then I use it in my code:

using STACK_OF_X509_ptr = std::unique_ptr<STACK_OF(X509),
decltype(&stackOfX509Deleter)>;

STACK_OF_X509_ptr chain(loadIntermediate(cert.string()),
stackOfX509Deleter);

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

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

Safe way to call a function with a void pointer parameter

In answer to your question. Use C++ style casts. They were added to C++ because C++ style casts to supersede C-Style casts. You can read more about the justification for C++ casts here: https://softwareengineering.stackexchange.com/questions/50442/c-style-casts-or-c-style-casts

As a bonus though, I'd suggest you eliminate the macro. As you have seen macros are a toxic element in C++. A vast improvement over either of your options would be:

if(BIO_ctrl(bio, BIO_C_SET_FILENAME, BIO_CLOSE | BIO_FP_WRITE, data(filename)) <= 0L)

This does use C++17's: data so if you don't have access to that feel free to just use the const_cast<char*> and allow the cast to void* to occur implicitly.

AES CBC encryption under openSSL gives unexpected Result

It's for padding. The functions pad the plaintext so that the resulting length is a multiple of the block size (16). Data that is already a multiple of the block is still padded, since otherwise it would be impossible to make the difference between a padding added on purpose, and plaintext that just happens to look like a valid padding.

The usual way of padding is to add N bytes of value N. So either one 0x01 byte , two 0x02 bytes etc., up to sixteen 0x10 bytes for a full block, as in your case.

From the documentation here:

If padding is enabled (the default) then EVP_EncryptFinal_ex() encrypts the "final" data, that is any data that remains in a partial block. It uses standard block padding (aka PKCS padding) as described in the NOTES section, below.

and under NOTES:

PKCS padding works by adding n padding bytes of value n to make the total length of the encrypted data a multiple of the block size. Padding is always added so if the data is already a multiple of the block size n will equal the block size. For example if the block size is 8 and 11 bytes are to be encrypted then 5 padding bytes of value 5 will be added.

But you can control if padding is enabled:

EVP_CIPHER_CTX_set_padding() enables or disables padding. By default encryption operations are padded using standard block padding and the padding is checked and removed when decrypting. If the pad parameter is zero then no padding is performed, the total amount of data encrypted or decrypted must then be a multiple of the block size or an error will occur.

Separating public and private keys of ECDSA keypair

I think the methods you want are:

EC_KEY_get0_private_key and
EC_KEY_get0_public_key

This works for me:

EC_KEY* key = EC_KEY_new_by_curve_name(NID_secp224r1);

if(!key)
{
std::cerr << "Error creating curve key" << '\n';
return EXIT_FAILURE;
}

if(!EC_KEY_generate_key(key))
{
std::cerr << "Error generating curve key" << '\n';
EC_KEY_free(key);
return EXIT_FAILURE;
}

BIGNUM const* prv = EC_KEY_get0_private_key(key);
if(!prv)
{
std::cerr << "Error getting private key" << '\n';
EC_KEY_free(key);
return EXIT_FAILURE;
}

std::cout << "Private key: " << prv << '\n';

EC_POINT const* pub = EC_KEY_get0_public_key(key);
if(!pub)
{
std::cerr << "Error getting public key" << '\n';
EC_KEY_free(key);
return EXIT_FAILURE;
}

std::cout << "Public key: " << pub << '\n';

// Use keys here ...

EC_KEY_free(key);

NOTE:

When using C libraries like this I often define a custom smart pointer to take care if the deletions. This makes the code less prone to memory leaks and "exception safe".

For example I would define something like this:

struct ec_key_dter{void operator()(EC_KEY* k)const{if(k) EC_KEY_free(k);}};
using ec_key_uptr = std::unique_ptr<EC_KEY, ec_key_dter>;

And use it like this:

auto key = ec_key_uptr(EC_KEY_new_by_curve_name(NID_secp224r1));

if(!key)
throw std::runtime_error("Error creating curve key");

if(!EC_KEY_generate_key(key.get()))
throw std::runtime_error("Error generating curve key");

if(!EC_KEY_check_key(key.get()))
throw std::runtime_error("Error checking curve key");

// ... etc ...

// Do not delete the key manually!!

How to send EVP_PKEY to other party?

But consider that I want to generate a keypair and send the public or private key as a char* over a socket to another person: how do I do that?

You need something for serialization and wire formats or a presentation format. Your public key and encrypted messages will likely have 0 characters, which appear as embedded NULL. So you need to have both a buffer and explicit length .

Use Google's ProtocolBuffers, Binary JSON, or even ASN.1/DER encoding. I think Google's ProtocolBuffers are message oriented, so they won't return a message until the complete message is available.

You could also Hex, Base32 or Base64 encode it. But you still need to communicate a length so the receiving party knows they got the whole message. On a local LAN you will probably never experience a problem. Over the Internet you will probably get occasional failures as your perform short reads on occasion.

Your thoughts on PEM_write_bio_PUBKEY are effectively Base64 encoding the key, so it suffers the same potential problem as Hex, Base32 or Base64 encoding.


how do I convert the char* back to EVP_PKEY

Well, you probably won't be using a char* based on your changes above. Once you refine the design, you should probably ask a new question.

But at the moment, and given you saved the key with PEM_write_bio_PUBKEY and PEM_write_bio_PrivateKey, then you would use PEM_read_bio_PUBKEY or PEM_read_bio_PrivateKey, respectively. Also see OpenSSL's PEM man page.


Related to C++, here are some tricks when working with OpenSSL. If you are using C++11, then unique_ptr really makes it easy to work with some OpenSSL objects.

  • unique_ptr and OpenSSL's STACK_OF(X509)*
  • How to get PKCS7_sign result into a char * or std::string
  • Non-printable character after generating random n-byte Base64 string
  • EVP Symmetric Encryption and Decryption | C++ Programs on the OpenSSL wiki


Related Topics



Leave a reply



Submit