diff --git a/postfix/HISTORY b/postfix/HISTORY index 0aa87e917..cd536e6ab 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -13255,6 +13255,18 @@ Apologies for any names omitted. multi-valued macro. Files: util/sys_defs.h, util/events.c, master/multi_server.c, *qmgr/qmgr_transport.c. +20070220 + + Work-around: Disable SSL/TLS ciphers when the underlying + symmetric algorithm is not available in the OpenSSL crypto + library at the required bit strength. Problem observed with + SunOS 5.10's bundled OpenSSL 0.9.7 and AES 256. Also possible + with OpenSSL 0.9.8 and CAMELLIA 256. Root cause fixed in + upcoming OpenSSL 0.9.7m, 0.9.8e and 0.9.9 releases. Victor + Duchovni, Morgan Stanley. Files: src/smtp/smtp_proto.c, + src/smtpd/smtpd.c, src/tls/tls.h, src/tls/tls_client.c, + src/tls/tls_misc.c and src/tls/tls_server.c. + Wish list: Update message content length when adding/removing headers. diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html index b18668161..9933040cf 100644 --- a/postfix/html/postconf.5.html +++ b/postfix/html/postconf.5.html @@ -11772,7 +11772,7 @@ setting.

tls_null_cipherlist -(default: !aNULL:eNULL+kRSA)
+(default: eNULL:!aNULL)

The OpenSSL cipherlist for "NULL" grade ciphers that provide authentication without encryption. This defines the meaning of the "null" diff --git a/postfix/html/smtp.8.html b/postfix/html/smtp.8.html index 418e3d1ef..7d4246aab 100644 --- a/postfix/html/smtp.8.html +++ b/postfix/html/smtp.8.html @@ -441,7 +441,7 @@ SMTP(8) SMTP(8) The OpenSSL cipherlist for "EXPORT" or higher grade ciphers. - tls_null_cipherlist (!aNULL:eNULL+kRSA) + tls_null_cipherlist (eNULL:!aNULL) The OpenSSL cipherlist for "NULL" grade ciphers that provide authentication without encryption. diff --git a/postfix/html/smtpd.8.html b/postfix/html/smtpd.8.html index b4b0a63ed..38b474b63 100644 --- a/postfix/html/smtpd.8.html +++ b/postfix/html/smtpd.8.html @@ -471,7 +471,7 @@ SMTPD(8) SMTPD(8) The OpenSSL cipherlist for "EXPORT" or higher grade ciphers. - tls_null_cipherlist (!aNULL:eNULL+kRSA) + tls_null_cipherlist (eNULL:!aNULL) The OpenSSL cipherlist for "NULL" grade ciphers that provide authentication without encryption. diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5 index 3522cd1ef..f35515d79 100644 --- a/postfix/man/man5/postconf.5 +++ b/postfix/man/man5/postconf.5 @@ -7148,7 +7148,7 @@ certificates). You are strongly encouraged to not change this setting. .PP This feature is available in Postfix 2.3 and later. -.SH tls_null_cipherlist (default: !aNULL:eNULL+kRSA) +.SH tls_null_cipherlist (default: eNULL:!aNULL) The OpenSSL cipherlist for "NULL" grade ciphers that provide authentication without encryption. This defines the meaning of the "null" setting in smtpd_mandatory_tls_ciphers, smtp_tls_mandatory_ciphers and diff --git a/postfix/man/man8/smtp.8 b/postfix/man/man8/smtp.8 index 9aebf5663..198d25c7d 100644 --- a/postfix/man/man8/smtp.8 +++ b/postfix/man/man8/smtp.8 @@ -362,7 +362,7 @@ The OpenSSL cipherlist for "MEDIUM" or higher grade ciphers. The OpenSSL cipherlist for "LOW" or higher grade ciphers. .IP "\fBtls_export_cipherlist (ALL:+RC4:@STRENGTH)\fR" The OpenSSL cipherlist for "EXPORT" or higher grade ciphers. -.IP "\fBtls_null_cipherlist (!aNULL:eNULL+kRSA)\fR" +.IP "\fBtls_null_cipherlist (eNULL:!aNULL)\fR" The OpenSSL cipherlist for "NULL" grade ciphers that provide authentication without encryption. .PP diff --git a/postfix/man/man8/smtpd.8 b/postfix/man/man8/smtpd.8 index 2794c906b..f76921ba0 100644 --- a/postfix/man/man8/smtpd.8 +++ b/postfix/man/man8/smtpd.8 @@ -386,7 +386,7 @@ The OpenSSL cipherlist for "MEDIUM" or higher grade ciphers. The OpenSSL cipherlist for "LOW" or higher grade ciphers. .IP "\fBtls_export_cipherlist (ALL:+RC4:@STRENGTH)\fR" The OpenSSL cipherlist for "EXPORT" or higher grade ciphers. -.IP "\fBtls_null_cipherlist (!aNULL:eNULL+kRSA)\fR" +.IP "\fBtls_null_cipherlist (eNULL:!aNULL)\fR" The OpenSSL cipherlist for "NULL" grade ciphers that provide authentication without encryption. .SH "OBSOLETE STARTTLS CONTROLS" diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index 33c9ac46f..844ecf173 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -10399,7 +10399,7 @@ strongly encouraged to not change this setting.

