diff --git a/seahub/utils/__init__.py b/seahub/utils/__init__.py index 7156dbafe6..fed458cb22 100644 --- a/seahub/utils/__init__.py +++ b/seahub/utils/__init__.py @@ -1002,7 +1002,7 @@ def send_html_email(subject, con_template, con_context, from_email, to_email, """ # get logo path - from seahub.utils.mail import add_smime_sign + from seahub.utils.mail import SmimeEmailMessage logo_path = LOGO_PATH custom_logo_file = os.path.join(MEDIA_ROOT, CUSTOM_LOGO_PATH) if os.path.exists(custom_logo_file): @@ -1022,13 +1022,9 @@ def send_html_email(subject, con_template, con_context, from_email, to_email, if reply_to is not None: headers['Reply-to'] = reply_to - msg = EmailMessage(subject, t.render(con_context), from_email, + msg = SmimeEmailMessage(subject, t.render(con_context), from_email, to_email, headers=headers) msg.content_subtype = "html" - - sig_part = add_smime_sign(msg) - if sig_part: - msg.attach(sig_part) msg.send() def gen_dir_share_link(token): diff --git a/seahub/utils/mail.py b/seahub/utils/mail.py index 6cade5f6df..7046b6fd0f 100644 --- a/seahub/utils/mail.py +++ b/seahub/utils/mail.py @@ -2,10 +2,14 @@ import os import logging from email.mime.application import MIMEApplication +from email.mime.multipart import MIMEMultipart + from cryptography.hazmat.primitives import serialization, hashes from cryptography.hazmat.primitives.serialization import pkcs7 from cryptography import x509 import base64 + +from django.core.mail.message import SafeMIMEMultipart from django.template import loader from django.core.mail import EmailMessage @@ -17,53 +21,80 @@ from seahub import settings logger = logging.getLogger(__name__) - -def add_smime_sign(msg): - if not ENABLE_SMIME: - return None +class SmimeEmailMessage(EmailMessage): - CERTS_DIR = getattr(settings, 'SMIME_CERTS_DIR', '/opt/seafile/seahub-data/certs') - cert_file = os.path.join(CERTS_DIR, 'cert.pem') - key_file = os.path.join(CERTS_DIR, 'private_key.pem') - if not os.path.exists(cert_file): - logger.warning('smime cert file %s does not exists.' % cert_file) - return None + def __init__(self, *args, **kwargs): + super(SmimeEmailMessage, self).__init__(*args, **kwargs) - if not os.path.exists(key_file): - logger.warning('smime key file %s does not exists.' % key_file) - return None - # Load the private key - with open(key_file, "rb") as f: - private_key = serialization.load_pem_private_key(f.read(), password=None) + def _signed(self, msg): + if not ENABLE_SMIME: + return msg - # Load the certificate - with open(cert_file, "rb") as f: - certificate = x509.load_pem_x509_certificate(f.read()) - - msg.mixed_subtype = "signed" + CERTS_DIR = getattr(settings, 'SMIME_CERTS_DIR', '/opt/seafile/seahub-data/certs') + cert_file = os.path.join(CERTS_DIR, 'cert.pem') + key_file = os.path.join(CERTS_DIR, 'private_key.pem') - msg_payload = msg.message().as_string().encode() + if not os.path.exists(cert_file): + logger.warning(f'smime cert file {cert_file} does not exist.') + return msg + if not os.path.exists(key_file): + logger.warning(f'smime key file {key_file} does not exist.') + return msg + + try: + # Load the private key + with open(key_file, "rb") as f: + private_key = serialization.load_pem_private_key(f.read(), password=None) + + # Load the certificate + with open(cert_file, "rb") as f: + certificate = x509.load_pem_x509_certificate(f.read()) + except Exception as e: + logger.error(e) + return msg - builder = pkcs7.PKCS7SignatureBuilder() \ - .set_data(msg_payload) \ - .add_signer(certificate, private_key, hashes.SHA256()) - pkcs7_signature = builder.sign(serialization.Encoding.SMIME, [pkcs7.PKCS7Options.DetachedSignature]) + original_msg = msg + msg_payload = msg.as_string().encode() - # Base64 encode the signature for S/MIME - signature_b64 = base64.b64encode(pkcs7_signature).decode() + builder = pkcs7.PKCS7SignatureBuilder() \ + .set_data(msg_payload) \ + .add_signer(certificate, private_key, hashes.SHA256()) + pkcs7_signature = builder.sign(serialization.Encoding.SMIME, [pkcs7.PKCS7Options.DetachedSignature]) - # Create the S/MIME signature part - sig_part = MIMEApplication( - signature_b64, - "pkcs7-signature", - ) - sig_part.set_param("name", "smime.p7s") - sig_part.add_header("Content-Disposition", "attachment", filename="smime.p7s") + signature = base64.b64encode(pkcs7_signature).decode() - return sig_part - - + signed_msg = SafeMIMEMultipart( + _subtype='signed', + protocol='application/pkcs7-signature', + micalg='sha-256' + ) + + + for header in ['Subject', 'From', 'To', 'Date']: + if header in original_msg.keys(): + signed_msg[header] = original_msg[header] + + signed_msg.attach(original_msg) + + sig_part = MIMEApplication( + signature, + 'pkcs7-signature', + name='smime.p7s', + ) + sig_part.add_header( + 'Content-Disposition', + 'attachment; filename="smime.p7s"' + ) + signed_msg.attach(sig_part) + + return signed_msg + + + def message(self): + original_msg = super().message() + return self._signed(original_msg) + def send_html_email_with_dj_template(recipients, subject, dj_template, context={}): """ @@ -92,15 +123,12 @@ def send_html_email_with_dj_template(recipients, subject, dj_template, context={ t = loader.get_template(dj_template) html_message = t.render(context) - mail = EmailMessage(subject=subject, body=html_message, to=[recipients]) + mail = SmimeEmailMessage(subject=subject, body=html_message, to=[recipients]) mail.content_subtype = "html" try: - sig_part = add_smime_sign(mail) - if sig_part: - mail.attach(sig_part) mail.send() return True except Exception as e: - logger.error(e) + logger.exception(e) return False