[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