TLS client certificate for IMAP and SMTP

This is a very common feature for email clients - Thunderbird and K-9 Mail support it. It should not be difficult to implement because it’s already built in the SSL library. On Android the built-in certificate store may be used.

Today I’ve configured mandatory client certificates with my Dovecot IMAP server and was going to do it with my Postfix SMTP server too. Unfortunately I have to disable this feature because Delta Chat doesn’t support it.

async-native-tls allows setting client certificate with identity method so it should be doable.

Could you provide links to the documentation you used to configure the server or examples of configuration? Maybe client TLS certificates are somehow useful for chatmail servers, but at least I want to be able to setup a testing server to check that client certificates actually work.

It’s not easy as it requires some weird manipulations with OpenSSL tool, and I’ve only done it for Dovecot, not Postfix. I’ll write a tutorial for both, but I need some time.

Get easy-rsa.

$ cd easy-rsa
$ ./easyrsa init-pki
$ ./easyrsa build-ca
$ ./easyrsa build-client-full client1 nopass
$ ./easyrsa gen-crl
$ cat pki/ca.crt pki/crl.pem > ca-crt-with-crl.pem
  • ca-crt-with-crl.pem – this file goes to server (Postfix and Dovecot).
  • pki/issued/client1.crt – client certificate file
  • pki/private/client1.key – client private key file

You may also create PKCS #12 (ext .p12) file that combines client certificate and private key. This is how they may be imported into Thunderbird. Also it looks like this is the easiest way to have them in async-native-tls (Identity in async_native_tls - Rust).

$ openssl pkcs12 -export -legacy      \
    -certfile pki/ca.crt              \
    -in       pki/issued/client1.crt  \
    -inkey    pki/private/client1.key \
    -out      client1.p12
  • client1.p12 – PKCS #12

Now we have to configure chatmail’s Dovecot and Postfix.

chatmail/cmdeploy/src/cmdeploy/dovecot/dovecot.conf.j2:

+ssl_ca = </path/to/ca-crt-with-crl.pem.pem
+ssl_verify_client_cert = yes
+
+protocol !smtp {
+  auth_ssl_require_client_cert=yes
+}

WARNING I’m not sure whether these Postfix config changes are production-ready, by they may help you to test client certificates.

cmdeploy/src/cmdeploy/postfix/master.cf.j2:

 submission inet n       -       y       -       5000    smtpd
+  -o smtpd_tls_req_ccert=yes
 smtps     inet  n       -       y       -       5000    smtpd
+  -o smtpd_tls_req_ccert=yes

cmdeploy/src/cmdeploy/postfix/main.cf.j2:

+tls_append_default_CA = no
+smtpd_tls_CApath =
+smtpd_tls_CAfile = /path/to/ca-crt-with-crl.pem.pem
1 Like

I am looking into certificate UI on Android.

On Android 14 you go into settings, “Security and privacy”, then “More security and privacy”, then “Encryption and credentials”, then “Install a certificate”, then “VPN and app user certificate”. This apparently allows to install certificates that can then be used in apps such as Conversations XMPP client to “Login with certificate” from the main screen.

In K-9 Mail 6.804 during account configuration you first type in your login and password, then get “Configuration found” popup once autoconfig is fetched and after clicking “EDIT CONFIGURATION” can select Client certificate in advanced settings. Field displays None and nothing happens when I click it even though it has a down arrow, probably because I have no client certificates installed yet.

Looking into Android APIs. It has an android.security.KeyChain object. After importing it with import android.security.KeyChain it is possible to directly call static method KeyChain.choosePrivateKeyAlias that will launch an activity where user can select previously imported key and will call the provided callback with some string called “alias” or null. This alias can then be used to request the private key using KeyChain.getPrivateKey and request the chain with KeyChain.getCertificateChain.

PrivateKey implements java.security.Key interface with a getEncoded() method that will return the key encoded to PKCS #8 (should be checked by calling getFormat()). So it seems we get the private key in PKCS #8 from Android.

KeyChain.getCertificateChain returns an array of X509Certificate which has a method getEncoded() that returns DER. I don’t see an easy way to get a PEM file for the whole chain from this.