Record of some of the computer tech I deal with so that it's documented at least somewhere.

Friday, 30 September 2011

DKIM signing with Postfix

Scenario

You use SMTP-AUTH (hopefully over TLS) on your phone to connect to a Postfix server which then sends your email over the public internet. You want to add some authority to the message somehow.

Enter OpenDKIM - look there for ./make install etc.

A DKIM Signature

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=maht0x0r.net; s=blogger; t=1317392525; 
        bh=AKfekrfl5wnhOUH0Zy1ZDQuS/dTepAwDyGPkx1cTdLk=;
        h=Content-Type:To:Subject:MIME-Version:Content-Transfer-Encoding: From:Date:Message-ID; 
        b=pJL4iPtbmXl2G+58UrJR6SeKbcoWx4sYwqUXitFs/tKvolBnSiy53D3+Mf79pYZ2L   
        5q35MwZcWEx94s4bFb88AYC44TdAhZx9hZuo/NbOvKRdUjDv028XRLJ0mOAgVR0jle  
        bCXqykNnCdduIgyO3xKyEaw+xRhwxPf74g9P2mdY=
Before signing the values of d=maht0x0r.net (domain) s=blogger (section) and the private key chosen are under your control based on the sender's email address.

I've got OpenDKIM running as a service under daemontools which is what I use for long running servers - anyway you need it listening on it's socket