This feature is available in Postfix 2.3 and later.

-%PARAM tls_null_cipherlist !aNULL:eNULL+kRSA +%PARAM tls_null_cipherlist eNULL:!aNULL

The OpenSSL cipherlist for "NULL" grade ciphers that provide authentication without encryption. This defines the meaning of the "null" diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h index 75cd22c99..ab4399f98 100644 --- a/postfix/src/global/mail_params.h +++ b/postfix/src/global/mail_params.h @@ -2692,7 +2692,7 @@ extern char *var_tls_low_clist; extern char *var_tls_export_clist; #define VAR_TLS_NULL_CLIST "tls_null_cipherlist" -#define DEF_TLS_NULL_CLIST "!aNULL:eNULL+kRSA" +#define DEF_TLS_NULL_CLIST "eNULL:!aNULL" extern char *var_tls_null_clist; /* diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 9d9e76760..553171c02 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,7 +20,7 @@ * Patches change both the patchlevel and the release date. Snapshots have no * patchlevel; they change the release date only. */ -#define MAIL_RELEASE_DATE "20070218" +#define MAIL_RELEASE_DATE "20070221" #define MAIL_VERSION_NUMBER "2.4" #ifdef SNAPSHOT diff --git a/postfix/src/smtp/smtp.c b/postfix/src/smtp/smtp.c index bba3b3ba0..91ee7e814 100644 --- a/postfix/src/smtp/smtp.c +++ b/postfix/src/smtp/smtp.c @@ -332,7 +332,7 @@ /* The OpenSSL cipherlist for "LOW" or higher grade ciphers. /* .IP "\fBtls_export_cipherlist (ALL:+RC4:@STRENGTH)\fR" /* The OpenSSL cipherlist for "EXPORT" or higher grade ciphers. -/* .IP "\fBtls_null_cipherlist (!aNULL:eNULL+kRSA)\fR" +/* .IP "\fBtls_null_cipherlist (eNULL:!aNULL)\fR" /* The OpenSSL cipherlist for "NULL" grade ciphers that provide /* authentication without encryption. /* .PP diff --git a/postfix/src/smtp/smtp_proto.c b/postfix/src/smtp/smtp_proto.c index 1f4912bf0..ed8d3f75e 100644 --- a/postfix/src/smtp/smtp_proto.c +++ b/postfix/src/smtp/smtp_proto.c @@ -753,7 +753,7 @@ static int smtp_start_tls(SMTP_STATE *state) vstring_sprintf_append(serverid, "&p=%s", tls_protocol_names(VAR_SMTP_TLS_MAND_PROTO, session->tls_protocols)); - if (session->tls_level >= TLS_LEV_ENCRYPT && session->tls_cipherlist) + if (session->tls_level >= TLS_LEV_ENCRYPT) vstring_sprintf_append(serverid, "&c=%s", session->tls_cipherlist); tls_props.ctx = smtp_tls_ctx; diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c index cce5136cc..f38a0fe3c 100644 --- a/postfix/src/smtpd/smtpd.c +++ b/postfix/src/smtpd/smtpd.c @@ -354,7 +354,7 @@ /* The OpenSSL cipherlist for "LOW" or higher grade ciphers. /* .IP "\fBtls_export_cipherlist (ALL:+RC4:@STRENGTH)\fR" /* The OpenSSL cipherlist for "EXPORT" or higher grade ciphers. -/* .IP "\fBtls_null_cipherlist (!aNULL:eNULL+kRSA)\fR" +/* .IP "\fBtls_null_cipherlist (eNULL:!aNULL)\fR" /* The OpenSSL cipherlist for "NULL" grade ciphers that provide /* authentication without encryption. /* OBSOLETE STARTTLS CONTROLS @@ -4311,6 +4311,8 @@ static void pre_jail_init(char *unused_name, char **unused_argv) enforce_tls ? var_smtpd_tls_mand_excl : TLS_END_EXCLUDE, TLS_END_EXCLUDE); + if (props.cipherlist == 0) + msg_panic("NULL export cipherlist"); } if (havecert || oknocert) smtpd_tls_ctx = tls_server_init(&props); diff --git a/postfix/src/tls/tls.h b/postfix/src/tls/tls.h index 7865180e1..b3ca996fe 100644 --- a/postfix/src/tls/tls.h +++ b/postfix/src/tls/tls.h @@ -130,6 +130,7 @@ extern NAME_CODE tls_cipher_level_table[]; #define TLS_END_EXCLUDE ((char *)0) extern const char *tls_cipher_list(int,...); +extern const char *tls_set_cipher_list(SSL_CTX *, const char *); /* * tls_client.c diff --git a/postfix/src/tls/tls_client.c b/postfix/src/tls/tls_client.c index 0680ee0cd..0c77c8c4a 100644 --- a/postfix/src/tls/tls_client.c +++ b/postfix/src/tls/tls_client.c @@ -626,6 +626,15 @@ TLScontext_t *tls_client_start(const tls_client_start_props *props) if (props->log_level >= 1) msg_info("setting up TLS connection to %s", props->host); + /* + * Before we create an SSL, update the SSL_CTX cipherlist if necessary. + */ + if (tls_set_cipher_list(props->ctx, props->cipherlist) == 0) { + msg_warn("Invalid cipherlist \"%s\": aborting TLS session", + props->cipherlist); + return (0); + } + /* * Allocate a new TLScontext for the new connection and get an SSL * structure. Add the location of TLScontext to the SSL to later retrieve @@ -710,24 +719,13 @@ TLScontext_t *tls_client_start(const tls_client_start_props *props) } /* - * Per session cipher selection for sessions with mandatory encryption + * Try to load an existing session from the TLS session cache. * * By the time a TLS client is negotiating ciphers it has already offered to * re-use a session, it is too late to renege on the offer. So we must * not attempt to re-use sessions whose ciphers are too weak. We expect * the caller to salt the session lookup key with the cipher list, so * that sessions found in the cache are always acceptable. - */ - if (props->cipherlist != 0) - if (SSL_set_cipher_list(TLScontext->con, props->cipherlist) == 0) { - msg_warn("Could not set cipherlist: %s", props->cipherlist); - tls_print_errors(); - tls_free_context(TLScontext); - return (0); - } - - /* - * Try to load an existing session from the TLS session cache. * * XXX To avoid memory leaks we must always call SSL_SESSION_free() after * calling SSL_set_session(), regardless of whether or not the session diff --git a/postfix/src/tls/tls_misc.c b/postfix/src/tls/tls_misc.c index ee3fe9197..2d05b0ba8 100644 --- a/postfix/src/tls/tls_misc.c +++ b/postfix/src/tls/tls_misc.c @@ -18,6 +18,10 @@ /* /* long tls_bug_bits() /* +/* const char *tls_set_cipher_list(ssl_ctx, cipher_list) +/* SSL_CTX *ssl_ctx; +/* char *cipher_list; +/* /* const char *tls_cipher_list(cipher_level, ...) /* int cipher_level; /* @@ -53,6 +57,11 @@ /* for the run-time library. Some of the bug work-arounds are /* not appropriate for some library versions. /* +/* tls_set_cipher_list() updates the cipher list of the specified SSL +/* context. Returns the new cipherlist on success, otherwise logs a +/* suitable warning and returns 0. The storage for the return value +/* is overwritted with each call. +/* /* tls_cipher_list() generates a cipher list from the specified /* grade, minus any ciphers specified via a null-terminated /* list of string-valued exclusions. The result is overwritten @@ -152,45 +161,6 @@ NAME_CODE tls_cipher_level_table[] = { 0, TLS_CIPHER_NONE, }; -#if 0 - -typedef struct { - char *algorithm; - char *exclusion; -} cipher_probe; - -static cipher_probe cipher_probe_list[] = { - - /* - * Check for missing AES256, OpenSSL only checks for AES128, and then - * enables both, because they only have one "is AES" boolean flag in the - * cipher property mask. The implementation cannot distinguish between - * AES128 and AES256. When some O/S distributions play games with - * libcrypto and exclude just the AES256 ciphers, they break the OpenSSL - * cipherlist construction code, with clients and servers potentially - * negotiating unimplemented ciphers. - * - * This problem is peculiar to AES, which is not a single cipher, but a - * family of related ciphers. The other OpenSSL symmetric ciphers are - * atomic, either implemented or not. We expect that future ciphers will - * either also be atomic, or will have one property bit per family member - * and will be filtered accurately by OpenSSL. - * - * If all else fails, this table can be expanded :-( - * - * XXX: the probe for AES256 is enclosed in #ifdef. OpenSSL 0.9.6 and and - * earlier don't have AES 256, this requires 0.9.7 or later. We recommend - * against use of 0.9.6, it has open issues solved in 0.9.7l and 0.9.8d, - * but we are not yet prepared to drop support for 0.9.6. - */ -#ifdef SN_aes_256_cbc - SN_aes_256_cbc, SSL_TXT_AES "+HIGH", -#endif - 0, 0, -}; - -#endif - /* * Parsed OpenSSL version number. */ @@ -202,17 +172,140 @@ typedef struct { int status; } TLS_VINFO; + /* + * OpenSSL adopted the cipher selection patch, so we don't expect any more + * broken ciphers other than AES and CAMELLIA. + */ +typedef struct { + char *ssl_name; + int alg_bits; + char *evp_name; +} cipher_probe_t; + +static cipher_probe_t cipher_probes[] = { + "AES", 256, "AES-256-CBC", + "CAMELLIA", 256, "CAMELLIA-256-CBC", + 0, 0, 0, +}; + +/* tls_exclude_missing - Append exclusions for missing ciphers */ + +static void tls_exclude_missing(SSL_CTX *ctx, VSTRING *buf) +{ + const char *myname = "tls_exclude_missing"; + static ARGV *exclude; /* Cached */ + SSL *s = 0; + + STACK_OF(SSL_CIPHER) * ciphers; + SSL_CIPHER *c; + cipher_probe_t *probe; + int alg_bits; + int num; + int i; + + /* + * Process a list of probes which specify: + * + * An SSL cipher-suite name for a family of ciphers that use the same + * symmetric algorithm at two or more key sizes, typically 128/256 bits. + * + * The key size (typically 256) that OpenSSL fails check, and assumes is + * available when another key size (typically 128) is usable. + * + * The OpenSSL name of the symmetric algorithm associated with the SSL + * cipher-suite. Typically, this is MUMBLE-256-CBC, where "MUMBLE" is the + * name of the SSL cipher-suite that use the MUMBLE symmetric algorithm. + * On systems that support the required encryption algorithm, the name is + * listed in the output of "openssl list-cipher-algorithms". + * + * When an encryption algorithm is not available at the given key size but + * the corresponding OpenSSL cipher-suite contains ciphers that have have + * this key size, the problem ciphers are explicitly disabled in Postfix. + * The list is cached in the static "exclude" array. + */ + if (exclude == 0) { + exclude = argv_alloc(1); + + /* + * Iterate over the probe list + */ + for (probe = cipher_probes; probe->ssl_name; ++probe) { + /* No exclusions if evp_name is a valid algorithm */ + if (EVP_get_cipherbyname(probe->evp_name)) + continue; + + /* + * Sadly there is no SSL_CTX_get_ciphers() interface, so we are + * forced to allocate and free an SSL object. Fatal error if we + * can't allocate the SSL object. + */ + ERR_clear_error(); + if (s == 0 && (s = SSL_new(ctx)) == 0) { + tls_print_errors(); + msg_fatal("%s: error allocating SSL object", myname); + } + + /* + * Cipher is not supported by libcrypto, nothing to do if also + * not supported by libssl. Flush the OpenSSL error stack. + * + * XXX: There may be additional places in pre-existing code where + * SSL errors are generated and ignored, that require a similar + * "flush". Better yet, is to always flush before calls that run + * tls_print_errors() on failure. + * + * Contrary to documentation, on SunOS 5.10 SSL_set_cipher_list() + * returns success with no ciphers selected, when this happens + * SSL_get_ciphers() produces a stack with 0 elements! + */ + if (SSL_set_cipher_list(s, probe->ssl_name) == 0 + || (ciphers = SSL_get_ciphers(s)) == 0 + || (num = sk_SSL_CIPHER_num(ciphers)) == 0) { + ERR_clear_error(); /* flush any generated errors */ + continue; + } + for (i = 0; i < num; ++i) { + c = sk_SSL_CIPHER_value(ciphers, i); + (void) SSL_CIPHER_get_bits(c, &alg_bits); + if (alg_bits == probe->alg_bits) + argv_add(exclude, SSL_CIPHER_get_name(c), ARGV_END); + } + } + if (s != 0) + SSL_free(s); + } + for (i = 0; i < exclude->argc; ++i) + vstring_sprintf_append(buf, ":!%s", exclude->argv[i]); +} + +/* tls_set_cipher_list - Set SSL_CTX cipher list */ + +const char *tls_set_cipher_list(SSL_CTX *ssl_ctx, const char *spec) +{ + static VSTRING *buf; + const char *ex_spec; + + if (buf == 0) + buf = vstring_alloc(10); + + vstring_strcpy(buf, spec); + tls_exclude_missing(ssl_ctx, buf); + ex_spec = vstring_str(buf); + + ERR_clear_error(); + if (SSL_CTX_set_cipher_list(ssl_ctx, ex_spec) != 0) + return (ex_spec); + + tls_print_errors(); + return (0); +} + /* tls_cipher_list - Cipherlist for given grade, less exclusions */ const char *tls_cipher_list(int cipher_level,...) { const char *myname = "tls_cipher_list"; static VSTRING *buf; -#if 0 - static ARGV *exclude_unavailable; - cipher_probe *probe; - int i; -#endif va_list ap; const char *exclude; char *tok; @@ -241,47 +334,34 @@ const char *tls_cipher_list(int cipher_level,...) case TLS_CIPHER_NONE: return 0; default: + + /* + * The caller MUST provide a valid cipher grade + */ msg_panic("%s: invalid cipher grade: %d", myname, cipher_level); } + /* + * The base lists for each grade can't be empty. + */ if (VSTRING_LEN(buf) == 0) msg_panic("%s: empty cipherlist", myname); - /* - * Exclude ciphers that clueless distributions leave out of libcrypto. - */ -#if 0 - if (exclude_unavailable == 0) { - exclude_unavailable = argv_alloc(1); - for (probe = cipher_probe_list; probe->algorithm; ++probe) - if (!EVP_get_cipherbyname(probe->algorithm)) - argv_add(exclude_unavailable, probe->exclusion, (char *) 0); - } - for (i = 0; i < exclude_unavailable->argc; ++i) - vstring_sprintf_append(buf, ":!%s", exclude_unavailable->argv[i]); -#endif - va_start(ap, cipher_level); while ((exclude = va_arg(ap, char *)) != 0) { if (*exclude == '\0') continue; save = cp = mystrdup(exclude); - while ((tok = mystrtok(&cp, "\t\n\r ,")) != 0) { + while ((tok = mystrtok(&cp, "\t\n\r ,:")) != 0) { /* - * Can't exclude ciphers that start with modifiers, or - * multi-element (":" separated) ciphers. + * Can't exclude ciphers that start with modifiers. */ if (strchr("!+-@", *tok)) { msg_warn("%s: can't exclude '!+-@' modifiers, '%s' ignored", myname, tok); continue; } - if (strchr(tok, ':')) { - msg_warn("%s: can't exclude compound ciphers, '%s' ignored", - myname, tok); - continue; - } vstring_sprintf_append(buf, ":!%s", tok); } myfree(save); diff --git a/postfix/src/tls/tls_server.c b/postfix/src/tls/tls_server.c index ed1ff72ba..481a67f98 100644 --- a/postfix/src/tls/tls_server.c +++ b/postfix/src/tls/tls_server.c @@ -329,12 +329,12 @@ SSL_CTX *tls_server_init(const tls_server_props *props) /* * Override the default cipher list with our own list. */ - if (*props->cipherlist != 0) - if (SSL_CTX_set_cipher_list(server_ctx, props->cipherlist) == 0) { - tls_print_errors(); - SSL_CTX_free(server_ctx); /* 200411 */ - return (0); - } + if (tls_set_cipher_list(server_ctx, props->cipherlist) == 0) { + SSL_CTX_free(server_ctx); + msg_warn("Invalid cipherlist \"%s\": disabling TLS support", + props->cipherlist); + return (0); /* Already logged */ + } /* * Load the CA public key certificates for both the server cert and for