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
# FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space [RFC5322]
FWS = r'(?:(?:\s*\r?\n)?\s+)?'
RE_BTAG = re.compile(r'([;\s]b'+FWS+r'=)(?:'+FWS+r'[a-zA-Z0-9+/=])*(?:\r?\n\Z)?')
FWS = br'(?:(?:\s*\r?\n)?\s+)?'
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,
sigheader, sig):
@@ -166,7 +166,7 @@ def validate_signature_fields(sig):
raise ValidationError(
"i= domain is not a subdomain of d= (i=%s d=%s)" %
(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(
"l= value is not a decimal integer (%s)" % sig[b'l'])
if b'q' in sig and sig[b'q'] != b"dns/txt":
@@ -224,16 +224,28 @@ def rfc822_parse(message):
i += 1
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):
"""Fold a header line into multiple crlf-separated lines at column 72.
>>> fold(b'foo')
>>> text(fold(b'foo'))
'foo'
>>> fold(b'foo '+b'foo'*24).splitlines()[0]
>>> text(fold(b'foo '+b'foo'*24).splitlines()[0])
'foo '
>>> fold(b'foo'*25).splitlines()[-1]
>>> text(fold(b'foo'*25).splitlines()[-1])
' foo'
>>> len(fold(b'foo'*25).splitlines()[0])
72
@@ -268,31 +280,31 @@ class DKIM(object):
#: be in the default FROZEN list, but that could also make signatures
#: more fragile than necessary.
#: @since: 0.5
RFC5322_SINGLETON = ('date','from','sender','reply-to','to','cc','bcc',
'message-id','in-reply-to','references')
RFC5322_SINGLETON = (b'date',b'from',b'sender',b'reply-to',b'to',b'cc',b'bcc',
b'message-id',b'in-reply-to',b'references')
#: Header fields to protect from additions by default.
#:
#: The short list below is the result more of instinct than logic.
#: @since: 0.5
FROZEN = ('from','date','subject')
FROZEN = (b'from',b'date',b'subject')
#: The rfc4871 recommended header fields to sign
#: @since: 0.5
SHOULD = (
'sender', 'reply-to', 'subject', 'date', 'message-id', 'to', 'cc',
'mime-version', 'content-type', 'content-transfer-encoding', 'content-id',
'content- description', 'resent-date', 'resent-from', 'resent-sender',
'resent-to', 'resent-cc', 'resent-message-id', 'in-reply-to', 'references',
'list-id', 'list-help', 'list-unsubscribe', 'list-subscribe', 'list-post',
'list-owner', 'list-archive'
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',
b'in-reply-to', 'references', b'list-id', b'list-help', b'list-unsubscribe',
b'list-subscribe', b'list-post', b'list-owner', b'list-archive'
)
#: The rfc4871 recommended header fields not to sign.
#: @since: 0.5
SHOULD_NOT = (
'return-path', 'received', 'comments', 'keywords', 'bcc', 'resent-bcc',
'dkim-signature'
b'return-path',b'received',b'comments',b'keywords',b'bcc',b'resent-bcc',
b'dkim-signature'
)
#: Create a DKIM instance to sign and verify rfc5322 messages.
@@ -331,7 +343,7 @@ class DKIM(object):
>>> dkim = DKIM()
>>> 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']
"""
self.frozen_sign.update(x.lower() for x in s
@@ -430,7 +442,7 @@ class DKIM(object):
include_headers = self.default_sign_headers()
# 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 exception for any SHOULD_NOT headers, call can modify
@@ -552,6 +564,8 @@ class DKIM(object):
if not s:
raise KeyFormatError("missing public key: %s"%name)
try:
if type(s) is str:
s = s.encode('ascii')
pub = parse_tag_value(s)
except InvalidTagValueList as e:
raise KeyFormatError(e)
@@ -568,8 +582,8 @@ class DKIM(object):
# fields when verifying. Since there should be only one From header,
# this shouldn't break any legitimate messages. This could be
# generalized to check for extras of other singleton headers.
if 'from' in include_headers:
include_headers.append('from')
if b'from' in include_headers:
include_headers.append(b'from')
h = hasher()
self.signed_headers = hash_headers(
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>
# Simple-mode signature header verification is wrong
# (should ignore FWS anywhere in signature tag: b=)
sample_msg = """\
sample_msg = b"""\
From: mbp@canonical.com
To: scottk@example.com
Subject: this is my
test message
""".replace('\n', '\r\n')
""".replace(b'\n', b'\r\n')
sample_privkey = """\
sample_privkey = b"""\
-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBANmBe10IgY+u7h3enWTukkqtUD5PR52Tb/mPfjC0QJTocVBq6Za/
PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQJAYFUKsD+uMlcFu1D3YNaR
@@ -136,7 +136,7 @@ b/mPfjC0QJTocVBq6Za/PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQ==
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))
# Folding dkim_header affects b= tag only, since dkim.sign folds
# 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"):
d = dkim.DKIM(message)
# 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,
include_headers=d.all_sign_headers(),
canonicalize=(header_algo, body_algo))
@@ -220,8 +220,8 @@ b/mPfjC0QJTocVBq6Za/PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQ==
identity = None
try:
sig = dkim.sign(message, selector, domain, read_test_data('test.private'), identity = identity)
except dkim.ParameterError as sigerror:
pass
except dkim.ParameterError as x:
sigerror = True
self.assertTrue(sigerror)
def test_suite():
+2 -2
View File
@@ -62,7 +62,7 @@ class TestParseTagValue(unittest.TestCase):
DuplicateTag, parse_tag_value, b'foo=bar;foo=baz')
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;
h=From:Subject:Date:To:MIME-Version:Content-Type;
bh=+qPyCOiDQkusTPstCoGjimgDgeZbUaJWIr1mdE6RFxk=;
@@ -71,7 +71,7 @@ class TestParseTagValue(unittest.TestCase):
3KzW0yB9JHwiDCw1EioVkv+OMHhAYzoIypA0bQyi2bc=;
'''
sig = parse_tag_value(hval)
self.assertEquals(sig[b't'],'1308078492')
self.assertEquals(sig[b't'],b'1308078492')
self.assertEquals(len(sig),11)