% cat /etc/opendkim.conf # the important bits Canonicalization relaxed/simple KeyTable file:/etc/postfix/ssl/opendkim/KeyTable SigningTable refile:/etc/postfix/ssl/opendkim/SigningTable ExternalIgnoreList refile:/etc/postfix/ssl/opendkim/trusted-hosts InternalHosts refile:/etc/postfix/ssl/opendkim/trusted-hosts Socket inet:8891@localhost Make some keys % opendkim-genkey -d maht0x0r.net -s blogger % ls -l --rw------- M 0 root root 887 Sep 30 13:26 blogger.private --rw------- M 0 root root 320 Sep 30 13:26 blogger.txt % cat blogger.private # used to sign outgoing email -----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDEfFH3nxvFnwck2oJCq7JojSPsfAvr93+N+M5cykwFruPgJXsW EIcNymwKyEABwS7kL78eg1SYggh/8RI1jZiXfLW+PQFZLrwqE9/L0ZzZhjrjYQjK WM182/Uaj6GWtOhariSLKmR6JYtRPFh/fiOr6YDDAt/puFSLCnUi3Ppj8QIDAQAB AoGBAKj/BMEmaIyhK1PXXRJa+yhRvQQ57UcXqO5DgbrthFWfBwBTPHrN3FtQL31W nzbjIaHCL2/fJXPG4+inQDIvh33BJZa7I2h7ZDMIzEiYJ/3Y0VEPpW+3W4GTMj2I JgJWJokpCuJGqBcujhVPW2xmKAFtd27+Y+urxgt+gCrj+KrBAkEA8iNseAFbKBoN Df2krnH8rEyfRMWaLqI6hP8AtAYfVBiNW/2P0+6bBWoZ6YV/iDQwubtQsCkW3jrp FNZW6CVSPwJBAM+72SRKdTPYnplTEYkdPzyKmNV094AUw9XRVDvM1MVEgXXQBctn kPrms/TKvDq6w66INK0rVUmiNeKsohUUXc8CQQCuaUGqNx/YBNu+ZiMG3GgqG47l VIg4avZH8f4prfdG9eBskHnXKBlVjxVXFafFDgLC5d+64/3q2Pgm4DT9RlS1AkBi 7hgkY11bup8Vb+0a+pXCFFNi3Nh94+W773wJGqx94fkxjUclLoZqJvKu8tofshA9 D1re2ZM9hQXalNJ/7XobAkArHhx9+quDr6/jj/lDb8vbrcpEne2+8FivmpHmeEHJ roIJtX5TyD4gfPxC45qQEL1/q88w2OhYCP915fMBtS+X -----END RSA PRIVATE KEY----- % cat blogger.txt # used by receivers to check your signate blogger._domainkey IN TXT "v=DKIM1; r=postmaster; g=*; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEfFH3nxvFnwck2oJCq7JojSPsfAvr93+N +M5cykwFruPgJXsWEIcNymwKyEABwS7kL78eg1SYggh/8RI1jZiXfLW+PQFZLrwqE9/L0ZzZhjrjYQjKWM182 /Uaj6GWtOhariSLKmR6JYtRPFh/fiOr6YDDAt/puFSLCnUi3Ppj8QIDAQAB" ; ----- DKIM blogger for maht0x0r.net r=postmaster (DKIM errors to $r@$domain) # something like that :) Then tell opendkim about your keys, here you name them : This is where keyname is used to determine d, s and the private key % cat /etc/postfix/ssl/opendkim/KeyTable # keyname d s keyfile maht maht0x0r.net:blogger:/etc/postfix/ssl/opendkim/blogger.private maht0x0r maht0x0r.net:blogger:/etc/postfix/ssl/opendkim/maht0x0r.net.private default example.com:flex:/etc/postfix/ssl/opendkim/smtp.private And in here you match email addresses to named keys % cat /etc/postfix/ssl/opendkim/SigningTable maht@maht0x0r.net maht *@maht0x0r.net maht0x0r * default First match wins in the SigningTable, which took me a while to realise when I had * as the first entry. OpenDKIM by default will only sign mails posted from inside the network. But we can control this # ExternalIgnoreList refile:/etc/postfix/ssl/opendkim/trusted-hosts # InternalHosts refile:/etc/postfix/ssl/opendkim/trusted-hosts % echo '*' > /etc/postfix/ssl/opendkim/trusted-hosts That's right, we just sign for anyone but only SASL-Authed clients can be sending so that means only SASL-Auth'd clients get a signature. OK, then you need a DKIM entry in your DNS % host -t txt blogger._domainkey.maht0x0r.net blogger._domainkey.maht0x0r.net descriptive text "v=DKIM1\; r=maht-dkim\; g=*\; k=rsa\; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEfFH3nxvFnwck2oJCq7JojSPsfAvr93+N+ M5cykwFruPgJXsWEIcNymwKyEABwS7kL78eg1SYggh/8RI1jZiXfLW+PQFZLrwqE9/L0ZzZhjrjYQjKWM182 /Uaj6GWtOhariSLKmR6JYtRPFh/fiOr6YDDAt/puFSLCnUi3Ppj8QIDAQAB" I tried to verify it with a few online DKIM testing suites, all failed. This is a header when I send it to google : Authentication-Results: mx.google.com; spf=pass (google.com: domain of maht@maht0x0r.net designates x.x.x.x as permitted sender) smtp.mail=maht@maht0x0r.net; dkim=pass header.i=@maht0x0r.net I didn't use the keys above, obviously, so my DNS /public key is different, which you can verify : host -t txt flex._domainkey.maht0x0r.net flex._domainkey.maht0x0r.net descriptive text "v=DKIM1\; r=maht-dkim\; g=*\; k=rsa\; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDeQqlOKodABflhh076+6yueVDiQAm5koXBEg QG31F04Al01HeyYxM7HllBjvrDfuQei102SyZH298QDDXmk/4Wtb8zh2SZ5xammqSR89HkKmbqFaSHD 2iEAgiEeWGf7nL/lK2NM0QljP5aOUpjOW1SGfRiB1AtipAcKNPLm1NRIQIDAQAB" Next job is to find out how to sign it manually without using an OpenDKIM process forever listening. Then I can add my own headers *before* signing it.

Thursday, 22 September 2011

SSL Encryption in PHP Without HTTPS

This all started because the EU decided that one can't use cookies without authorisation, so how to hand state around a website without client side storage.

I investigated some things and played with SSL.

It works like this :

plain -> encryption with public key -> cipher

cipher -> decryption with private key -> plain

or

plain -> encryption with private key -> cipher

cipher -> decryption with public key -> plain

Using this mechanism one can envisage a system where two entities can communicate by first exchanging public keys and then encrypted data that only the recipient can decrypt.

First off we're going to need some keys. These are generated using the openssl command line tools.

% openssl genrsa -out private.pem 1024 % openssl rsa -in private.pem -out public.pem -outform PEM -pubout

If you're feeling fruity you can keep them both in the same file, remembering not to send it when you mean to just send the public part !

