I like the idea of digital signatures, so I recently obtained an S/MIME certificate.
My early attempts with GPG had poor results, because the adoption rate is extremely low even between the nerds that I am usually in contact with.
I decided to give a chance to S/MIME, because, despite experts call it technically worse, it has wider support.
There is native support on iOS/macOS native mail clients, mutt, Thunderbird and also some web clients.
I noticed two things when using S/MIME in my daily routines.
When signing emails in native Apple Mail app, the certificate is taken directly from the System Keychain.
If I want to use my S/MIME certificate in mutt macOS, i need to import the certificate to mutt specific keychain.
This apparently turns out to be the same case for Thunderbird.
But there must be a way how to use certificates from macOS keychain to sign arbitrary content.
In this article, I will:
Explore some S/MIME signed emails and figure out what is the signature and what data are actually signed.
Just the body? Or some headers?
What is the signature format? What parameters does it need to be recognized as valid by my email clients?
Discover how Apple Mail uses the certificates from system keychain to sign emails, and finally
combine all the above knowledge to sign emails in mutt with certificates from macOS system Keychain
How is the signing done in mutt
I will use mutt to learn more about the message formats and signing in general.
It follows the nice UNIX philosophy of single tool doing one thing great and thus it offloads the cryptographic responsibility to an external program.
The actual commands used to sign/verify/encrypt/decrypt are configurable and we can find defaults in contrib/smime.rc file.
These commands are actually format strings with special escape sequences expanding to the path of file with message content, file with signature etc (see docs).
By utilizing this nice architecture I modified the format strings so that I get the expanded commands and also copied the files with content/signature etc.
Deducing the proper format of inputs to the signing command was then more or less straightforward.
I care mainly about signing and verifying emails, so I will skip the commands related to en/decryption.
Below find the exercept from default mutt smime.rc file with signing and verification commands.
(I replaced the hash of my private key by default-key-hash value)
set smime_sign_command="openssl smime -sign -md %d -signer %c -inkey %k -passin stdin -in %f -certfile %i -outform DER"# Verify a signature of type multipart/signed
set smime_verify_command="openssl smime -verify -inform DER -in %s %C -content %f"# Verify a signature of type application/x-pkcs7-mime
openssl smime -verify -inform DER -in %s %C || \
openssl smime -verify -inform DER -in %s -noverify 2>/dev/null"
For these commands to work, you must import your S/MIME keys with special tool smime_keys that is shipped with mutt.
This tool manages own database of keys in ~/.smime and the expanded escape sequences from the above commands often point there.
Consulting the official mutt documentation provided me with a limited information.
I was able to learn what is which sequence, but this doesn’t gave me an answer to what format is which file etc.
I modified the config parameter to get the following sign_command:
Content-Type: text/plain; charset=us-ascii
This is the test email body.
As we can see, mutt created the ~/signcontent file by taking the original message body and adding some new headers to it.
With this information, I can now invoke the command with proper paths and I will obtain the actual signature.
The signature itself is binary blob in the DER format.
To attach it to the message, mail client encodes it in base64 and attaches to the email as an attachment.
If you send such message to someone using a mail client without S/MIME support, the signature is actually presented as smime.p7s attached file.
Although it is very long, I attached the whole message so everyone can try to verify the signature yourself.
Verifying the signature
By consulting the mutt configuration file, I got the signature verification command:
openssl smime -verify -inform DER -content ~/signcontent -in message.sig
The ~/signcontent file is the same file as above.
The content of a message.sig file is base64 decoded signature from the email.
To obtain the message.sig, do something like echo MIAGCSqGSIb3DQEHAqCAMI...irpl4AAAAAAAA | base64 -decode > message.sig
Running the above openssl command should verify the signature and say “Verification Successful”.
(Note that you need to have the ~/signcontent with CRLF line endings, otherwise the verification fails.)
macOS Keychain CLI util
The macOS already contains a built in way of managing certificates and private keys.
This framework can be accessed in GUI by the means of Keychain Access.app or via security(1) cli tool.
Both apps are provided directly by Apple and are part of clean macOS installation.
I want to avoid having the certificates stored twice on my computer because I’d have to import all of them to each email client separately.
There is an official API for the Security Framework with bindings to Objective-C and Swift.
After some digging in the documentation I found the CMSEncodeContent function that does exactly what I was looking for.
I wrote a simple terminal wrapper for the function, before I accidentally bumped to the macOS security(1) tool which has a cms subcommand that does the exact same thing.
Equivalent of the openssl smime -sign ... command is therefore security cms -S -T -i signcontent -N "<certificate id>".
The -T option must be used to generate a detached signature.
If you ommit it, the email signature will be correctly recognized and verified by Apple Mail on macOS, but the verification on iOS and in mutt will fail.
Because of the nice architecture of mutt, all we need to do is change the configuration.
Use with mutt
By replacing the sign command in mutt, it is possible to sign emails without importing the key to special S/MIME keychain (smime_keys for mutt)
When you use the security(1) tool to generate the signature, you keep getting a verification error.
I spent half of my saturday by comparing different versions of email contents and its signatures, before I eventually noticed an inconsistency in newline handling.
The content that mutt handed over to the command for signature used LF only, while the file handed for verification used CRLF.
Because of this, the verification command actually tries to verify different data and it couldn’t be possibly considered as a valid signature.
In my setup I solved this issue by prepending the unix2dos -q -o %f; command to my smime_sign_command, so that the newlines are properly converted to CRLF before the signing utility is launched.
The CRLF line breaks are anyway requred by the RFC2045 and mutt should probably pass the content to be signed in this format.
Following is the snippet with working options:
# %c is replaced by smime_default_key
set smime_sign_command="unix2dos -q -o %f ;security cms -S -T -i %f -N %c"
It might require some trial and error to determine the proper value of smime_default_key, as it depends on your key.
The utility attempts to access the system keychain and as such it presents the user with GUI password prompt.
As mutt is not aware of this, it keeps asking for the certificate password every time a message is to be signed.
This can be safely ignored and confirmed with no input as the CLI utility doesn’t require any.