Skip to content
Snippets Groups Projects

Resolve "SMIME Support"

Merged lazarog98 requested to merge 232-smime-support into dev
Compare and Show latest version
5 files
+ 125
23
Compare changes
  • Side-by-side
  • Inline
Files
5
+ 553
0
//
// openssl-helpers.c
//
//
// Created by lazarog98 on 02.03.20.
//
#include "smime-helpers.h"
#include <glob.h>
// This is a wrapper around the OpenSSL functions, where everz function is primarily inspired by the SMIME demos in the OpenSSL github
// https://github.com/openssl/openssl/tree/master/demos/smime
pem_with_fp * cert_stack_to_array(STACK_OF(X509) *stack) {
pem_with_fp *arr = malloc(sizeof(pem_with_fp)*sk_X509_num(stack));
int i = 0;
while (sk_X509_num(stack) > 0) {
BIO *out = BIO_new(BIO_s_mem());
char *tmp = NULL;
X509 *cert = sk_X509_pop(stack);
PEM_write_bio_X509(out, cert);
long size = BIO_get_mem_data(out, &tmp);
char *str_perm = (char *) malloc(size+1);
memcpy(str_perm, tmp, size);
str_perm[size]=0; // To Nullterminate the string
arr[i].pem = str_perm;
arr[i].fp = get_fingerprint_from_X509(cert, 0);
i++;
X509_free(cert);
BIO_free(out);
}
return arr;
}
void OpenSSL_print_ver(void) {
printf("%s", OPENSSL_VERSION_TEXT);
}
array_with_length *create_list_of_errors() {
unsigned long err = 0;
linked_list *head = NULL;
linked_list *cur = NULL;
int first = 1;
array_with_length *res = malloc(sizeof(array_with_length));
int length = 0;
unsigned long * arr = NULL;
while ((err = ERR_get_error()) != 0) {
linked_list * newerr = malloc(sizeof(linked_list));
newerr->content = malloc(sizeof(unsigned long));
memcpy(newerr->content, &err, sizeof(unsigned long));
newerr->next = NULL;
if (first)
{
first=0;
head = newerr;
}
else
{
cur->next=newerr;
}
cur=newerr;
length++;
}
if (length) {
arr = malloc(sizeof(unsigned long) * length);
linked_list *cur = head;
int i = 0;
while (cur != NULL) {
arr[i] = *((unsigned long*) cur->content);
linked_list *old = cur;
cur = cur->next;
free(old->content);
free(old);
i++;
}
}
res->arr = arr;
res->size = length;
return res;
}
result * OpenSSL_encrypt(const char *text, char **pems, int num_certs) {
// https://github.com/openssl/openssl/blob/master/demos/cms/cms_enc.c
BIO *in = NULL, *out = NULL, *rec_cert_bio = NULL;
// recipient certificate
X509 *rec_cert = NULL;
STACK_OF(X509) *cert_stack = NULL;
CMS_ContentInfo *cms = NULL;
array_with_length *temp = NULL;
result *res =new_result();
res->certs = NULL;
res->num_certs = 0;
cert_stack = sk_X509_new_null();
if (!cert_stack) {
printf("Couldn't initialize cert stack!\n");
goto deinit;
}
in = BIO_new_mem_buf(text, (int) strlen(text));
for (int i = 0; i<num_certs; i++) {
rec_cert_bio = BIO_new_mem_buf(pems[i], (int) strlen(pems[i]));
if (!rec_cert_bio) {
printf("Failed reading mykey.pem!\n");
goto deinit_loop;
}
rec_cert = PEM_read_bio_X509(rec_cert_bio, NULL, 0, NULL);
if (!rec_cert) {
printf("Failed reading pem\n");
goto deinit_loop;
}
// note that if the stack is initialized correctly, the recipient certificate is pushed as a test
if (!sk_X509_push(cert_stack, rec_cert)) {
printf("Failed at push_stack");
X509_free(rec_cert);
goto deinit_loop;
}
deinit_loop:
BIO_free(rec_cert_bio);
}
// TODO: change AES CBC to AES GCM for compliance with SMIME 4.0
// as of OpenSSL version 1.1.1d GCM isn't supported for CMS
// https://github.com/openssl/openssl/pull/8024
cms = CMS_encrypt(cert_stack, in, EVP_aes_256_cbc(), CMS_STREAM);
if (!cms) {
printf("Failed at P7enc");
goto deinit;
}
out = BIO_new(BIO_s_mem());
if (!SMIME_write_CMS(out,cms,in,CMS_STREAM)) {
printf("Failed at SMIME_WRITE");
goto deinit;
}
bio_to_str(out, &(res->res));
deinit:
// TODO: Collect all errors in a list and return them with the result
temp = create_list_of_errors();
res->errors = temp->arr;
res->num_errors = temp->size;
free(temp);
CMS_ContentInfo_free(cms);
BIO_free(in);
BIO_free(out); // also frees tmp
sk_X509_pop_free(cert_stack, X509_free);
return res;
}
result * OpenSSL_decrypt(const char *str, const char *pem_cert, const char *pem_key) {
// https://github.com/openssl/openssl/blob/master/demos/cms/cms_dec.c
BIO *in = NULL, *out = NULL, *rec_cert_bio = NULL, *rec_key_bio = NULL;
// recipient certificate
X509 *rec_cert = NULL;
CMS_ContentInfo *cms = NULL;
EVP_PKEY *rkey = NULL;
result *res = new_result();
array_with_length *temp = NULL;
res->certs = NULL;
res->num_certs = 0;
// this trick allows to hardcode a certificate as a string
rec_cert_bio = BIO_new_mem_buf(pem_cert, (int) strlen(pem_cert));
rec_key_bio = BIO_new_mem_buf(pem_key, (int) strlen(pem_key));
in = BIO_new_mem_buf(str,(int) strlen(str)); // simpletest
if (!rec_cert_bio) {
printf("Failed reading cert!\n");
goto deinit;
}
if (!rec_key_bio) {
printf("Failed reading mykey.pem!\n");
goto deinit;
}
rec_cert = PEM_read_bio_X509(rec_cert_bio, NULL, 0, NULL);
if (!rec_cert ) {
printf("Failed reading pem cert\n");
goto deinit;
}
rkey = PEM_read_bio_PrivateKey(rec_key_bio, NULL, 0, NULL);
if (!rkey) {
printf("Failed reading pem key\n");
goto deinit;
}
cms = SMIME_read_CMS(in, NULL);
if (!cms) {
printf("DEC Failed at SMIME_READ");
goto deinit;
}
out = BIO_new(BIO_s_mem());
if (!CMS_decrypt(cms, rkey, rec_cert, NULL, out, CMS_STREAM)) {
printf("Failed at Decrypt");
goto deinit;
}
bio_to_str(out, &(res->res));
deinit:
temp = create_list_of_errors();
res->errors = temp->arr;
res->num_errors = temp->size;
free(temp);
CMS_ContentInfo_free(cms);
BIO_free(in);
BIO_free(out); // also frees tmp
BIO_free(rec_cert_bio);
BIO_free(rec_key_bio);
EVP_PKEY_free(rkey);
X509_free(rec_cert);
// OpenSSL ver 1.0.2.f has a bug (seemingly) that causes a crash when freeing cms content info pointers
return res;
}
result * OpenSSL_sign(const char *text, const char *pem_cert, const char *pem_key, const int detached) {
BIO *in = NULL, *out = NULL, *sig_cert_bio = NULL, *sig_key_bio = NULL;
// recipient certificate
X509 *sig_cert = NULL;
STACK_OF(X509) *cert_stack = NULL;
CMS_ContentInfo *cms = NULL;
EVP_PKEY *skey = NULL;
int flags = CMS_STREAM | CMS_PARTIAL;
result *res = new_result();
res->certs = NULL;
res->num_certs = 0;
array_with_length *temp = NULL;
if (detached) flags |= CMS_DETACHED;
in = BIO_new_mem_buf(text,(int) strlen(text)); // simpletest
sig_cert_bio = BIO_new_mem_buf(pem_cert, (int) strlen(pem_cert));
if (!sig_cert_bio) {
printf("Failed reading cert!\n");
goto deinit;
}
sig_key_bio = BIO_new_mem_buf(pem_key, (int) strlen(pem_key));
if (!sig_key_bio) {
printf("Failed reading mykey.pem!\n");
goto deinit;
}
sig_cert = PEM_read_bio_X509(sig_cert_bio, NULL, 0, NULL);
if (!sig_cert ) {
printf("Failed reading pem cert\n");
goto deinit;
}
cert_stack = sk_X509_new_null();
// note that if the stack is initialized correctly, the recipient certificate is pushed as a test
if (!cert_stack || !sk_X509_push(cert_stack, sig_cert)) {
printf("Failed at push_stack");
goto deinit;
}
skey = PEM_read_bio_PrivateKey(sig_key_bio, NULL, 0, NULL);
if (!skey) {
printf("Failed reading pem key\n");
goto deinit;
}
cms = CMS_sign(NULL, NULL, NULL, in, flags);
if (!cms) {
goto deinit;
}
if (!CMS_add1_signer(cms, sig_cert, skey, EVP_sha256(), flags)) {
goto deinit;
}
out = BIO_new(BIO_s_mem());
if (!SMIME_write_CMS(out,cms,in,flags)) {
printf("Failed at SMIME_WRITE");
goto deinit;
}
bio_to_str(out, &(res->res));
deinit:
temp = create_list_of_errors();
res->errors = temp->arr;
res->num_errors = temp->size;
free(temp);
CMS_ContentInfo_free(cms);
BIO_free(in);
BIO_free(out); // also frees tmp
BIO_free(sig_cert_bio);
BIO_free(sig_key_bio);
EVP_PKEY_free(skey);
X509_free(sig_cert);
sk_X509_pop_free(cert_stack, X509_free);
return res;
}
result * OpenSSL_verify(const char *text, char **pem_cert, const int num_certs) {
// https://github.com/openssl/openssl/blob/master/demos/cms/cms_dec.c
result *ver = new_result();
BIO *in = NULL, *out = NULL;
// recipient certificate
X509 **sig_certs = malloc(num_certs*sizeof(void*));
STACK_OF(X509) *cert_stack= NULL;
X509_STORE *cert_store = NULL;
CMS_ContentInfo *cms = NULL;
BIO *detached = NULL;
array_with_length *temp = NULL;
cert_store = X509_STORE_new();
// this trick allows to hardcode a certificate as a string
for (int i = 0; i<num_certs; i++) {
BIO *sig_cert_bio = BIO_new_mem_buf(pem_cert[i], (int) strlen(pem_cert[i]));
sig_certs[i] = NULL;
if (!sig_cert_bio) {
printf("VERT Failed reading mykey.pem!\n");
goto loopend;
}
sig_certs[i] = PEM_read_bio_X509(sig_cert_bio, NULL, 0, NULL);
if (!sig_certs[i] ) {
printf("VERT Failed reading pem cert %d\n |", i);
goto loopend;
}
if (!X509_STORE_add_cert(cert_store, sig_certs[i])) {
printf("VERT Failed at adding cert to store\n");
goto loopend;
}
loopend:
BIO_free(sig_cert_bio);
}
in = BIO_new_mem_buf(text,(int) strlen(text)); // simpletest
cms = SMIME_read_CMS(in, &detached);
if (!cms) {
printf("\nVERT VER Failed at SMIME_READ");
goto deinit;
}
out = BIO_new(BIO_s_mem());
ERR_clear_error();
if (!CMS_verify(cms, NULL, cert_store, detached, out, 0)) {
printf("VERT Verification failed");
goto deinit;
}
bio_to_str(out, &(ver->res));
cert_stack = CMS_get0_signers(cms);
ver->num_certs = sk_X509_num(cert_stack);
pem_with_fp *certs_fps = cert_stack_to_array(cert_stack);
ver->certs = malloc(sizeof(char*) * ver->num_certs);
ver->fingerprints = malloc(sizeof(char*) * ver->num_certs);
for (int i = 0; i<ver->num_certs; i++) {
ver->certs[i] = certs_fps[i].pem;
ver->fingerprints[i] = certs_fps[i].fp;
}
free(certs_fps);
deinit:
temp = create_list_of_errors();
ver->errors = temp->arr;
ver->num_errors = temp->size;
free(temp);
CMS_ContentInfo_free(cms);
if (detached != NULL) BIO_free(detached);
BIO_free(in);
BIO_free(out); // also frees tmp
X509_STORE_free(cert_store);
for (int i =0;i<num_certs;i++) X509_free(sig_certs[i]); //We need to free all certs
free(sig_certs);
sk_X509_pop_free(cert_stack, X509_free);
// OpenSSL ver 1.0.2.f has a bug (seemingly) that causes a crash when freeing cms content info pointers
return ver;
}
char * get_fingerprint_from_X509(X509 *cert, int md_alg) {
// https://github.com/openssl/openssl/blob/master/demos/cms/cms_enc.c
unsigned int len;
unsigned char md[EVP_MAX_MD_SIZE];
const EVP_MD *alg=NULL;
char *fp = NULL;
switch(md_alg)
{
case 0:
alg = EVP_sha256();
break;
case 1:
alg = EVP_sha1();
break;
default:
alg= EVP_sha256();
}
if (!X509_digest(cert, alg, md, &len)) {
printf("Failed at digest!\n");
goto deinit;
}
fp = bin_to_hex(md, len);
deinit:
return fp;
}
result * get_fingerprint_from_pem(const char *pem, int md_alg) {
// https://github.com/openssl/openssl/blob/master/demos/cms/cms_enc.c
BIO *rec_cert_bio = NULL;
// recipient certificate
X509 *rec_cert = NULL;
array_with_length *temp = NULL;
result *res = new_result();
// this trick allows to hardcode a certificate as a string
rec_cert_bio = BIO_new_mem_buf(pem, (int) strlen(pem));
if (!rec_cert_bio) {
printf("Failed reading mykey.pem!\n");
goto deinit;
}
rec_cert = PEM_read_bio_X509(rec_cert_bio, NULL, 0, NULL);
if (!rec_cert) {
printf("Failed reading pem\n");
goto deinit;
}
res->res = get_fingerprint_from_X509(rec_cert, md_alg);
deinit:
temp = create_list_of_errors();
res->errors = temp->arr;
res->num_errors = temp->size;
free(temp);
BIO_free(rec_cert_bio);
X509_free(rec_cert);
return res;
}
result * get_pkey_from_enc_pem(const char *pem, const char *passwd) {
// https://github.com/openssl/openssl/blob/master/demos/cms/cms_enc.c
BIO *rec_cert_bio = NULL, *out = NULL;
// recipient certificate
array_with_length *temp = NULL;
result *res = new_result();
EVP_PKEY *skey = NULL;
// this trick allows to hardcode a certificate as a string
rec_cert_bio = BIO_new_mem_buf(pem, (int) strlen(pem));
if (!rec_cert_bio) {
printf("Failed reading mykey.pem!\n");
goto deinit;
}
skey = PEM_read_bio_PrivateKey(rec_cert_bio, NULL, NULL, (void*) passwd);
if (!skey) {
printf("Failed reading pem key\n");
goto deinit;
}
out = BIO_new(BIO_s_mem());
if (!PEM_write_bio_PrivateKey(out, skey, NULL, NULL, 0, NULL, NULL)) {
printf("Failed writing pem key\n");
goto deinit;
}
bio_to_str(out, &(res->res));
deinit:
temp = create_list_of_errors();
res->errors = temp->arr;
res->num_errors = temp->size;
free(temp);
BIO_free(out);
BIO_free(rec_cert_bio);
EVP_PKEY_free(skey);
return res;
}
result * get_enc_pem_from_pkey(const char *pem, const char *passwd) {
// https://github.com/openssl/openssl/blob/master/demos/cms/cms_enc.c
BIO *rec_cert_bio = NULL, *out = NULL;
// recipient certificate
array_with_length *temp = NULL;
result *res = new_result();
EVP_PKEY *skey = NULL;
// this trick allows to hardcode a certificate as a string
rec_cert_bio = BIO_new_mem_buf(pem, (int) strlen(pem));
if (!rec_cert_bio) {
printf("Failed reading mykey.pem!\n");
goto deinit;
}
skey = PEM_read_bio_PrivateKey(rec_cert_bio, NULL, NULL, NULL);
if (!skey) {
printf("Failed reading pem key\n");
goto deinit;
}
out = BIO_new(BIO_s_mem());
// TODO: The current version (1.1.1) of OpenSSL doesn't support AES GCM, change the cypher to GCM from CBC as soon as there's support for it
if (!PEM_write_bio_PKCS8PrivateKey(out, skey, EVP_aes_256_cbc(), (char *) passwd, (int) strlen(passwd), NULL, NULL)) {
printf("Failed writing pem key\n");
goto deinit;
}
bio_to_str(out, &(res->res));
deinit:
temp = create_list_of_errors();
res->errors = temp->arr;
res->num_errors = temp->size;
free(temp);
BIO_free(out);
BIO_free(rec_cert_bio);
EVP_PKEY_free(skey);
return res;
}
Loading