Browse Source

Sign DKIM at app level

- add DKIM_PRIVATE_KEY_PATH param
- create email_utils.add_dkim_signature
- add DKIM signature for transactional emails
- add DKIM signature for forward & reply emails. In reply phase, only non-custom-domain emails have DKIM added.
Son NK 5 years ago
parent
commit
a61b900675
6 changed files with 75 additions and 4 deletions
  1. 4 1
      .env.example
  2. 8 0
      app/config.py
  3. 35 2
      app/email_utils.py
  4. 12 1
      email_handler.py
  5. 15 0
      local_data/dkim.key
  6. 1 0
      tests/env.test

+ 4 - 1
.env.example

@@ -28,12 +28,15 @@ EMAIL_SERVERS_WITH_PRIORITY=[(10, "email.hostname.")]
 
 # these emails are ignored when computing stats
 # IGNORED_EMAILS = ["my_email@domain.com"]
+
+# the DKIM private key used to compute DKIM-Signature
+DKIM_PRIVATE_KEY_PATH=local_data/dkim.key
 # <<< END Email related settings >>>
 
 
 # <<< Database >>>
 # delete and recreate sqlite database, for local development
-RESET_DB=true
+# RESET_DB=true
 
 # DB Connection
 DB_URI=sqlite:///db.sqlite

+ 8 - 0
app/config.py

@@ -57,6 +57,14 @@ if os.environ.get("IGNORED_EMAILS"):
 else:
     IGNORED_EMAILS = []
 
+DKIM_PRIVATE_KEY_PATH = get_abs_path(os.environ["DKIM_PRIVATE_KEY_PATH"])
+DKIM_SELECTOR = b"dkim"
+
+with open(DKIM_PRIVATE_KEY_PATH) as f:
+    DKIM_PRIVATE_KEY = f.read()
+
+DKIM_HEADERS = [b'from', b'to', b'subject']
+
 # Database
 DB_URI = os.environ["DB_URI"]
 

+ 35 - 2
app/email_utils.py

@@ -3,9 +3,18 @@ from email.message import EmailMessage
 from email.utils import make_msgid, formatdate
 from smtplib import SMTP
 
+import dkim
 from jinja2 import Environment, FileSystemLoader
 
-from app.config import SUPPORT_EMAIL, ROOT_DIR, POSTFIX_SERVER, NOT_SEND_EMAIL
+from app.config import (
+    SUPPORT_EMAIL,
+    ROOT_DIR,
+    POSTFIX_SERVER,
+    NOT_SEND_EMAIL,
+    DKIM_SELECTOR,
+    DKIM_PRIVATE_KEY,
+    DKIM_HEADERS,
+)
 from app.log import LOG
 
 
@@ -123,7 +132,12 @@ def send_email(to_email, subject, plaintext, html):
     LOG.d("Date header: %s", date_header)
     msg["Date"] = date_header
 
-    smtp.send_message(msg, from_addr=SUPPORT_EMAIL, to_addrs=[to_email])
+    # add DKIM
+    email_domain = SUPPORT_EMAIL[SUPPORT_EMAIL.find("@") + 1 :]
+    add_dkim_signature(msg, email_domain)
+
+    msg_raw = msg.as_string().encode()
+    smtp.sendmail(SUPPORT_EMAIL, to_email, msg_raw)
 
 
 def get_email_name(email_from):
@@ -146,3 +160,22 @@ def get_email_part(email_from):
         return email_from[email_from.find("<") + 1 : email_from.find(">")].strip()
 
     return email_from
+
+
+def add_dkim_signature(msg: EmailMessage, email_domain: str):
+    # Specify headers in "byte" form
+    # Generate message signature
+    sig = dkim.sign(
+        msg.as_string().encode(),
+        DKIM_SELECTOR,
+        email_domain.encode(),
+        DKIM_PRIVATE_KEY.encode(),
+        include_headers=DKIM_HEADERS,
+    )
+    sig = sig.decode()
+
+    # remove linebreaks from sig
+    sig = sig.replace("\n", " ").replace("\r", "")
+
+    # Add the DKIM-Signature
+    msg.add_header("DKIM-Signature", sig[len("DKIM-Signature: ") :])

+ 12 - 1
email_handler.py

@@ -39,7 +39,12 @@ from smtplib import SMTP
 from aiosmtpd.controller import Controller
 
 from app.config import EMAIL_DOMAIN, POSTFIX_SERVER, URL
-from app.email_utils import get_email_name, get_email_part, send_email
+from app.email_utils import (
+    get_email_name,
+    get_email_part,
+    send_email,
+    add_dkim_signature,
+)
 from app.extensions import db
 from app.log import LOG
 from app.models import GenEmail, ForwardEmail, ForwardEmailLog
@@ -179,6 +184,8 @@ class MailHandler:
                 envelope.rcpt_options,
             )
 
+            add_dkim_signature(msg, EMAIL_DOMAIN)
+
             # smtp.send_message has UnicodeEncodeErroremail issue
             # encode message raw directly instead
             msg_raw = msg.as_string().encode()
@@ -242,6 +249,10 @@ class MailHandler:
             )
             del msg["DKIM-Signature"]
 
+        # add DKIM-Signature for non-custom-domain alias
+        if alias.endswith(EMAIL_DOMAIN):
+            add_dkim_signature(msg, EMAIL_DOMAIN)
+
         # email seems to come from alias
         msg.replace_header("From", alias)
         msg.replace_header("To", forward_email.website_email)

+ 15 - 0
local_data/dkim.key

@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQCxhcKgFHz+HbZiuUhH7iGCVsaZYQ7xzf64ui+09QFlSYzl7d28
+LVlr7nvM0+xDbwwsgu2D1vweklroWM5FjbfVtJX3HvSnNbwceX5du/m8RHelmX0/
+vLSfsEcnvdNjBmwl/gSIUb660pEp2yo6dUBDTzTDUBNoL6qmnnTNhriRoQIDAQAB
+AoGAdhGEtHtr9odEerzIei7DUrDsPa70BZcAR1Rtzmj1mKwmbfaad0GiK8rdxAlf
+JiqBaklaN0mRPbQRil8mMdRj4z8gBYbiHWIL7q6zEjjo8f6CUnNqKgs2trTApqLq
+L4l110fFSHCmIava5Ly9hJhdOWuJ+PUbcbp0l3j2yoz7RhECQQDa8IMEB/qqeM+e
+FTz2+F3HhPI3tGALKWYyCbqcip9UUePfPQ/m547YXsdc1ATzb8OsI7emqTcZLu7H
+joX+8WN/AkEAz5J9uFnp2+fWvmkNV1imoys38OwOq7yYUBSfgDymuzWrf8D2L5mt
+gSK2LToIjfMRwdJ1RFLGv6oCy6ge3aga3wJAaEKKkZvfIdkgPY6tloqV1hKYajCK
+YCZZ1VBOvodA8p2An2lrrjDtFFqmI62PogHCM7JanZINe/+elAdqBgsbrwJBAIN1
+wY2Z1FRjlkttePeSu6anXnyE5B28CbLd/M5YmzgBm6YDbWdkKtCYTUyDbpuID/zy
+7zXgPuNwJukYhsPXDX0CQGD3laRUSRZiVSD/rJwsJTG2o1FZcsv13CO/0jY7sYxk
+IjBK29XMHhTB/dip+beU0RCLFjB3nNK8VyMWmmn1WJ0=
+-----END RSA PRIVATE KEY-----

+ 1 - 0
tests/env.test

@@ -10,6 +10,7 @@ ADMIN_EMAIL=to_fill
 # Max number emails user can generate for free plan
 MAX_NB_EMAIL_FREE_PLAN=3
 EMAIL_SERVERS_WITH_PRIORITY=[(10, "email.hostname.")]
+DKIM_PRIVATE_KEY_PATH=local_data/dkim.key
 
 # Database
 RESET_DB=true