diff --git a/conf.c b/conf.c index 3e3c7d3..c4eb247 100644 --- a/conf.c +++ b/conf.c @@ -218,7 +218,22 @@ parse_conf(const char *config_path) config.masquerade_user = user; } else if (strcmp(word, "STARTTLS") == 0 && data == NULL) config.features |= STARTTLS; - else if (strcmp(word, "OPPORTUNISTIC_TLS") == 0 && data == NULL) + else if (strcmp(word, "FINGERPRINT") == 0) { + if (strlen(data) != SHA256_DIGEST_LENGTH * 2) { + errlogx(EX_CONFIG, "invalid sha256 fingerprint length"); + } + unsigned char *fingerprint = malloc(SHA256_DIGEST_LENGTH); + if (fingerprint == NULL) { + errlogx(EX_CONFIG, "fingerprint allocation failed"); + } + for (unsigned int i = 0; i < SHA256_DIGEST_LENGTH; i++) { + if(sscanf(data + 2 * i, "%02hhx", &fingerprint[i]) != 1) { + errlogx(EX_CONFIG, "failed to read fingerprint"); + } + } + free(data); + config.fingerprint = fingerprint; + } else if (strcmp(word, "OPPORTUNISTIC_TLS") == 0 && data == NULL) config.features |= TLS_OPP; else if (strcmp(word, "SECURETRANSFER") == 0 && data == NULL) config.features |= SECURETRANS; diff --git a/crypto.c b/crypto.c index f9bc360..c37345d 100644 --- a/crypto.c +++ b/crypto.c @@ -78,6 +78,29 @@ init_cert_file(SSL_CTX *ctx, const char *path) return (0); } +int +verify_server_fingerprint(const X509 *cert) +{ + unsigned char fingerprint[EVP_MAX_MD_SIZE] = {0}; + unsigned int fingerprint_len = 0; + if(!X509_digest(cert, EVP_sha256(), fingerprint, &fingerprint_len)) { + syslog(LOG_WARNING, "failed to load fingerprint of server's certicate: %s", + ssl_errstr()); + return (1); + } + if(fingerprint_len != SHA256_DIGEST_LENGTH) { + syslog(LOG_WARNING, "sha256 fingerprint has unexpected length of %d bytes", + fingerprint_len); + return (1); + } + if(memcmp(fingerprint, config.fingerprint, SHA256_DIGEST_LENGTH) != 0) { + syslog(LOG_WARNING, "fingerprints do not match"); + return (1); + } + syslog(LOG_DEBUG, "verified server's fingerprint"); + return (0); +} + int smtp_init_crypto(int fd, int feature, struct smtp_features* features) { @@ -173,6 +196,11 @@ smtp_init_crypto(int fd, int feature, struct smtp_features* features) if (cert == NULL) { syslog(LOG_WARNING, "remote delivery deferred: Peer did not provide certificate: %s", ssl_errstr()); + return (1); + } + if(config.fingerprint != NULL && verify_server_fingerprint(cert)) { + X509_free(cert); + return (1); } X509_free(cert); diff --git a/dma.8 b/dma.8 index d29c85b..ea18292 100644 --- a/dma.8 +++ b/dma.8 @@ -226,6 +226,9 @@ Uncomment if you want TLS/SSL secured transfer. Uncomment if you want to use STARTTLS. Only useful together with .Sq SECURETRANS . +.It Ic FINGERPRINT Xo +Pin the server certificate by specifying its SHA256 fingerprint. +Only makes sense if you use a smarthost. .It Ic OPPORTUNISTIC_TLS Xo (boolean, default=commented) .Xc diff --git a/dma.c b/dma.c index 4006bce..d62bf2c 100644 --- a/dma.c +++ b/dma.c @@ -85,6 +85,7 @@ struct config config = { .mailname = NULL, .masquerade_host = NULL, .masquerade_user = NULL, + .fingerprint = NULL, }; diff --git a/dma.conf b/dma.conf index 6d5a51c..fa95fc1 100644 --- a/dma.conf +++ b/dma.conf @@ -25,6 +25,10 @@ # SECURETRANSFER) #STARTTLS +# Pin the server certificate by specifying its SHA256 fingerprint. +# Only makes sense if you use a smarthost. +#FINGERPRINT 1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF + # Uncomment if you have specified STARTTLS above and it should be allowed # to fail ("opportunistic TLS", use an encrypted connection when available # but allow an unencrypted one to servers that do not support it) diff --git a/dma.h b/dma.h index ed0d0fc..5841362 100644 --- a/dma.h +++ b/dma.h @@ -138,6 +138,7 @@ struct config { const char *mailname; const char *masquerade_host; const char *masquerade_user; + const unsigned char *fingerprint; /* XXX does not belong into config */ SSL *ssl;