#include <openssl/evp.h>
#include <libcitadel.h>
#include "../../config.h"
-#include "../../internet_addressing.h"
-
// This utility function is used by the body canonicalizer
char *dkim_rtrim(char *str) {
}
+// 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");
+ }
+
+ return(pkey);
+}
+
+
// 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)
// 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.
}
+#ifndef DKIM_VERIFY_SIGNATURE
// Generate a private key and selector for DKIM if needed. This is called during server startup.
void dkim_init(void) {
char *dkim_private_key = CtdlGetConfigStr("dkim_private_key");
- if (dkim_private_key) {
+ if (!IsEmptyStr(dkim_private_key)) {
syslog(LOG_DEBUG, "dkim: private key exists and will continue to be used.");
}
else {
// If the DKIM key, DKIM selector, or set of signing domains has changed, we need to tell the administrator about it.
-void dkim_check_advisory(void) {
+void dkim_check_advisory(char *inetcfg_in) {
// If there is no DKIM ... there is nothing to discuss
if (IsEmptyStr(CtdlGetConfigStr("dkim_private_key"))) return;
StrBufAppendBufPlain(hashsrc, CtdlGetConfigStr("dkim_private_key"), strlen(CtdlGetConfigStr("dkim_private_key")), 0);
StrBufAppendBufPlain(hashsrc, CtdlGetConfigStr("dkim_selector"), strlen(CtdlGetConfigStr("dkim_selector")), 0);
- char *ptr = inetcfg;
+ char *ptr = inetcfg_in;
while (ptr && *ptr) {
char *sep = strchr(ptr, '|');
if (sep && !strncasecmp(sep+1, HKEY("localhost"))) {
" \n"
);
- ptr = inetcfg;
+ ptr = inetcfg_in;
while (ptr && *ptr) {
char *sep = strchr(ptr, '|');
if (sep && !strncasecmp(sep+1, HKEY("localhost"))) {
- StrBufAppendPrintf(message, " Record name : %s._domainkey.%s\n",
- CtdlGetConfigStr("dkim_selector"),
- "fixme.FIXME.com"
- );
- StrBufAppendPrintf(message, " Record type : TXT\n");
- StrBufAppendPrintf(message, " Record value: c=dkim1 etc. etc. etc.\n");
- StrBufAppendPrintf(message, " \n");
+ StrBufAppendPrintf(message, " Host name : %s._domainkey.", CtdlGetConfigStr("dkim_selector"));
+ StrBufAppendBufPlain(message, ptr, sep-ptr, 0);
+ StrBufAppendBufPlain(message, HKEY("\r\n"), 0);
+ StrBufAppendPrintf(message, " Record type: TXT\n");
+ StrBufAppendBufPlain(message, HKEY(" Value : v=DKIM1;k=rsa;p="), 0);
+
+ // figure out the public key and get it
+
+ StrBufAppendPrintf(message, "\n \n");
}
ptr = strchr(ptr, '\n');
if (ptr) ++ptr;
}
+#if 0
CtdlAideMessage(ChrPtr(message), "DKIM records");
+#endif
FreeStrBuf(&message);
}
CtdlSetConfigStr("dkim_config_hash", encoded_config_hash);
free(encoded_config_hash);
}
+#endif
&& !IsEmptyStr(dkim_private_key) // Do we have a private signing key?
&& !IsEmptyStr(dkim_selector) // and a selector to go with it?
) {
- char *pkey = strdup(dkim_private_key); // If you answered "yes" to all of the above questions,
- if (pkey) { // congratulations! We get to DKIM-sign the message!
- char *sp;
- while (sp = strchr(pkey, '_')) { // The dkim_private_key record contains our RSA private key,
- *sp = '\n'; // but we have to convert all the newlines back to underscores.
- }
- syslog(LOG_DEBUG, "smtpclient: dkim-signing with private key for selector <%s> domain <%s>",
- dkim_selector, dkim_from_domain);
- dkim_sign(s.TheMessage, pkey, dkim_from_domain, dkim_selector);
- free(pkey);
- }
+ // If you answered "yes" to all of the above questions, congratulations! We get to sign the message!
+ syslog(LOG_DEBUG, "smtpclient: dkim-signing for selector <%s> in domain <%s>", dkim_selector, dkim_from_domain);
+
+ // Remember, the dkim_sign() function is capable of handling a PEM-encoded PKCS#7 private key that
+ // has had all of its newlines replaced by underscores -- which is exactly how we store it.
+ dkim_sign(s.TheMessage,dkim_private_key, dkim_from_domain, dkim_selector);
}
// Prepare the buffer for transmittal