Relaying with TLS in Sendmail

Setup | Configuration | Certificates | Relay Access | Custom Relay Access | Verify Server | Related

This document describes how to setup the Sendmail Mail Transport Agent (MTA) to allow encrypted email via Transport Layer Security (TLS). Roaming client systems such as laptops running Unix can relay mail through the static servers using this setup. TLS relaying works well for remote relaying of systems, or where a x509 certificate infrastructure is in place. To allow remote relaying for users, SMTP AUTH is usually a better fit.

In the following example, a sendmail 8.12 client is configured to talk with a sendmail 8.12 server via TLS. The steps are to get STARTTLS support in sendmail, tell sendmail where to look for certificate files, and finally to generate and setup the required TLS certificates.

These notes assume one is reasonably competent with Unix, Sendmail, and OpenSSL. For debugging purposes, the values of various Sendmail macro may need to be logged. This can be done via a custom ruleset:

LOCAL_CONFIG
Ksyslog syslog

LOCAL_RULESETS
HSubject: $>Log_Subject

SLog_Subject
R$* $: $(syslog $&{cert_issuer} $)

Setup

Sendmail on both the client and server systems will need to be compiled with STARTTLS support. Run the following command to see if STARTTLS support is available on the systems in question.

$ sendmail -d0 < /dev/null | grep -i tls
PIPELINING SCANF STARTTLS TCPWRAPPERS USERDB XDEBUG

If not, recompile sendmail with STARTTLS support via a custom site.config.m4. OpenSSL must be installed on the system in question first. Alternatively, STARTTLS may be available in a special package or port of sendmail, depending on the vendor in question.

