diff -u dkimpy-0.5.1/debian/changelog dkimpy-0.5.1/debian/changelog --- dkimpy-0.5.1/debian/changelog +++ dkimpy-0.5.1/debian/changelog @@ -1,3 +1,14 @@ +dkimpy (0.5.1-1ubuntu2) precise; urgency=low + + * Cherrypick bug fixes from upstream trunk + - Fixed hashing problem when using sha1 (rev 85) (LP: #969206) + - Added test suite coverage for bag handling of b= tag folding (rev 87) + - Fold b= tags before signing to work around validation issues at Hotmail + and Yahoo and correctly (per RFC) ignore FWS in b= tag when verifying + signatures (rev 88) + + -- Scott Kitterman Tue, 24 Apr 2012 09:36:00 -0400 + dkimpy (0.5.1-1ubuntu1) precise; urgency=low * Change default header signing canonicalization from 'simple' to diff -u dkimpy-0.5.1/dkim/__init__.py dkimpy-0.5.1/dkim/__init__.py --- dkimpy-0.5.1/dkim/__init__.py +++ dkimpy-0.5.1/dkim/__init__.py @@ -89,11 +89,6 @@ """Validation error.""" pass -def _remove(s, t): - i = s.find(t) - assert i >= 0 - return s[:i] + s[i+len(t):] - def select_headers(headers, include_headers): """Select message header fields to be signed/verified. @@ -119,14 +114,17 @@ lastindex[h] = i return sign_headers +FWS = r'(?:\r\n\s+)?' +RE_BTAG = re.compile(r'([; ]b'+FWS+r'=)(?:'+FWS+r'[a-zA-Z0-9+/=])*(?:\r\n\Z)?') + def hash_headers(hasher, canonicalize_headers, headers, include_headers, - sigheaders, sig): + sigheader, sig): """Update hash for signed message header fields.""" sign_headers = select_headers(headers,include_headers) # The call to _remove() assumes that the signature b= only appears # once in the signature header cheaders = canonicalize_headers.canonicalize_headers( - [(sigheaders[0][0], _remove(sigheaders[0][1], sig[b'b']))]) + [(sigheader[0], RE_BTAG.sub(b'\\1',sigheader[1]))]) # the dkim sig is hashed with no trailing crlf, even if the # canonicalization algorithm would add one. for x,y in sign_headers + [(x, y.rstrip()) for x,y in cheaders]: @@ -435,25 +433,33 @@ (b't', str(int(time.time())).encode('ascii')), (b'h', b" : ".join(include_headers)), (b'bh', bodyhash), - (b'b', b""), + # Force b= to fold onto it's own line so that refolding after + # adding sig doesn't change whitespace for previous tags. + (b'b', b'0'*60), ] if x] include_headers = [x.lower() for x in include_headers] # record what verify should extract self.include_headers = tuple(include_headers) sig_value = fold(b"; ".join(b"=".join(x) for x in sigfields)) + sig_value = RE_BTAG.sub(b'\\1',sig_value) dkim_header = (b'DKIM-Signature', b' ' + sig_value) - h = hashlib.sha256() + h = hasher() sig = dict(sigfields) self.signed_headers = hash_headers( - h, canon_policy, headers, include_headers, [dkim_header],sig) + h, canon_policy, headers, include_headers, dkim_header,sig) self.logger.debug("sign headers: %r" % self.signed_headers) try: sig2 = RSASSA_PKCS1_v1_5_sign(h, pk) except DigestTooLargeError: raise ParameterError("digest too large for modulus") - sig_value += base64.b64encode(bytes(sig2)) + # Folding b= is explicity allowed, but yahoo and live.com are broken + #sig_value += base64.b64encode(bytes(sig2)) + # Instead of leaving unfolded (which lets an MTA fold it later and still + # breaks yahoo and live.com), we change the default signing mode to + # relaxed/simple (for broken receivers), and fold now. + sig_value = fold(sig_value + base64.b64encode(bytes(sig2))) self.domain = domain self.selector = selector @@ -480,7 +486,6 @@ except InvalidTagValueList as e: raise MessageFormatError(e) - sig = parse_tag_value(sigheaders[idx][1]) logger = self.logger logger.debug("sig: %r" % sig) @@ -540,7 +545,7 @@ include_headers.append('from') h = hasher() self.signed_headers = hash_headers( - h, canon_policy, headers, include_headers, sigheaders, sig) + h, canon_policy, headers, include_headers, sigheaders[idx], sig) try: self.signature_fields = sig signature = base64.b64decode(re.sub(br"\s+", b"", sig[b'b'])) only in patch2: unchanged: --- dkimpy-0.5.1.orig/dkim/tests/test_dkim.py +++ dkimpy-0.5.1/dkim/tests/test_dkim.py @@ -86,9 +86,12 @@ res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc) self.assertFalse(res) - def test_dkim_dignature_canonicalization(self): + def test_dkim_signature_canonicalization(self): # # Relaxed-mode header signing is wrong + # + # Simple-mode signature header verification is wrong + # (should ignore FWS anywhere in signature tag: b=) sample_msg = """\ From: mbp@canonical.com To: scottk@example.com @@ -125,8 +128,13 @@ dkim_header = dkim.sign(sample_msg, 'example', 'canonical.com', sample_privkey, canonicalize=(header_mode, dkim.Relaxed)) - signed = dkim_header + sample_msg - + # Folding dkim_header affects b= tag only, since dkim.sign folds + # sig_value with empty b= before hashing, and then appends the + # signature. So folding dkim_header again adds FWS to + # the b= tag only. This should be ignored even with + # simple canonicalization. + # http://tools.ietf.org/html/rfc4871#section-3.5 + signed = dkim.fold(dkim_header) + sample_msg result = dkim.verify(signed,dnsfunc=lambda x: _dns_responses[x]) self.assertTrue(result)