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
Calling a Random Number Generating Member Function Doesn't Produce Entirely Random Numbers
Aligning Static String Literals
Qt 5 and Qprocess Redirect Stdout with Signal/Slot Readyread
How to Compile C++11 Code with Orwell Dev-C++
Why Can't I Use a "Break" Statement Inside a Ternary Conditional Statement in C++
Undefined Symbol on a Template Operator Overloading Function
What Happens When I Assign a Number Larger Than Int_Max to an Int
How to Change Text or Background Color in a Windows Console Application
Toy Shell Not Piping Correctly
C++:Handle Resources If Constructors May Throw Exceptions (Reference to Faq 17.4]
Initialize Base Class with No Default Constructor in Constructor of Derived Class
Get a Reverse Iterator from a Forward Iterator Without Knowing the Value Type
How Can Cstring Be Passed to Format String %S
Application Has Failed to Start Because Msvcp100D.Dll Was Not Found, Reinstalling App May Work
How to Check If a File Has Been Opened by Another Application in C++