Authenticating a mail sender with PGP
This blog post summarizes the third week of the Google Summer of Code 2016 project - Mailhandler.
As mentioned in the previous blog post, the Mailhandler prototype for Drupal 8 has been finished. The prototype has already one of the main features implemented - processing mail messages and creating nodes. However, processed messages could be created by anyone which is not really nice for a module aiming to be used in production. To solve this problem, I've been working on user authentication implementation for almost a week.
The goal was to authenticate a user (sender of an email) before doing any mail processing. The meta issue with its child issues formalizes the requirements.
An obvious way to identify a mail sender is by checking the
From field from the mail header. As this field is required to be present in mail headers (RFC 2822) we can assume we will always be able to identify the sender's mail address.
While writing a blog post about building a prototype, I acquainted you with Inmail. This is the module we are going to rely on to process mail messages. Inmail has a nice concept of plugins separated into deliverers, analyzers, and handlers. Deliverers are used to specify where a mail is coming from, analyzers to identify different types of messages and handlers to do some actions with mail messages and analyzer results. Last week, I already added a handler plugin and here I am going to talk about analyzers.
I started implementing an Inmail analyzer plugin and named it
MailhandlerAnalyzer. It inherits
AnalyzerBase from Inmail and currently has only one method -
analyze() which accepts
$message (represents a mail message entity) and
processor_result (collects outcomes of a single mail processing pass) parameters. To preserve analyzer results,
MailhandlerAnalyzerResult is created with
$body properties. The
$sender will represent a sender's mail address (obtained from
From mail header field),
$user is a user entity in the current system and the
$body is analyzed message body. After populating those fields in the analyzer, the job of
MailhandlerNode is to authenticate and authorize the sender. If the conditions are met (user is validated) the handler will process the message and do the actions (create a node). This gives us a basic level of security.
From mail handler field can be faked by a malicious user. To bridge this obstacle, we introduced support for digitally signed emails. By its definition: digital signature is a mathematical scheme for demonstrating the authenticity of digital documents and it provides:
- Authentication - since the secret (private) key is bound to a specific user, successful signature verification will show the identity of the sender
- Integrity - it guarantees the message was not changed during transmission. Any change of the message content after signing it invalidates the signature
- Non-repudiation - a user can not deny sending and signing the message
Handling digital signature is not supported in PHP by default and we had to find the tools to do it. We decided to use GnuPG implementation of the OpenPGP (Pretty Good Privacy) standard defined by RFC 4880 and gnupg PHP extension. If you are not familiar with the concept of digital signature I would recommend you looking into this ~2 minutes read.
As a starting point, we have to add
mailhandler_gpg_key field to store GPG Key for each user. I wanted to preserve
fingerprint properties for each key and decided to go with custom field type. Drupal.org documentation provides a nice explanation how to create a custom field type, corresponding widgets and formatters in Drupal 8.
The next thing in our PGP journey is to extend analyzer and handlers to deal with signed messages. Usually, when handling emails, you will have to deal with regular expressions a lot. This case is not an exception. A new method called
isSigned() is added to
MailhandlerAnalyzer which analyzes the message and returns a boolean value. While we are dealing with messages at the low level,
isSigned() populates the context data in case of signed messages. Context consists of:
- PGP Type - the signed message could be inline-signed (clear-text signature) or as nowadays standard PGP/MIME.
- Signed text - extracted the actual body without PGP signature
- Signature - PGP signature
Populating the general result properties of the analyzer result instance (In case of signed messages
MailhandlerAnalyzerResultSigned), the analyzer finishes its job. At this point, we can analyze the signed message and be sure if we are dealing with signed or unsigned mail messages.
Next, we have to adapt our handler to support signed messages too. That looks easy, as all the hard work is completed by the analyzer. The only thing we have to do is to verify the signature. We do that by getting the signed text and signature from the analyzer result and pass them to
gnupg_verify() function. If the signature is valid and the corresponding user's GPG key is not disabled, revoked or expired we will continue the handling process. One last step to check before creating a node is to assert the user is authorized for that action.
To summarize, implementing support for digitally signed emails certainly provides an acceptable level of security for Mailhandler module. Keep in mind, that you will need to have GnuPG PHP extension enabled on your server to get GPG support. In either way, you will benefit from Mailhandler's default authentication system. The interesting thing about last week is that authentication support produced more than 1,2k new lines of code in the Github pull request.
Next week, I am going to work on footer detection in a mail message. This will allow us to remove any unnecessary and unintended content from a node body. To support all those features, most of the work will be done in the analyzer. The mid-term evaluation period starts in less than a week which means the next week will be used to do preparation for it as well.
Over and out.
I'm really glad to hear about your work on this Mikos, it will really help me create the collaboration solution my community needs (comment by email reply). Thanks!