% cat private.pem public.pem | tee sslkeys.pem -----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQDnv6zntTRDw9r0O2wJWrzyfyeVO/++9JaXAq6M60uKKNGK/Lhe j4rrMYyN4l8arUBWJBHLaAn9VPHymXA9VdoXaPIGu+YS9fhraKUkz3eXcQhxgPXx xAkyzOMsyEbLt7E9rQ9r9cNZVJ7rQBwloY0WHjYsZGBoT2fZI8wzz1+jTwIDAQAB AoGAVnoAxCmqygqgfnhZ9Rel3/swwxAze4b7VnhKuAzpEDHxFyL8jVSU6vR/VUZ+ ZI73re0hsrws1hpHelZlOo35pgLZif230IgpNfZwYnvil4nTSa9oFXAr11ta/T0g NJwxc7HovZhtwWn9vE5abRMPiZ2HAXzL5gBXjT7DsaHuNVECQQD2Tftl08GdxtVh RNNEkHmoLHyXvfR8HLP7veGK9nGrZRhWVAqGEeKTiSw7YbMFr3Yss99o7m1fMlnP Y9e3jsxJAkEA8N8Da2hlK+p2uNnAIukslRB4Z8VyqIWA7fNYB8u2PMO8Xsxi27NU MNzmk69Yz2JCpmn+TiPY/3SSmS81qw2C1wJBAMA2SyJEBqziJlMqKtUvCkG7td+V Vd4laC/lFsYjXMGsuzljjHLkMjWArwwISnT9YPOxy39P0fqgiIXYHNgakEECP0Hc uRKleQSJF+1znRXurEIWPtYhJzjtSFPINknraekznE5PlLh+UIcL4ACB8cbDF3Zp hR/YrX0sYul//yzGhQJAaFvthOCajq0el+aa4xMCsrtNnvFSSXSLDbAZsK0HPcpF mVUe3tRX0dsTZ0ant35PMAEYMQhgxB9s9G+/Un/c/g== -----END RSA PRIVATE KEY----- -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDnv6zntTRDw9r0O2wJWrzyfyeV O/++9JaXAq6M60uKKNGK/Lhej4rrMYyN4l8arUBWJBHLaAn9VPHymXA9VdoXaPIG u+YS9fhraKUkz3eXcQhxgPXxxAkyzOMsyEbLt7E9rQ9r9cNZVJ7rQBwloY0WHjYs ZGBoT2fZI8wzz1+jTwIDAQAB -----END PUBLIC KEY-----

OBVIOUSLY don't use it for anything sensitive !

You can generate pem files that are passworded too, probably a good idea, when I find out how to do it, I'll post an update. The code below would then use openssl_get_privatekey($pem, $passphrase);

On to the PHP bit

All very simple really :

<?php function private_encrypt($pem, $plain) { openssl_get_privatekey($pem); openssl_private_encrypt($plain, $cipher, $pem); return $cipher; } function private_decrypt($pem, $cipher) { openssl_get_privatekey($pem); openssl_private_decrypt($cipher, $plain, $pem); return $plain; } function public_encrypt($pem, $plain) { openssl_get_publickey($pem); openssl_public_encrypt($plain, $cipher, $pem); return $cipher; } function public_decrypt($pem, $cipher) { openssl_get_publickey($pem); openssl_public_decrypt($cipher, $plain, $pem); return $plain; } ?>

You can use either the combined sslkeys.pem or the appropriate public.pem / private.pem files for $pem

Do not keep the private part or combined file in your webspace

$cipher is binary so if you want to send it over text channels you will need to base64_encode it or something

e.g.

<?php function encode($plain) { static $pem; if(!$pem) $pem = file_get_contents("/etc/ssl/sslkeys.pem"); return base64_encode(public_encrypt($pem, $plain)); } function decode($cipher) { static $pem; if(!$pem) $pem = file_get_contents("/etc/ssl/sslkeys.pem"); return private_decrypt($pem, base64_decode($cipher)); } ?> the static keyword means that the value of the variable is preserved between function calls

What to use it for

Well as it happens, I'm using it to talk to Paypal. You can sent a custom string from your Buy button and this will be sent back to you later. So I'm sending the buyer an encrypted transaction code, they send it to Paypal and then I get it back from Paypal. Like many things in the world of encryption it's easy to be fooled into thinking things are more secure than they are. But hey, at least no-one knows what I'm sending :)

Another thing that can be done is if I encrypt something with my private key, you can decrypt with my public key. By doing this you know only I (or someone who has stolen my private key :) has sent the message. If I encrypt it with your private key first then no-one else can read it except you.

I could even base64 it, upload it to pastebin, and post the URL - like so http://pastebin.com/kSnWgzHs

Then linking you and I together is much, much harder.

RIPA can lick my balls.