Difference between revisions of "Postfix cert based relay"
m (→python client) |
m (→php client) |
||
Line 312: | Line 312: | ||
Something to be careful about in the php tls library is that the local_cert includes both the certificate and the key, so just concatenate them using cat to a new file and as always, do not make that world readable :-) | Something to be careful about in the php tls library is that the local_cert includes both the certificate and the key, so just concatenate them using cat to a new file and as always, do not make that world readable :-) | ||
− | < | + | <syntaxhighlight lang="php"> |
+ | |||
<?php | <?php | ||
$server = "host.domain.tld"; | $server = "host.domain.tld"; | ||
Line 330: | Line 331: | ||
fread($smtp, 512); | fread($smtp, 512); | ||
+ | </syntaxhighlight> | ||
// Switch to TLS | // Switch to TLS | ||
fwrite($smtp,"STARTTLS\r\n"); | fwrite($smtp,"STARTTLS\r\n"); |
Revision as of 22:31, 22 July 2015
In order to securely allow roaming smtp clients to relay through a postfix smtp server one common setup is using SASL authentication in combination with starttls and a (usually virtual) user database.
There are plenty of info about how to set that up so I will not do it here.
What not many people know is that you can setup postfix to allow relaying using a certificates (PKI).
Postfix has two ways of allowing relaying with certificates, but here I will only specify one.
in order to allow relaying we need to have some settings in place:
Contents
starttls
# TLS SERVER settings # offer tls to clients smtpd_use_tls = yes
local cert and key
in this case I use the excellent startssl.com free certificates because they are trusted by most devices and they are free. The smtpd_tls_cert_file has the startssl chain cert (just cat your.cert startssl.crt > postfix.crt to get it).
The smtpd_tls_key_file should be readonly for root. I share this key with apache, so it is alse readonly for apache, but not for the rest (440 perms).
# local cert smtpd_tls_key_file = /etc/pki/tls/private/startssl_asenjo_nl.key smtpd_tls_cert_file = /etc/pki/tls/certs/postfix_certchain.crt
trusted CA bundly file
centos sets it here:
# CA bundle smtpd_tls_CAfile = /etc/pki/tls/certs/ca-bundle.crt
entropy generator , logs, headers
# random source generator tls_random_source = dev:/dev/urandom # log level tls # 0 default no logging # 1 startup and cert info # 2: 1 + info on tls negotiation # 3: 2 + hex and ascii dumps negotiation # 4: 3 + hex and ascii dumps trasnmission after client starttls smtpd_tls_loglevel = 1 # add tls header info smtpd_tls_received_header = yes
tls caching, tls ciphers
# tls session cache smtpd_tls_session_cache_database = btree:$data_directory/smtpd_cache smtpd_tls_session_cache_timeout = 3600s # disable insecure ciphers smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3 smtpd_tls_protocols = !SSLv2, !SSLv3 smtp_tls_mandatory_protocols = !SSLv2, !SSLv3 smtp_tls_protocols = !SSLv2, !SSLv3
server side certificate based relaying
Just three settings:
# ask for certificates: smtpd_tls_ask_ccert = yes # these certs may relay relay_clientcerts = hash:/etc/postfix/relay_clientcerts smtpd_tls_fingerprint_digest = sha1
the file relay_clientcerts is a normal postfix hash database with in the left hand side the fingerprint and on the right hand side any field we want. The most logical thing to put in there is the name of the owner of the certificate because locating one based just on its fingerprint is more involved ;-)
So use your favourite tool to find the fingerprint of the certificates you want to allow relaying through your server and create that file like this:
AB:9D:0F:F6...rest of fingerprint name_owner
After you are done, postmap the file like with any other postfix hash database.
smtpd restrictions
finally, one needs to allow the people using the certificates to relay. You can accomplish this like so:
# smtpd client restrictions smtpd_client_restrictions = permit_tls_clientcerts, reject_rbl_client zen.spamhaus.org # smtpd recipient restriction smtpd_recipient_restrictions = permit_tls_clientcerts, reject_non_fqdn_recipient, reject_non_fqdn_sender, permit_mynetworks, reject_unauth_destination, reject_rbl_client zen.spamhaus.org, check_policy_service unix:postgrey/socket,
so we add permit_tls_clientcerts before other reject directives (the first one wins) and after that you can reload postfix. If everything went fine we should be able to relay from our clients.
test certificate authority
create test CA with openssl tools
you can totally skip this step if you already have a working PKI in place like Active Directory Certificate Services or Dogtag in the freeipa.org project.
Plenty of tutorials on how to create a test CA using openssl. I used this one but others may work as well. The tldr; version is:
openssl genrsa -out rootCA.key 4096 openssl req -x509 -new -nodes -key rootCA.key -days 365 -out rootCA.crt -subj '/C=NL/ST=Gelderland/L=Arnhem/CN=myhost' openssl genrsa -out myuser.key 4096 -subj '/C=NL/ST=Gelderland/L=Arnhem/CN=myuser' openssl req -new -key myuser.key -out myuser.csr -subj '/C=NL/ST=Gelderland/L=Arnhem/CN=myuser' openssl x509 -req -in myuser.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out myuser.crt -days 365
So we create a CA key, a CA certificate. The we create a user key file, genereate a certificate signing request for that user and sing it. Obviously replace the NL stuff with your own stuff. The result is a directory with all those files.
add the rootCA.crt to the trusted CA's file in the postix server
Now we have a CA, and a user (or host or whatever) certificate that we can use in combination with postfix. In order to avoid those annoying invalid/unknown certificate warnings, we import the rootCA.crt in the postfix host. In RHEL/centos it is quite simple, just drop that file in /etc/pki/ca-trust/source/anchors and run update-ca-trust-enable. Done.
verify rootCA.crt imported in ca bundle file
You can verify it's in there using this simple perl script found in serverfault
#!/usr/bin/perl # script for splitting multi-cert input into individual certs # Artistic Licence # # v0.0.1 Nick Burch <nick@tirian.magd.ox.ac.uk> # v0.0.2 Tom Yates <tyates@gatekeeper.ltd.uk> # $filename = shift; unless($filename) { die("You must specify a cert file.\n"); } open INP, "<$filename" or die("Unable to load \"$filename\"\n"); $thisfile = ""; while(<INP>) { $thisfile .= $_; if($_ =~ /^\-+END(\s\w+)?\sCERTIFICATE\-+$/) { print "Found a complete certificate:\n"; print `echo "$thisfile" | openssl x509 -noout -text`; $thisfile = ""; } } close INP;
in our case just run it with as first argument the path to the ca bundle file and pipe it to less so you can scroll and search for the name of your CA.
Once the test rootCA is known to the postfix server we can test if this all works. You can try using thunderbird but I thought using some programming languages could be a bit more useful (and fun).
perl client
the key file should *not* be readable but by the user running this script. You need to have the IO::Socket::SSL library which in centos is is perl-IO-Socket-SSL.noarch. Net::SMTP is part of the core libraries.
#!/usr/bin/env perl
use strict;
use warnings;
use Net::SMTP;
my $smtp = Net::SMTP->new(
Host => 'host.domain.tld',
Hello => 'hi there',
Timeout => 5,
Debug => 4,
);
$smtp->starttls(
SSL_cert_file => "/path/to/myuser.crt",
SSL_key_file => "/path/to/myuser.key",
);
$smtp->mail("user\+perl\@domain\.tld\n");
$smtp->to("user\@gmail\.com\n");
$smtp->data;
$smtp->datasend("From: Little John <user\@domain.tld>\n");
$smtp->datasend("To: Big John <suer\@gmail.com>\n");
$smtp->datasend("Subject: certificate based relay testing\n");
$smtp->datasend("MIME-Version: 1.0\n");
$smtp->datasend("Content-Type: text/plain; charset=us-ascii\n");
$smtp->datasend("X-Mailer: Net::SMTP IO::Socket::SSL\n");
$smtp->datasend( "X-mydate: " . localtime() . "\n" );
$smtp->datasend("\n");
$smtp->datasend("testing again\n");
$smtp->dataend;
$smtp->quit;
Now run it and you should see something like this:
$ perl script.pl Net::SMTP>>> Net::SMTP(3.07) Net::SMTP>>> Net::Cmd(3.07) Net::SMTP>>> Exporter(5.72) Net::SMTP>>> IO::Socket::IP(0.37) Net::SMTP>>> IO::Socket(1.37) Net::SMTP>>> IO::Handle(1.35) Net::SMTP=GLOB(0x237d4e0)<<< 220 host.domain.tld ESMTP Postfix Net::SMTP=GLOB(0x237d4e0)>>> EHLO hi there Net::SMTP=GLOB(0x237d4e0)<<< 250-host.domain.tld Net::SMTP=GLOB(0x237d4e0)<<< 250-PIPELINING Net::SMTP=GLOB(0x237d4e0)<<< 250-SIZE 10240000 Net::SMTP=GLOB(0x237d4e0)<<< 250-VRFY Net::SMTP=GLOB(0x237d4e0)<<< 250-ETRN Net::SMTP=GLOB(0x237d4e0)<<< 250-STARTTLS Net::SMTP=GLOB(0x237d4e0)<<< 250-ENHANCEDSTATUSCODES Net::SMTP=GLOB(0x237d4e0)<<< 250-8BITMIME Net::SMTP=GLOB(0x237d4e0)<<< 250 DSN Net::SMTP=GLOB(0x237d4e0)>>> STARTTLS Net::SMTP=GLOB(0x237d4e0)<<< 220 2.0.0 Ready to start TLS Net::SMTP::_SSL=GLOB(0x237d4e0)>>> EHLO hi there Net::SMTP::_SSL=GLOB(0x237d4e0)<<< 250-host.domain.tld Net::SMTP::_SSL=GLOB(0x237d4e0)<<< 250-PIPELINING Net::SMTP::_SSL=GLOB(0x237d4e0)<<< 250-SIZE 10240000 Net::SMTP::_SSL=GLOB(0x237d4e0)<<< 250-VRFY Net::SMTP::_SSL=GLOB(0x237d4e0)<<< 250-ETRN Net::SMTP::_SSL=GLOB(0x237d4e0)<<< 250-ENHANCEDSTATUSCODES Net::SMTP::_SSL=GLOB(0x237d4e0)<<< 250-8BITMIME Net::SMTP::_SSL=GLOB(0x237d4e0)<<< 250 DSN Net::SMTP::_SSL=GLOB(0x237d4e0)>>> MAIL FROM:<user+perl@domain.tld> Net::SMTP::_SSL=GLOB(0x237d4e0)<<< 250 2.1.0 Ok Net::SMTP::_SSL=GLOB(0x237d4e0)>>> RCPT TO:<user@gmail.com> Net::SMTP::_SSL=GLOB(0x237d4e0)<<< 250 2.1.5 Ok Net::SMTP::_SSL=GLOB(0x237d4e0)>>> DATA Net::SMTP::_SSL=GLOB(0x237d4e0)<<< 354 End data with <CR><LF>.<CR><LF> Net::SMTP::_SSL=GLOB(0x237d4e0)>>> From: Big John <user@domain.tld> Net::SMTP::_SSL=GLOB(0x237d4e0)>>> To: Little John <user@gmail.com> Net::SMTP::_SSL=GLOB(0x237d4e0)>>> Subject: certificate based relay testing Net::SMTP::_SSL=GLOB(0x237d4e0)>>> MIME-Version: 1.0 Net::SMTP::_SSL=GLOB(0x237d4e0)>>> Content-Type: text/plain; charset=us-ascii Net::SMTP::_SSL=GLOB(0x237d4e0)>>> X-Mailer: Net::SMTP IO::Socket::SSL Net::SMTP::_SSL=GLOB(0x237d4e0)>>> X-mydate: Wed Jul 22 21:28:02 2015 Net::SMTP::_SSL=GLOB(0x237d4e0)>>> testing again Net::SMTP::_SSL=GLOB(0x237d4e0)>>> . Net::SMTP::_SSL=GLOB(0x237d4e0)<<< 250 2.0.0 Ok: queued as 5493F8008A Net::SMTP::_SSL=GLOB(0x237d4e0)>>> QUIT Net::SMTP::_SSL=GLOB(0x237d4e0)<<< 221 2.0.0 Bye
In your postfix mail logs you should see something like this:
Jul 22 21:28:02 host postfix/smtpd[32469]: connect from host.domain.tld [xx.xx.xx.xx] Jul 22 21:28:02 host postfix/smtpd[32469]: setting up TLS connection from host.domain.tld [xx.xx.xx.xx] Jul 22 21:28:02 host postfix/smtpd[32469]: host.domain.nl[xx.xx.xx.xx]: Trusted: subject_CN=myuser, issuer=myhost, fingerprint=AB:9D:0F:F6:BA:52........rest of fingerprint Jul 22 21:28:02 host postfix/smtpd[32469]: Trusted TLS connection established from host.domain.nl[xx.xx.xx.xx]: TLSv1.2 with cipher AES128-SHA256 (128/128 bits) Jul 22 21:28:02 host postfix/smtpd[32469]: 5493F8008A: client=host.domain.nl[xx.xx.xx.xx] Jul 22 21:28:02 host postfix/cleanup[32474]: 5493F8008A: message-id=<> Jul 22 21:28:02 host postfix/qmgr[26512]: 5493F8008A: from=<user+perl@host.com>, size=596, nrcpt=1 (queue active) Jul 22 21:28:02 host postfix/smtpd[32469]: disconnect from host.domain.nl[xx.xx.xx.xx] Jul 22 21:28:02 host postfix/smtp[32476]: setting up TLS connection to gmail-smtp-in.l.google.com[173.194.65.26]:25 Jul 22 21:28:02 host postfix/smtp[32476]: Trusted TLS connection established to gmail-smtp-in.l.google.com[173.194.65.26]:25: TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits) Jul 22 21:28:03 host postfix/smtp[32476]: 5493F8008A: to=<user@gmail.com>, relay=gmail-smtp-in.l.google.com[173.194.65.26]:25, delay=1.3, delays=0.1/0.05/0.42/0.69, dsn=2.0.0, status=sent (250 2.0.0 OK 1437593283 tn8si4104968wjc.133 - gsmtp) Jul 22 21:28:03 host postfix/qmgr[26512]: 5493F8008A: removed
And in the inbox of your gmail account you should see a message relayed from your postfix server.
The IO::Socket::SSL is pretty picky and you should use exactly the exact hostname in the subject of the postfix server certificate or you will get client certificate errors.
python client
the python standard library has everything we need
import smtplib
from email.mime.text import MIMEText
fp = open('kk.pl', 'rb')
msg = MIMEText(fp.read())
msg['Subject'] = 'the contents of %s' % 'kk.pl'
msg['From'] = 'user@domain.tld'
msg['To'] = 'user@gmail.com'
fp.close()
s = smtplib.SMTP('host.domain.tld')
s.ehlo()
s.starttls('myuser.key', 'myuser.crt')
s.ehlo()
s.sendmail('user+python@domain.tld, 'user@gmail.com', msg.as_string())
s.quit
In this case I use a text file (in fact the perl script) as the message we are sending, I just copied one of the examples of the python documenation and adapted it a bit. The python library is not so picky about the host name, it works with a different A record than the one in the certificate subject.
php client
this one is not yet fully functional. I get to relay it through postfix but I keep some errors about 'improper command pipelining after RCPT' and the connection gets dropped sometimes. But it's a start.
Something to be careful about in the php tls library is that the local_cert includes both the certificate and the key, so just concatenate them using cat to a new file and as always, do not make that world readable :-)
<?php
$server = "host.domain.tld";
$myself = "myuser.example.com";
$cabundle = '/etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt';
$localcert = '/path/to/user.crt';
$env_from = "user@domain.tld";
$env_to = "user@gmail.com";
$header_from = 'Little John <$env_from>';
$header_to = 'Big John <$env_to>';
// Establish the connection
$smtp = fsockopen( "tcp://$server", 25, $errno, $errstr );
fread( $smtp, 512 );
fwrite($smtp,"HELO $myself\r\n");
fread($smtp, 512);
// Switch to TLS fwrite($smtp,"STARTTLS\r\n"); fread($smtp, 512); stream_set_blocking($smtp, true); stream_context_set_option($smtp, 'ssl', 'verify_peer', true); stream_context_set_option($smtp, 'ssl', 'allow_self_signed', false); stream_context_set_option($smtp, 'ssl', 'capture_peer_cert', true); stream_context_set_option($smtp, 'ssl', 'cafile', $cabundle); stream_context_set_option($smtp, 'ssl', 'local_cert', $localcert ); $secure = stream_socket_enable_crypto($smtp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); stream_set_blocking($smtp, false);
fwrite( $smtp, "mail from: <" . $env_from . ">" . "\r\n"); fwrite( $smtp, "rcpt to: <" . $env_to . ">" . "\r\n"); fwrite($smtp, 'DATA'."\r\n"); fwrite($smtp, 'Subject: hi there' . "\r\n"); fwrite($smtp, '.' . "\r\n");
?> </pre>
In all cases If you remove the starttls bits with the cert/key I get this:
Net::SMTP=GLOB(0x17de9c8)<<< 554 5.7.1 Service unavailable; Client host [xx.xx.xx.xx] blocked using zen.spamhaus.org; http://www.spamhaus.org/query/bl?ip=xx.xx.xx.xx
So postfix does not allow relaying in this case because I am in a consumer ip block.