Merges some of the changes from Diane Trout to pass unit tests in python 2/3.

This commit is contained in:
Stuart D Gathman
2015-02-21 19:34:20 -05:00
parent 94c5e093bb
commit c6523a3d72
3 changed files with 55 additions and 41 deletions
+36 -22
View File
@@ -119,8 +119,8 @@ def select_headers(headers, include_headers):
return sign_headers return sign_headers
# FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space [RFC5322] # FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space [RFC5322]
FWS = r'(?:(?:\s*\r?\n)?\s+)?' FWS = br'(?:(?:\s*\r?\n)?\s+)?'
RE_BTAG = re.compile(r'([;\s]b'+FWS+r'=)(?:'+FWS+r'[a-zA-Z0-9+/=])*(?:\r?\n\Z)?') RE_BTAG = re.compile(br'([;\s]b'+FWS+br'=)(?:'+FWS+br'[a-zA-Z0-9+/=])*(?:\r?\n\Z)?')
def hash_headers(hasher, canonicalize_headers, headers, include_headers, def hash_headers(hasher, canonicalize_headers, headers, include_headers,
sigheader, sig): sigheader, sig):
@@ -166,7 +166,7 @@ def validate_signature_fields(sig):
raise ValidationError( raise ValidationError(
"i= domain is not a subdomain of d= (i=%s d=%s)" % "i= domain is not a subdomain of d= (i=%s d=%s)" %
(sig[b'i'], sig[b'd'])) (sig[b'i'], sig[b'd']))
if b'l' in sig and re.match(br"\d{,76}$", sig['l']) is None: if b'l' in sig and re.match(br"\d{,76}$", sig[b'l']) is None:
raise ValidationError( raise ValidationError(
"l= value is not a decimal integer (%s)" % sig[b'l']) "l= value is not a decimal integer (%s)" % sig[b'l'])
if b'q' in sig and sig[b'q'] != b"dns/txt": if b'q' in sig and sig[b'q'] != b"dns/txt":
@@ -224,16 +224,28 @@ def rfc822_parse(message):
i += 1 i += 1
return (headers, b"\r\n".join(lines[i:])) return (headers, b"\r\n".join(lines[i:]))
def text(s):
"""Normalize bytes/str to str for python 2/3 compatible doctests.
>>> text(b'foo')
'foo'
>>> text(u'foo')
'foo'
>>> text('foo')
'foo'
"""
if type(s) is str: return s
s = s.decode('ascii')
if type(s) is str: return s
return s.encode('ascii')
def fold(header): def fold(header):
"""Fold a header line into multiple crlf-separated lines at column 72. """Fold a header line into multiple crlf-separated lines at column 72.
>>> fold(b'foo') >>> text(fold(b'foo'))
'foo' 'foo'
>>> fold(b'foo '+b'foo'*24).splitlines()[0] >>> text(fold(b'foo '+b'foo'*24).splitlines()[0])
'foo ' 'foo '
>>> fold(b'foo'*25).splitlines()[-1] >>> text(fold(b'foo'*25).splitlines()[-1])
' foo' ' foo'
>>> len(fold(b'foo'*25).splitlines()[0]) >>> len(fold(b'foo'*25).splitlines()[0])
72 72
@@ -268,31 +280,31 @@ class DKIM(object):
#: be in the default FROZEN list, but that could also make signatures #: be in the default FROZEN list, but that could also make signatures
#: more fragile than necessary. #: more fragile than necessary.
#: @since: 0.5 #: @since: 0.5
RFC5322_SINGLETON = ('date','from','sender','reply-to','to','cc','bcc', RFC5322_SINGLETON = (b'date',b'from',b'sender',b'reply-to',b'to',b'cc',b'bcc',
'message-id','in-reply-to','references') b'message-id',b'in-reply-to',b'references')
#: Header fields to protect from additions by default. #: Header fields to protect from additions by default.
#: #:
#: The short list below is the result more of instinct than logic. #: The short list below is the result more of instinct than logic.
#: @since: 0.5 #: @since: 0.5
FROZEN = ('from','date','subject') FROZEN = (b'from',b'date',b'subject')
#: The rfc4871 recommended header fields to sign #: The rfc4871 recommended header fields to sign
#: @since: 0.5 #: @since: 0.5
SHOULD = ( SHOULD = (
'sender', 'reply-to', 'subject', 'date', 'message-id', 'to', 'cc', b'sender', b'reply-to', b'subject', b'date', b'message-id', b'to', b'cc',
'mime-version', 'content-type', 'content-transfer-encoding', 'content-id', b'mime-version', b'content-type', b'content-transfer-encoding',
'content- description', 'resent-date', 'resent-from', 'resent-sender', b'content-id', b'content- description', b'resent-date', b'resent-from',
'resent-to', 'resent-cc', 'resent-message-id', 'in-reply-to', 'references', b'resent-sender', b'resent-to', b'resent-cc', b'resent-message-id',
'list-id', 'list-help', 'list-unsubscribe', 'list-subscribe', 'list-post', b'in-reply-to', 'references', b'list-id', b'list-help', b'list-unsubscribe',
'list-owner', 'list-archive' b'list-subscribe', b'list-post', b'list-owner', b'list-archive'
) )
#: The rfc4871 recommended header fields not to sign. #: The rfc4871 recommended header fields not to sign.
#: @since: 0.5 #: @since: 0.5
SHOULD_NOT = ( SHOULD_NOT = (
'return-path', 'received', 'comments', 'keywords', 'bcc', 'resent-bcc', b'return-path',b'received',b'comments',b'keywords',b'bcc',b'resent-bcc',
'dkim-signature' b'dkim-signature'
) )
#: Create a DKIM instance to sign and verify rfc5322 messages. #: Create a DKIM instance to sign and verify rfc5322 messages.
@@ -331,7 +343,7 @@ class DKIM(object):
>>> dkim = DKIM() >>> dkim = DKIM()
>>> dkim.add_frozen(DKIM.RFC5322_SINGLETON) >>> dkim.add_frozen(DKIM.RFC5322_SINGLETON)
>>> sorted(dkim.frozen_sign) >>> [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', 'subject', 'to']
""" """
self.frozen_sign.update(x.lower() for x in s self.frozen_sign.update(x.lower() for x in s
@@ -430,7 +442,7 @@ class DKIM(object):
include_headers = self.default_sign_headers() include_headers = self.default_sign_headers()
# rfc4871 says FROM is required # rfc4871 says FROM is required
if 'from' not in ( x.lower() for x in include_headers ): if b'from' not in ( x.lower() for x in include_headers ):
raise ParameterError("The From header field MUST be signed") raise ParameterError("The From header field MUST be signed")
# raise exception for any SHOULD_NOT headers, call can modify # raise exception for any SHOULD_NOT headers, call can modify
@@ -552,6 +564,8 @@ class DKIM(object):
if not s: if not s:
raise KeyFormatError("missing public key: %s"%name) raise KeyFormatError("missing public key: %s"%name)
try: try:
if type(s) is str:
s = s.encode('ascii')
pub = parse_tag_value(s) pub = parse_tag_value(s)
except InvalidTagValueList as e: except InvalidTagValueList as e:
raise KeyFormatError(e) raise KeyFormatError(e)
@@ -568,8 +582,8 @@ class DKIM(object):
# fields when verifying. Since there should be only one From header, # fields when verifying. Since there should be only one From header,
# this shouldn't break any legitimate messages. This could be # this shouldn't break any legitimate messages. This could be
# generalized to check for extras of other singleton headers. # generalized to check for extras of other singleton headers.
if 'from' in include_headers: if b'from' in include_headers:
include_headers.append('from') include_headers.append(b'from')
h = hasher() h = hasher()
self.signed_headers = hash_headers( self.signed_headers = hash_headers(
h, canon_policy, headers, include_headers, sigheaders[idx], sig) h, canon_policy, headers, include_headers, sigheaders[idx], sig)
+7 -7
View File
@@ -108,14 +108,14 @@ Y+vtSBczUiKERHv1yRbcaQtZFh5wtiRrN04BLUTD21MycBX5jYchHjPY/wIDAQAB"""
# <https://bugs.launchpad.net/dkimpy/+bug/939128> # <https://bugs.launchpad.net/dkimpy/+bug/939128>
# Simple-mode signature header verification is wrong # Simple-mode signature header verification is wrong
# (should ignore FWS anywhere in signature tag: b=) # (should ignore FWS anywhere in signature tag: b=)
sample_msg = """\ sample_msg = b"""\
From: mbp@canonical.com From: mbp@canonical.com
To: scottk@example.com To: scottk@example.com
Subject: this is my Subject: this is my
test message test message
""".replace('\n', '\r\n') """.replace(b'\n', b'\r\n')
sample_privkey = """\ sample_privkey = b"""\
-----BEGIN RSA PRIVATE KEY----- -----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBANmBe10IgY+u7h3enWTukkqtUD5PR52Tb/mPfjC0QJTocVBq6Za/ MIIBOwIBAAJBANmBe10IgY+u7h3enWTukkqtUD5PR52Tb/mPfjC0QJTocVBq6Za/
PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQJAYFUKsD+uMlcFu1D3YNaR PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQJAYFUKsD+uMlcFu1D3YNaR
@@ -136,7 +136,7 @@ b/mPfjC0QJTocVBq6Za/PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQ==
for header_mode in [dkim.Relaxed, dkim.Simple]: for header_mode in [dkim.Relaxed, dkim.Simple]:
dkim_header = dkim.sign(sample_msg, 'example', 'canonical.com', dkim_header = dkim.sign(sample_msg, b'example', b'canonical.com',
sample_privkey, canonicalize=(header_mode, dkim.Relaxed)) sample_privkey, canonicalize=(header_mode, dkim.Relaxed))
# Folding dkim_header affects b= tag only, since dkim.sign folds # Folding dkim_header affects b= tag only, since dkim.sign folds
# sig_value with empty b= before hashing, and then appends the # sig_value with empty b= before hashing, and then appends the
@@ -173,7 +173,7 @@ b/mPfjC0QJTocVBq6Za/PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQ==
for body_algo in (b"simple", b"relaxed"): for body_algo in (b"simple", b"relaxed"):
d = dkim.DKIM(message) d = dkim.DKIM(message)
# bug requires a repeated header to manifest # bug requires a repeated header to manifest
d.should_not_sign.remove('received') d.should_not_sign.remove(b'received')
sig = d.sign(b"test", b"example.com", self.key, sig = d.sign(b"test", b"example.com", self.key,
include_headers=d.all_sign_headers(), include_headers=d.all_sign_headers(),
canonicalize=(header_algo, body_algo)) canonicalize=(header_algo, body_algo))
@@ -220,8 +220,8 @@ b/mPfjC0QJTocVBq6Za/PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQ==
identity = None identity = None
try: try:
sig = dkim.sign(message, selector, domain, read_test_data('test.private'), identity = identity) sig = dkim.sign(message, selector, domain, read_test_data('test.private'), identity = identity)
except dkim.ParameterError as sigerror: except dkim.ParameterError as x:
pass sigerror = True
self.assertTrue(sigerror) self.assertTrue(sigerror)
def test_suite(): def test_suite():
+2 -2
View File
@@ -62,7 +62,7 @@ class TestParseTagValue(unittest.TestCase):
DuplicateTag, parse_tag_value, b'foo=bar;foo=baz') DuplicateTag, parse_tag_value, b'foo=bar;foo=baz')
def test_trailing_whitespace(self): def test_trailing_whitespace(self):
hval = '''v=1; a=rsa-sha256; d=facebookmail.com; s=s1024-2011-q2; c=relaxed/simple; hval = b'''v=1; a=rsa-sha256; d=facebookmail.com; s=s1024-2011-q2; c=relaxed/simple;
q=dns/txt; i=@facebookmail.com; t=1308078492; q=dns/txt; i=@facebookmail.com; t=1308078492;
h=From:Subject:Date:To:MIME-Version:Content-Type; h=From:Subject:Date:To:MIME-Version:Content-Type;
bh=+qPyCOiDQkusTPstCoGjimgDgeZbUaJWIr1mdE6RFxk=; bh=+qPyCOiDQkusTPstCoGjimgDgeZbUaJWIr1mdE6RFxk=;
@@ -71,7 +71,7 @@ class TestParseTagValue(unittest.TestCase):
3KzW0yB9JHwiDCw1EioVkv+OMHhAYzoIypA0bQyi2bc=; 3KzW0yB9JHwiDCw1EioVkv+OMHhAYzoIypA0bQyi2bc=;
''' '''
sig = parse_tag_value(hval) sig = parse_tag_value(hval)
self.assertEquals(sig[b't'],'1308078492') self.assertEquals(sig[b't'],b'1308078492')
self.assertEquals(len(sig),11) self.assertEquals(len(sig),11)