Package: django-secure-mail
Created byblag. Forked from PR#39and#40ofdjango-email-extrasbyStephen McDonald.Introductiondjango-secure-mail is a Django reusable app providing a mail backend to send
opportunistically signed and encrypted emails using PGP. Also provided are
models and an admin page to manage uploaded PGP keys.Note that the provided backend only signs outgoing mail if the recipient has
uploaded a valid public key. Users without valid public keys willnothave
their outgoing mail signed or encrypted.Dependenciespython-gnupgis
required for sending PGP encrypted email.InstallationThe easiest way to install django-secure-mail is directly from PyPi
usingpipby running the command
below:$pipinstalldjango-secure-mailOtherwise you can download django-secure-mail and install it directly
from source:$pythonsetup.pyinstallConfigurationAddsecure_mailto yourINSTALLED_APPSsetting and run database
migrations:$pythonmanage.pymigratesecure_mailSetEMAIL_BACKENDin your settings module tosecure_mail.backends.EncryptingSmtpEmailBackendor one of the
development/testing backends listed inDevelopment and Testing.Set theSECURE_MAIL_GNUPG_HOMEsetting to a directory that contains the
GPG keyring. If you are running multiple Django nodes, each node will need
readand writeaccess to this directory.Set theSECURE_MAIL_GNUPG_ENCODINGvariable to the encoding your GPG
executable requires. This is generallylatin-1for GPG 1.x andutf-8for GPG 2.x.Whle it is not required to send encrypted email, it ishighly recommendedthat you generate a signing key for outgoing mail. Please follow the
instructions in theGenerate Signing Keysection. All nodes that will
be sending outgoing mail will need to have read access to the directory
specified bySECURE_MAIL_GNUPG_HOME.There are additional configuration options available. Please see theOptionssection for a complete list.Generate Signing KeyAdding a private/public signing keypair is different than importing a
public encryption key, since the private key will be stored on the
server.This project ships with a Django management command to generate and
export signing keys:email_signing_key.You first need to set theSECURE_MAIL_SIGNING_KEY_DATAoption in your
project’ssettings.py. This is a dictionary that is passed as keyword arguments directly toGPG.gen_key(), so please read and understand all of
the availableoptions in their documentation. The default settings are:SECURE_MAIL_SIGNING_KEY_DATA={'key_type':"RSA",'key_length':4096,'name_real':settings.SITE_NAME,'name_comment':"Outgoing email server",'name_email':settings.DEFAULT_FROM_EMAIL,'expire_date':'2y',}You may wish to change thekey_typeto a signing-only type of key,
such as DSA, or the expire date.Once you are content with the signing key settings, generate a new
signing key with the--generateoption:$pythonmanage.pyemail_signing_key--generateTo work with specific keys, identify them by their fingerprint$pythonmanage.pyemail_signing_key7AB59FE794A7AC12EBA87507EF33F601153CFE28You can print the private key to your terminal/console with:$pythonmanage.pyemail_signing_key7AB59FE794A7AC12EBA87507EF33F601153CFE28--print-private-keyAnd you can upload the public signing key to one or more specified
keyservers by passing the key server hostnames with the-kor--keyserveroptions:$pythonmanage.pyemail_signing_key7AB59FE794A7AC12EBA87507EF33F601153CFE28-kkeys.ubuntu.comkeys.redhat.com-kpgp.mit.eduYou can also perform all tasks with one command:$pythonmanage.pyemail_signing_key--generate--keyserverpgp.mit.edu--print-private-keyUse the--helpoption to see the complete help text for the command.Once you have generated the signing key, you will need to configuresecure_mailto use it. Set theSECURE_MAIL_KEY_FINGERPRINTsetting to
the fingerprint of the outgoing signing key you wish to use.OptionsThere are a few settings you can configure in your project’ssettings.pymodule:SECURE_MAIL_GNUPG_HOME- String representing a custom location
for the GNUPG keyring. If you are running multiple Django nodes, this should
be set to a directory shared by all nodes, and thegpgexecutable on all
nodes will need read and write access to it.SECURE_MAIL_USE_GNUPG- Boolean that controls whether the PGP
encryption features are used. Defaults toTrueifSECURE_MAIL_GNUPG_HOMEis specified, otherwiseFalse.SECURE_MAIL_GNUPG_ENCODING- The encoding the localgpgexecutable
expects. This option is passed through to thestr.encodefunction. In
general, it should be set tolatin-1for GPG 1.x andutf-8for GPG
2.x. Check outpython-gnupg documentationfor more info.SECURE_MAIL_FAILURE_HANDLERS- A dictionary that maps failed types to the
dotted-path notation of error handlers. See theError Handlingsection for
details and an example.SECURE_MAIL_ALWAYS_TRUST_KEYS- Skip key validation and assume that used
keys are always fully trusted. This simply sets--always-trust(or--trust-modelfor more modern versions of GPG). See the GPG documentation
on the--trust-modeloption for more detail about this setting.SECURE_MAIL_SIGNING_KEY_PASSPHRASE- A passphrase that is passed to GPG
when generating or printing private signing keys. Defaults to''.SECURE_MAIL_SIGNING_KEY_DATA- A dictionary of key options for generating
new signing keys. See thepython-gnupg documentationfor more
details.Default:{'key_type':"RSA",'key_length':4096,'name_real':getattr(settings,'SITE_NAME',''),'name_comment':"Outgoing email server",'name_email':settings.DEFAULT_FROM_EMAIL,'expire_date':'2y','passphrase':settings.SECURE_MAIL_SIGNING_KEY_PASSPHRASE,}SECURE_MAIL_KEY_FINGERPRINT- The fingerprint of the key to use when
signing outgoing mail, must exist in the configured keyring.Sending PGP Encrypted EmailOnce the backend is configured and specified by theEMAIL_BACKENDsetting,
all outgoing mail will be opportunistically signed and encrypted. This means
that if a message is being sent to a recipient who has a valid public key in
the database and the GPG/PGP keyring, the backend will attempt to sign and
encrypt outgoing mail to them.Error HandlingThis backend allows users to specify custom error handlers when encryption
fails for the following objects:The plain text message itselfAny message attachmentsAny message alternatives (for instance: HTML mail delivered with a plain text
fallback)Error handlers are called when an exception is raised and are passed the raised
exception.defhandle_failed_encryption(exception):# Handle errorsdefhandle_failed_alternative_encryption(exception):# Handle errorsdefhandle_failed_attachment_encryption(exception):# Handle errorsThe default error handlers simply re-raise the exception, but this may be
undesirable for all cases.To assist with handling errors, the package provides a few helper functions
that can be used in custom error handlers:force_send_message- Accepts the unencrypted message as an argument,
and sends the message without attempting to encrypt or sign it.force_delete_key- Accepts the recipient’s address as an argument and
forcibly removes all keys from the database and the GPG/PGP keyring.force_mail_admins- Accepts the unencrypted message and the failing
address as arguments. If the address is in theADMINSsetting, it sends
the message unencrypted, otherwise, it mails the admins a message containing
the subject of the original message and the original intended recipient.get_variable_from_exception- Accepts the exception and a variable name
as arguments, then digs back through the stacktrace to find the first
variable with the specified name.To specify a custom error handlers, set keys in theSECURE_MAIL_FAILURE_HANDLERSsetting dictionary in your project’ssettings.pyto the dotted-path of your error handler/s:SECURE_MAIL_FAILURE_HANDLERS={'message':'myapp.handlers.handle_failed_encryption','alternative':'myapp.handlers.handle_failed_alternative_encryption','attachment':'myapp.handlers.handle_failed_attachment_encryption',}You do not have to override all of the handlers, you can override as many or as
few as you wish.Development and TestingThis package provides a backend mixin (EncryptingEmailBackendMixin) if you
wish to extend the backend or create a custom backend of your own:classEncryptingLocmemEmailBackend(EncryptingEmailBackend,LocmemBackend):passFor a working, real-world example of using theEncryptingEmailBackendMixinin another Django app, check out theemailhub.backends.secure_mail.EncryptingEmailBackendMixinfrom thedjango-emailhubproject:In addition to the providedEncryptingSmtpEmailBackend, this package ships
with a few more backends that mirror the built-in Django backends:EncryptingConsoleEmailBackendEncryptingLocmemEmailBackendEncryptingFilebasedEmailBackendDatabase ModelsPGP explanationUsingpython-gnupg, two models are defined insecure_mail.models-KeyandAddresswhich represent a PGP key and an email address for a
successfully imported key. These models exist purely for the sake of importing
keys and removing keys for a particular address via the Django
Admin.When adding a key, the key is imported into the key ring on
the server and the instance of theKeymodel is not saved. The
email address for the key is also extracted and saved as anAddressinstance.TheAddressmodel is then used when sending email to check for
an existing key to determine whether an email should be encrypted.
When anAddressis deleted via the Django Admin, the key is
removed from the key ring on the server.Alternative Django AppsOther Django apps with similar functionality are:django-email-extras-
Provides two functions for sending PGP encrypted, multipart emails using
Django’s template system. Also provides a mail backend that displays HTML
mail in the browser during development.django-gnupg-mails-
Provides aGnuPGMessage(subclass of Django’sEmailMessage) to send
PGP/MIME signed email.Both of those apps require third party app developers to “opt-in” to sending
encrypted mail. This project automatically encrypts and signs all outgoing mail
for all apps.