As the patch is rather small, I also attached it to this message.
This smtpd_sasl_tls_ccert_username option can be used in the following way: Using smtpd_sasl_tls_ccert_username = commonNameAfter providing a verified client certificate, postfix advertises auth external and the user can authenticate with the username being the commonName of the certificate. This is for users having control over the CA issuing the certificates and resembles the way cyrus imap handles the situation.
Using smtpd_sasl_tls_ccert_username = relay_clientcertsWhen a client presents a certificate, where the fingerprint matches in relay_clientcerts, the lookup value (previously unused) is used to get the username for sasl. The client can then perform an auth external with this username successfully. This is a solution for users, which cannot control the CAs or do not want to trust them or cope with crls, ... It fits in the way postfix currently handles client certificates.
Both solutions then cause permit_sasl_authenticated to succeed and the sasl username to be set correctly.
The default for smtpd_sasl_tls_ccert_username (the empty value) or any other value cause auth external to not be advertised and neither succeed; the same situation as without the patch.
I have the first version running successfully in a small local installation using cyrus sasl, where K-9 and Thunderbird are both able to use client certificates (and simple username/password login). With cyrus sasl the setup is more or less straight forward. I also setup a virtual machine to test a dovecot sasl setup. Here the setup is more complicated, as dovecot has to be setup to allow an empty password, when using external auth (and only, when using external auth). This is only possible since version dovecot version 2.2.28. Here I have tested my patch using the s_client only.
Best regards, Bastian On 11.03.19 03:47, Matthew Horan wrote:
> On Jan 8, 2019, at 5:17 PM, Bastian Schmidt <[hidden email]> wrote:I have an email client (K-9 on Android), which, when using TLS client certificates insists on sending an auth external. However, postfix/SASL does not advertise external auth, which causes the client to not being able to use client certificates with postfix. As I see it, postfix is missing the external mechanism as specified in RFC 2222 (SASL) completely. Thus, I have implemented this feature (for TLS CA client certs) and I am currently successfully running this on a local installation using cyrus sasl. I would be willing to provide a patch and would really like to see this integrated in future versions of postfix.I'm quite excited about seeing this feature added to Postfix. I have a similar configuration, and have been putting off making the proposed changes myself. I had previously posted on the Dovecot mailing list [1] to no avail. I'm happy to know that there are at least two of us out there who would benefit from this feature! Thanks, Matt [1] https://www.dovecot.org/list/dovecot/2017-February/106884.html -- Sent from: http://postfix.1071664.n5.nabble.com/Postfix-Users-f2.html
--- a/mantools/postlink 2018-01-14 17:48:00.000000000 +0100 +++ b/mantools/postlink 2019-01-16 17:34:24.202943858 +0100 @@ -692,6 +692,7 @@ s;\bsmtp_balance_inet_protocols\b;<a href="postconf.5.html#smtp_balance_inet_protocols">$&</a>;g; s;\bsmtpd_enforce_tls\b;<a href="postconf.5.html#smtpd_enforce_tls">$&</a>;g; s;\bsmtpd_sasl_tls_security_options\b;<a href="postconf.5.html#smtpd_sasl_tls_security_options">$&</a>;g; + s;\bsmtpd_sasl_tls_ccert_username\b;<a href="postconf.5.html#smtpd_sasl_tls_ccert_username">$&</a>;g; s;\bsmtpd_sasl_type\b;<a href="postconf.5.html#smtpd_sasl_type">$&</a>;g; s;\bsmtpd_start[-</bB>]*\n* *[<bB>]*tls_timeout\b;<a href="postconf.5.html#smtpd_starttls_timeout">$&</a>;g; s;\bsmtpd_tls_CAfile\b;<a href="postconf.5.html#smtpd_tls_CAfile">$&</a>;g; --- a/proto/postconf.proto 2018-11-11 01:40:29.000000000 +0100 +++ b/proto/postconf.proto 2019-01-16 17:34:24.206943858 +0100 @@ -9671,9 +9671,12 @@ Postfix version 2.5). </p> <p> Postfix lookup tables are in the form of (key, value) pairs. -Since we only need the key, the value can be chosen freely, e.g. -the name of the user or host: -D7:04:2F:A7:0B:8C:A5:21:FA:31:77:E1:41:8A:EE:80 lutzpc.at.home </p> +When doing SASL external authentication using client certificates +and using relay_clientcerts to derive the username +(smtpd_sasl_tls_ccert_username = relay_clientcerts), the lookup result +will determine the SASL username. In other cases, +the value can be chosen freely: +D7:04:2F:A7:0B:8C:A5:21:FA:31:77:E1:41:8A:EE:80 user1 </p> <p> Example: </p> @@ -10284,6 +10287,37 @@ <p> This feature is available in Postfix 2.2 and later. </p> +%PARAM smtpd_sasl_tls_ccert_username + +<p> Use a client certificate to perform SASL external authentication +and specify how the SASL username is derived from the certificate. </p> + +<p> To allow passwordless authentication for client certificates specify +one of the following: </p> + +<dl> + +<dt><b>commonName</b></dt> + +<dd>Use the commonName of the certificate as a SASL username. +The certificate had to be verified successfully.</dd> + +<dt><b>relay_clientcerts</b></dt> + +<dd>Use the fingerprint of the client certificate and map it +to a username using <b>relay_clientcerts</b>>. +</dd> + +</dl> + +<p> +Example: +</p> + +<pre> +smtp_sasl_tls_ccert_username = commonName +</pre> + %PARAM smtp_generic_maps <p> Optional lookup tables that perform address rewriting in the --- a/src/global/mail_params.h 2018-02-18 14:38:58.000000000 +0100 +++ b/src/global/mail_params.h 2019-01-16 17:34:24.238943859 +0100 @@ -1636,6 +1636,10 @@ #define DEF_SMTPD_SASL_TLS_OPTS "$" VAR_SMTPD_SASL_OPTS extern char *var_smtpd_sasl_tls_opts; +#define VAR_SMTPD_SASL_TLS_CCERT_USERNAME "smtpd_sasl_tls_ccert_username" +#define DEF_SMTPD_SASL_TLS_CCERT_USERNAME "" +extern char *var_smtpd_sasl_tls_ccert_username; + #define VAR_SMTPD_SASL_REALM "smtpd_sasl_local_domain" #define DEF_SMTPD_SASL_REALM "" extern char *var_smtpd_sasl_realm; --- a/src/smtpd/smtpd.c 2018-11-17 23:33:15.000000000 +0100 +++ b/src/smtpd/smtpd.c 2019-01-16 17:34:24.238943859 +0100 @@ -1347,6 +1347,7 @@ #ifdef USE_TLS char *var_smtpd_relay_ccerts; char *var_smtpd_sasl_tls_opts; +char *var_smtpd_sasl_tls_ccert_username; int var_smtpd_starttls_tmout; char *var_smtpd_tls_CAfile; char *var_smtpd_tls_CApath; @@ -5964,6 +5965,7 @@ #ifdef USE_TLS VAR_RELAY_CCERTS, DEF_RELAY_CCERTS, &var_smtpd_relay_ccerts, 0, 0, VAR_SMTPD_SASL_TLS_OPTS, DEF_SMTPD_SASL_TLS_OPTS, &var_smtpd_sasl_tls_opts, 0, 0, + VAR_SMTPD_SASL_TLS_CCERT_USERNAME, DEF_SMTPD_SASL_TLS_CCERT_USERNAME, &var_smtpd_sasl_tls_ccert_username, 0, 0, VAR_SMTPD_TLS_CERT_FILE, DEF_SMTPD_TLS_CERT_FILE, &var_smtpd_tls_cert_file, 0, 0, VAR_SMTPD_TLS_KEY_FILE, DEF_SMTPD_TLS_KEY_FILE, &var_smtpd_tls_key_file, 0, 0, VAR_SMTPD_TLS_DCERT_FILE, DEF_SMTPD_TLS_DCERT_FILE, &var_smtpd_tls_dcert_file, 0, 0, --- a/src/smtpd/smtpd_check.c 2018-01-07 00:12:51.000000000 +0100 +++ b/src/smtpd/smtpd_check.c 2019-01-16 17:34:24.238943859 +0100 @@ -319,7 +319,7 @@ static NAMADR_LIST *perm_mx_networks; #ifdef USE_TLS -static MAPS *relay_ccerts; +MAPS *relay_ccerts; #endif --- a/src/smtpd/smtpd_sasl_glue.c 2017-02-19 02:58:21.000000000 +0100 +++ b/src/smtpd/smtpd_sasl_glue.c 2019-01-16 18:33:23.359016476 +0100 @@ -130,6 +130,7 @@ /* Utility library. */ +#include <name_code.h> #include <msg.h> #include <mymalloc.h> #include <stringops.h> @@ -137,6 +138,11 @@ /* Global library. */ #include <mail_params.h> +#include <maps.h> + +/* tls library. */ + +#include <tls.h> /* XSASL library. */ @@ -150,6 +156,8 @@ #ifdef USE_SASL_AUTH +extern MAPS *relay_ccerts; + /* * Silly little macros. */ @@ -188,6 +196,14 @@ const char *mechanism_list; XSASL_SERVER_CREATE_ARGS create_args; int tls_flag; + const char *verified_user; +#ifdef USE_TLS + static const NAME_CODE sasl_tls_user[] = { + SASL_TLS_USER_NAME_COMMON_NAME, SASL_TLS_USER_COMMON_NAME, + SASL_TLS_USER_NAME_RELAY_CLIENTCERTS, SASL_TLS_USER_RELAY_CLIENTCERTS, + 0, SASL_TLS_USER_NONE + }; +#endif /* * Sanity check. @@ -209,8 +225,40 @@ */ #ifdef USE_TLS tls_flag = state->tls_context != 0; + verified_user = 0; + + if (var_smtpd_sasl_tls_ccert_username != 0) { + switch (name_code(sasl_tls_user, NAME_CODE_FLAG_NONE, var_smtpd_sasl_tls_ccert_username)) { + case SASL_TLS_USER_NONE: + verified_user = 0; + break; + case SASL_TLS_USER_COMMON_NAME: + verified_user = TLS_CERT_IS_TRUSTED(state->tls_context) ? state->tls_context->peer_CN : 0; + break; + case SASL_TLS_USER_RELAY_CLIENTCERTS: + if ( TLS_CERT_IS_PRESENT(state->tls_context) ) { + int i; + char *prints[2]; + + prints[0] = state->tls_context->peer_cert_fprint; + prints[1] = state->tls_context->peer_pkey_fprint; + + for (i = 0; i < 2; i++) { + if ( ( verified_user = maps_find(relay_ccerts, prints[i], DICT_FLAG_NONE) ) != 0 ) { + if (msg_verbose) + msg_info("Allowing external auth for certified client: %s", verified_user); + break; // for + } else if (relay_ccerts->error != 0) { + msg_warn("smtpd_sasl_tls_ccert_username: lookup error for fingerprint '%s'", prints[i]); + } + } + } + break; + } + } #else tls_flag = 0; + verified_user = 0; #endif #define ADDR_OR_EMPTY(addr, unknown) (strcmp(addr, unknown) ? addr : "") #define REALM_OR_NULL(realm) (*(realm) ? (realm) : (char *) 0) @@ -230,7 +278,8 @@ service = var_smtpd_sasl_service, user_realm = REALM_OR_NULL(var_smtpd_sasl_realm), security_options = sasl_opts_val, - tls_flag = tls_flag)) == 0) + tls_flag = tls_flag, + verified_user = verified_user)) == 0) msg_fatal("SASL per-connection initialization failed"); /* --- a/src/smtpd/smtpd_sasl_glue.h 2011-12-18 18:52:45.000000000 +0100 +++ b/src/smtpd/smtpd_sasl_glue.h 2019-01-16 17:34:24.242943859 +0100 @@ -23,6 +23,14 @@ #define smtpd_sasl_is_active(s) ((s)->sasl_server != 0) #define smtpd_sasl_set_inactive(s) ((void) ((s)->sasl_server = 0)) +/* for SASL external auth */ +#define SASL_TLS_USER_NAME_COMMON_NAME "commonName" +#define SASL_TLS_USER_NAME_RELAY_CLIENTCERTS "relay_clientcerts" + +#define SASL_TLS_USER_NONE 0 +#define SASL_TLS_USER_COMMON_NAME 1 +#define SASL_TLS_USER_RELAY_CLIENTCERTS 2 + /* LICENSE /* .ad /* .fi --- a/src/xsasl/xsasl_cyrus_server.c 2019-01-15 19:53:52.000000000 +0100 +++ b/src/xsasl/xsasl_cyrus_server.c 2019-01-16 17:34:24.242943859 +0100 @@ -366,6 +366,16 @@ != XSASL_AUTH_OK) XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0); + /* + * If an external user is already authenticated, e.g. using a verified client certificate, tell SASL about it. + * Cyrus SASL will then announce EXTERNAL and succeed if the username passed there is the same. + */ + if (args->verified_user != 0) { + if ((sasl_status = sasl_setprop(server->sasl_conn, SASL_AUTH_EXTERNAL, args->verified_user)) != SASL_OK) { + msg_warn("SASL per-connection set auth external username: %s", xsasl_cyrus_strerror(sasl_status)); + } + } + XSASL_CYRUS_SERVER_CREATE_RETURN(&server->xsasl); } --- a/src/xsasl/xsasl_dovecot_server.c 2016-01-24 01:50:54.000000000 +0100 +++ b/src/xsasl/xsasl_dovecot_server.c 2019-01-17 00:35:30.550960757 +0100 @@ -171,6 +171,7 @@ ARGV *mechanism_argv; /* ditto */ char *client_addr; /* remote IP address */ char *server_addr; /* remote IP address */ + char *verified_user; /* already verified username, e.g. per certificate */ } XSASL_DOVECOT_SERVER; /* @@ -456,6 +457,7 @@ args->security_options, NAME_MASK_ANY_CASE | NAME_MASK_FATAL); server->client_addr = mystrdup(args->client_addr); + server->verified_user = args->verified_user != 0 ? mystrdup(args->verified_user) : 0; /* * XXX Temporary code until smtpd_peer.c is updated. @@ -509,6 +511,8 @@ myfree(server->service); myfree(server->server_addr); myfree(server->client_addr); + if (server->verified_user) + myfree(server->verified_user); myfree((void *) server); } @@ -673,6 +677,8 @@ if (server->tls_flag) /* XXX Encapsulate for logging. */ vstream_fputs("\tsecured", server->impl->sasl_stream); + if (server->verified_user != 0) + vstream_fprintf(server->impl->sasl_stream, "\tvalid-client-cert\tcert_username=%s", server->verified_user); if (init_response) { /* --- a/src/xsasl/xsasl.h 2016-06-24 22:26:24.000000000 +0200 +++ b/src/xsasl/xsasl.h 2019-01-16 17:34:24.242943859 +0100 @@ -55,6 +55,7 @@ const char *user_realm; const char *security_options; int tls_flag; + const char *verified_user; } XSASL_SERVER_CREATE_ARGS; typedef struct XSASL_SERVER_IMPL { @@ -67,10 +68,10 @@ #define xsasl_server_create(impl, args) \ (impl)->create((impl), (args)) -#define XSASL_SERVER_CREATE(impl, args, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) \ +#define XSASL_SERVER_CREATE(impl, args, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) \ xsasl_server_create((impl), (((args)->a1), ((args)->a2), ((args)->a3), \ ((args)->a4), ((args)->a5), ((args)->a6), ((args)->a7), ((args)->a8), \ - ((args)->a9), ((args)->a10), (args))) + ((args)->a9), ((args)->a10), ((args)->a11), (args))) #define xsasl_server_done(impl) (impl)->done((impl)); /*
smime.p7s
Description: S/MIME Cryptographic Signature