For a while now I’ve been interested in using client certificates for authentication of e-mail clients using IMAP and SMTP, while still permitting password authentication. This week I finally decided to actually figure out how I could get this to work. Because it took me quite some effort to discover how to do this but couldn’t find anyone else documenting the same I thought it might help other people to describe my setup.
I’m using Postfix 2.11.3 for SMTP and a patched Dovecot 2.2.27 for IMAP. I had to patch Dovecot because the required functionality only gets introduced with 2.2.28 in cdf00f5:
auth: Support filtering by SASL mechanism: passdb { mechanisms }
So if you want to get the same functionality either use Dovecot 2.2.28 or higher or backport the mentioned commit like I did.
Certificate Authority¶
To manage my CA I’m using the easy-rsa utility. For brevity I’m not documenting how to use that, except to mention how I’m exporting the combined CA+CRL file which is what I’m using in the rest of this article. Producing that file is done as follows:
$ cd easy-rsa/easyrsa3
$ ./easyrsa gen-crl
$ cat pki/ca.crt pki/crl.pem > pki/ca+crl.pem
SMTP server Postfix¶
First lets add certificate authentication to Postfix as it’s the easiest. We’re assuming that any valid certificate, signed by our CA, is authorized to use this server for relaying mail. If you actually want more complicated authentication than that I don’t think Postfix can currently help you.
In /etc/postfix/master.cf
configure TLS to be required and ask for a client certificate on the submission
port.
You don’t want to do this globally,
in main.cf
,
because some servers,
wishing to deliver mail to you,
might not deal well with being asked for a client certificate.
submission inet n - - - - smtpd
# mandatory encryption. 'may', opportunistic encryption, works too, but you
# probably don't want that
-o smtpd_tls_security_level=encrypt
# enable regular SASL authentication (with passwords, assuming you currently
# have this and want to retain it)
-o smtpd_sasl_auth_enable=yes
# ask for a client certificate: gives the client the opportunity to provide
# one, not the obligation to do so
-o smtpd_tls_ask_ccert=yes
Then in /etc/postfix/main.cf
configure certificate verification and give permission for relay access:
smtpd_tls_CAfile = /etc/easy-rsa/easyrsa3/pki/ca+crl.pem
# necessary to prevent any random, public, CA from giving relay access to your server
tls_append_default_CA = no
# add to either smtpd_recipient_restrictions or smtpd_relay_restrictions
# depending on which of those you use to control authenticated relay access
smtpd_relay_restrictions =
permit_sasl_authenticated
permit_tls_all_clientcerts
IMAP server Dovecot¶
For setting up Dovecot I’ll assume you already have it running with TLS enabled.
I’m only describing the additional configuration options that are needed.
In /etc/dovecot/conf.d/10-ssl.conf
add:
# Our CA that we use to sign the client certificates
ssl_ca = </etc/easy-rsa/easyrsa3/pki/ca+crl.pem
# only permit non-revoked certificates
ssl_require_crl = yes
# ask for client certificates (but don't require them)
ssl_verify_client_cert = yes
# I'm using a full mail address as username and storing it in the emailAddres
# field of the certificate
ssl_cert_username_field = emailAddres
In /etc/dovecot/conf.d/10-auth.conf
add EXTERNAL
as an authentication mechanism:
# Use the username taken from the client certificate
auth_ssl_username_from_cert = yes
# Add 'external' to auth_mechanisms. That will use the username extracted from
# the certificate combined with the empty string as password to authenticate.
# This is my list of enabled mechanisms
auth_mechanisms = plain login external
Now comes the “trick” to making the combination of password and certificate authentication work.
Because Dovecot’s EXTERNAL
authentication mechanism attempts to authenticate with the empty string as password,
we need to have a password database that permits that.
At the same time though,
we want to prevent regular logins,
with password,
to succeed when specifying the empty string as password.
This requires the filtering of password databases by SASL mechanism added to Dovecot 2.2.28 that I mentioned earlier.
I’m adding a passwd-file password database containing only the usernames,
without passwords,
as /etc/dovecot/users-external
:
user@example.com:::::::
other-user@example.com:::::::
Subsequently I’m adding an EXTERNAL
-specific password database to /etc/dovecot/conf.d/auth-passwdfile.conf.ext
.
Make sure to also include it,
near the bottom of /etc/dovecot/10-auth.conf
.
passdb {
driver = passwd-file
# the PLAIN scheme prevents us from having to hash the empty string
args = scheme=PLAIN username_format=%u /etc/dovecot/users-external
# this option requires Dovecot 2.2.28 (or the patch), without it this setup
# is insecure because it permits logins with the empty string as password
mechanisms = external
# explicitly permit empty passwords
override_fields = nopassword
}
You can test this on a terminal with the openssl s_client:
$ openssl s_client -CAfile /etc/ssl/certs/ca-certificates.crt -verify 4 \
> -cert ${PATH_TO_CLIENT_CERT} \
> -key ${PATH_TO_PRIVATE_KEY_FOR_CLIENT_CERT} \
> -tls1 -starttls imap -connect imap.example.com:imap
<lots of text scrolling by>
. OK Pre-login capabilities listed, post-login capabilities have more.
A001 AUTHENTICATE EXTERNAL ""
* CAPABILITY IMAP4rev1 ......
A001 OK Logged in
Thunderbird as Client¶
When using Thunderbird as a client you can specify the “TLS certificate” “authentication method” in the “security settings” portion of the “server settings” for your account settings. Unfortunately you cannot choose this during the account setup wizard. So during the wizard you’ll still need to use password authentication. For SMTP you can just use “no authentication” as the “authentication method” (it’s a misleading name).
Comments
tlhackque
Posted on , in reply to.
I share the goal of client certificate authentication to IMAP, while still allowing passwords by users without certificates. However, I’m missing something in your description of how to accomplish this.
I read in 10-auth.conf that
auth_ssl_username_from_cert
will take the DN_CN from the client certificate, and pass that as the username. I don’t seessl_cert_username_field
documented there, but it is in the code.Presumably, plain and login won’t recognize the username (foo@example.com), so it will get to external - where you pass the username as %u. That all makes sense - if foo@example.com is in the file, the lookup will succeed.
On the other hand, telling my e-mail client to login with “foo” and “” will fail, as plain/login will reject it. And “foo” isn’t in the external file. This is all good.
But: what prevents a user from telling their e-mail client to login with “foo@example.net” and “” (the null password)? A malicious actor certainly knows the e-mail address that he is trying to access…
I don’t see the access to EXTERNAL conditioned by the username coming from a validated certificate. It seems to me that this is required.
https://wiki.dovecot.org/Variables indicates that %k /%{cert} could be passed to an external driver to allow it to respond “unknown user” to lookups where the username doesn’t come from a validated certificate. But I don’t see any reference to that.
(I’m looking at the code of 2.2.31, and I don’t see these variables referenced, except by a test.)
Presumably you avoided this scenario. What am I missing?
(I thought it would be necessary to write a custom driver to handle this case, but if you have solved all the problems, I don’t want to re-invent the wheel!)
Giel van Schijndel
Posted on , in reply to.
Hi tlhackque,
I eventually switched away from this setup because of other, unrelated, deployment problems.
I was using an older version of Dovecot than the 2.2.31 you’re referencing, so perhaps the
%k
variable you mention didn’t exist yet at that time. I definitely don’t recall seeing it before.As for how this should work: The mechanism filter, introduced with cdf00f5, expressed as
mechanisms = external
in the config file, should prevent the separate passdb from being used for anything else.I.e. I had two passdb entries.
/etc/dovecot/users-external
was one of those and I produced it by taking my regular/ect/dovecot/users
and emptying all fields except the username one. The external database only got used for theEXTERNAL
mechanism, and the regular database was still permitted for all mechanisms.This ensured that the empty password was only permitted (required actually) for the
EXTERNAL
mechanism. The username itself got taken from theemailAddress
field of the certificate due to thessl_cert_username_field
setting. This ensures that the username being used is under control of the party controlling the CA.I don’t think I mentioned this very clearly in my blog post, but the
EXTERNAL
-specific password database config section was in addition to what was already present in the default config.This is the complete content of
/etc/dovecot/conf.d/auth-passwdfile.conf.ext
(taken from a backup):Comment Atom Feed