#include <openssl/evp.h>
#include <libcitadel.h>
-
+// This utility function is used by the body canonicalizer
char *dkim_rtrim(char *str) {
char *end;
int len = strlen(str);
}
+// This utility function is used by the body canonicalizer
char *dkim_rtrim_lines(char *str) {
char *end;
int len = strlen(str);
}
-// Second step to canonicalize a block of headers as per the "relaxed" algorithm.
+// First step to canonicalize a block of headers as per the "relaxed" algorithm.
// Unfold all headers onto single lines.
void dkim_unfold_headers(StrBuf *unfolded_headers) {
char *headers_start = (char *)ChrPtr(unfolded_headers);
}
char *end_of_this_line = strstr(ptr, "\r\n");
+ // replace all multiple whitespace runs with a single space
+ int replaced_something;
+ do {
+ replaced_something = 0;
+ char *double_space = strstr(ptr, " "); // space-space?
+ if (!double_space) {
+ double_space = strstr(ptr, " \t"); // space-tab?
+ }
+ if (!double_space) {
+ double_space = strstr(ptr, "\t "); // tab-space?
+ }
+ if (!double_space) {
+ double_space = strstr(ptr, "\t\t"); // tab-tab?
+ }
+ if (double_space) {
+ StrBufReplaceToken(headers, (long)(double_space-cheaders), 2, HKEY(" "));
+ ++replaced_something;
+ }
+ } while (replaced_something);
+
+ // remove whitespace at the end of the line
+ do {
+ replaced_something = 0;
+ char *trailing_space = strstr(ptr, " \r\n"); // line ends in a space?
+ if (!trailing_space) { // no?
+ trailing_space = strstr(ptr, "\t\r\n"); // how about a tab?
+ }
+ if (trailing_space) {
+ StrBufReplaceToken(headers, (long)(trailing_space-cheaders), 3, HKEY("\r\n"));
+ ++replaced_something;
+ }
+ } while (replaced_something);
+
// Convert header field names to all lower case
for (char *c = start_of_this_line; c<colon; ++c) {
cheaders[c-cheaders] = tolower(cheaders[c-cheaders]);
}
+// Supplied with a PEM-encoded PKCS#7 private key, that might also have newlines replaced with underscores, return an EVP_PKEY.
+// Caller is responsible for freeing it.
+EVP_PKEY *dkim_import_key(char *pkey_in) {
+
+ if (!pkey_in) {
+ return(NULL);
+ }
+
+ // Citadel Server stores the private key in PEM-encoded PKCS#7 format, but with all newlines replaced by underscores.
+ // Fix that before we try to decode it.
+ char *pkey_with_newlines = strdup(pkey_in);
+ if (!pkey_with_newlines) {
+ return(NULL);
+ }
+ char *sp;
+ while (sp = strchr(pkey_with_newlines, '_')) {
+ *sp = '\n';
+ }
+
+ // Load the private key into an OpenSSL "BIO" structure
+ BIO *bufio = BIO_new_mem_buf((void *)pkey_with_newlines, strlen(pkey_with_newlines));
+ if (bufio == NULL) {
+ syslog(LOG_ERR, "dkim: BIO_new_mem_buf() failed");
+ free(pkey_with_newlines);
+ return(NULL);
+ }
+
+ // Now import the private key
+ EVP_PKEY *pkey = NULL; // Don't combine this line with the next one. It will barf.
+ pkey = PEM_read_bio_PrivateKey(
+ bufio, // BIO to read the private key from
+ &pkey, // pointer to EVP_PKEY structure
+ NULL, // password callback - can be NULL
+ NULL // parameter passed to callback or password if callback is NULL
+ );
+
+ free(pkey_with_newlines);
+ BIO_free(bufio);
+
+ if (pkey == NULL) {
+ syslog(LOG_ERR, "dkim: PEM_read_bio_PrivateKey() failed with error 0x%lx", ERR_get_error());
+ }
+
+ return(pkey);
+}
+
+
+// Get the public key from our DKIM signing pair.
+// Returns a string that must be freed by the caller.
+char *dkim_get_public_key(EVP_PKEY *pkey) {
+ char *b64key = NULL;
+ EVP_PKEY_CTX *ctx;
+ ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+ if (ctx) {
+ BIO *bio = NULL;
+ bio = BIO_new(BIO_s_mem());
+ if (bio) {
+ PEM_write_bio_PUBKEY(bio, pkey);
+ b64key = malloc(4096);
+ if (b64key) {
+ size_t readbytes;
+ BIO_read_ex(bio, b64key, 4096, &readbytes);
+ b64key[readbytes] = 0;
+
+ // strip the header
+ if (!strncasecmp(b64key, HKEY("-----BEGIN PUBLIC KEY-----\n"))) {
+ strcpy(b64key, &b64key[27]);
+ }
+
+ // strip the footer
+ char *foot = strstr(b64key, "\n-----END PUBLIC KEY-----");
+ if (foot) {
+ *foot = 0;
+ }
+
+ // remove newlines
+ char *nl;
+ while (nl = strchr(b64key, '\n')) {
+ strcpy(nl, nl+1);
+ }
+ }
+ BIO_free(bio);
+ }
+ EVP_PKEY_CTX_free(ctx);
+ }
+ return(b64key);
+}
+
// DKIM-sign an email, supplied as a full RFC2822-compliant message stored in a StrBuf
void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector) {
int i = 0;
return;
}
+ // Import the private key
+ EVP_PKEY *pkey = dkim_import_key(pkey_in);
+ if (pkey == NULL) {
+ return;
+ }
+
// find the break between headers and body
size_t msglen = StrLength(email); // total length of message (headers + body)
// hash of the canonicalized body
unsigned char *body_hash = malloc(SHA256_DIGEST_LENGTH);
- SHA256((unsigned char*)relaxed_body, relaxed_body_len, body_hash);
+ SHA256((unsigned char *)relaxed_body, relaxed_body_len, body_hash);
free(relaxed_body); // all we need now is the hash
relaxed_body = NULL;
// 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.
- // Load the private key into an OpenSSL "BIO" structure
- BIO *bufio = BIO_new_mem_buf((void*)pkey_in, strlen(pkey_in));
- if (bufio == NULL) {
- syslog(LOG_ERR, "dkim: BIO_new_mem_buf() failed");
- abort();
- }
-
- // Now import the private key
- EVP_PKEY *pkey = NULL; // Don't combine this line with the next one. It will barf.
- pkey = PEM_read_bio_PrivateKey(
- bufio, // BIO to read the private key from
- &pkey, // pointer to EVP_PKEY structure
- NULL, // password callback - can be NULL
- NULL // parameter passed to callback or password if callback is NULL
- );
- if (pkey == NULL) {
- syslog(LOG_ERR, "dkim: PEM_read_bio_PrivateKey() failed");
- abort();
- }
- BIO_free(bufio); // Don't need this anymore, we have `pkey` now
-
// The hashing/signing library calls are documented at https://wiki.openssl.org/index.php/EVP_Signing_and_Verifying
// NOTE: EVP_DigestSign*() functions are supplied with the actual data to be hashed and signed.
// That means we don't hash it first, otherwise we would be signing double-hashed (and therefore wrong) data.
EVP_MD_CTX_free(mdctx);
// This is an optional routine to verify our own signature.
- // The test program in tests/dkimtester enables it. It is not enabled during server operation.
+ // The test program in tests/dkimtester enables it. It is not enabled in Citadel Server.
#ifdef DKIM_VERIFY_SIGNATURE
mdctx = EVP_MD_CTX_new();
if (mdctx) {
// And we're done!
}
+
+