APPENDDEF(`confENVDEF', `-DSTARTTLS')
APPENDDEF(`confLIBS', `-lssl -lcrypto')

Configuration

Once STARTTLS support is compiled into sendmail, various TLS parameters will needed to be added to the sendmail configuration file. These parameters tell sendmail where to find the certificates needed for TLS, among other things.

Add the following to the sendmail.mc file for sendmail.cf, and the equivalent submit.mc for the sendmail 8.12 client system(s). Then rebuild the *.cf files.

dnl where to store certificates under
define(`CERT_DIR', `MAIL_SETTINGS_DIR`'certs')

dnl Certificate Authority file & directory (for verifying other sites)
define(`confCACERT', `CERT_DIR/cacert.pem')
define(`confCACERT_PATH', `CERT_DIR/CA')

dnl local server certificate and keyfiles (to certify this host)
dnl key file should be mode 0400 for security reasons
define(`confSERVER_CERT', `CERT_DIR/host.cert')
define(`confSERVER_KEY', `CERT_DIR/host.key')
define(`confCLIENT_CERT', `CERT_DIR/host.cert')
define(`confCLIENT_KEY', `CERT_DIR/host.key')

Also ensure the confTLS_SRV_OPTIONS configuration option is not set to V, as this will disable verification of client certificates. Some vendors, such as Debian Linux, set this by default. Instead:

define(`confTLS_SRV_OPTIONS', `')dnl

These settings are copied verbatim from the sample sendmail configurations on this site.

Server Notes

Additionally, the server will need to be configured with access map support so matching can be done on certificate information. Add the following to the sendmail.mc file for sendmail.cf.

FEATURE(`access_db')

The client will also need to be configured to support the access map, if verification of the server will be done.

Client Notes

The sendmail client may need group readable permissions set of the key file, if both the sm-mta (listening to localhost, for example) and the sm-msp (for locally submitted mail) are employed. Set the following in the submit.mc.

define(`confDONT_BLAME_SENDMAIL', `GroupReadableKeyFile')

The key file will need to be readable by root (for sm-mta) and smmsp (for sm-msp).

# chmod 440 host.key
# chgrp smmsp host.key
# ls -l host.key
-r--r----- 1 root smmsp 891 Nov 15 11:29 host.key

Some sites block or redirect outgoing mail at port 25 for various reasons; to avoid this, have the server listen at port 587 (recent sendmail do this by default), and direct sm-msp mail to this port via the following definition of msp in the submit.mc file.

FEATURE(`msp', `mail.example.org', `MSA')

On my laptop, I do not run any sendmail daemons. I use a script to manually run the queue if things have been piling up (only when I send e-mail with the network down or my server is unavailable for some reason), which essentially runs the following command.

$ sudo /usr/sbin/sendmail -L sm-msp-queue -Ac -q

Certificates

Generating the required certificates is the most difficult part: there are several different ways the certificates can be generated, the resulting certificates must be properly installed where sendmail can find them, and portions of the certificate data must be translated into sendmail?s access map file on the server.

How to setup an OpenSSL Certificate Authority. An easier but much less scalable option is to use self-signed certificates.

For my main server, I purchase a certificate from a third-party Certificate Authority (CA) to allow other mail clients and servers to easily verify my system. For internal hosts like my laptop, I run my own CA and generate certificates as needed.

Certificate Setup

The signing certificate(s) for the system certificate(s) in question will need to be present in the confCACERT file, or be in proper hashed format in the confCACERT_PATH directory. Scripts can assist in the task of generating system certificates or creating the certificate hash format used by OpenSSL.

To support a new client, I generate a Certificate Signing Requests (CSR) from the certs directory of my client configuration, and sign the request in the CA area for my site, then save the resulting certificate as host.cert. I have updated the system-wide openssl.cnf to use defaults for where I am.

# cd /etc/mail/certs
# make csr
Generating RSA private key, 1024 bit long modulus
.........................++++++
..................................++++++
e is 65537 (0x10001)
Using configuration from /System/Library/OpenSSL/openssl.cnf
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [US]:
State or Province Name (full name) [Washington]:
Locality Name (eg, city) [Seattle]:
Organization Name (eg, company) [Sial.org]:
Organizational Unit Name (eg, section) []:
Common Name (eg, YOUR name) []:legacy.sial.org
Email Address []:jmates@sial.org

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
# cp host.csr /Volumes/sial-ca/newreq.pem
# cd /Volumes/sial-ca
# export OPENSSL_CONF=openssl.cnf
# ./CA.sh -sign
Using configuration from openssl.cnf
Enter PEM pass phrase:
Check that the request matches the signature
Signature ok
The Subjects Distinguished Name is as follows
countryName :PRINTABLE:'US'
stateOrProvinceName :PRINTABLE:'Washington'
localityName :PRINTABLE:'Seattle'
organizationName :PRINTABLE:'Sial.org'
commonName :PRINTABLE:'legacy.sial.org'
emailAddress :IA5STRING:'jmates@sial.org'
Certificate is to be certified until Feb 11 06:30:20 2004 GMT (365 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
?
Signed certificate is in newcert.pem
# mv newcert.pem /etc/mail/certs/host.cert
# rm newreq.pem
# cd /etc/mail/certs
# rm host.csr

Still a lot of typing: the process could be streamlined with improved tools.

Relay Access

To allow relaying by TLS clients, the access map will need to be populated on the server with entries allowing relaying for certificates matching the specified certificate data. This check will only be done for certificates that have a {verify} macro status of OK: that is, when sendmail is able to validate the certificate of the client in question.

  1. Import signing certificates.
  2. To verify, sendmail must know about the signing certificate. This means any custom CA certificates must be imported into either the confCACERT or properly hashed in the confCACERT_PATH directory. Using my setup, I copy the cacert.pem from my CA area and save it as exampleca.cert under the /etc/mail/certs/CA directory on my mail systems, then use the make links to create the proper hash link OpenSSL requires.

    # make links
    # ls -l
    total 6
    lrwxr-xr-x 1 root wheel 11 Feb 10 22:15 2bd1fe27.0@ -> exampleca.cert
    -rw-r--r-- 1 root wheel 1654 Oct 27 09:41 Makefile
    -rwxr-xr-x 1 root wheel 1951 Aug 29 09:22 extract*
    -rw-r--r-- 1 root wheel 1289 Feb 10 22:15 exampleca.cert

    This will need to be done for all custom certificates, whether from a private CA, a self-signed certificate, or a third-party CA certificate not present in the global confCACERT file. Use of the confCACERT_PATH directory is encouraged, as too many entries in the global file may cause OpenSSL to not work as expected.

  3. Server access map setup.
  4. In the access map file, add CERTIssuer (or additionally CERTSubject) entries and rebuild the hashed version of access with makemap. The following involved example shows how to extract the x509 issuer data from the certificate /etc/mail/certs/CA/exampleca.cert with openssl and encode it for use in sendmail with perl.

    $ openssl x509 -issuer -noout < /etc/mail/certs/CA/exampleca.cert \
    | perl -ple 's/^issuer= /CERTIssuer:/' \
    | perl -ple 's/([[:^print:]<>()"+ ])/sprintf "+%02x", ord $1/ge' \
    | perl -ple 's/$/\tRELAY/'

    CERTIssuer:/C=US/ST=Washington/L=Seattle/O=example.org/CN=example.org+20CA/?
    Email=postmaster@example.org RELAY

    The above line can then be appended to the access map and the map file rebuilt to allow relaying for all certificates signed with the matching certificate. Ideally, it should be encapsulated into a script or Makefile to hide the dirty work that needs to be done.

    # openssl x509 -issuer -noout < /etc/mail/certs/CA/exampleca.cert \
    | perl -ple 's/^issuer= /CERTIssuer:/' \
    | perl -ple 's/([[:^print:]<>()"+ ])/sprintf "+%02x", ord $1/ge' \
    | perl -ple 's/$/\tRELAY/' >> /etc/mail/access

    # (cd /etc/mail && make all)

If things do not work the first time (or second, third, and subsequent tries), remember that log files are your friend. And that embarrassingly simple typos will often be spotted by someone else reviewing your work.

I have seen the openssl output change somewhere between the 0.9.6 and 0.9.7 releases (?) with the email attribute changing from Email to emailAddress, which caused the exact match access map entries to fail. Regenerating the access map entries from the certificate data solved the problem.

Custom Relay Access

An alternative to the access map CERTIssuer and CERTSubject relaying is to create a custom ruleset that allows relaying for certain certificates. In the following example, the md5 fingerprints of the client certificates will be used to allow relaying. This method is better suited to environments that lack central certificate authority certificates or a key signing infrastructure.

These instructions assume various steps outlined above have already been taken care of, for example STARTTLS support in sendmail.

  1. Create server relay ruleset.
  2. A custom ruleset will need to be created on the server to allow relaying by certificate fingerprints. In the sendmail.mc file, add the following at the bottom. It allows relaying should the client certificate presented be verified (exist locally on the server) and the md5 fingerprint of the certificate exist with a RELAY value in the hashed md5map file.

    LOCAL_CONFIG
    Kmd5map hash -o /etc/mail/md5map

    LOCAL_RULESETS
    SLocal_check_rcpt
    R$* $: <?> $&{verify}
    R<?> OK $: OK authenticated: continue
    R<?> $* $@ NO not authenticated
    R$* $: $&{cert_md5}
    R$+ $: $(md5map $1 $)
    RRELAY $# RELAY
    R$* $: NO

    Rebuild sendmail.cf from the sendmail.mc file and restart sendmail.

  3. Obtain the client certificate fingerprint.
  4. Only the fingerprint of the client certificate will be available, not of any signing certificate. This information will need to be obtained from every client certificate that will be allowed to relay, which may not scale well with large numbers of clients.

    $ openssl x509 -fingerprint -noout < /etc/mail/certs/host.cert
    MD5 Fingerprint=65:67:73:3D:A5:50:34:3F:84:A9:9E:77:38:C9:DA:A1

  5. Upload client certificate to server.
  6. Each client certificate (host.cert using my standard setup) will also ideally need to be transferred to the server; this step is necessary to allow the server to properly verify the certificate in question. Client certificates should be stored in the confCACERT_PATH directory in the proper hash-as-name format. Otherwise, remove the {verify} macro ruleset checks from the ruleset above. Not verifying the certificate would in theory allow an attacker to generate a different certificate that shares the same md5 of a certificate in use to relay through the system in question.

    client$ scp /etc/mail/certs/host.cert server:

    server# cd /etc/mail/certs/CA
    server# cp ?/host.cert `openssl x509 -noout -hash < ?/host.cert`.0

  7. Add the client MD5 fingerprint to md5map, and rebuild the hashed database.
  8. # echo -e '65:67:73:3D:A5:50:34:3F:84:A9:9E:77:38:C9:DA:A1\tRELAY' >> md5map
    # makemap hash md5map < md5map

Verify Server

If the client must only send out e-mail through a specific server, enable access map support on the client, and require the outgoing server to support TLS and optionally present a certain certificate.

  1. Enable access map.
  2. For a client that only relays to a remote mail server, and runs no local daemon, set the following (along with the other required options) in the submit.mc and rebuild the submit.cf.

    dnl so can verify server
    FEATURE(`access_db')

    dnl outgoing mail via remote system
    FEATURE(`msp', `mail.example.org')

    dnl no automatic e-mail attempts
    define(`confDELIVERY_MODE', `deferred')

  3. Access map rules to verify server.
  4. At minimum, a TLS_Srv entry for the outgoing e-mail server mail.example.org will need to be made. However, someone could create a malicious MX record that points to a different server. Thus, a TLS_Rcpt entry is needed to ensure a particular certificate is used by the server. The following rules show how to verify a named server as well as ensure the server uses a verified certificate that matches the named certificate issuer data.

    TLS_Srv:mail.example.org VERIFY:128
    TLS_Rcpt: VERIFY:128+CI:/CN=ExampleCA/C=US/ST=Washington/L=Seattle/?
    O=example.org/emailAddress=postmaster@example.org

Other, more complex access map entries or custom rulesets will be needed if alternate outgoing servers are used for different domains.

Related