diff --git a/ChangeLog b/ChangeLog index 17a0a55..94f12c0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,9 +1,19 @@ -UNRELEASED Version 0.8.2 +UNRELEASED Version 0.9.0 + - Update oversigned (frozen) header field list to reduce signature + fragility (removes 'date' and 'subject' fields from being oversigned by + default - see usage section of README for information on how to restore + the previous behavior) + - Added new add_should_not for DKIM/ARC classes to prevent additional + header fields from being signed + - Added 'from' to should sign list (to prevent it from not being signed at + all in the unusual event that 'from' is locally removed from the frozen + header field set (LP: #1525048) - Updates for experimental ARC support: - Limit to rsa-sha256, rsa-sha1 not used by ARC and multi-signature design TBD - Raise error when ARC signing if i= instance limit value of 50 is exceeded + - Specified that for ARC, Authentication-Results should not be signed - Fix DNS lookups to be compatible with EAI addresses in domains and selectors (John Levine) - Add type Hinting for sign and verify functions (LP: #1782596) diff --git a/README b/README index 2c21495..7a8bfc2 100644 --- a/README +++ b/README @@ -11,7 +11,7 @@ signing and verification. VERSION -This is dkimpy 0.8.0. +This is dkimpy 0.9.0. REQUIREMENTS @@ -91,6 +91,17 @@ function takes an RFC822 formatted message, and returns True or False depending on whether the signature verifies correctly. There is also a DKIM class which can be used to perform these functions in a more modern way. +In version 0.9.0, the default set of header fields that are oversigned was +changed from 'from', 'subject', 'date' to 'from' to reduce fragility of +signatures. To restore the previous behavior, you can add them back after +instantiating your DKIM class using the add_frozen function as shown in the +following example: + +>>> dkim = DKIM() +>>> dkim.add_frozen((b'date',b'subject')) +>>> [text(x) for x in sorted(dkim.frozen_sign)] +['date', 'from', 'subject'] + RFC8301 updated DKIM requirements in two ways: 1. It set the minimum valid RSA key size to 1024 bits. @@ -145,7 +156,7 @@ code 1. As of version 0.6.0, dkimpy provides experimental support for ARC (Authenticated Received Chain): -https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-01 +https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-18 This new functionality is marked experimental because the protocol is still under development. There are no guarantees about API stability or diff --git a/dkim/__init__.py b/dkim/__init__.py index f467d8a..fb2b5a9 100644 --- a/dkim/__init__.py +++ b/dkim/__init__.py @@ -486,12 +486,12 @@ class DomainSigner(object): #: #: The short list below is the result more of instinct than logic. #: @since: 0.5 - FROZEN = (b'from',b'date',b'subject') + FROZEN = (b'from',) #: The rfc6376 recommended header fields to sign #: @since: 0.5 SHOULD = ( - b'sender', b'reply-to', b'subject', b'date', b'message-id', b'to', b'cc', + b'from', b'sender', b'reply-to', b'subject', b'date', b'message-id', b'to', b'cc', b'mime-version', b'content-type', b'content-transfer-encoding', b'content-id', b'content-description', b'resent-date', b'resent-from', b'resent-sender', b'resent-to', b'resent-cc', b'resent-message-id', @@ -526,11 +526,30 @@ class DomainSigner(object): >>> dkim = DKIM() >>> dkim.add_frozen(DKIM.RFC5322_SINGLETON) >>> [text(x) for x in sorted(dkim.frozen_sign)] - ['cc', 'date', 'from', 'in-reply-to', 'message-id', 'references', 'reply-to', 'sender', 'subject', 'to'] + ['cc', 'date', 'from', 'in-reply-to', 'message-id', 'references', 'reply-to', 'sender', 'to'] + >>> dkim2 = DKIM() + >>> dkim2.add_frozen((b'date',b'subject')) + >>> [text(x) for x in sorted(dkim2.frozen_sign)] + ['date', 'from', 'subject'] """ self.frozen_sign.update(x.lower() for x in s if x.lower() not in self.should_not_sign) + + def add_should_not(self,s): + """ Add headers not in should_not_sign to frozen_sign. + @param s: list of headers to add to frozen_sign + @since: 0.5 + + >>> dkim = DKIM() + >>> dkim.add_should_not(DKIM.RFC5322_SINGLETON) + >>> [text(x) for x in sorted(dkim.should_not_sign)] + ['bcc', 'cc', 'comments', 'date', 'dkim-signature', 'in-reply-to', 'keywords', 'message-id', 'received', 'references', 'reply-to', 'resent-bcc', 'return-path', 'sender', 'to'] + """ + self.should_not_sign.update(x.lower() for x in s + if x.lower() not in self.frozen_sign) + + #: Load a new message to be signed or verified. #: @param message: an RFC822 formatted message to be signed or verified #: (with either \\n or \\r\\n line endings) @@ -900,6 +919,7 @@ class ARC(DomainSigner): timestamp=None, standardize=False): INSTANCE_LIMIT = 50 # Maximum allowed i= value + self.add_should_not(('Authentication-Results',)) # check if authres has been imported try: AuthenticationResultsHeader diff --git a/dkim/tests/test_arc.py b/dkim/tests/test_arc.py index 2ddcb88..e02f2eb 100644 --- a/dkim/tests/test_arc.py +++ b/dkim/tests/test_arc.py @@ -69,11 +69,12 @@ Y+vtSBczUiKERHv1yRbcaQtZFh5wtiRrN04BLUTD21MycBX5jYchHjPY/wIDAQAB""" return _dns_responses[domain] def test_signs_and_verifies(self): - # A message verifies after being signed. + # A message verifies after being signed + self.maxDiff = None sig_lines = dkim.arc_sign( self.message, b"test", b"example.com", self.key, b"lists.example.org", timestamp="12345") - expected_sig = [b'ARC-Seal: i=1; cv=none; a=rsa-sha256; d=example.com; s=test; t=12345; \r\n b=3jOfBfTKcq+3r3Xv158DybT4mWFxrGcop+cgyLUX2ETCMHqNXYwGx2h+NY46tr\r\n k0Lg6R8i+560+KC8PLcCURYYJNJUHLHPIifhddy1aMNL9l4CoI+Oz+rocd2IZeb/\r\n I9V5amOUOWnAlOvyrSt0XfzLJRTS8qJW3Is1CRkkgyLoI=\r\n', b'ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; \r\n d=example.com; s=test; t=12345; h=message-id : \r\n date : from : to : subject : date : from : \r\n subject; \r\n bh=wE7NXSkgnx9PGiavN4OZhJztvkqPDlemV3OGuEnLwNo=; \r\n b=Bj/AEKhmzMbltWXrfLA8UZNp6/5cj8/IzqbgQec4vGobDZRsa\r\n C0YIPM4tcqK2uTS62kwh40cndXTDsCppvRsBy1sIO3eRNyuLUOh\r\n 0XGrz0AdLQMv+IOdyQqZfMVkq8DuQ4Qdl7ee99uYf3D8S+L7GuD\r\n wJSk7dyH+P2BKxz2nyB0=\r\n', b'ARC-Authentication-Results: i=1; lists.example.org; arc=none;\r\n spf=pass smtp.mfrom=jqd@d1.example;\r\n dkim=pass (1024-bit key) header.i=@d1.example;\r\n dmarc=pass\r\n'] + expected_sig = [b'ARC-Seal: i=1; cv=none; a=rsa-sha256; d=example.com; s=test; t=12345; \r\n b=2DTLaiAcKznJwwNOWoOG8WBsdTq+/S92TZbURDxkgjGCmsSw8czQiisf02sC92\r\n 0nswz3JItA80l70iguM00onrj3eCe41yRDzB8lQL3kbrDyM+wUewmyhPoifRTsng\r\n t9ELTFrax4kCeHv6SdNz3uJfGYwQc+WCFEchXt3szNTRM=\r\n', b'ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; \r\n d=example.com; s=test; t=12345; h=message-id : \r\n date : from : to : subject : from; \r\n bh=wE7NXSkgnx9PGiavN4OZhJztvkqPDlemV3OGuEnLwNo=; \r\n b=a0f6qc3k9eECTSR155A0TQS+LjqPFWfI/brQBA83EUz00SNxj\r\n 1wmWykvs1hhBVeM0r1kEQc6CKbzRYaBNSiFj4q8JBpRIujLz1qL\r\n yGmPuAI6ddu/Z/1hQxgpVcp/odmI1UMV2R+d+yQ7tUp3EQxF/GY\r\n Nt22rV4rNmDmANZVqJ90=\r\n', b'ARC-Authentication-Results: i=1; lists.example.org; arc=none;\r\n spf=pass smtp.mfrom=jqd@d1.example;\r\n dkim=pass (1024-bit key) header.i=@d1.example;\r\n dmarc=pass\r\n'] self.assertEqual(expected_sig, sig_lines) diff --git a/setup.py b/setup.py index e04ab6f..e0c9584 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ from setuptools import setup import os import sys -version = "0.8.2" +version = "0.9.0" kw = {} # Work-around for lack of 'or' requires in setuptools. try: