Digitally sign your email on the MTA side
Digitally sign your email on the MTA side

Why sign your email directly on the MTA (here we will talk about Postfix MTA) ? I don’t find a simple webmail client for my email server that include a S/MIME and/or PGP functionality to sign/encrypt outgoing messages.

So I found and change a little bit a script that get the outgoing message on the MTA and sign them with OpenSSL. The steps are:
– A running Postfix server
– Create the user account that will run the signing script and lock the account to prevent logging in:
useradd -M pfsigner
usermod -L pfsigner
– Create the folder /var/spool/signing. This folder will be used to store temporary message header and content
chmod 700 /var/spool/signing
chown pfsigner:pfsigner /var/spool/signing
– Edit your /etc/postfix/master.cf . We will add a new TCP port for the smtpd. This port will be used on your mail/webmail client SMTP configuration. Add these lines:

2525      inet  n       -       -       -       -       smtpd
  -o content_filter=sign:dummy
sign      unix  -       n       n       -       10      pipe
  flags=Rq user=pfsigner null_sender=
  argv=/usr/local/bin/sign.sh -f ${sender} -- ${recipient}

– request your certificate using the procedure here and export the certificate+private key to a pfx file
– convert your pfx file to pem (use your email name in the pem filename as shown below). If your email is youremail@yourdomain.com, use the following command:
openssl pkcs12 -in yourcert.pfx -out youremail@yourdomain.com.pem -nodes

– create the folder certs
mkdir -p /usr/local/src/smtp-signer/certs

– copy the certificate to the certs folder created above
– create the script file /usr/local/bin/sign.sh :

#!/bin/sh
# SMTP Signer - POSIX Shell Implementation (using sed)

USERNAME="pfsigner"
SIGN_DIR="/var/spool/sign"
CERT_DIR="/usr/local/src/smtp-signer/certs"
SENDMAIL="/usr/sbin/sendmail -G -i"
OPENSSL="/usr/bin/openssl"
CERT_FILE="${CERT_DIR}/$2.pem"

E_TEMPFAIL=75

cd ${SIGN_DIR} || {
  printf "${SIGN_DIR} does not exist"
  exit ${E_TEMPFAIL}
}

# TRAP DEFINITION
# Number        SIG             Meaning
# 0             0               On exit from shell
# 1             SIGHUP          Clean tidyup
# 2             SIGINT          Interrupt
# 3             SIGQUIT         Quit
# 6             SIGABRT         Abort
# 9             SIGKILL         Die Now (cannot be trap'ped)
# 14            SIGALRM         Alarm Clock
# 15            SIGTERM         Terminate
trap "rm -f in.$$ body.$$ body2.$$ header1.$$ header2.$$ header.$$ signed.$$" 0 1 2 3 15

# Get the outgoing message : header and body
cat >in.$$

# get the header and store it in the file header.$$
sed '/^$/q' <in.$$ >header1.$$
sed '$d' <header1.$$ >header.$$

# find the line that begin with Content-type in the header text
CONTENT_TYPE=`sed -n '/Content-Type:/p' <header1.$$`

printf "\n" >body.$$
sed '1,/^$/ d' <in.$$ >>body.$$

# You can uncomment the following both lines if you want to troubleshoot and see the content of the header and message body in the log file user.log
# logger -f header.$$
# logger -f body.$$

# If a certificate exist and the email is not already signed, sign it with openssl
if [[ -f "${CERT_FILE}" ]] && [[ $CONTENT_TYPE != *"signed"* ]]; then
  # Sign mail and relay
  MSG="Signed mail from $2"

  CONTENT_TYPE_BODY=`sed -n '/Content-Type/,+1p' header.$$`
  printf "${CONTENT_TYPE_BODY}\n" > body2.$$
  cat body.$$ >>body2.$$
  sed '/Content-Type/,+1 d' <header.$$ >header2.$$

  # Sign the message body with OpenSSL
  ${OPENSSL} smime -sign -signer ${CERT_FILE} -in body2.$$ -out signed.$$
  sed '/Content-Type/,+1 d' <header.$$ >header2.$$

  # Concaetnante header and the signed message and send it using sendmail tool
  cat header2.$$ signed.$$ | ${SENDMAIL} "$@"
else
  # Relay without signing
  MSG="Unsigned mail from $2 because message already signed"
  cat in.$$ | ${SENDMAIL} "$@"
fi

STATUS=$?

if [ "${STATUS}" -eq 0 ]; then
  LOG_LEVEL="notice"
else
  LOG_LEVEL="err"
fi
logger -p "mail.${LOG_LEVEL}" -t "sign.sh" "${MSG}"

exit ${STATUS}

You can now configure your favorite email client and do not forget to specify port TCP 2525 for the SMTP settings to send your signed emails.

<>

My Powershell script categories

Digitally sign your email on the MTA side

13 thoughts on “Digitally sign your email on the MTA side

  • June 15, 2016 at 4:17 pm
    Permalink

    Thanks for the excellent script.
    Only bug I found: when you add a .JPG to the email (attached file), the signed message doesn’t validate anymore . For example, Outlook 2007 complains about “corrupted message”, or something like this.
    regards, JS

    Reply
    • June 15, 2016 at 8:38 pm
      Permalink

      Thank you for your comment. I will try to solve this issue. I will keep you informed.
      Nico

      Reply
    • June 16, 2016 at 5:12 pm
      Permalink

      I have tested successfully that case :
      – I send message from webmail client (Rainloop) configured on my custom postfix. Image attached
      – I receive successfully the message + attachment on outlook (version 2016 for me)

      Probably a problem with the method you have chosen to send your message with attachment…

      Reply
      • June 17, 2016 at 9:09 am
        Permalink

        Hi Nicolas
        (en fait, tu parles francais ? )
        Merci d’avoir pris du temps pour ce problème. L’envoi des messages avec fichiers attachés fonctionne correctement, et je reçois aussi le message ainsi que le fichier joint, mais il y a un problème au niveau de la signature. Outlook 2007 m’indique que le message a été altéré, et donc le chiffrement ne correspond pas.
        J’utilise ton script sur mon postfix personnel, webmail roundcube. La méthode d’attachement du fichier est standard.
        Peut-etre que Outlook 2007 est bugué au niveau de la vérification de la signature, et que le bug a été résolu sous 2016 ? Ca me semble très improbable, car d’autres messages signés avec attachement sont correctement validés.
        Il n’y a pas de probleme avec l’en-tete ? Je peux t’envoyer les fichiers temporaires (body, header, header1… ) qui sont créés, si besoin
        Merci, Julien

        Reply
  • June 15, 2016 at 4:17 pm
    Permalink

    Thanks for the excellent script.
    Only bug I found: when you add a .JPG to the email (attached file), the signed message doesn’t validate anymore . For example, Outlook 2007 complains about “corrupted message”, or something like this.
    regards, JS

    Reply
    • June 15, 2016 at 8:38 pm
      Permalink

      Thank you for your comment. I will try to solve this issue. I will keep you informed.
      Nico

      Reply
    • June 16, 2016 at 5:12 pm
      Permalink

      I have tested successfully that case :
      – I send message from webmail client (Rainloop) configured on my custom postfix. Image attached
      – I receive successfully the message + attachment on outlook (version 2016 for me)

      Probably a problem with the method you have chosen to send your message with attachment…

      Reply
      • June 17, 2016 at 9:09 am
        Permalink

        Hi Nicolas
        (en fait, tu parles francais ? )
        Merci d’avoir pris du temps pour ce problème. L’envoi des messages avec fichiers attachés fonctionne correctement, et je reçois aussi le message ainsi que le fichier joint, mais il y a un problème au niveau de la signature. Outlook 2007 m’indique que le message a été altéré, et donc le chiffrement ne correspond pas.
        J’utilise ton script sur mon postfix personnel, webmail roundcube. La méthode d’attachement du fichier est standard.
        Peut-etre que Outlook 2007 est bugué au niveau de la vérification de la signature, et que le bug a été résolu sous 2016 ? Ca me semble très improbable, car d’autres messages signés avec attachement sont correctement validés.
        Il n’y a pas de probleme avec l’en-tete ? Je peux t’envoyer les fichiers temporaires (body, header, header1… ) qui sont créés, si besoin
        Merci, Julien

        Reply
  • June 22, 2016 at 7:41 am
    Permalink

    Hi,
    I can confirm that your script is working correctly with rainloop, but it’s not the case with roundcube.
    Regards, Julien

    Reply
    • June 22, 2016 at 12:09 pm
      Permalink

      Hello Julien,

      Oui je parle bien francais 🙂 Thank you for your feedback… I don’t have a lot of time to test the script with Roundcube… but I will keep you informed when it will be done !

      Nico

      Reply
  • June 22, 2016 at 7:41 am
    Permalink

    Hi,
    I can confirm that your script is working correctly with rainloop, but it’s not the case with roundcube.
    Regards, Julien

    Reply
  • September 28, 2018 at 1:41 pm
    Permalink

    1) Your sign.sh has wrong SIGN_DIR? Is it /var/spool/signing?
    2). Maybe private key and certificate should not combine together and with different file permissions:
    PRIV_KEY_FILE=”${CERT_DIR}/$2-private-nopass.pem”
    then
    ${OPENSSL} smime -sign -signer ${CERT_FILE} -inkey ${PRIV_KEY_FILE} -in body2.$$ -out signed.$$

    Reply
  • September 28, 2018 at 1:41 pm
    Permalink

    1) Your sign.sh has wrong SIGN_DIR? Is it /var/spool/signing?
    2). Maybe private key and certificate should not combine together and with different file permissions:
    PRIV_KEY_FILE=”${CERT_DIR}/$2-private-nopass.pem”
    then
    ${OPENSSL} smime -sign -signer ${CERT_FILE} -inkey ${PRIV_KEY_FILE} -in body2.$$ -out signed.$$

    Reply

Leave a Reply to Nicolas HAHANG Cancel reply

Your email address will not be published.