Exim may use some fields from SSL client certificates in the authentication procedure, e.g. mailAddress, but others are not supported. The x500UniqueIdentifier field is not understood, which could be used by Dovecot to force the login user name to be the one from the SSL certificate. To use x500UniqueIdentifierin Exim and Dovecot this QUITE HACKY workaround can be used. Thus a stolen client SSL certificate can only be used to bruteforce the login of certificate owner but not other Exim or Dovecot logins.


SSL client certificates are quite useful to improve security in authentication procedures: they are like some second factor that is required by an attacker before he can start bruteforcing accounts passwords or log in when he already knows the password.

Especially on large setups theft of singe client certificates is getting more likely and hence usually cannot be ruled out. Therefore it would be nice when an attacker could only access or bruteforce a singe account associated with the stolen client certificate, not others. Therefore the username has to be encoded in the client certificate. As DER (distinguished encoding rules) supports adding fields denoted by OID catalogues, the username could be stored in different ways in the client certificate:

As Exim provides more generic (and security wise risky) configuration options, it seems easier to make Exim accept the x500UniqueIdentifier. Therefore Dovecot is straightforward configured to this field:

ssl = required ssl_verify_client_cert = yes ssl_cert_username_field = x500UniqueIdentifier

As Exim does not recognize the x500UniqueIdentifier, the variable ${tls_in_peerdn} containing the client certificate field information reports x500UniqueIdentifier as OID field with a hex-encoded UTF-8 field content, not suitable to pass it to the authenticator logic. To decode the field data a quite dirty Exim hack can be employed.

Mangling the tls_in_peerdn values can be done employing the Exim ${run{}} directive. The run statement executes an external program to perform operations that might be tricky to impossible using other Exim configuration syntax only. The external program will receive its input as command line arguments and return the result on stdout or via exit status. To mitigate security risks with special characters or empty call arguments, all values are prepended a single character before being base64 encoded. The following example shows how to decode the x500UniqueIdentifier inside an Exim plain text authenticator:

server_plain: driver = plaintext public_name = PLAIN server_condition = ${if and{\ {forany{<, ${tls_in_peerdn}}\ {and{\ # Match the OID for x500UniqueIdentifier to avoid calling the # external program in vain. {match {$item}{\N^2\.5\.4\.45=#.*\N}}\ {bool{\ ${run{/usr/lib/exim4/DerUtf8StringCompare ${base64:A${item}} ${base64:A${auth2}}}{1}{0}}\ }}\ }}\ }\ {pam{$auth2:$auth3}{1}{0}} \ }{1}{0}} server_set_id = ${auth2}

The external program called via run just decodes the call arguments, performs the comparison and returns the result as exit status. All those tasks can be performed using the tiny python program DerUtf8StringCompare.




There is no forum in place yet. Send any comments to me-at-halfdog-net citing the page URL to add the comment, also referencing previous comments you want to reply to.

Last modified 20190131
Contact e-mail: me (%)