python-3.xemailsmtplibsmimepython-cryptography

How to sign a mail with smime with python and the module cryptography?


I want to send a signed mail with smime with the module cryptography. But all my mail programms can't detect the subject if I sign the mail. If I don't sign the mail it shows the subject.

import smtplib
from email.header import Header
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
from email.mime.text import MIMEText
from email.utils import formataddr, formatdate

from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.serialization import pkcs7
from jinja2 import Template

email_from = "root@wikipedia.org"
email_to = "user@wikipedia.org"

message = MIMEMultipart("mixed", protocol="application/x-pkcs7-signature", micalg="sha-256")
message["Subject"] = Header("mail subject", "utf-8").encode()
message["From"] = formataddr(("Sender", email_from))
message["To"] = email_to
message["Date"] = formatdate(localtime=True)
message["Auto-Submitted"] = "auto-generated"

html_template = (
    Path(EMAIL_TEMPLATES_DIR) / f"{template}.html"
).read_text()
html_j2_template = Template(html_template)
html = html_j2_template.render(environment)
message.attach(MIMEText(html, "html"))

ca_cert = open(CA_CERT_PATH, "rb").read()
ca_key = open(CA_KEY_PATH, "rb").read()

cert = x509.load_pem_x509_certificate(ca_cert)
key = serialization.load_pem_private_key(ca_key, None)
options = [pkcs7.PKCS7Options.DetachedSignature]
signed_text = (
    pkcs7.PKCS7SignatureBuilder()
    .set_data(message.as_bytes())
    .add_signer(cert, key, hashes.SHA256())
    .sign(serialization.Encoding.SMIME, options)
)

with smtplib.SMTP() as server:
    server.connect(25)
    server.sendmail(email_from, email_to, signed_text)

And here the version with the module email for python>=3.6.

import smtplib
from email.headerregistry import Address
from email.message import EmailMessage
from email.utils import formatdate

from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.serialization import pkcs7
from jinja2 import Template

email_from = "root@wikipedia.org"
email_to = "user@wikipedia.org"

message = EmailMessage()
message["Subject"] = subject_template
message["From"] = Address("Sender", email_from)
message["To"] = Address("Receiver", email_to)
message["Date"] = formatdate(localtime=True)
message["Auto-Submitted"] = "auto-generated"

html_template = (
    Path(EMAIL_TEMPLATES_DIR) / f"{template}.html"
).read_text()
html_j2_template = Template(html_template)
html = html_j2_template.render(environment)
message.set_content(html, subtype="html")

ca_cert = open(CA_CERT_PATH, "rb").read()
ca_key = open(CA_KEY_PATH, "rb").read()

cert = x509.load_pem_x509_certificate(ca_cert)
key = serialization.load_pem_private_key(ca_key, None)
options = [pkcs7.PKCS7Options.DetachedSignature]
signed_text = (
    pkcs7.PKCS7SignatureBuilder()
    .set_data(message.as_bytes())
    .add_signer(cert, key, hashes.SHA256())
    .sign(serialization.Encoding.SMIME, options)
)

with smtplib.SMTP() as server:
    server.connect(25)
    server.sendmail(email_from, email_to, signed_text)

Solution

  • The solution is a dirty hack, but it works for the moment

    import smtplib
    from email.header import Header
    from email.message import EmailMessage
    
    from cryptography import x509
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.serialization import pkcs7
    
    
    message = EmailMessage()
    message["Subject"] = subject_template
    message["From"] = Address("Sender", email_from)
    message["To"] = Address("Receiver", email_to)
    message["Date"] = formatdate(localtime=True)
    message["Auto-Submitted"] = "auto-generated"
    
    body = "This a plain text body!"
    message.set_content(body)
    
    ca_cert = open("cert.pem", "rb").read()
    ca_key = open("key.pem", "rb").read()
    
    cert = x509.load_pem_x509_certificate(ca_cert)
    key = serialization.load_pem_private_key(ca_key, None)
    options = [pkcs7.PKCS7Options.DetachedSignature]
    
    signed_text = (
        pkcs7.PKCS7SignatureBuilder()
        .set_data(message.as_bytes())
        .add_signer(cert, key, hashes.SHA256())
        .sign(serialization.Encoding.SMIME, options)
    )
    
    header = (
        f"""From: {Address("Sender", email_from)}\n"""
        f"""To: {Address("Receiver", email_to)}\n"""
        f"""Date: {formatdate(localtime=True)}\n"""
        f"""Auto-Submitted: auto-generated\n"""
        f"""Subject: {Header("Mail Subject", "utf-8").encode()}\n"""
    )
    
    signed_text = header.encode() + signed_text
    
    with smtplib.SMTP() as server:
        server.connect(port=25)
        server.sendmail(from_addr, to_addr, signed_text)