Skip to content

Instantly share code, notes, and snippets.

@mfukar
Created November 12, 2025 09:17
Show Gist options
  • Select an option

  • Save mfukar/e9ea2703ab2683810736775e6a770ab0 to your computer and use it in GitHub Desktop.

Select an option

Save mfukar/e9ea2703ab2683810736775e6a770ab0 to your computer and use it in GitHub Desktop.
/**
* This program will accept two pathnames as arguments,
* one expected to point to a X.509 certificate, and
* one expected to point to a non-empty file,
* and will encrypt the data in the latter with a symmetric key,
* encrypt the symmetric key with the key in the X.509 certificate,
* and put that data in a CMS envelope.
*
* This program performs no decryption.
*
* The pathname of the generated CMS file will be the pathname to the original
* data file, suffixed with ".enc".
*
* Build with:
* clang++ --std=c++20 $(pkg-config --cflags openssl) $(pkg-config --libs openssl) -O0 -o cms-encrypt cms-encrypt.cpp
*/
#include <openssl/safestack.h> /* Certificate stack handling */
#include <openssl/x509.h> /* X509 type */
#include <openssl/cms.h> /* All encrypted coredumps are in CMS enveloped data */
#include <openssl/pem.h> /* Certificate is in PEM format. */
#include <openssl/err.h> /* Error handling */
#include <openssl/safestack.h> /* Certificate stack handling */
#include <openssl/x509.h> /* X509 type */
#include <stdexcept>
#include <filesystem>
#include <iostream>
#include <dirent.h>
/* Group data necessary to encrypt a file: */
struct encryption_metadata {
STACK_OF(X509) * x509_certificate_stack;
int flags; /* Flags for OpenSSL functions */
};
/* All files created by this program have this file suffix: */
#define ENCRYPTED_FILE_SUFFIX ".enc"
class crypto {
bool can_encrypt; /* Set to true only if constructed properly and can encrypt with a valid certificate */
struct encryption_metadata metadata;
bool push_cert_to_stack (struct encryption_metadata *metadata, X509 *x509_cert);
public:
crypto();
bool load_certificate (std::filesystem::path certificate_path);
bool can_encrypt_with_this (void);
bool is_encrypted (std::filesystem::path path);
bool encrypt_file (std::filesystem::path path);
};
crypto::crypto () {
can_encrypt = false;
metadata.x509_certificate_stack = NULL;
metadata.flags = 0;
/* Initialise OpenSSL */
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
metadata.x509_certificate_stack = sk_X509_new_null();
if (metadata.x509_certificate_stack == NULL) {
throw std::runtime_error ("Unable to create certificate stack, encryption not possible!");
}
/* CMS_STREAM for streaming encryption to limit memory usage,
* even though we only ever encrypt files and not streams.
* CMS_BINARY to not attempt to interpret data */
metadata.flags = CMS_STREAM | CMS_BINARY;
}
bool crypto::push_cert_to_stack (struct encryption_metadata *meta, X509 *x509_cert) {
/* If there is a stack assigned, destroy it: */
if (sk_X509_num(meta->x509_certificate_stack) > 0) {
while (sk_X509_num(meta->x509_certificate_stack) > 0) {
X509_free (sk_X509_pop (meta->x509_certificate_stack));
}
if (meta->x509_certificate_stack != NULL) {
sk_X509_free(meta->x509_certificate_stack);
meta->x509_certificate_stack = NULL;
}
}
meta->x509_certificate_stack = sk_X509_new_null();
if (meta->x509_certificate_stack == NULL) {
/* Unable to create certificate stack, encryption not possible */
return false;
}
if (!sk_X509_push (meta->x509_certificate_stack, x509_cert)) {
/* Unable to add certificate to stack, encryption not possible */
return false;
}
return true;
}
bool crypto::load_certificate (std::filesystem::path certificate_path) {
BIO *bio_cert = BIO_new_file (certificate_path.c_str(), "r");
if (!bio_cert) {
/* Could not open certificate file, encryption cannot happen! */
return false;
}
X509 * x509_cert = PEM_read_bio_X509 (bio_cert, NULL, 0, NULL);
if (!x509_cert) {
BIO_free (bio_cert);
/* Could not load a valid X509 certificate, encryption cannot happen! */
return false;
}
BIO_free (bio_cert); /* Not needed beyond this point. */
ASN1_TIME *expiration_date = X509_get_notAfter (x509_cert);
if (X509_cmp_current_time (expiration_date) < 0) {
/* Expired certificate, will not encrypt with this! */
X509_free (x509_cert);
return false;
}
/* All good, add the certificate to our internal metadata */
push_cert_to_stack(&metadata, x509_cert);
can_encrypt = true;
return true;
}
/*
* Returns true if the caller can use this object to encrypt a file.
*/
bool crypto::can_encrypt_with_this (void) {
return can_encrypt;
}
/* Returns true if the path given has an extension of
* `ENCRYPTED_FILE_SUFFIX`, false otherwise.
*
* It does not attempt to verify the contents of the file.
*/
bool crypto::is_encrypted (std::filesystem::path path) {
return !path.empty() && path.extension() == ENCRYPTED_FILE_SUFFIX;
}
/* Encrypts the data in the file at `path`, names the resulting file `path.enc`
* Returns `false` on any error, `true` otherwise when encrypted data
* has been written.
*/
bool crypto::encrypt_file (std::filesystem::path path) {
/* Precondition: */
if (!can_encrypt) {
return false;
}
if (path.empty()) {
return false;
}
BIO * bio_in = BIO_new_file (path.c_str(), "rb");
if (!bio_in) {
return false;
}
path += ENCRYPTED_FILE_SUFFIX;
BIO * bio_out = BIO_new_file (path.c_str(), "wb");
if (!bio_out) {
BIO_free (bio_in);
return false;
}
/* The output must be CMS enveloped data: */
CMS_ContentInfo * cms = CMS_encrypt (metadata.x509_certificate_stack, NULL,
EVP_aes_256_cbc(),
static_cast<unsigned int>(metadata.flags));
/* No actual data is encrypted here if the CMS_STREAM flag is set above */
if (!cms) {
BIO_free (bio_out);
BIO_free (bio_in);
return false;
}
if (i2d_CMS_bio_stream (bio_out, cms, bio_in, metadata.flags) == 0) {
CMS_ContentInfo_free (cms);
BIO_free (bio_out);
BIO_free (bio_in);
return false;
}
CMS_ContentInfo_free (cms);
BIO_free (bio_out);
BIO_free (bio_in);
return true;
}
int main (int argc, char * argv[]) {
if (argc < 3) {
std::cerr << "Usage: " << argv[0] << " <certificate-path> <plaintext-path>" << std::endl;
return -1;
}
std::filesystem::path path_to_certificate{argv[1]};
crypto pter;
if (!pter.load_certificate(path_to_certificate)) {
std::cerr << "Could not load certificate, get your shit together" << std::endl;
return -2;
}
if (!pter.can_encrypt_with_this()) {
std::cerr << "crypto object is corrupted, get your shit together" << std::endl;
return -3;
}
std::filesystem::path target{argv[2]};
if (!pter.encrypt_file(target)) {
std::cerr << "Could not encrypt " << target << " get your environment in order" << std::endl;
return -4;
}
if (!pter.is_encrypted(target += ENCRYPTED_FILE_SUFFIX)) {
std::cerr << (target += ENCRYPTED_FILE_SUFFIX) << " does not exist, get your environment in order" << std::endl;
return -5;
}
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment