[CalendarServer-changes] [9796] CalendarServer/branches/users/cdaboo/ischedule-dkim
source_changes at macosforge.org
source_changes at macosforge.org
Mon Sep 10 12:41:02 PDT 2012
Revision: 9796
http://trac.macosforge.org/projects/calendarserver/changeset/9796
Author: cdaboo at apple.com
Date: 2012-09-10 12:40:59 -0700 (Mon, 10 Sep 2012)
Log Message:
-----------
Latest DKIM checkpoint: spec updates, private exchange key lookup, well-known resource lookup, well-known resource creation.
Modified Paths:
--------------
CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/util.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/dkim.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_dkim.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/simpleresource.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/stdconfig.py
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/util.py 2012-09-09 03:10:25 UTC (rev 9795)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/util.py 2012-09-10 19:40:59 UTC (rev 9796)
@@ -58,6 +58,7 @@
from calendarserver.push.applepush import APNSubscriptionResource
from twistedcaldav.directorybackedaddressbook import DirectoryBackedAddressBookResource
from twistedcaldav.resource import AuthenticationWrapper
+from twistedcaldav.scheduling.ischedule.dkim import DKIMUtils, DomainKeyResource
from twistedcaldav.scheduling.ischedule.resource import IScheduleInboxResource
from twistedcaldav.simpleresource import SimpleResource, SimpleRedirectResource
from twistedcaldav.timezones import TimezoneCache
@@ -585,7 +586,9 @@
else:
addSystemEventTrigger("after", "startup", timezoneStdService.onStartup)
- # iSchedule service is optional
+ #
+ # iSchedule service
+ #
if config.Scheduling.iSchedule.Enabled:
log.info("Setting up iSchedule inbox resource: %r"
% (iScheduleResourceClass,))
@@ -595,6 +598,17 @@
newStore,
)
root.putChild("ischedule", ischedule)
+
+ # Do DomainKey resources
+ DKIMUtils.validConfiguration(config)
+ if config.Scheduling.iSchedule.DKIM.Enabled:
+ 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
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/dkim.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/dkim.py 2012-09-09 03:10:25 UTC (rev 9795)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/dkim.py 2012-09-10 19:40:59 UTC (rev 9796)
@@ -16,13 +16,21 @@
from twext.python.log import Logger
from twext.web2.client.http import ClientRequest
-from twext.web2.dav.util import allDataFromStream
+from twext.web2.dav.util import allDataFromStream, joinURL
+from twext.web2.http import Response
+from twext.web2.http_headers import MimeType
from twext.web2.stream import MemoryStream
-from twisted.internet.defer import inlineCallbacks, returnValue
+
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twistedcaldav.client.geturl import getURL
+from twistedcaldav.config import ConfigurationError
+from twistedcaldav.simpleresource import SimpleResource, SimpleDataResource
+
import base64
import binascii
import collections
import hashlib
+import os
import rsa
import textwrap
import time
@@ -39,6 +47,7 @@
RSA256 = "rsa-sha256"
Q_DNS = "dns/txt"
Q_HTTP = "http/well-known"
+Q_PRIVATE = "private-exchange"
KEY_SERVICE_TYPE = "ischedule"
@@ -54,6 +63,70 @@
"""
@staticmethod
+ def validConfiguration(config):
+ if config.Scheduling.iSchedule.DKIM.Enabled:
+
+ if not config.Scheduling.iSchedule.DKIM.Domain and not config.ServerHostName:
+ msg = "DKIM: No domain specified"
+ log.error(msg)
+ raise ConfigurationError(msg)
+
+ if not config.Scheduling.iSchedule.DKIM.KeySelector:
+ msg = "DKIM: No selector specified"
+ log.error(msg)
+ raise ConfigurationError(msg)
+
+ if config.Scheduling.iSchedule.DKIM.SignatureAlgorithm not in (RSA1, RSA256):
+ msg = "DKIM: Invalid algorithm: %s" % (config.Scheduling.iSchedule.SignatureAlgorithm,)
+ log.error(msg)
+ raise ConfigurationError(msg)
+
+ try:
+ with open(config.Scheduling.iSchedule.DKIM.PrivateKeyFile) as f:
+ key_data = f.read()
+ except IOError, e:
+ msg = "DKIM: Cannot read private key file: %s %s" % (config.Scheduling.iSchedule.DKIM.PrivateKeyFile, e,)
+ log.error(msg)
+ raise ConfigurationError(msg)
+ try:
+ rsa.PrivateKey.load_pkcs1(key_data)
+ except:
+ msg = "DKIM: Invalid private key file: %s" % (config.Scheduling.iSchedule.DKIM.PrivateKeyFile,)
+ log.error(msg)
+ raise ConfigurationError(msg)
+
+ try:
+ with open(config.Scheduling.iSchedule.DKIM.PublicKeyFile) as f:
+ key_data = f.read()
+ except IOError, e:
+ msg = "DKIM: Cannot read public key file: %s %s" % (config.Scheduling.iSchedule.DKIM.PublicKeyFile, e,)
+ log.error(msg)
+ raise ConfigurationError(msg)
+ try:
+ rsa.PublicKey.load_pkcs1(key_data)
+ except:
+ msg = "DKIM: Invalid public key file: %s" % (config.Scheduling.iSchedule.DKIM.PublicKeyFile,)
+ log.error(msg)
+ raise ConfigurationError(msg)
+
+ if config.Scheduling.iSchedule.DKIM.PrivateExchanges:
+ if not os.path.exists(config.Scheduling.iSchedule.DKIM.PrivateExchanges):
+ try:
+ os.makedirs(config.Scheduling.iSchedule.DKIM.PrivateExchanges)
+ except IOError, e:
+ msg = "DKIM: Cannot create public key private exchange directory: %s" % (config.Scheduling.iSchedule.DKIM.PrivateExchanges,)
+ log.error(msg)
+ raise ConfigurationError(msg)
+ if not os.path.isdir(config.Scheduling.iSchedule.DKIM.PrivateExchanges):
+ msg = "DKIM: Invalid public key private exchange directory: %s" % (config.Scheduling.iSchedule.DKIM.PrivateExchanges,)
+ log.error(msg)
+ raise ConfigurationError(msg)
+
+ log.info("DKIM: Enabled")
+ else:
+ log.info("DKIM: Disabled")
+
+ @staticmethod
def hashlib_method(algorithm):
"""
Return hashlib function for DKIM algorithm.
@@ -83,8 +156,11 @@
splits = [item.strip() for item in data.split(";")]
dkim_tags = {}
for item in splits:
- name, value = item.split("=", 1)
- dkim_tags[name.strip()] = value.strip()
+ try:
+ name, value = item.split("=", 1)
+ dkim_tags[name.strip()] = value.strip()
+ except ValueError:
+ pass
return dkim_tags
@staticmethod
@@ -139,6 +215,7 @@
sign_headers,
useDNSKey,
useHTTPKey,
+ usePrivateExchangeKey,
expire,
):
"""
@@ -166,6 +243,8 @@
@type useDNSKey: C{bool}
@param useHTTPKey: whether or not to add HTTP .well-known as a key lookup option
@type useHTTPKey: C{bool}
+ @param usePrivateExchangeKey: whether or not to add private-exchange as a key lookup option
+ @type usePrivateExchangeKey: C{bool}
@param expire: number of seconds to expiration of signature
@type expire: C{int}
"""
@@ -181,7 +260,7 @@
assert self.domain
assert self.selector
assert self.algorithm in (RSA1, RSA256,)
- assert useDNSKey or useHTTPKey
+ assert useDNSKey or useHTTPKey or usePrivateExchangeKey
self.hash_method = DKIMUtils.hashlib_method(self.algorithm)
self.hash_name = DKIMUtils.hash_name(self.algorithm)
@@ -189,6 +268,7 @@
self.keyMethods = []
if useDNSKey: self.keyMethods.append(Q_DNS)
if useHTTPKey: self.keyMethods.append(Q_HTTP)
+ if usePrivateExchangeKey: self.keyMethods.append(Q_PRIVATE)
self.message_id = str(uuid.uuid4())
@@ -244,6 +324,10 @@
self.headers.addRawHeader(ISCHEDULE_MESSAGE_ID, self.message_id)
self.sign_headers += (ISCHEDULE_VERSION, ISCHEDULE_MESSAGE_ID,)
+ # Need Cache-Control
+ self.headers.setRawHeaders("Cache-Control", ("no-cache", "no-transform",))
+ self.sign_headers += ("Cache-Control",)
+
# Figure out all the existing headers to sign
headers = []
sign_headers = []
@@ -389,7 +473,7 @@
"v": ("1",),
"a": (RSA1, RSA256,),
"c": ("relaxed", "relaxed/simple",),
- "q": (Q_DNS, Q_HTTP,),
+ "q": (Q_DNS, Q_HTTP, Q_PRIVATE,),
}
for tag, values in check_values.items():
if tag not in required_tags and tag not in self.dkim_tags:
@@ -485,6 +569,7 @@
returnValue(None)
+
class PublicKeyLookup(object):
"""
Abstract base class for public key lookup methods.
@@ -623,12 +708,139 @@
Get a token used to uniquely identify the key being looked up. Token format will
depend on the lookup method.
"""
- return "https://%s/.well-known/domainkey/%s" % (self.dkim_tags["d"], self.dkim_tags["s"],)
+ host = ".".join(self.dkim_tags["d"].split(".")[-2:])
+ return "https://%s/.well-known/domainkey/%s/%s" % (host, self.dkim_tags["d"], self.dkim_tags["s"],)
+
+ @inlineCallbacks
def _lookupKeys(self):
"""
Do the key lookup using the actual lookup method.
"""
- raise NotImplementedError
+
+ response = (yield getURL(self._getSelectorKey()))
+ if response is None or response.code / 100 != 2:
+ log.debug("DKIM: Failed http/well-known lookup: %s %s" % (self._getSelectorKey(), response,))
+ returnValue(())
+
+ ct = response.headers.getRawHeaders("content-type", ("bogus/type",))[0]
+ ct = ct.split(";", 1)
+ ct = ct[0].strip()
+ if ct not in ("text/plain",):
+ log.debug("DKIM: Failed http/well-known lookup: wrong content-type returned %s %s" % (self._getSelectorKey(), ct,))
+ returnValue(())
+
+ returnValue(tuple([DKIMUtils.extractTags(line) for line in response.data.splitlines()]))
+
+
+class PublicKeyLookup_PrivateExchange(PublicKeyLookup):
+
+
+ method = Q_PRIVATE
+ directory = None
+
+
+ def _getSelectorKey(self):
+ """
+ Get a token used to uniquely identify the key being looked up. Token format will
+ depend on the lookup method.
+ """
+ return "%s#%s" % (self.dkim_tags["d"], self.dkim_tags["s"],)
+
+
+ def _lookupKeys(self):
+ """
+ Key information is stored in a file, one record per line.
+ """
+
+ # Check validity of paths
+ if PublicKeyLookup_PrivateExchange.directory is None:
+ log.debug("DKIM: Failed private-exchange lookup: no directory configured")
+ return succeed(())
+ keyfile = os.path.join(PublicKeyLookup_PrivateExchange.directory, self._getSelectorKey())
+ if not os.path.exists(keyfile):
+ log.debug("DKIM: Failed private-exchange lookup: no path %s" % (keyfile,))
+ return succeed(())
+
+ # Now read the data
+ try:
+ with open(keyfile) as f:
+ keys = f.read()
+ except IOError, e:
+ log.debug("DKIM: Failed private-exchange lookup: could not read %s %s" % (keyfile, e,))
+ return succeed(())
+
+ return succeed(tuple([DKIMUtils.extractTags(line) for line in keys.splitlines()]))
+
+
+
+class DomainKeyResource (SimpleResource):
+ """
+ Domainkey well-known resource.
+ """
+
+ def __init__(self, domain, selector, pubkeyfile):
+ """
+ """
+ assert domain
+ assert selector
+
+ SimpleResource.__init__(self, principalCollections=None, isdir=True, defaultACL=SimpleResource.allReadACL)
+ self.makeKeyData(domain, selector, pubkeyfile)
+ self.domain = domain
+ self.selector = selector
+
+
+ def makeKeyData(self, domain, selector, pubkeyfile):
+ """
+ Check that a valid key exists, create the TXT record format data and make the needed child resources.
+ """
+
+ # Get data from file
+ try:
+ with open(pubkeyfile) as f:
+ key_data = f.read()
+ except IOError, e:
+ log.error("DKIM: Unable to open the public key file: %s because of %s" % (pubkeyfile, e, ))
+ raise
+
+ # Make sure we can parse a valid public key
+ try:
+ rsa.PublicKey.load_pkcs1(key_data)
+ except:
+ log.error("DKIM: Invalid public key file: %s" % (pubkeyfile,))
+ raise
+
+ # Make the TXT record
+ key_data = "".join(key_data.strip().splitlines()[1:-1])
+ txt_data = "v=DKIM1; s=ischedule; p=%s" % (key_data,)
+
+ # Setup resource hierarchy
+ domainResource = SimpleResource(principalCollections=None, isdir=True, defaultACL=SimpleResource.allReadACL)
+ self.putChild(domain, domainResource)
+
+ selectorResource = SimpleDataResource(principalCollections=None, content_type=MimeType.fromString("text/plain"), data=txt_data, defaultACL=SimpleResource.allReadACL)
+ domainResource.putChild(selector, selectorResource)
+
+
+ def contentType(self):
+ return MimeType.fromString("text/html; charset=utf-8")
+
+
+ def render(self, request):
+ output = """<html>
+<head>
+<title>DomainKey Resource</title>
+</head>
+<body>
+<h1>DomainKey Resource.</h1>
+<a href="%s">Domain: %s<br>
+Selector: %s</a>
+</body
+</html>""" % (joinURL(request.uri, self.domain, self.selector), self.domain, self.selector,)
+
+ response = Response(200, {}, output)
+ response.headers.setHeader("content-type", MimeType("text", "html"))
+ return response
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_dkim.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_dkim.py 2012-09-09 03:10:25 UTC (rev 9795)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_dkim.py 2012-09-10 19:40:59 UTC (rev 9796)
@@ -19,12 +19,13 @@
from twisted.internet.defer import inlineCallbacks, succeed
from twistedcaldav.scheduling.ischedule.dkim import DKIMRequest, DKIMVerifier,\
DKIMVerificationError, DKIMUtils, PublicKeyLookup_DNSTXT,\
- PublicKeyLookup_HTTP_WellKnown
+ PublicKeyLookup_HTTP_WellKnown, PublicKeyLookup_PrivateExchange
import base64
import hashlib
import rsa
import time
import twistedcaldav.test.util
+import os
class TestDKIMBase (twistedcaldav.test.util.TestCase):
"""
@@ -111,7 +112,7 @@
headers.addRawHeader("Originator", "mailto:user01 at example.com")
headers.addRawHeader("Recipient", "mailto:user02 at example.com")
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, 3600)
+ request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", "/tmp/key", algorithm, ("Originator", "Recipient", "Content-Type",), True, True, True, 3600)
hash = base64.b64encode(hash_method(data).digest())
result = (yield request.bodyHash())
self.assertEqual(result, hash)
@@ -130,7 +131,7 @@
headers.addRawHeader("Originator", "mailto:user01 at example.com")
headers.addRawHeader("Recipient", "mailto:user02 at example.com")
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, 3600)
+ 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())
@@ -163,7 +164,7 @@
headers.addRawHeader("Originator", "mailto:user01 at example.com")
headers.addRawHeader("Recipient", "mailto:user02 at example.com")
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, 3600)
+ request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", self.private_keyfile, algorithm, ("Originator", "Recipient", "Content-Type",), True, True, True, 3600)
result, _ignore_tags = (yield request.signatureHeaders())
# Manually create what should be the correct thing to sign
@@ -173,7 +174,9 @@
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=dns/txt:http/well-known; http=UE9TVDov; c=relaxed/simple; h=Originator:Recipient:Content-Type:iSchedule-Version:iSchedule-Message-ID; bh=%s; b=
+cache-control:no-cache
+cache-control:no-transform
+dkim-signature:v=1; d=example.com; s=dkim; t=%s; x=%s; a=%s; q=dns/txt:http/well-known:private-exchange; http=UE9TVDov; c=relaxed/simple; h=Originator:Recipient:Content-Type:iSchedule-Version:iSchedule-Message-ID:Cache-Control:Cache-Control; bh=%s; b=
""".replace("\n", "\r\n") % (headers.getRawHeaders("Content-Type")[0], request.message_id, request.time, request.expire, algorithm, bodyhash)
self.assertEqual(result, sign_this)
@@ -192,7 +195,7 @@
headers.addRawHeader("Originator", "mailto:user01 at example.com")
headers.addRawHeader("Recipient", "mailto:user02 at example.com")
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, 3600)
+ 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
@@ -202,7 +205,9 @@
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=dns/txt:http/well-known; http=UE9TVDov; c=relaxed/simple; h=Originator:Recipient:Content-Type:iSchedule-Version:iSchedule-Message-ID; bh=%s; b=
+cache-control:no-cache
+cache-control:no-transform
+dkim-signature:v=1; d=example.com; s=dkim; t=%s; x=%s; a=%s; q=dns/txt:http/well-known:private-exchange; http=UE9TVDov; c=relaxed/simple; h=Originator:Recipient:Content-Type:iSchedule-Version:iSchedule-Message-ID:Cache-Control:Cache-Control; bh=%s; b=
""".replace("\n", "\r\n") % (headers.getRawHeaders("Content-Type")[0], request.message_id, request.time, request.expire, algorithm, bodyhash)
key = rsa.PrivateKey.load_pkcs1(open(self.private_keyfile).read())
signature = base64.b64encode(rsa.sign(sign_this, key, hash_name))
@@ -210,7 +215,7 @@
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=dns/txt:http/well-known; http=UE9TVDov; c=relaxed/simple; h=Originator:Recipient:Content-Type:iSchedule-Version:iSchedule-Message-ID; bh=%s; b=%s" % (request.time, request.expire, algorithm, bodyhash, signature,)
+ updated_header = "v=1; d=example.com; s=dkim; t=%s; x=%s; a=%s; q=dns/txt:http/well-known:private-exchange; http=UE9TVDov; c=relaxed/simple; h=Originator:Recipient:Content-Type:iSchedule-Version:iSchedule-Message-ID:Cache-Control:Cache-Control; 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
@@ -466,7 +471,7 @@
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, 3600)
+ 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
@@ -630,11 +635,15 @@
def test_selector_key(self):
- for lookup, result in (
- (PublicKeyLookup_DNSTXT, "dkim._domainkey.example.com"),
- (PublicKeyLookup_HTTP_WellKnown, "https://example.com/.well-known/domainkey/dkim")
+ 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=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
+ 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)
@@ -643,7 +652,7 @@
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 ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
+ 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,))]
@@ -651,7 +660,7 @@
self.assertNotEqual(pubkey, None)
# Valid with more tags
- dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
+ 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,))]
@@ -659,7 +668,7 @@
self.assertNotEqual(pubkey, None)
# Invalid - key type
- dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
+ 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,))]
@@ -667,7 +676,7 @@
self.assertEqual(pubkey, None)
# Invalid - hash
- dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
+ 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,))]
@@ -675,7 +684,7 @@
self.assertEqual(pubkey, None)
# Invalid - service
- dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
+ 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,))]
@@ -683,7 +692,7 @@
self.assertEqual(pubkey, None)
# Invalid - revoked
- dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
+ 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=")]
@@ -691,7 +700,7 @@
self.assertEqual(pubkey, None)
# Multiple valid
- dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
+ 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 = [
@@ -703,7 +712,7 @@
self.assertNotEqual(pubkey, 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 ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
+ 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 = [
@@ -716,7 +725,7 @@
self.assertNotEqual(pubkey, None)
# Multiple - invalid
- dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
+ 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 = [
@@ -731,7 +740,7 @@
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 ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
+ 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,))]
@@ -739,16 +748,59 @@
self.assertNotEqual(pubkey, None)
# Cache valid
- dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
+ 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.assertNotEqual(pubkey, None)
# Cache invalid
- dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
+ 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.assertEqual(pubkey, None)
+
+ @inlineCallbacks
+ 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.assertNotEqual(pubkey, 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.assertEqual(pubkey, 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.assertEqual(pubkey, 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.assertNotEqual(pubkey, None)
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/simpleresource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/simpleresource.py 2012-09-09 03:10:25 UTC (rev 9795)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/simpleresource.py 2012-09-10 19:40:59 UTC (rev 9796)
@@ -23,17 +23,20 @@
"SimpleResource",
"SimpleCalDAVResource",
"SimpleRedirectResource",
+ "SimpleDataResource",
]
from twext.web2 import http
-from txdav.xml import element as davxml
from twext.web2.dav.noneprops import NonePropertyStore
+from twext.web2.http import Response
from twisted.internet.defer import succeed
+from twistedcaldav.config import config
from twistedcaldav.resource import CalDAVResource
-from twistedcaldav.config import config
+from txdav.xml import element as davxml
+
class SimpleResource (
CalDAVResource,
):
@@ -79,6 +82,8 @@
SimpleCalDAVResource = SimpleResource
+
+
class SimpleRedirectResource(SimpleResource):
"""
A L{SimpleResource} which always performs a redirect.
@@ -96,3 +101,29 @@
def renderHTTP(self, request):
return http.RedirectResponse(request.unparseURL(host=config.ServerHostName, **self._kwargs))
+
+
+
+class SimpleDataResource(SimpleResource):
+ """
+ A L{SimpleResource} which returns fixed content.
+ """
+
+ def __init__(self, principalCollections, content_type, data, defaultACL=SimpleResource.authReadACL):
+ """
+ @param content_type: the mime content-type of the data
+ @type content_type: L{MimeType}
+ @param data: the data
+ @type data: C{str}
+ """
+ SimpleResource.__init__(self, principalCollections=principalCollections, isdir=False, defaultACL=defaultACL)
+ self.content_type = content_type
+ self.data = data
+
+ def contentType(self):
+ return self.content_type
+
+ def render(self, request):
+ response = Response(200, {}, self.data)
+ response.headers.setHeader("content-type", self.content_type)
+ return response
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/stdconfig.py 2012-09-09 03:10:25 UTC (rev 9795)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/stdconfig.py 2012-09-10 19:40:59 UTC (rev 9796)
@@ -609,13 +609,15 @@
"DKIM" : { # DKIM options
"Enabled" : True, # DKIM signing/verification enabled
"Domain" : "", # Domain for DKIM (defaults to ServerHostName)
+ "KeySelector" : "ischedule", # Selector for public key
"SignatureAlgorithm" : "rsa-sha256", # Signature algorithm (one of rsa-sha1 or rsa-sha256)
- "UseDNSKey" : True, # Public key stored in DNS
- "UseHTTPKey" : True, # Public key stored in HTTP /.well-known
- "KeySelector" : "ischedule", # Selector for public key
+ "UseDNSKey" : True, # This server's public key stored in DNS
+ "UseHTTPKey" : True, # This server's public key stored in HTTP /.well-known
+ "UsePrivateExchangeKey" : True, # This server's public key manually exchanged with others
"ExpireSeconds" : 3600, # Expiration time for signature verification
"PrivateKeyFile" : "", # File where private key is stored
"PublicKeyFile" : "", # File where public key is stored
+ "PrivateExchanges" : "", # Directory where private exchange public keys are stored
},
},
@@ -1031,6 +1033,9 @@
("DataRoot", "AttachmentsRoot"),
("DataRoot", ("TimezoneService", "BasePath",)),
("ConfigRoot", "SudoersFile"),
+ ("ConfigRoot", ("Scheduling", "iSchedule", "DKIM", "PrivateKeyFile",)),
+ ("ConfigRoot", ("Scheduling", "iSchedule", "DKIM", "PublicKeyFile",)),
+ ("ConfigRoot", ("Scheduling", "iSchedule", "DKIM", "PrivateExchanges",)),
("LogRoot", "AccessLogFile"),
("LogRoot", "ErrorLogFile"),
("LogRoot", ("Postgres", "LogFile",)),
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120910/84d67257/attachment-0001.html>
More information about the calendarserver-changes
mailing list