]> code.citadel.org Git - citadel.git/blobdiff - citadel/server/modules/smtp/dkim.c
Remove preprocessor tests for OpenSSL. It's a requirement.
[citadel.git] / citadel / server / modules / smtp / dkim.c
index 248a89564182c8a0fb81fdd6a7b7debbbf032c2d..76a7a61122f1a55f77938df50cf0f4edf532cda8 100644 (file)
@@ -30,7 +30,7 @@
 #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);
@@ -85,6 +85,7 @@ void dkim_wrap_header_strbuf(StrBuf *header_in) {
 }
 
 
+// This utility function is used by the body canonicalizer
 char *dkim_rtrim_lines(char *str) {
        char *end;
        int len = strlen(str);
@@ -209,7 +210,7 @@ char *dkim_canonicalize_body(char *body) {
 }
 
 
-// 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);
@@ -255,6 +256,39 @@ void dkim_canonicalize_unfolded_headers(StrBuf *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]);
@@ -325,6 +359,94 @@ void dkim_final_header_list(char *header_list, size_t header_list_size, StrBuf *
 }
 
 
+// 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;
@@ -333,6 +455,12 @@ void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector) {
                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)
 
@@ -354,7 +482,7 @@ void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector) {
 
        // 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;
 
@@ -399,27 +527,6 @@ void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector) {
        // 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.
@@ -470,7 +577,7 @@ void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector) {
        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) {
@@ -510,3 +617,5 @@ void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector) {
 
        // And we're done!
 }
+
+