From 7ec4ffbdbc562c4a2909d9bf4f3093072e0e3ac1 Mon Sep 17 00:00:00 2001 From: Manuel Mausz Date: Wed, 27 Jun 2018 01:06:16 +0200 Subject: OpenSSL 1.1 compatibility This adds compatibility for OpenSSL 1.1 Since renegotiation is removed from TLS 1.3 we also removed support for authentication via client certificates (control/tlsclients). In general this is still supported by TLS 1.3 however I'm just lazy and we don't need this feature anyway. This also adds optional support for OpenSSL configuration commands for qmail-smtpd and qmail-remote. Commands are loaded from control/opensslconf. For a list of supported commands see https://www.openssl.org/docs/man1.0.2/ssl/SSL_CONF_cmd.html#SUPPORTED-CONFIGURATION-FILE-COMMANDS --- qmail-smtpd.c | 177 ++++++++++++++++++++++++++-------------------------------- 1 file changed, 80 insertions(+), 97 deletions(-) (limited to 'qmail-smtpd.c') diff --git a/qmail-smtpd.c b/qmail-smtpd.c index 18795bc..f40a4c5 100644 --- a/qmail-smtpd.c +++ b/qmail-smtpd.c @@ -71,7 +71,6 @@ char *relayclient; # define SERVERCERT "control/servercert.pem" void tls_init(); -int tls_verify(); void tls_nogateway(); int ssl_rfd = -1, ssl_wfd = -1; /* SSL_get_Xfd() are broken */ stralloc proto = {0}; @@ -508,9 +507,6 @@ int addrallowed() int r; r = rcpthosts(addr.s,str_len(addr.s)); if (r == -1) die_control(); -#ifdef TLS - if (r == 0) if (tls_verify()) r = -2; -#endif return r; } @@ -1261,19 +1257,44 @@ void smtp_tls(char *arg) * Grab well-defined DH parameters from OpenSSL, see the get_rfc* * functions in for all available primes. */ -static DH *make_dh_params(BIGNUM *(*prime)(BIGNUM *), const char *gen) +#if OPENSSL_VERSION_NUMBER < 0x10100005L +static int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) { - DH *dh = DH_new(); - if (!dh) - return NULL; - - dh->p = prime(NULL); - BN_dec2bn(&dh->g, gen); - if (!dh->p || !dh->g) { - DH_free(dh); - return NULL; - } - return dh; + /* q is optional */ + if (p == NULL || g == NULL) + return 0; + BN_free(dh->p); + BN_free(dh->q); + BN_free(dh->g); + dh->p = p; + dh->q = q; + dh->g = g; + + if (q != NULL) + dh->length = BN_num_bits(q); + + return 1; +} +#endif + +static DH *make_dh_params(BIGNUM *(*prime)(BIGNUM *)) +{ + BIGNUM *p, *g; + DH *dh = DH_new(); + if (!dh) + return NULL; + + p = prime(NULL); + g = BN_new(); + if (g != NULL) + BN_set_word(g, 2); + if (!p || !g || !DH_set0_pqg(dh, p, NULL, g)) { + DH_free(dh); + BN_free(p); + BN_free(g); + return NULL; + } + return dh; } /* Storage and initialization for DH parameters. */ @@ -1300,7 +1321,7 @@ static struct dhparam { static DH *tmp_dh_cb(SSL *ssl, int export, int keylen) { EVP_PKEY *pkey = SSL_get_privatekey(ssl); - int type = pkey ? EVP_PKEY_type(pkey->type) : EVP_PKEY_NONE; + int type = pkey ? EVP_PKEY_base_id(pkey) : EVP_PKEY_NONE; unsigned n; /* @@ -1321,7 +1342,7 @@ static DH *tmp_dh_cb(SSL *ssl, int export, int keylen) for (n = 0; n < sizeof(dhparams)/sizeof(dhparams[0]); n++) { if (keylen >= dhparams[n].min) { if (dhparams[n].dh == NULL) - dhparams[n].dh = make_dh_params(dhparams[n].prime, "2"); + dhparams[n].dh = make_dh_params(dhparams[n].prime); return dhparams[n].dh; } } @@ -1338,6 +1359,8 @@ static DH *ssl_dh_GetParamFromFile(const char *file) return NULL; dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); BIO_free(bio); + if (!dh) + (void)ERR_get_error(); return dh; } @@ -1351,6 +1374,8 @@ static EC_GROUP *ssl_ec_GetParamFromFile(const char *file) return NULL; group = PEM_read_bio_ECPKParameters(bio, NULL, NULL, NULL); BIO_free(bio); + if (!group) + (void)ERR_get_error(); return group; } @@ -1378,85 +1403,6 @@ void tls_out(const char *s1, const char *s2) } void tls_err(const char *s) { tls_out(s, ssl_error()); if (smtps) die_read(); } -int tls_verify() -{ - stralloc clients = {0}; - struct constmap mapclients; - - if (!ssl || relayclient || ssl_verified) return 0; - ssl_verified = 1; /* don't do this twice */ - - /* request client cert to see if it can be verified by one of our CAs - * and the associated email address matches an entry in tlsclients */ - switch (control_readfile(&clients, "control/tlsclients", 0)) - { - case 1: - if (constmap_init(&mapclients, clients.s, clients.len, 0)) { - /* if CLIENTCA contains all the standard root certificates, a - * 0.9.6b client might fail with SSL_R_EXCESSIVE_MESSAGE_SIZE; - * it is probably due to 0.9.6b supporting only 8k key exchange - * data while the 0.9.6c release increases that limit to 100k */ - STACK_OF(X509_NAME) *sk = SSL_load_client_CA_file(CLIENTCA); - if (sk) { - SSL_set_client_CA_list(ssl, sk); - SSL_set_verify(ssl, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, NULL); - break; - } - constmap_free(&mapclients); - } - case 0: alloc_free(clients.s); return 0; - case -1: die_control(); - } - - if (ssl_timeoutrehandshake(timeout, ssl_rfd, ssl_wfd, ssl) <= 0) { - const char *err = ssl_error_str(); - tls_out("rehandshake failed", err); die_read(); - } - - do { /* one iteration */ - X509 *peercert; - X509_NAME *subj; - stralloc email = {0}; - - int n = SSL_get_verify_result(ssl); - if (n != X509_V_OK) - { ssl_verify_err = X509_verify_cert_error_string(n); break; } - peercert = SSL_get_peer_certificate(ssl); - if (!peercert) break; - - subj = X509_get_subject_name(peercert); - n = X509_NAME_get_index_by_NID(subj, NID_pkcs9_emailAddress, -1); - if (n >= 0) { - const ASN1_STRING *s = X509_NAME_get_entry(subj, n)->value; - if (s) { email.len = s->length; email.s = s->data; } - } - - if (email.len <= 0) - ssl_verify_err = "contains no email address"; - else if (!constmap(&mapclients, email.s, email.len)) - ssl_verify_err = "email address not in my list of tlsclients"; - else { - /* add the cert email to the proto if it helped allow relaying */ - --proto.len; - if (!stralloc_cats(&proto, "\n (cert ") /* continuation line */ - || !stralloc_catb(&proto, email.s, email.len) - || !stralloc_cats(&proto, ")") - || !stralloc_0(&proto)) die_nomem(); - relayclient = ""; - protocol = proto.s; - } - - X509_free(peercert); - } while (0); - constmap_free(&mapclients); alloc_free(clients.s); - - /* we are not going to need this anymore: free the memory */ - SSL_set_client_CA_list(ssl, NULL); - SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL); - - return relayclient ? 1 : 0; -} - void tls_init() { SSL *myssl; @@ -1525,6 +1471,7 @@ void tls_init() SSL_set_cipher_list(myssl, ciphers); alloc_free(saciphers.s); + //TODO: we shouldn't use hardcoded DH: see https://weakdh.org/ SSL_set_tmp_dh_callback(myssl, tmp_dh_cb); /* try to read DH parameters from certificate */ @@ -1550,6 +1497,42 @@ void tls_init() EC_KEY_free(eckey); #endif +#if OPENSSL_VERSION_NUMBER >= 0x10100005L + stralloc opensslconf = {0}; + if (control_readfile(&opensslconf, "control/opensslconf", 0) == -1) + { SSL_free(myssl); die_control(); } + if (opensslconf.len) { + SSL_CONF_CTX *cctx = SSL_CONF_CTX_new(); + SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE); + SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SERVER); + SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CERTIFICATE); + SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SHOW_ERRORS); + SSL_CONF_CTX_set_ssl(cctx, myssl); + + int i, j, next = 0; + char *cmd, * arg; + for (i = 0; i < opensslconf.len; i += next) { + cmd = opensslconf.s + i; + next = str_len(cmd) + 1; + + j = str_chr(cmd, ' '); + arg = cmd + j; + while (*arg == ' ') ++arg; + cmd[j] = 0; + + if (SSL_CONF_cmd(cctx, cmd, arg) <= 0) { + enew(); eout("opensslconf \""); eout(cmd); eout(" "); eout(arg); + eout("\" failed: "); eout(ssl_error()); eout("\n"); eflush(); + tls_out("OpenSSL", "unable to initialize ssl"); + SSL_free(myssl); + die_read(); + } + } + + (void)SSL_CONF_CTX_finish(cctx); + } +#endif + SSL_set_rfd(myssl, ssl_rfd = substdio_fileno(&ssin)); SSL_set_wfd(myssl, ssl_wfd = substdio_fileno(&ssout)); -- cgit v1.2.3