SMIME TOOL 29.10.1999, 17.11.1999, Sampo Kellomaki <sampo@iki.fi> NOTE: This is still highly experimental code and build system has not been perfected yet. No Windows build is known to exist (contributions?). OFFICIAL WEB SITE http://www.bacus.pt/Net_SSLeay/smime.html BUILDING (build and install OpenSSL-0.9.4, from www.openssl.org) tar xzf smime-0.7.tgz cd smime-0.7 cons smime # get cons from http://www.dsmit.com/cons/ cons SMIMEutil.so # build the perl module (optional) ./smime -help # shows quick usage ./smime -dv ../smime-0.7.tgz (cut my certificate and distribution signature from the web site and paste to stdin) TUTORIAL PART 1: SIGNING AND ENCRYPTING First you need to have certicate and private key in pem format. To produce them, use openssl tool or export them from your browser. I illustrate the latter method first, because I'm going to use Netscape browser for interoperability testing later. You can peek at TUTORIAL PART3, Key generation if you need to do this yourself. - Go to security info dialog in Netscape browser. - From Certificates-Yours export your certificate (if you don't have a certificate installed yet, read the FAQs and mailing list archieves at www.openssl.org), save it as me.p12. It will ask for password to protect your private key. openssl pkcs12 -clcerts <me.p12 >me.pem - it will ask for the password to open your private key and then asks you to invent a new password that will be used to protect your private key in pem format more me.pem You should see something like this: Bag Attributes friendlyName: your@email.com localKeyID: F3 85 A8 4B DA 39 B6 40 6B D6 20 01 39 46 6A 94 47 9D 2C 0F Key Attributes: <No Attributes> -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,541E04862A13F6B1 8+2vo6Iz49uj/Mf31JTgaRuIq9ueHsknsHXhmXp7s1BmS8xulT22Zzpdh6g1yqAO (snip)XeQsZrWykdWvN2qGu/cNa2HnUQAG0p25tNZ3CKmqpJBVg0RXr20JlQ== -----END RSA PRIVATE KEY----- Bag Attributes friendlyName: your@email.com localKeyID: F3 85 A8 4B DA 39 B6 40 6B D6 20 01 39 46 6A 94 47 9D 2C 0F subject=/C=PT/L=City/O=Company/OU=Dept/CN=Your Name/Email=your@email.com issuer= /C=PT/L=City/O=Your CA/OU=Personal Certs/CN=End user CA/Email=certifier@ca.com -----BEGIN CERTIFICATE----- MIIDEzCCAnygAwIBAgIBAzANBgkqhkiG9w0BAQQFADCBlTELMAkGA1UEBhMCUFQx (snip)Tj0JYGZMzSUfzOG3wajK6B39d6EyXK8= -----END CERTIFICATE----- Ok. Now you are all set to use smime tool. First lets create simple mime entity (see RFC1521 for definition): echo foo | ./smime -mime text/plain | tee foo.mime Signing Now, let's sign it: ./smime -s me.pem password <foo.mime | tee foo.smime And send it: ./send.pl 'Sig test' your@email.com your@email.com <foo.smime Now go to your email reading software (Netscape Communicator suggested for this exercise) and read the mail you just sent. It should display as signed. In the previous command the second argument is the From: header which must match friendlyname/EMAIL in me.pem You can repeat this success using following pipeline: echo foo | ./smime -mime text/plain | ./smime -s me.pem password \ | ./send.pl 'Sig test' your@email.com your@email.com (Note how \ is used for folding the lines. In reality you should type all the stuff in one line.) Clear signing The previous method produces a base64 blob that you probaly would not like to send to a news group. Clear signing allows mail to be read even if the reader is not S/MIME aware: echo foo | ./smime -mime text/plain | ./smime -cs me.pem password \ | ./send.pl 'Sig test' your@email.com your@email.com Note how the message has "foo" visible. Now go read this with your mail reader. It should also display as signed. If not, then its possible that canonization was not correctly done. That might even be an error in my part. Encrypting To encrypt you need to know recipient's cert. Here I use our own because its by definition already installed in our browser. echo foo | ./smime -mime text/plain | ./smime -e me.pem \ | ./send.pl 'Enc test' your@email.com your@email.com Now you should be able to read your mail (it may ask for a password to open your private key) and see the message in plain text, but marked as encrypted. Signing and Encrypting Next we'll first sign a message and then wrap it in encryption. This is the usual way and hides the signatories from eavesdroppers: echo foo | ./smime -mime text/plain | ./smime -cs me.pem password \ | ./smime -e me.pem | ./send.pl 'test' your@email.com your@email.com Again your mail reader should show the message marked as Encrypted and Signed. Encrypting and Signing The other way is to first encrypt and then sign the encrypted message. This might be useful in some automated context where a robot would verify the signature but the robot should not be allowed to see the message: echo foo | ./smime -mime text/plain | ./smime -e me.pem \ | ./smime -cs me.pem password \ | ./send.pl 'Enc test' your@email.com your@email.com Now Netscape shows two icons: one saying "Signed" and placed near the headers. Other icon that says "Encrypted" appears in the content area. In fact S/MIME specification allows arbitrary nesting of encryptions and signatures, what ever your application may need. Multipart content Now for the final part that is not really S/MIME specific, but nice to have. You can first compose a normal mime multipart message, possibly even containing some encrypted components and some not. Try this: echo bar | ./smime -m image/gif some.gif \ | ./smime -cs me.pem password | ./smime -e me.pem \ | ./send.pl 'Enc test' your@email.com your@email.com Now you should see signed and encrypted message with an image attached. The multipart functionality implemented by -m is very basic and by no means anything generic. Its only a demonstration. TUTORIAL PART 2: DECRYPTING AND VERIFYING SIGNATURES Decrypting To demonstrate decrypting, I'll cut the mail from the loop. Basically I'll demonstrate introperability with myself. If you want to try interoperability with Netscape, you can use Netscape to send mail and grab it from mail spool and then feed it into pipeline. As this is a bit messy and might involve editing the file manually, I wont go into that now. Here's a simple encryption - decryption: echo foo | ./smime -mime text/plain | ./smime -e me.pem \ | tee foo.p7m | ./smime -d me.pem passwd foo wrapped in mime headers should come out. I also used tee(1) to put the encrypted form in a file in case I also want to verify it with Netscape ./send.pl 'Enc test' your@email.com your@email.com <foo.p7m or in qmail /var/qmail/bin/qmail-inject your@email.com < foo.p7m This is most convenient method because it allows you to import certificates belonging to others than yourself and still deliver mail to yourself. You want this, because you will soon discover that Netscape can hold in its certificate database only one certificate per email address (or is it distinguished name?) and if that one "slot" is occupied, say, in "People" section, then you can't import it any more to "Yours" section. Hence the solution is to use different email address (and friendly name) every single time. Verifying signature Due to somewhat incomplete nature of OpenSSL-0.9.4 PKCS7 signature verification code, I have implemented my own signature verfication scheme that works in five steps 1. find out who signed the message 2. verify message signature using signer's certificate 3. find out who certified the signer's certificate 4. verify signer's cert using certifier's cert 5. repeat 3. & 4. until at root of the chain This has the advantage that there are less obscure stuff and assumptions hidden inside the program, i.e. user must understand what signature and certificate verification is all about and you get to observe every step of the way. The down side is that its less automatic. In my particular application this does not happen to be a problem because I have out of band information about who is supposed to have signed what. Here we go: lets produce a signature to play with echo foo | ./smime -mime text/plain | tee foo.mime \ | ./smime -s me.pem password | tee foo.p7m Now see who signed it ./smime -qs < foo.p7m Now I assume that I have some way of finding the certificate using the issuer DN and serial number returned in the previous step. These two pieces of information constitute unique ID for a certificate. Perhaps I keep my certs in LDAP or some other database. Anyway, assume the certificate is found, then ./smime -v me.pem <foo.p7m | tee foo2.mime diff foo.mime foo2.mime # just to check! Note that diff may show white space differences (try diff -b) because line endings in your mime message were canonized to CRLF for signing. This is mandated by S/MIME specification. Now proceed with verifying the certificate: ./smime -qc <me.pem Now out of band means are used to find the signer's certificate. Then to check that the signature matches: ./smime -vc ca.pem <me.pem Verifying clear signature (does not currently work :-( Under construction: chain verification (does not currently work) mkdir certs cd certs cp ../me.pem . ../hash-certs.pl *.pem cd .. echo foo | ./smime -mime text/plain | ./smime -s me.pem password \ | ./smime -v TUTORIAL PART 3: A KEY GENERATION METHOD Bootstrapping a simple public key infrastructure Suppose entity M is funding projects and needs to have all project leaders (Ps) sign a contract specifying responsibilities. M distributes a simple application that incorporates the smime tool. Ps use the application to sign the contracts and send them electronically to M. If irregularities develop, M sues P to court and uses the digitally signed contract as evidence. For this to work I. M must convince the court that the system does not have technical flaws that could work in his favor. This proof is much easier with digital signatures than with systems that depend on procedural integrity (e.g. passwords over SSL protected connection may prove at the moment the intention of P, but when document is recovered from backup 10 years later, all procedural proofs vanish) II. P's signature must be as valid as paper and ink 1. P's real world identity must be connected to digital signature 2. P must have understood what it means to sign contract digitally 3. P must have sufficient integrity or the system must technically guarantee that P's private key could not have been used by any one else 4. P must have acted by free will and in full powers of mind 5. law must not prohibit digital signature M needs to establish a simple public key infrastructure. I propose that the application generates key pairs for P's and sends certification requests, further, it also prints the certification request in paper form including, - fingerprint of public key (as number and as bar code) - full dump of public key and all attributes appearing in the certification request - legal language to guarantee 2. & 3. (on point 3 we rely on integrity) - details of conventional identification of P - space for signature With this paper P goes to some commonly agreed and trustworthy notary. This could be notary public or it could be some trusted administrative organ in P's organization. It could even be M, but that would cause a bureaucracy bottle neck at M, and hence increase costs of the solution. P signs the paper and proves his identity in presence of the notary who confirms the act. The paper is sent to M. This takes care of 1. and 4. As paper contract about use of digital signatures now exists between M and P, 5. is only of concern if it explicitly nullifies such contract (or if court practice still does not consider digital signature valid?). Finally paper arrives to M where a clerk processes it (the bar code helps here). He finds the certification request from data base and sees that it matches the paper and issues a digital cerificate that is immediately placed on a public server for everybody to see. The paper is securely archieved forever. Once the certificate is publically available, signing and verifying contracts using it is trivial and can even be done between P and some other party than M (e.g. certificate could be used for email). However, to bootstrap the system it should, ideally, be possible to sign your first contract even without waiting for the certification to happen. After all, the impulse for P to adhere to PKI of M came from needing to sign a contract (dead line for research proposals may be very close). The contract would be in signed, but not verified status until the certification happens. Here the OpenSSL (or PKCS7?) does not serve us well, because it needs a certificate even for the signing operation, although common sense says that the private key and attributes of certification request (ok, you can't know what serial number will be assigned) should be enough. I solve this problem by signing the first contract with a self signed certificate. Although this is quite suboptimal, it allwos me to get going without hacking OpenSSL innards too much. Only complication arises from needing to establish that also the certified public key is able to decrypt the hash of the signed material. Key generation Smime tool contains simple key generation command that will make certification request as well as self signed cert. This is bit simplistic and really geared towards my particular application as the X509v3 certificate options are hardwired. Be sure to read the source code to check they are the way you want. echo "commonName=Joe Smith|emailAddress=joe@test.com" \ | ./smime "d=foo" passwd req.pem >priv_ss.pem The stuff that is echoed to stdin is your distinguished name. You must use long forms of attribute name and you can only use attributes known to OpenSSL. The req.pem should be sent to certification authority for signing. Meanwhile you can use the self signed certificate which was output to stdout, here priv_ss.pem. Note that the private key is also output to the stdout so do not give that file to anyone. If you need to give the certificate, you should edit a copy of priv_ss.pem and remove the private key. To be able to import your private key and certificate to Netscape you can use ./smime -pem-p12 you@test.com passwd pw-for-p12 <priv_ss.pem >me.p12 The first argument (the email address) is the friendly name. Netscape appears to match this against From mail header when verifying signatures. For minimum troubles you should keep this equal to emailAddress field of your certificate. Being a certificate authority Once you have made your req.pem, you can send it to some commercial certification authority or you can just be your own. The CA functionality of smime tool is not very complete. Basically it allows you to do the crypto part (signing certificate request with CA's private key) but you must manually do book keeping to ensure uniqueness of serial numbers and to make sure you do not issue the same certificate twice, etc. Here's how you'd sign a request (you probably used -kg to make the CA's certificate): ./smime -ca ca-id.pem very-secret 1 <req.pem >cert.pem The numebr one is the serial number. As I said, you should do bookkeeping to ensure you never reuse a serial number. Many systems depend on being uniquely able to identify certificate by its issuing authority and serial number. A certificate authority that can not guarantee uniqueness of serial numbers is trustworthy. Please note that the -ca hard wires all X509v3 options and extensions. Be sure to read the source to check they are the way you want them. TUTORIAL PART 4: SIGNING AND VERIFYING SOFTWARE DISTRIBUTIONS smime tool has detached signature feature which is meant for signing and verifying software distributions. Here's how. First make yourself an identity echo 'commonName=my dist key|emailAddress=you@test.com' \ | ./smime -kg '' pw dist-req.pem >dist-id.pem Now open dist-id.pem in editor and save the certificate part as dist-cert.pem. Pubish dist-cert.pem on your web site. Then sign your software package ./smime -ds dist-id.pem pw <your-dist-1.00.tgz >your-dist-1.00.sig I suggest convention of naming the signature file with same name as the tarball, but extension `.sig'. Now put the .sig file available where ever you put your tarball. You should also publish it on your web site so people can get it even if they forgot to download the .sig file. Or you could produce a combination signature and certificate file and put that available on your website ./smime -ds dist-id.pem pw <your-dist-1.00.tgz \ | cat - dist-cert.pem >your-dist-1.00.sigcert To verify a distribution signature one would say ./smime -dv your-dist-1.00.tgz (cut certificate and distribution signature from the web site and paste to stdin) The dv looks for the -----BEGIN/END CERTIFICATE----- separator to figure out where the certificate ends and signature starts. If you already had the .sig or the certificate you could just say cat dist-cert.pem your-dist-1.00.sig | ./smime -dv your-dist-1.00.tgz (OK, PGP already exists to do this stuff, but I always found it quite messy to deal with detached signatures in PGP. I'm sure poor usability leads to less people verifying the signatures.) SMIMEUTIL LIBRARY AND PERL MODULE The smimeutil library is documented in smimeutil.h, see smime.c for some examples of usage. The SMIMEUtil:: perl module is not currently documented. For usage examples see test.pl. CAVEATS For signing to work correctly, your mime entity must be canonized the same way as the recipient will canonize it. In general this means that you must use CRLF as line termination and must include all headers. If you are clear signing, then you may want to consult RFC2311 for some ways the message might get ruined (e.g. changes in whitespace). In my experience most important requirement seems to be to not use any trailing whitespace. YMMV. For signatures to verify correctly, the From: header of the mail must be equal to "friendlyname:" and EMAIL fields in your cert. When encrypting, do not forget to wrap your message in mime entity. If you don't, ./smime -d will silently return emptiness, Netscape reports "improperly formatted DER-encoded message". This will _not_ work: echo "foo" | ./smime -e me.pem | ./smime -d me.pem secret # WRONG! SECURITY CAVEATS Passing passwords on command line is insecure. The smime tool is intended more as a demonstration than a production tool. See pass-password.pl for an example how to use file descriptor to pass the passwords more securely. smimeutil.c compiles by default to use DES-EDE3-CBC cipher which is not known by export versions of many browsers. See around line 400 in smime-enc.c if you need to change this. Be ware that RC2-40-CBC can be cracked in real time by trivial resources. Never-the-less its the only cipher that interoperates with all versions of browsers. Randomnumbers are not (yet) initialized as they should. Certificate verification scheme puts the burden of verification on user. For example, the user must notice if purported CA certificate has X509v3 attribute that forbids it from being CA cert. TIP You can use Netscape to encrypt and sign messages and then look at them in mail spool. This way you see their raw structure before any mail reader gets to interpret it. This is the best way to debug differences between what you produce and what is presumably standards compatible. TO DO Parsing mime messages in robust and fully correct way BUGS Due to the way the API is defined all stuff is kept in memory. While this is simple and easy, you might get into trouble with large files. I'd say the memory consumption will not exceed five times the file size, but don't bet on it. Signature verification in perl module still needs some work. SEE ALSO RFC1521 (MIME) RFC2111 (S/MIME v2) *** RFCXXXX (S/MIME v3) http://www.openssl.org http://www.bacus.pt/Net_SSLeay/smime.html (this stuff) USAGE (reproduced from ./smime -help) ./smime -cs private password <mime-entity >smime # clear sign ./smime -cv cert <smime-entity >data # verify clear sig ./smime -ds private passwd <file >smime-sig # make detached sig ./smime -dv file <smime-sig-entity # verify detached ./smime -s private password <mime-entity >smime # sign ./smime -qs <smime-entity >signing-cert-info # find out who signed ./smime -v cert <smime-entity >signer-dn # verify signature ./smime -vc cacert <cert # verify certificate ./smime -e public <mime-entity >smime-ent # encrypt ./smime -d private password <smime-entity >mime # decrypt ./smime -qr <req.pem # Query all you can about request ./smime -qc <cert.pem # Query all you can about certificate ./smime -ca ca_cert passwd serial <req.pem >cert.pem # sign a req ./smime -p12-pem p12pw pempw <x.p12 >x.pem # convert PKCS12 to pem ./smime -pem-p12 frindly@name.com pempw p12pw <x.pem >x.p12 ./smime -m type1 file1 type2 file2 type3 file3 <text # make multipart ./smime -m image/gif foo.gif <message | ./smime -s private pass >smime ./smime -kg attr passwd req.pem <dn >priv_ss.pem # keygen ./smime -base64 <file >file.base64 ./smime -unbase64 <file.base64 >file ./smime -mime text/plain <file >mime-entity ./smime -mime_base64 image/gif <file.gif >mime-entity ./smime -split dirprefix <multipart # splits multipart ./smime -base64 <in | ./smime -unbase64 >out ./smime -cat <in >out # copy input to output using slurp and barf ./smime -kg 'description=Test' secret req.pem <me.dn >ss.pem --Sampo