Skip to main content

Authenticate clients using X.509 certificates

For client authentication, in addition to various password authentication methods, Halon supports X.509 client certificate authentication. During the STARTTLS handshake it's possible for the server (Halon) to ask for a X.509 client certificate (peer certificate). If the client provides a certificate it can later be obtained from the $connection variable. If Halon is acting as a client (delivering mail), you can set a client certificate using the tls_client_cert option to the Try function in the Pre-delivery context.

While some clients expect the authentication/verification of the X.509 certificate be performed during a custom SASL phase called "AUTH EXTERNAL" other implementations may simply perform the verification in the MAIL FROM or RCPT TO phase in order to easily restrict permission for different senders/recipients.

Halon will by default not ask clients for a client certificate as this is an extension to the TLS protocol. This extension can be enabled per SMTP server in the configuration.

AUTH EXTERNAL

This example allows client verification using SHA-1 fingerprint matching by implementing a custom SASL authentication mechanism. The SASL username will be set to the "CN" (Common Name) of the certificate's subject field.

AUTH context
if (isset($connection["tls"]["peercert"]["x509"])) {
$x509 = X509($connection["tls"]["peercert"]["x509"]);
if (sha1($x509->export()) == "xxx")
Accept(["username" => array_find(function ($x) { return $x[0] === "CN"; }, $x509->subject())[1] ?: "X509"]);
}

MAIL FROM

The same script as in the AUTH EXTERNAL example can be used in the MAIL FROM context however additional restrictions can also easily be applied.

MAIL FROM context
$tlsfingerprints = [
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" => "example.com",
"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy" => "example.org"
];
if (isset($connection["tls"]["peercert"]["x509"])) {
$x509 = X509($connection["tls"]["peercert"]["x509"]);
$fp = sha1($x509->export());
if (isset($tlsfingerprints[$fp]) and $tlsfingerprints[$fp] == $arguments["address"]["domain"])
Accept();
}

Reject("No valid X.509 client certificate for " . $arguments["address"]["domain"]);