From 3717ff11f0d8b3716f2095a4ff550432e4cd3e29 Mon Sep 17 00:00:00 2001 From: Sergey Lavrinenko Date: Tue, 31 Mar 2026 23:55:54 +0300 Subject: [PATCH 1/2] Add dkimpy to requirements/base.txt, document key parse cost --- emails/signers.py | 3 ++- requirements/base.txt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/emails/signers.py b/emails/signers.py index 47a61e9..5f8b6da 100644 --- a/emails/signers.py +++ b/emails/signers.py @@ -28,7 +28,8 @@ def __init__(self, selector: str, domain: str, key: str | bytes | IO[bytes] | No # Normalize to bytes privkey_bytes = privkey if isinstance(privkey, bytes) else str(privkey).encode() - # Validate key by attempting to parse it + # Validate key early; dkim.sign() re-parses on each call but + # the PEM parse cost is negligible vs the RSA operation (~0ms vs ~2.5ms). try: dkim.crypto.parse_pem_private_key(privkey_bytes) except UnparsableKeyError as exc: diff --git a/requirements/base.txt b/requirements/base.txt index 466b8f5..c70ac4a 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -5,3 +5,4 @@ python-dateutil requests premailer>=2.8.3 puremagic +dkimpy From 8d6bffc3f217e64a2b9aaf9779fe7b1354f5ab2f Mon Sep 17 00:00:00 2001 From: Sergey Lavrinenko Date: Wed, 1 Apr 2026 00:14:34 +0300 Subject: [PATCH 2/2] Add dkimpy to requirements, remove unsafe key-cache monkey-patch - Add dkimpy to requirements/base.txt (was missing after #205) - Remove thread-unsafe monkey-patch of dkim.parse_pem_private_key; PEM parse cost is negligible vs RSA (~0ms vs ~2.5ms per sign) - Add test_dkim_sign_after_error for signer recovery after errors --- emails/signers.py | 4 ++-- emails/testsuite/message/test_dkim.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/emails/signers.py b/emails/signers.py index 5f8b6da..a3e796f 100644 --- a/emails/signers.py +++ b/emails/signers.py @@ -28,8 +28,8 @@ def __init__(self, selector: str, domain: str, key: str | bytes | IO[bytes] | No # Normalize to bytes privkey_bytes = privkey if isinstance(privkey, bytes) else str(privkey).encode() - # Validate key early; dkim.sign() re-parses on each call but - # the PEM parse cost is negligible vs the RSA operation (~0ms vs ~2.5ms). + # Validate key upfront; dkim.sign() re-parses PEM on each call + # but the cost is negligible vs the RSA operation (~0ms vs ~2.5ms). try: dkim.crypto.parse_pem_private_key(privkey_bytes) except UnparsableKeyError as exc: diff --git a/emails/testsuite/message/test_dkim.py b/emails/testsuite/message/test_dkim.py index 87a7b89..1ad4821 100644 --- a/emails/testsuite/message/test_dkim.py +++ b/emails/testsuite/message/test_dkim.py @@ -140,6 +140,22 @@ def test_dkim_as_bytes(): assert b'DKIM-Signature: ' in result +def test_dkim_sign_after_error(): + """After a sign error with ignore_sign_errors, normal signing still works.""" + priv_key, pub_key = _generate_key(length=1024) + + # First: sign with invalid include_headers (missing From), error ignored + m1 = Message(**common_email_data()) + m1.dkim(key=priv_key, selector='_dkim', domain='somewhere.net', + ignore_sign_errors=True, include_headers=['To']) + m1.as_string() # should not raise + + # Second: normal sign with same key must still work + m2 = Message(**common_email_data()) + m2.dkim(key=priv_key, selector='_dkim', domain='somewhere.net') + assert _check_dkim(m2, pub_key) + + def test_dkim_sign_twice(): # Test #44: