I'm trying to send an encrypted mail, using s/mime protocol, but the mail client doesn't acknowledge the attachments, and doesn't decrypt them. I've checked some answers on stack overflow, but none of them worked for me. Please note that the encryption itself does work (see below)
Here is my code:
import os
import ssl
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from smtplib import SMTP
from typing import List, Optional, Union
from M2Crypto import BIO, SMIME, X509
port = 587
def send_email_by_smtp(sender: str, receiver: List[str], message: Union[bytes, str]):
# getting the credentials fron evironemnt
host = os.environ.get("SMTP_FQDN")
user = os.environ.get("SMTP_USER_ID")
password = os.environ.get("SMTP_PASSWORD")
# setting up ssl context
context = ssl.create_default_context()
# creating an unsecure smtp connection
with SMTP(host, port) as server:
# securing using tls
server.starttls(context=context)
# authenticating with the server to prove our identity
server.login(user=user, password=password)
# sending a plain text email
return server.sendmail(sender, receiver, message)
def send_mail(
*,
recipient: List[str],
subject: str,
body: str,
attachments: Optional[List[str]] = None,
):
if attachments is None:
attachments = []
message = MIMEMultipart()
from_address = os.environ.get("FROM_ADDRESS")
body_part = MIMEText(body, "html")
message.attach(body_part)
for attachment in attachments:
with open(attachment, "rb") as fh:
attachment_part = MIMEApplication(fh.read())
filename = os.path.basename(fh.name)
attachment_part.add_header(
"Content-Disposition",
"attachment",
filename=filename,
Content_Transfer_Encoding="base64",
Content_ID=f"<{filename}>",
Content_Type="text/csv",
Content_Disposition=f"attachment; filename={filename}",
)
message.attach(attachment_part)
# data = message.as_string()
data = encrypt_message(
from_address,
recipient,
subject,
message.as_bytes(),
from_key="certs/new/signer_key.pem",
from_cert="certs/new/signer.pem",
to_certs=["certs/new/recipient.pem"],
)
return send_email_by_smtp(from_address, recipient, data)
def encrypt_message(from_addr, to_addrs, subject, msg, from_key, from_cert=None, to_certs=None):
msg_bio = BIO.MemoryBuffer(msg)
sign = from_key
encrypt = to_certs
p7 = None
s = SMIME.SMIME()
if sign:
s.load_key(from_key, from_cert)
if encrypt:
p7 = s.sign(msg_bio, flags=SMIME.PKCS7_TEXT)
else:
p7 = s.sign(msg_bio, flags=SMIME.PKCS7_TEXT | SMIME.PKCS7_DETACHED)
msg_bio = BIO.MemoryBuffer(msg) # Recreate coz sign() has consumed it.
if encrypt:
sk = X509.X509_Stack()
for x in to_certs:
sk.push(X509.load_cert(x))
s.set_x509_stack(sk)
s.set_cipher(SMIME.Cipher("aes_256_cbc"))
tmp_bio = BIO.MemoryBuffer()
if sign:
s.write(tmp_bio, p7)
else:
tmp_bio.write(msg)
p7 = s.encrypt(tmp_bio)
out = BIO.MemoryBuffer()
out.write(f"From: {from_addr}\r\n")
out.write(f"To: {', '.join(to_addrs)}\r\n")
out.write(f"Subject: {subject}\r\n")
if encrypt:
s.write(out, p7)
else:
if sign:
s.write(out, p7, msg_bio, SMIME.PKCS7_TEXT)
else:
out.write("\r\n")
out.write(msg)
out.close()
return out.read()
if __name__ == "__main__":
res = send_mail(recipient=os.environ.get("TO_ADDRESS", "").split(","), subject="some subject", body="some body", attachments=[__file__])
print(res)
and the results on the client :
Content-Type: multipart/mixed; boundary="===============2300897735206349356=="
MIME-Version: 1.0
--===============2300897735206349356==
Content-Type: text/html; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
some body
--===============2300897735206349356==
Content-Type: application/octet-stream
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smtp_test.py";
Content-Transfer-Encoding="base64"; Content-ID="<smtp_test.py>";
Content-Type="text/csv"; Content-Disposition="attachment;
filename=smtp_test.py"
aW1wb3J0IG9zCmltcG9ydCBzc2wKZnJvbSBlbWFpbC5taW1lLnRleHQgaW1wb3J0IE1JTUVUZXh0
CmZyb20gZW1haWwubWltZS5hcHBsaWNhdGlvbiBpbXBvcnQgTUlNRUFwcGxpY2F0aW9uCmZyb20g
ZW1haWwubWltZS5tdWx0aXBhcnQgaW1wb3J0IE1JTUVNdWx0aXBhcnQKZnJvbSBzbXRwbGliIGlt
cG9ydCBTTVRQCmZyb20gdHlwaW5nIGltcG9ydCBMaXN0LCBPcHRpb25hbCwgVW5pb24KCmZyb20g
TTJDcnlwdG8gaW1wb3J0IEJJTywgU01JTUUsIFg1MDkKCnBvcnQgPSA1ODcKCgpkZWYgc2VuZF9l
bWFpbF9ieV9zbXRwKHNlbmRlcjogc3RyLCByZWNlaXZlcjogTGlzdFtzdHJdLCBtZXNzYWdlOiBV
bmlvbltieXRlcywgc3RyXSk6CiAgICAjIGdldHRpbmcgdGhlIGNyZWRlbnRpYWxzIGZyb24gZXZp
cm9uZW1udAogICAgaG9zdCA9IG9zLmVudmlyb24uZ2V0KCJTTVRQX0ZRRE4iKQogICAgdXNlciA9
IG9zLmVudmlyb24uZ2V0KCJTTVRQX1VTRVJfSUQiKQogICAgcGFzc3dvcmQgPSBvcy5lbnZpcm9u
LmdldCgiU01UUF9QQVNTV09SRCIpCgogICAgIyBzZXR0aW5nIHVwIHNzbCBjb250ZXh0CiAgICBj
b250ZXh0ID0gc3NsLmNyZWF0ZV9kZWZhdWx0X2NvbnRleHQoKQoKICAgICMgY3JlYXRpbmcgYW4g
dW5zZWN1cmUgc210cCBjb25uZWN0aW9uCiAgICB3aXRoIFNNVFAoaG9zdCwgcG9ydCkgYXMgc2Vy
dmVyOgogICAgICAgICMgc2VjdXJpbmcgdXNpbmcgdGxzCiAgICAgICAgc2VydmVyLnN0YXJ0dGxz
KGNvbnRleHQ9Y29udGV4dCkKCiAgICAgICAgIyBhdXRoZW50aWNhdGluZyB3aXRoIHRoZSBzZXJ2
ZXIgdG8gcHJvdmUgb3VyIGlkZW50aXR5CiAgICAgICAgc2VydmVyLmxvZ2luKHVzZXI9dXNlciwg
cGFzc3dvcmQ9cGFzc3dvcmQpCgogICAgICAgICMgc2VuZGluZyBhIHBsYWluIHRleHQgZW1haWwK
ICAgICAgICByZXR1cm4gc2VydmVyLnNlbmRtYWlsKHNlbmRlciwgcmVjZWl2ZXIsIG1lc3NhZ2Up
CgoKZGVmIHNlbmRfbWFpbCgKICAgICosCiAgICByZWNpcGllbnQ6IExpc3Rbc3RyXSwKICAgIHN1
YmplY3Q6IHN0ciwKICAgIGJvZHk6IHN0ciwKICAgIGF0dGFjaG1lbnRzOiBPcHRpb25hbFtMaXN0
W3N0cl1dID0gTm9uZSwKKToKICAgIGlmIGF0dGFjaG1lbnRzIGlzIE5vbmU6CiAgICAgICAgYXR0
YWNobWVudHMgPSBbXQoKICAgIG1lc3NhZ2UgPSBNSU1FTXVsdGlwYXJ0KCkKICAgIGZyb21fYWRk
cmVzcyA9IG9zLmVudmlyb24uZ2V0KCJGUk9NX0FERFJFU1MiKQoKICAgIGJvZHlfcGFydCA9IE1J
TUVUZXh0KGJvZHksICJodG1sIikKICAgIG1lc3NhZ2UuYXR0YWNoKGJvZHlfcGFydCkKICAgIGZv
ciBhdHRhY2htZW50IGluIGF0dGFjaG1lbnRzOgogICAgICAgIHdpdGggb3BlbihhdHRhY2htZW50
LCAicmIiKSBhcyBmaDoKICAgICAgICAgICAgYXR0YWNobWVudF9wYXJ0ID0gTUlNRUFwcGxpY2F0
aW9uKGZoLnJlYWQoKSkKICAgICAgICAgICAgZmlsZW5hbWUgPSBvcy5wYXRoLmJhc2VuYW1lKGZo
Lm5hbWUpCiAgICAgICAgICAgIGF0dGFjaG1lbnRfcGFydC5hZGRfaGVhZGVyKAogICAgICAgICAg
ICAgICAgIkNvbnRlbnQtRGlzcG9zaXRpb24iLAogICAgICAgICAgICAgICAgImF0dGFjaG1lbnQi
LAogICAgICAgICAgICAgICAgZmlsZW5hbWU9ZmlsZW5hbWUsCiAgICAgICAgICAgICAgICBDb250
ZW50X1RyYW5zZmVyX0VuY29kaW5nPSJiYXNlNjQiLAogICAgICAgICAgICAgICAgQ29udGVudF9J
RD1mIjx7ZmlsZW5hbWV9PiIsCiAgICAgICAgICAgICAgICBDb250ZW50X1R5cGU9InRleHQvY3N2
IiwKICAgICAgICAgICAgICAgIENvbnRlbnRfRGlzcG9zaXRpb249ZiJhdHRhY2htZW50OyBmaWxl
bmFtZT17ZmlsZW5hbWV9IiwKICAgICAgICAgICAgKQogICAgICAgIG1lc3NhZ2UuYXR0YWNoKGF0
dGFjaG1lbnRfcGFydCkKCiAgICAjIGRhdGEgPSBtZXNzYWdlLmFzX3N0cmluZygpCiAgICBkYXRh
ID0gZW5jcnlwdF9tZXNzYWdlKAogICAgICAgIGZyb21fYWRkcmVzcywKICAgICAgICByZWNpcGll
bnQsCiAgICAgICAgc3ViamVjdCwKICAgICAgICBtZXNzYWdlLmFzX2J5dGVzKCksCiAgICAgICAg
ZnJvbV9rZXk9ImNlcnRzL25ldy9zaWduZXJfa2V5LnBlbSIsCiAgICAgICAgZnJvbV9jZXJ0PSJj
ZXJ0cy9uZXcvc2lnbmVyLnBlbSIsCiAgICAgICAgdG9fY2VydHM9WyJjZXJ0cy9uZXcvcmVjaXBp
ZW50LnBlbSJdLAogICAgKQogICAgcmVzdWx0ID0gKHNlbmRfZW1haWxfYnlfc210cChmcm9tX2Fk
ZHJlc3MsIHJlY2lwaWVudCwgZGF0YSkpCiAgICBwcmludChyZXN1bHQpCiAgICByZXR1cm4gcmVz
dWx0CgoKZGVmIGVuY3J5cHRfbWVzc2FnZShmcm9tX2FkZHIsIHRvX2FkZHJzLCBzdWJqZWN0LCBt
c2csIGZyb21fa2V5LCBmcm9tX2NlcnQ9Tm9uZSwgdG9fY2VydHM9Tm9uZSk6CiAgICBtc2dfYmlv
ID0gQklPLk1lbW9yeUJ1ZmZlcihtc2cpCiAgICBzaWduID0gZnJvbV9rZXkKICAgIGVuY3J5cHQg
PSB0b19jZXJ0cwogICAgcDcgPSBOb25lCgogICAgcyA9IFNNSU1FLlNNSU1FKCkKICAgIGlmIHNp
Z246CiAgICAgICAgcy5sb2FkX2tleShmcm9tX2tleSwgZnJvbV9jZXJ0KQogICAgICAgIGlmIGVu
Y3J5cHQ6CiAgICAgICAgICAgIHA3ID0gcy5zaWduKG1zZ19iaW8sIGZsYWdzPVNNSU1FLlBLQ1M3
X1RFWFQpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcDcgPSBzLnNpZ24obXNnX2JpbywgZmxh
Z3M9U01JTUUuUEtDUzdfVEVYVCB8IFNNSU1FLlBLQ1M3X0RFVEFDSEVEKQogICAgICAgIG1zZ19i
aW8gPSBCSU8uTWVtb3J5QnVmZmVyKG1zZykgICMgUmVjcmVhdGUgY296IHNpZ24oKSBoYXMgY29u
c3VtZWQgaXQuCgogICAgaWYgZW5jcnlwdDoKICAgICAgICBzayA9IFg1MDkuWDUwOV9TdGFjaygp
CiAgICAgICAgZm9yIHggaW4gdG9fY2VydHM6CiAgICAgICAgICAgIHNrLnB1c2goWDUwOS5sb2Fk
X2NlcnQoeCkpCiAgICAgICAgcy5zZXRfeDUwOV9zdGFjayhzaykKICAgICAgICBzLnNldF9jaXBo
ZXIoU01JTUUuQ2lwaGVyKCJhZXNfMjU2X2NiYyIpKQogICAgICAgIHRtcF9iaW8gPSBCSU8uTWVt
b3J5QnVmZmVyKCkKICAgICAgICBpZiBzaWduOgogICAgICAgICAgICBzLndyaXRlKHRtcF9iaW8s
IHA3KQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHRtcF9iaW8ud3JpdGUobXNnKQogICAgICAg
IHA3ID0gcy5lbmNyeXB0KHRtcF9iaW8pCgogICAgb3V0ID0gQklPLk1lbW9yeUJ1ZmZlcigpCiAg
ICBvdXQud3JpdGUoZiJGcm9tOiB7ZnJvbV9hZGRyfVxyXG4iKQogICAgb3V0LndyaXRlKGYiVG86
IHsnLCAnLmpvaW4odG9fYWRkcnMpfVxyXG4iKQogICAgb3V0LndyaXRlKGYiU3ViamVjdDoge3N1
YmplY3R9XHJcbiIpCiAgICBpZiBlbmNyeXB0OgogICAgICAgIHMud3JpdGUob3V0LCBwNykKICAg
IGVsc2U6CiAgICAgICAgaWYgc2lnbjoKICAgICAgICAgICAgcy53cml0ZShvdXQsIHA3LCBtc2df
YmlvLCBTTUlNRS5QS0NTN19URVhUKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIG91dC53cml0
ZSgiXHJcbiIpCiAgICAgICAgICAgIG91dC53cml0ZShtc2cpCiAgICBvdXQuY2xvc2UoKQoKICAg
IHJldHVybiBvdXQucmVhZCgpCgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgcmVzID0g
c2VuZF9tYWlsKHJlY2lwaWVudD1vcy5lbnZpcm9uLmdldCgiVE9fQUREUkVTUyIsICIiKS5zcGxp
dCgiLCIpLCBzdWJqZWN0PSJzb21lIHN1YmplY3QiLCBib2R5PSJzb21lIGJvZHkiLCBhdHRhY2ht
ZW50cz1bX19maWxlX19dKQogICAgcHJpbnQocmVzKQo=
--===============2300897735206349356==--
I would suggest the following: