def test_generateSignature(self): data = "Hello World!" for algorithm, hash_method in ( ("rsa-sha1", hashlib.sha1,), ("rsa-sha256", hashlib.sha256,), ): stream = MemoryStream(data) headers = Headers() headers.addRawHeader("Originator", "mailto:[email protected]") headers.addRawHeader("Recipient", "mailto:[email protected]") headers.setHeader("Content-Type", MimeType("text", "calendar", **{"component": "VEVENT", "charset": "utf-8"})) request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", self.private_keyfile, algorithm, ("Originator", "Recipient", "Content-Type",), True, True, True, 3600) # Manually create what should be the correct thing to sign bodyhash = base64.b64encode(hash_method(data).digest()) sign_this = """originator:mailto:[email protected] recipient:mailto:[email protected] content-type:%s ischedule-version:1.0 dkim-signature:v=1; d=example.com; s=dkim; t=%s; x=%s; a=%s; q=dns/txt:http/well-known; c=ischedule-relaxed/simple; h=Originator:Recipient; bh=%s; b=""".replace("\n", "\r\n") % (headers.getRawHeaders("Content-Type")[0], str(int(time.time())), str(int(time.time() + 3600)), algorithm, bodyhash) result = request.generateSignature(sign_this) key = RSA.importKey(open(self.private_keyfile).read()) signature = DKIMUtils.sign(sign_this, key, DKIMUtils.hash_func(algorithm)) self.assertEqual(result, signature)
def test_sign(self): data = "Hello World!" for algorithm, hash_method in ( ("rsa-sha1", hashlib.sha1,), ("rsa-sha256", hashlib.sha256,), ): stream = MemoryStream(data) headers = Headers() headers.addRawHeader("Originator", "mailto:[email protected]") headers.addRawHeader("Recipient", "mailto:[email protected]") headers.setHeader("Content-Type", MimeType("text", "calendar", **{"component": "VEVENT", "charset": "utf-8"})) request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", self.private_keyfile, algorithm, ("Originator", "Recipient", "Content-Type",), True, True, True, 3600) result = (yield request.sign()) # Manually create what should be the correct thing to sign and make sure signatures match bodyhash = base64.b64encode(hash_method(DKIMUtils.canonicalizeBody(data)).digest()) sign_this = """originator:mailto:[email protected] recipient:mailto:[email protected] content-type:%s ischedule-version:1.0 ischedule-message-id:%s dkim-signature:v=1; d=example.com; s=dkim; t=%s; x=%s; a=%s; q=private-exchange:http/well-known:dns/txt; c=ischedule-relaxed/simple; h=Originator:Recipient:Content-Type:iSchedule-Version:iSchedule-Message-ID; bh=%s; b=""".replace("\n", "\r\n") % (headers.getRawHeaders("Content-Type")[0], request.message_id, request.time, request.expire, algorithm, bodyhash) key = RSA.importKey(open(self.private_keyfile).read()) signature = DKIMUtils.sign(sign_this, key, DKIMUtils.hash_func(algorithm)) self.assertEqual(result, signature) # Make sure header is updated in the request updated_header = "v=1; d=example.com; s=dkim; t=%s; x=%s; a=%s; q=private-exchange:http/well-known:dns/txt; c=ischedule-relaxed/simple; h=Originator:Recipient:Content-Type:iSchedule-Version:iSchedule-Message-ID; bh=%s; b=%s" % (request.time, request.expire, algorithm, bodyhash, signature,) self.assertEqual(request.headers.getRawHeaders("DKIM-Signature")[0], updated_header) # Try to verify result using public key pubkey = RSA.importKey(open(self.public_keyfile).read()) self.assertEqual(DKIMUtils.verify(sign_this, result, pubkey, DKIMUtils.hash_func(algorithm)), None)
def test_cached_key(self): # Create cache entry dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = TestPublicKeyLookup.PublicKeyLookup_Testing( DKIMUtils.extractTags(dkim)) lookup.flushCache() lookup.keys = [ DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data, )) ] pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is not None) # Cache valid dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = TestPublicKeyLookup.PublicKeyLookup_Testing( DKIMUtils.extractTags(dkim)) lookup.keys = [] pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is not None) # Cache invalid dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = TestPublicKeyLookup.PublicKeyLookup_Testing( DKIMUtils.extractTags(dkim)) lookup.flushCache() lookup.keys = [] pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is None)
def test_locate_public_key(self): """ L{DKIMVerifier.locatePublicKey} correctly finds key matching headers. """ data = ( # Valid ( """Host:example.com Content-Type: text/calendar ; charset = "utf-8" Originator: mailto:[email protected] Recipient: mailto:[email protected] ,\t mailto:[email protected]\t\t DKIM-Signature: v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def Cache-Control:no-cache Connection:close """, [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))], True, ), # Invalid - no method ( """Host:example.com Content-Type: text/calendar ; charset = "utf-8" Originator: mailto:[email protected] Recipient: mailto:[email protected] ,\t mailto:[email protected]\t\t DKIM-Signature: v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt\t\t; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def Cache-Control:no-cache Connection:close """, [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))], False, ), # Invalid - wrong algorithm ( """Host:example.com Content-Type: text/calendar ; charset = "utf-8" Originator: mailto:[email protected] Recipient: mailto:[email protected] ,\t mailto:[email protected]\t\t DKIM-Signature: v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def Cache-Control:no-cache Connection:close """, [DKIMUtils.extractTags("v=DKIM1; h=sha-1; p=%s" % (self.public_key_data,))], False, ), ) for hdrs, keys, result in data: headers = [hdr.split(":", 1) for hdr in hdrs.splitlines()] TestPublicKeyLookup.PublicKeyLookup_Testing.keys = keys TestPublicKeyLookup.PublicKeyLookup_Testing.flushCache() verifier = DKIMVerifier(self._makeHeaders(headers), "", key_lookup=(TestPublicKeyLookup.PublicKeyLookup_Testing,)) verifier.processDKIMHeader() pkey = (yield verifier.locatePublicKey()) if result: self.assertTrue(pkey is not None) else: self.assertTrue(pkey is None)
def test_canonicalize_body(self): """ L{DKIMUtils.canonicalizeBody} correctly canonicalizes bodies. """ data = ( ( """Simple""", """Simple\n""", ), ( """Simple\n""", """Simple\n""", ), ( """Simple\n\n""", """Simple\n""", ), ) for text, result in data: self.assertEqual( DKIMUtils.canonicalizeBody(text.replace("\n", "\r\n")), result.replace("\n", "\r\n"), )
def test_body_hash(self): data = "Hello World!" for algorithm, hash_method in ( ( "rsa-sha1", hashlib.sha1, ), ( "rsa-sha256", hashlib.sha256, ), ): stream = str(data) headers = Headers() headers.addRawHeader("Originator", "mailto:[email protected]") headers.addRawHeader("Recipient", "mailto:[email protected]") headers.setHeader( "Content-Type", MimeType("text", "calendar", **{ "component": "VEVENT", "charset": "utf-8" })) request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", "/tmp/key", algorithm, ( "Originator", "Recipient", "Content-Type", ), True, True, True, 3600) hash = base64.b64encode( hash_method(DKIMUtils.canonicalizeBody(data)).digest()) result = (yield request.bodyHash()) self.assertEqual(result, hash)
def test_HTTP_URI_key(self): # Need to setup a fake resolver module = getModule(__name__) dataPath = module.filePath.sibling("data") bindPath = dataPath.child("db.example.com") self.patch(config.Scheduling.iSchedule, "DNSDebug", bindPath.path) utils.DebugResolver = None utils._initResolver() for d, s, result in ( ("example.com", "_ischedule", "https://key.example.com:8443/.well-known/domainkey/example.com/_ischedule" ), ("www.example.com", "_ischedule", "http://key.example.com/.well-known/domainkey/www.example.com/_ischedule" ), ("example.org", "_ischedule", "https://example.org/.well-known/domainkey/example.org/_ischedule" ), ): dkim = "v=1; d=%s; s = %s; t = 1234; a=rsa-sha1; q=http/well-known ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" % ( d, s, ) tester = PublicKeyLookup_HTTP_WellKnown( DKIMUtils.extractTags(dkim)) uri = (yield tester._getURI()) self.assertEqual(uri, result)
def test_canonicalize_header(self): """ L{DKIMVerifier.canonicalizeHeader} correctly canonicalizes headers. """ data = ( ("Content-Type", " text/calendar ; charset = \"utf-8\" ", "content-type:text/calendar ; charset = \"utf-8\"\r\n"), ("Originator", " mailto:[email protected] ", "originator:mailto:[email protected]\r\n"), ("Recipient", " mailto:[email protected] ,\t mailto:[email protected]\t\t ", "recipient:mailto:[email protected],mailto:[email protected]\r\n"), ("iSchedule-Version", " 1.0 ", "ischedule-version:1.0\r\n"), ( "DKIM-Signature", " v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; c=ischedule-relaxed/simple; h=Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=a b c; b=d ef", "dkim-signature:v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; c=ischedule-relaxed/simple; h=Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=a b c; b=", ), ( "DKIM-Signature", " v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; b= def ; c=ischedule-relaxed/simple; h=Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=a\t bc", "dkim-signature:v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; b= ; c=ischedule-relaxed/simple; h=Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=a bc", ), ) for name, value, result in data: verifier = DKIMVerifier(self._makeHeaders(((name, value,),)), "") if name == "DKIM-Signature": verifier.processDKIMHeader() canonicalized = DKIMUtils.canonicalizeHeader(name, value, verifier.dkim_tags if name == "DKIM-Signature" else None) self.assertEqual(canonicalized, result)
def test_private_exchange(self): keydir = self.mktemp() PublicKeyLookup_PrivateExchange.directory = keydir os.mkdir(keydir) keyfile = os.path.join(keydir, "example.com#dkim") with open(keyfile, "w") as f: f.write("""v=DKIM1; p=%s """ % (self.public_key_data, )) dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = PublicKeyLookup_PrivateExchange(DKIMUtils.extractTags(dkim)) pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is not None) dkim = "v=1; d=example.com; s = dkim2; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = PublicKeyLookup_PrivateExchange(DKIMUtils.extractTags(dkim)) lookup.flushCache() pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is None) with open(keyfile, "w") as f: f.write("""v=DKIM1; s=email; p=%s """ % (self.public_key_data, )) dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = PublicKeyLookup_PrivateExchange(DKIMUtils.extractTags(dkim)) lookup.flushCache() pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is None) with open(keyfile, "w") as f: f.write("""v=DKIM1; s=email; p=%s v=DKIM1; s=ischedule; p=%s """ % ( self.public_key_data, self.public_key_data, )) dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = PublicKeyLookup_PrivateExchange(DKIMUtils.extractTags(dkim)) lookup.flushCache() pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is not None)
def test_private_exchange(self): keydir = self.mktemp() PublicKeyLookup_PrivateExchange.directory = keydir os.mkdir(keydir) keyfile = os.path.join(keydir, "example.com#dkim") with open(keyfile, "w") as f: f.write("""v=DKIM1; p=%s """ % (self.public_key_data,)) dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = PublicKeyLookup_PrivateExchange(DKIMUtils.extractTags(dkim)) pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is not None) dkim = "v=1; d=example.com; s = dkim2; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = PublicKeyLookup_PrivateExchange(DKIMUtils.extractTags(dkim)) lookup.flushCache() pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is None) with open(keyfile, "w") as f: f.write("""v=DKIM1; s=email; p=%s """ % (self.public_key_data,)) dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = PublicKeyLookup_PrivateExchange(DKIMUtils.extractTags(dkim)) lookup.flushCache() pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is None) with open(keyfile, "w") as f: f.write("""v=DKIM1; s=email; p=%s v=DKIM1; s=ischedule; p=%s """ % (self.public_key_data, self.public_key_data,)) dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = PublicKeyLookup_PrivateExchange(DKIMUtils.extractTags(dkim)) lookup.flushCache() pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is not None)
def test_selector_key(self): for lookup, d, result in ( (PublicKeyLookup_DNSTXT, "example.com", "dkim._domainkey.example.com"), (PublicKeyLookup_DNSTXT, "calendar.example.com", "dkim._domainkey.calendar.example.com"), (PublicKeyLookup_HTTP_WellKnown, "example.com", "https://example.com/.well-known/domainkey/example.com/dkim"), (PublicKeyLookup_HTTP_WellKnown, "calendar.example.com", "https://example.com/.well-known/domainkey/calendar.example.com/dkim"), (PublicKeyLookup_PrivateExchange, "example.com", "example.com#dkim"), (PublicKeyLookup_PrivateExchange, "calendar.example.com", "calendar.example.com#dkim"), ): dkim = "v=1; d=%s; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" % (d,) tester = lookup(DKIMUtils.extractTags(dkim)) self.assertEqual(tester._getSelectorKey(), result)
def test_cached_key(self): # Create cache entry dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim)) lookup.flushCache() lookup.keys = [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))] pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is not None) # Cache valid dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim)) lookup.keys = [] pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is not None) # Cache invalid dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim)) lookup.flushCache() lookup.keys = [] pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is None)
def test_body_hash(self): data = "Hello World!" for algorithm, hash_method in ( ("rsa-sha1", hashlib.sha1,), ("rsa-sha256", hashlib.sha256,), ): stream = str(data) headers = Headers() headers.addRawHeader("Originator", "mailto:[email protected]") headers.addRawHeader("Recipient", "mailto:[email protected]") headers.setHeader("Content-Type", MimeType("text", "calendar", **{"component": "VEVENT", "charset": "utf-8"})) request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", "/tmp/key", algorithm, ("Originator", "Recipient", "Content-Type",), True, True, True, 3600) hash = base64.b64encode(hash_method(DKIMUtils.canonicalizeBody(data)).digest()) result = (yield request.bodyHash()) self.assertEqual(result, hash)
def test_HTTP_URI_key(self): # Need to setup a fake resolver module = getModule(__name__) dataPath = module.filePath.sibling("data") bindPath = dataPath.child("db.example.com") self.patch(config.Scheduling.iSchedule, "DNSDebug", bindPath.path) utils.DebugResolver = None utils._initResolver() for d, s, result in ( ("example.com", "_ischedule", "https://key.example.com:8443/.well-known/domainkey/example.com/_ischedule"), ("www.example.com", "_ischedule", "http://key.example.com/.well-known/domainkey/www.example.com/_ischedule"), ("example.org", "_ischedule", "https://example.org/.well-known/domainkey/example.org/_ischedule"), ): dkim = "v=1; d=%s; s = %s; t = 1234; a=rsa-sha1; q=http/well-known ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" % (d, s,) tester = PublicKeyLookup_HTTP_WellKnown(DKIMUtils.extractTags(dkim)) uri = (yield tester._getURI()) self.assertEqual(uri, result)
def test_TXT_key(self): # Need to setup a fake resolver module = getModule(__name__) dataPath = module.filePath.sibling("data") bindPath = dataPath.child("db.example.com") self.patch(config.Scheduling.iSchedule, "DNSDebug", bindPath.path) utils.DebugResolver = None utils._initResolver() for d, s, result in ( ("example.com", "_ischedule", True), ("example.com", "_revoked", False), ("example.com", "dkim", False), ("calendar.example.com", "_ischedule", False), ("example.org", "_ischedule", False), ): dkim = "v=1; d=%s; s = %s; t = 1234; a=rsa-sha1; q=dns/txt ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" % (d, s,) tester = PublicKeyLookup_DNSTXT(DKIMUtils.extractTags(dkim)) pkey = yield tester.getPublicKey(False) self.assertEqual(pkey is not None, result)
root.putChild(config.Servers.ConduitName, conduit) # # iSchedule service (not used for podding) # if config.Scheduling.iSchedule.Enabled: log.info("Setting up iSchedule inbox resource: {cls}", cls=iScheduleResourceClass) ischedule = iScheduleResourceClass( root, newStore, ) root.putChild("ischedule", ischedule) # Do DomainKey resources DKIMUtils.validConfiguration(config) if config.Scheduling.iSchedule.DKIM.Enabled: log.info("Setting up domainkey resource: {res}", res=DomainKeyResource) domain = config.Scheduling.iSchedule.DKIM.Domain if config.Scheduling.iSchedule.DKIM.Domain else config.ServerHostName dk = DomainKeyResource( domain, config.Scheduling.iSchedule.DKIM.KeySelector, config.Scheduling.iSchedule.DKIM.PublicKeyFile, ) wellKnownResource.putChild("domainkey", dk) # # WebCal # if config.WebCalendarRoot: log.info("Setting up WebCalendar resource: {res}",
root.putChild(config.Servers.InboxName, ischedule) # # iSchedule service (not used for podding) # if config.Scheduling.iSchedule.Enabled: log.info("Setting up iSchedule inbox resource: {cls}", cls=iScheduleResourceClass) ischedule = iScheduleResourceClass( root, newStore, ) root.putChild("ischedule", ischedule) # Do DomainKey resources DKIMUtils.validConfiguration(config) if config.Scheduling.iSchedule.DKIM.Enabled: log.info("Setting up domainkey resource: {res}", res=DomainKeyResource) domain = config.Scheduling.iSchedule.DKIM.Domain if config.Scheduling.iSchedule.DKIM.Domain else config.ServerHostName dk = DomainKeyResource( domain, config.Scheduling.iSchedule.DKIM.KeySelector, config.Scheduling.iSchedule.DKIM.PublicKeyFile, ) wellKnownResource.putChild("domainkey", dk) # # WebCal # if config.WebCalendarRoot: log.info("Setting up WebCalendar resource: {res}",
def test_get_key(self): # Valid dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim)) lookup.flushCache() lookup.keys = [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))] pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is not None) # Valid with more tags dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim)) lookup.flushCache() lookup.keys = [DKIMUtils.extractTags("v=DKIM1; k = rsa ; h= sha1 : sha256 ; s=ischedule ; p=%s" % (self.public_key_data,))] pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is not None) # Invalid - key type dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim)) lookup.flushCache() lookup.keys = [DKIMUtils.extractTags("v=DKIM1; k=dsa ; p=%s" % (self.public_key_data,))] pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is None) # Invalid - hash dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim)) lookup.flushCache() lookup.keys = [DKIMUtils.extractTags("v=DKIM1; k=rsa ; h=sha512 ; p=%s" % (self.public_key_data,))] pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is None) # Invalid - service dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim)) lookup.flushCache() lookup.keys = [DKIMUtils.extractTags("v=DKIM1; k=rsa ; s=email ; p=%s" % (self.public_key_data,))] pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is None) # Invalid - revoked dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim)) lookup.flushCache() lookup.keys = [DKIMUtils.extractTags("v=DKIM1; k=rsa ; s=email ; p=")] pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is None) # Multiple valid dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim)) lookup.flushCache() lookup.keys = [ DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,)), DKIMUtils.extractTags("v=DKIM1; k = rsa ; h= sha1 : sha256 ; s=ischedule ; p=%s" % (self.public_key_data,)), DKIMUtils.extractTags("v=DKIM1; k = rsa ; h= sha1 : sha256 ; s=* ; p=%s" % (self.public_key_data,)), ] pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is not None) # Multiple - some valid, some invalid dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim)) lookup.flushCache() lookup.keys = [ DKIMUtils.extractTags("v=DKIM1; k=rsa ; s=email ; p="), DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,)), DKIMUtils.extractTags("v=DKIM1; k = rsa ; h= sha1 : sha256 ; s=ischedule ; p=%s" % (self.public_key_data,)), DKIMUtils.extractTags("v=DKIM1; k = rsa ; h= sha1 : sha256 ; s=* ; p=%s" % (self.public_key_data,)), ] pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is not None) # Multiple - invalid dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim)) lookup.flushCache() lookup.keys = [ DKIMUtils.extractTags("v=DKIM1; k=rsa ; s=email ; p="), DKIMUtils.extractTags("v=DKIM1; k=rsa ; s=email ; p="), ] pubkey = (yield lookup.getPublicKey()) self.assertTrue(pubkey is None)
class TestDKIMVerifier (TestDKIMBase): """ L{DKIMVerifier} support tests. """ class StubRequest(object): def __init__(self, method, uri, headers, body): self.method = method self.uri = uri self.headers = Headers() for name, value in headers: self.headers.addRawHeader(name, value) self.stream = MemoryStream(body) def _makeHeaders(self, headers_pairs): headers = Headers() for name, value in headers_pairs: headers.addRawHeader(name, value) return headers def test_valid_dkim_headers(self): """ L{DKIMVerifier.processDKIMHeader} correctly validates DKIM-Signature headers. """ data = ( # Bogus ((("DKIM-Signature", "v=1"),), False,), # More than one (( ("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; a=rsa-sha1; q=dns/txt:http/well-known; c=ischedule-relaxed/simple; h=Originator:Recipient; bh=abc; b=def"), ("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; a=rsa-sha256; q=dns/txt:http/well-known; c=ischedule-relaxed/simple; h=Originator:Recipient; bh=abc; b=def"), ), False,), # Valid ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; a=rsa-sha1; q=dns/txt:http/well-known; c=ischedule-relaxed/simple; h=Originator:Recipient; bh=abc; b=def"),), True,), ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; a=rsa-sha256; q=dns/txt; c=ischedule-relaxed; h=Originator:Recipient; bh=abc; b=def"),), True,), ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; x=%d; a=rsa-sha256; q=dns/txt; c=ischedule-relaxed; h=Originator:Recipient; bh=abc; b=def" % (int(time.time() + 30),)),), True,), # Invalid ((("DKIM-Signature", "v=2; d=example.com; s=dkim; t=1234; a=rsa-sha1; q=dns/txt:http/well-known; c=ischedule-relaxed/simple; h=Originator:Recipient; bh=abc; b=def"),), False,), ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; a=rsa-sha512; q=dns/txt:http/well-known; c=ischedule-relaxed/simple; h=Originator:Recipient; bh=abc; b=def"),), False,), ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; a=rsa-sha1; q=dns/txt:http/well-known; c=ischedule-relaxed/relaxed; h=Originator:Recipient; bh=abc; b=def"),), False,), ((("DKIM-Signature", "v=1; d=example.com; t=1234; a=rsa-sha1; q=dns/txt:http/well-known; c=ischedule-relaxed/simple; h=Originator:Recipient; bh=abc; b=def"),), False,), ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; x=%d; a=rsa-sha256; q=dns/txt; c=ischedule-relaxed; h=Originator:Recipient; bh=abc; b=def" % (int(time.time() - 30),)),), False,), ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; x=%d; a=rsa-sha256; q=dns/txt; c=ischedule-relaxed; h=Originator:Recipient; bh=abc; b=def" % (int(time.time() - 30),)),), False,), ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; x=%d; a=rsa-sha256; q=dns/txt; c=ischedule-relaxed; h=Originator:Recipient; bh=abc; b=def" % (int(time.time() - 30),)),), False,), ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; x=%d; a=rsa-sha256; q=dns/txt; c=ischedule-relaxed; h=Originator:Recipient; bh=abc; b=def" % (int(time.time() - 30),)),), False,), ) for headers, result in data: verifier = DKIMVerifier(self._makeHeaders(headers), "") if result: verifier.processDKIMHeader() else: self.assertRaises(DKIMVerificationError, verifier.processDKIMHeader) def test_canonicalize_header(self): """ L{DKIMVerifier.canonicalizeHeader} correctly canonicalizes headers. """ data = ( ("Content-Type", " text/calendar ; charset = \"utf-8\" ", "content-type:text/calendar ; charset = \"utf-8\"\r\n"), ("Originator", " mailto:[email protected] ", "originator:mailto:[email protected]\r\n"), ("Recipient", " mailto:[email protected] ,\t mailto:[email protected]\t\t ", "recipient:mailto:[email protected],mailto:[email protected]\r\n"), ("iSchedule-Version", " 1.0 ", "ischedule-version:1.0\r\n"), ( "DKIM-Signature", " v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; c=ischedule-relaxed/simple; h=Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=a b c; b=d ef", "dkim-signature:v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; c=ischedule-relaxed/simple; h=Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=a b c; b=", ), ( "DKIM-Signature", " v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; b= def ; c=ischedule-relaxed/simple; h=Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=a\t bc", "dkim-signature:v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; b= ; c=ischedule-relaxed/simple; h=Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=a bc", ), ) for name, value, result in data: verifier = DKIMVerifier(self._makeHeaders(((name, value,),)), "") if name == "DKIM-Signature": verifier.processDKIMHeader() canonicalized = DKIMUtils.canonicalizeHeader(name, value, verifier.dkim_tags if name == "DKIM-Signature" else None) self.assertEqual(canonicalized, result) def test_extract_headers(self): """ L{DKIMVerifier.extractSignedHeaders} correctly extracts canonicalizes headers. """ data = ( # Count on Recipient ("""Host:example.com Content-Type: text/calendar ; charset = "utf-8" Originator: mailto:[email protected] Recipient: mailto:[email protected] ,\t mailto:[email protected]\t\t iSchedule-Version: 1.0 DKIM-Signature: v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def Cache-Control:no-cache Connection:close """, """content-type:text/calendar ; charset = "utf-8" originator:mailto:[email protected] recipient:mailto:[email protected],mailto:[email protected] ischedule-version:1.0 dkim-signature:v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=""" ), # Exact count on Recipient ("""Host:example.com Content-Type: text/calendar ; charset = "utf-8" Originator: mailto:[email protected] Recipient: mailto:[email protected] ,\t mailto:[email protected]\t\t Recipient:\t\t mailto:[email protected] iSchedule-Version: 1.0 DKIM-Signature: v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def Cache-Control:no-cache Connection:close """, """content-type:text/calendar ; charset = "utf-8" originator:mailto:[email protected] recipient:mailto:[email protected],mailto:[email protected],mailto:[email protected] ischedule-version:1.0 dkim-signature:v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=""" ), # Re-ordered Content-Type ("""Host:example.com iSchedule-Version: 1.0 Originator: mailto:[email protected] Recipient: mailto:[email protected] ,\t mailto:[email protected]\t\t DKIM-Signature: v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def Content-Type: text/calendar ; charset = "utf-8" Cache-Control:no-cache Connection:close """, """content-type:text/calendar ; charset = "utf-8" originator:mailto:[email protected] recipient:mailto:[email protected],mailto:[email protected] ischedule-version:1.0 dkim-signature:v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=""" ), ) for hdrs, result in data: headers = [hdr.split(":", 1) for hdr in hdrs.splitlines()] verifier = DKIMVerifier(self._makeHeaders(headers), "") verifier.processDKIMHeader() extracted = verifier.extractSignedHeaders() self.assertEqual(extracted, result.replace("\n", "\r\n")) def test_locate_public_key(self): """ L{DKIMVerifier.locatePublicKey} correctly finds key matching headers. """ data = ( # Valid ("""Host:example.com Content-Type: text/calendar ; charset = "utf-8" Originator: mailto:[email protected] Recipient: mailto:[email protected] ,\t mailto:[email protected]\t\t DKIM-Signature: v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def Cache-Control:no-cache Connection:close """, [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))], True, ), # Invalid - no method ("""Host:example.com Content-Type: text/calendar ; charset = "utf-8" Originator: mailto:[email protected] Recipient: mailto:[email protected] ,\t mailto:[email protected]\t\t DKIM-Signature: v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt\t\t; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def Cache-Control:no-cache Connection:close """, [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))], False, ), # Invalid - wrong algorithm ("""Host:example.com Content-Type: text/calendar ; charset = "utf-8" Originator: mailto:[email protected] Recipient: mailto:[email protected] ,\t mailto:[email protected]\t\t DKIM-Signature: v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def Cache-Control:no-cache Connection:close """, [DKIMUtils.extractTags("v=DKIM1; h=sha-1; p=%s" % (self.public_key_data,))], False, ), ) for hdrs, keys, result in data: headers = [hdr.split(":", 1) for hdr in hdrs.splitlines()] TestPublicKeyLookup.PublicKeyLookup_Testing.keys = keys verifier = DKIMVerifier(self._makeHeaders(headers), "", key_lookup=(TestPublicKeyLookup.PublicKeyLookup_Testing,)) verifier.processDKIMHeader() pkey = (yield verifier.locatePublicKey()) if result: self.assertNotEqual(pkey, None) else: self.assertEqual(pkey, None) @inlineCallbacks def test_verify(self): """ L{DKIMVerifier.verify} correctly finds key matching headers. """ @inlineCallbacks def _verify(hdrs, body, keys, result, sign_headers=("Originator", "Recipient", "Content-Type",), manipulate_request=None): for algorithm in ("rsa-sha1", "rsa-sha256",): # Create signature stream = MemoryStream(body) headers = Headers() for name, value in [hdr.split(":", 1) for hdr in hdrs.splitlines()]: headers.addRawHeader(name, value) request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", self.private_keyfile, algorithm, sign_headers, True, True, True, 3600) yield request.sign() # Possibly munge the request after the signature is done if manipulate_request is not None: manipulate_request(request) # Verify signature TestPublicKeyLookup.PublicKeyLookup_Testing.keys = keys data = (yield allDataFromStream(request.stream)) verifier = DKIMVerifier(request.headers, data, key_lookup=(TestPublicKeyLookup.PublicKeyLookup_Testing,)) TestPublicKeyLookup.PublicKeyLookup_Testing({}).flushCache() try: yield verifier.verify() except Exception, e: if result: self.fail("DKIMVerifier:verify failed: %s" % (e,)) else: if not result: self.fail("DKIMVerifier:verify did not fail") # Valid yield _verify( """Host:example.com Content-Type: text/calendar ; charset = "utf-8" Originator: mailto:[email protected] Recipient: mailto:[email protected] ,\t mailto:[email protected]\t\t Cache-Control:no-cache Connection:close """, """BEGIN:DATA END:DATA """, [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))], True, ) # Invalid - key revoked yield _verify( """Host:example.com Content-Type: text/calendar ; charset = "utf-8" Originator: mailto:[email protected] Recipient: mailto:[email protected] ,\t mailto:[email protected]\t\t Cache-Control:no-cache Connection:close """, """BEGIN:DATA END:DATA """, [DKIMUtils.extractTags("v=DKIM1; p=")], False, ) # Invalid - missing header yield _verify( """Host:example.com Content-Type: text/calendar ; charset = "utf-8" Originator: mailto:[email protected] Recipient: mailto:[email protected] ,\t mailto:[email protected]\t\t Cache-Control:no-cache Connection:close """, """BEGIN:DATA END:DATA """, [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))], False, manipulate_request=lambda request: request.headers.removeHeader("Originator") ) # Invalid - changed header yield _verify( """Host:example.com Content-Type: text/calendar ; charset = "utf-8" Originator: mailto:[email protected] Recipient: mailto:[email protected] ,\t mailto:[email protected]\t\t Cache-Control:no-cache Connection:close """, """BEGIN:DATA END:DATA """, [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))], False, manipulate_request=lambda request: request.headers.setRawHeaders("Originator", ("mailto:[email protected]",)) ) # Invalid - changed body yield _verify( """Host:example.com Content-Type: text/calendar ; charset = "utf-8" Originator: mailto:[email protected] Recipient: mailto:[email protected] ,\t mailto:[email protected]\t\t Cache-Control:no-cache Connection:close """, """BEGIN:DATA END:DATA """, [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))], False, manipulate_request=lambda request: setattr(request, "stream", MemoryStream("BEGIN:DATA\n")), ) # Invalid - extra header yield _verify( """Host:example.com Content-Type: text/calendar ; charset = "utf-8" Originator: mailto:[email protected] Recipient: mailto:[email protected] ,\t mailto:[email protected]\t\t Cache-Control:no-cache Connection:close """, """BEGIN:DATA END:DATA """, [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))], False, manipulate_request=lambda request: request.headers.getRawHeaders("Recipient").insert(0, "mailto:[email protected]"), ) # Valid - header yield _verify( """Host:example.com Content-Type: text/calendar ; charset = "utf-8" Originator: mailto:[email protected] Recipient: mailto:[email protected] ,\t mailto:[email protected]\t\t Cache-Control:no-cache Connection:close """, """BEGIN:DATA END:DATA """, [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))], True, sign_headers=("Originator", "Recipient", "Content-Type",), ) # Invalid - over sign header extra header yield _verify( """Host:example.com Content-Type: text/calendar ; charset = "utf-8" Originator: mailto:[email protected] Recipient: mailto:[email protected] ,\t mailto:[email protected]\t\t Cache-Control:no-cache Connection:close """, """BEGIN:DATA END:DATA """, [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))], False, sign_headers=("Originator", "Recipient", "Content-Type",), manipulate_request=lambda request: request.headers.addRawHeader("Recipient", ("mailto:[email protected]",)) )