Created
November 12, 2025 09:17
-
-
Save mfukar/e9ea2703ab2683810736775e6a770ab0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * 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