#include <ctype.h>
#include <string.h>
#include <time.h>
+#include <syslog.h>
+#include <assert.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
#include <openssl/engine.h>
#include <openssl/pem.h>
#include <openssl/buffer.h>
#include <openssl/err.h>
+#include <openssl/evp.h>
#include <libcitadel.h>
char *body_ptr = strstr(ChrPtr(email), "\r\n\r\n");
if (body_ptr == NULL) {
- fprintf(stderr, "dkim: this message cannot be signed because it has no body\n");
+ syslog(LOG_DEBUG, "dkim: this message cannot be signed because it has no body");
return;
}
size_t body_offset = body_ptr - ChrPtr(email); // offset at which message body begins
StrBuf *header_block = NewStrBufPlain(ChrPtr(email), body_offset+2); // headers only (the +2 makes it include final CRLF)
- //fprintf(stderr, "Supplied headers:\n-----\n%s-----\n", ChrPtr(header_block));
+ //syslog(LOG_DEBUG, "Supplied headers:\n-----\n%s-----\n", ChrPtr(header_block));
// This removes the headers from the supplied email buffer. We MUST put them back in later.
StrBufCutLeft(email, body_offset+4); // The +4 makes it NOT include the CRLFCRLF
- //fprintf(stderr, "Body:\n-----\n%s-----\n", ChrPtr(email));
+ //syslog(LOG_DEBUG, "Body:\n-----\n%s-----\n", ChrPtr(email));
// Apply the "relaxed" canonicalization to the message body
char *relaxed_body = dkim_canonicalize_body((char *)ChrPtr(email));
int relaxed_body_len = strlen(relaxed_body);
- //fprintf(stderr, "Canonicalized body:\n-----\n%s-----\n", relaxed_body);
+ //syslog(LOG_DEBUG, "Canonicalized body:\n-----\n%s-----\n", relaxed_body);
// hash of the canonicalized body
- unsigned char *uhash = malloc(SHA256_DIGEST_LENGTH);
- SHA256((unsigned char*)relaxed_body, relaxed_body_len, uhash);
- free(relaxed_body); // don't need this anymore
+ unsigned char *body_hash = malloc(SHA256_DIGEST_LENGTH);
+ SHA256((unsigned char*)relaxed_body, relaxed_body_len, body_hash);
+ free(relaxed_body); // all we need now is the hash
relaxed_body = NULL;
// base64 encode the body hash
- char *encoded_body_hash = dkim_base64_encode(uhash, SHA256_DIGEST_LENGTH);
- free(uhash);
+ char *encoded_body_hash = dkim_base64_encode(body_hash, SHA256_DIGEST_LENGTH);
+ free(body_hash); // all we need now is the encoded hash
// In the test harness email, the body hash should be 2PMBIMGyD2GZlndKI2MRsbhiMr6jD5rCxhq+mCvY7os=
- // fprintf(stderr, "Body hash: %s\n", encoded_body_hash);
+ syslog(LOG_DEBUG, "dkim: body hash: %s", encoded_body_hash);
// "relaxed" header canonicalization, step 1 : unfold the headers
StrBuf *unfolded_headers = NewStrBufDup(header_block);
char *header_list = "from:to:cc:reply-to:subject:date:list-unsubscribe:list-unsubscribe-post";
dkim_reduce_canonicalized_headers(unfolded_headers, header_list);
- // fprintf(stderr, "Canonicalized headers:\n-----\n%s-----\n", (char *)ChrPtr(unfolded_headers));
+ //syslog(LOG_DEBUG, "%s", (char *)ChrPtr(unfolded_headers));
+ //syslog(LOG_DEBUG, "\033[34m-----(final_len=%d)\033[0m\n", StrLength(unfolded_headers));
// Make a new header list containing only the ones we actually have.
char final_header_list[1024];
dkim_final_header_list(final_header_list, sizeof(final_header_list), unfolded_headers);
+ syslog(LOG_DEBUG, "dkim: final header list: %s", final_header_list);
// create DKIM header
time_t now = time(NULL); // signature timestamp
StrBuf *dkim_header = NewStrBuf();
StrBufPrintf(dkim_header,
- "v=1; a=rsa-sha256; s=%s; d=%s; l=%d; t=%ld; c=relaxed/relaxed; h=%s; bh=%s; b=",
+ "v=1; a=rsa-sha256; s=%s; d=%s; l=%d; t=%d; c=relaxed/relaxed; h=%s; bh=%s; b=",
selector,
domain,
relaxed_body_len,
free(encoded_body_hash); // Don't need this anymore either.
// Add the initial DKIM header (which is still missing the value after "b=") to the headers to be signed.
- // As far as I can tell, RFC6376 does *not* want us to include any "\r\n" after "b="
+ // RFC6376 3.7 tells us NOT to include CRLF after "b="
StrBufAppendBufPlain(unfolded_headers, HKEY("dkim-signature:"), 0);
StrBufAppendBuf(unfolded_headers, dkim_header, 0);
- // fprintf(stderr, "Canonicalized headers:\n-----\n%s\n-----\n", (char *)ChrPtr(unfolded_headers));
+ //syslog(LOG_DEBUG, "%s", (char *)ChrPtr(unfolded_headers));
+ //syslog(LOG_DEBUG, "\n\033[34m-----(final_len=%d)\033[0m\n", StrLength(unfolded_headers));
+
+ // Compute a hash of the canonicalized headers, and then sign that hash with our private key.
+ // RFC6376 says that we hash and sign everything up to the "b=" and then we'll add the rest at the end.
- // Compute a hash of the canonicalized headers. RFC6376 says that we hash and sign everything up to the "b="
- // and then we'll add the rest at the end.
- unsigned char *headers_hash = malloc(SHA256_DIGEST_LENGTH);
- SHA256((unsigned char*)ChrPtr(unfolded_headers), StrLength(unfolded_headers), headers_hash);
- FreeStrBuf(&unfolded_headers); // All we need now is the hash.
+ // Don't need to do this -- the EVP_DigestSign* functions do both the hashing and signature for us.
+ //unsigned char *headers_hash = malloc(SHA256_DIGEST_LENGTH);
+ //SHA256((unsigned char*)ChrPtr(unfolded_headers), StrLength(unfolded_headers), headers_hash);
// Load the private key into an OpenSSL "BIO" structure
BIO *bufio = BIO_new_mem_buf((void*)pkey_in, strlen(pkey_in));
if (bufio == NULL) {
- fprintf(stderr, "dkim: BIO_new_mem_buf() failed\n");
+ syslog(LOG_ERR, "dkim: BIO_new_mem_buf() failed");
abort();
}
NULL // parameter passed to callback or password if callback is NULL
);
if (pkey == NULL) {
- fprintf(stderr, "dkim: PEM_read_bio_PrivateKey() failed\n");
+ syslog(LOG_ERR, "dkim: PEM_read_bio_PrivateKey() failed");
abort();
}
BIO_free(bufio); // Don't need this anymore, we have `pkey` now
- // Everything I ever needed to know I learned from https://wiki.openssl.org/index.php/EVP_Signing_and_Verifying
- EVP_MD_CTX *mdctx = NULL;
+
+ // ok, here's where we are on thursday night, may 9.
+ // It's correctly signing *something* , because when I change the private key, the test utility
+ // recognizes that the signature doesn't match the public key. So we're likely either creating a bad
+ // digest, or signing it wrong.
+
+ // The hashing/signing library calls are documented at https://wiki.openssl.org/index.php/EVP_Signing_and_Verifying
// Create the Message Digest Context
- mdctx = EVP_MD_CTX_create();
+ EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
if (mdctx == NULL) {
- fprintf(stderr, "dkim: EVP_MD_CTX_create() failed\n");
+ syslog(LOG_ERR, "dkim: EVP_MD_CTX_create() failed with error 0x%lx", ERR_get_error());
abort();
}
// Initialize the DigestSign operation using SHA-256 algorithm
if (EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, pkey) != 1) {
- fprintf(stderr, "dkim: EVP_DigestSignInit() failed\n");
+ syslog(LOG_ERR, "dkim: EVP_DigestSignInit() failed with error 0x%lx", ERR_get_error());
abort();
}
- // free the private key (we don't need it past here)
- EVP_PKEY_free(pkey);
-
// Call update with the "message" (the canonicalized headers)
- if (EVP_DigestSignUpdate(mdctx, headers_hash, SHA256_DIGEST_LENGTH) != 1) {
- fprintf(stderr, "dkim: EVP_DigestSignUpdate() failed\n");
+ // Hey ... I think we need to update with the actual data, not a pre-calculated hash, since it does its own hash
+ if (EVP_DigestSignUpdate(mdctx, (char *)ChrPtr(unfolded_headers), StrLength(unfolded_headers)) != 1) {
+ syslog(LOG_ERR, "dkim: EVP_DigestSignUpdate() failed with error 0x%lx", ERR_get_error());
abort();
}
- free(headers_hash);
-
- // Finalize the DigestSign operation.
+ // Finalize the DigestSign operation.
// First call EVP_DigestSignFinal with a NULL sig parameter to obtain the length of the signature.
// Length is returned in slen
size_t slen;
if (EVP_DigestSignFinal(mdctx, NULL, &slen) != 1) {
- fprintf(stderr, "dkim: EVP_DigestSignFinal() failed\n");
+ syslog(LOG_ERR, "dkim: EVP_DigestSignFinal() failed");
abort();
}
+ // Sanity check. The signature length should be the same as the size of the private key.
+ assert(slen == EVP_PKEY_size(pkey));
+
// Allocate memory for the signature based on size in slen
- unsigned char *sig = NULL;
- sig = OPENSSL_malloc(slen);
+ unsigned char *sig = OPENSSL_malloc(slen);
if (sig == NULL) {
- fprintf(stderr, "dkim: OPENSSL_malloc() failed\n");
+ syslog(LOG_ERR, "dkim: OPENSSL_malloc() failed");
abort();
}
// Obtain the signature
if (EVP_DigestSignFinal(mdctx, sig, &slen) != 1) {
- fprintf(stderr, "dkim: EVP_DigestSignFinal() failed\n");
+ syslog(LOG_ERR, "dkim: EVP_DigestSignFinal() failed");
abort();
}
+
+ // Verify the signature. We can comment this bit out when we're done.
+ EVP_MD_CTX *md_ctx_verify = EVP_MD_CTX_new();
+ if (md_ctx_verify) {
+ EVP_DigestVerifyInit(md_ctx_verify, NULL, EVP_sha256(), NULL, pkey);
+ EVP_DigestVerifyUpdate(md_ctx_verify, (char *)ChrPtr(unfolded_headers), StrLength(unfolded_headers));
+ syslog(LOG_DEBUG, "Verify signature: %s", (EVP_DigestVerifyFinal(md_ctx_verify, sig, slen)) ? "PASS" : "FAIL");
+ EVP_MD_CTX_free(md_ctx_verify);
+ }
+ // End verify
+
+ // ok we're done with all this crap now
EVP_MD_CTX_free(mdctx);
+ EVP_PKEY_free(pkey);
+ FreeStrBuf(&unfolded_headers);
// base64 encode the signature
char *sig_b64 = dkim_base64_encode(sig, slen);
int sig_b64_len = strlen(sig_b64);
free(sig); // Free the raw signature, keep the b64-encoded one.
- // We should probably wrap the dkim signature here.
+ // wrap dkim header
+ // sig_b64 = dkim_wrap(sig_b64, sig_b64_len);
- // Add the signature to the original header block. Also append the extra CRLF indicating end of headers.
- StrBufAppendPrintf(header_block, "DKIM-Signature: %s%s\r\n\r\n", (char *)ChrPtr(dkim_header), sig_b64);
+ StrBuf *output_msg = NewStrBuf();
+ StrBufPrintf(output_msg, "DKIM-Signature: %s%s\r\n", (char *)ChrPtr(dkim_header), sig_b64);
FreeStrBuf(&dkim_header); // These were added to the final header block.
free(sig_b64); // So we don't need them anymore.
-
- // Append the body.
- StrBufAppendBuf(header_block, email, 0);
- SwapBuffers(header_block, email);
+ StrBufAppendBuf(output_msg, header_block, 0);
FreeStrBuf(&header_block);
+ StrBufAppendPrintf(output_msg, "\r\n");
+ StrBufAppendBuf(output_msg, email, 0);
+ SwapBuffers(output_msg, email);
+ FreeStrBuf(&output_msg);
// And we're done!
}