Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
Browse files

Authentication factories not safe for unencrypted connections are aut…

…omatically not advertised when an insecure (e.g. non-SSL) connection is made

git-svn-id: https://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk@10129 e27351fd-9f3e-4f54-a53b-843176b1656c
  • Loading branch information
m0rgen committed Dec 6, 2012
1 parent 5f95ca3 commit 5ac86d0c06e56bbcbfb71f374910f5c5f4f197ab
@@ -85,7 +85,8 @@ def setUp(self):
self.root = auth.AuthenticationWrapper(
root,
portal,
credentialFactories=(basic.BasicCredentialFactory("Test realm"),),
(basic.BasicCredentialFactory("Test realm"),),
(basic.BasicCredentialFactory("Test realm"),),
loginInterfaces=(auth.IPrincipal,))

self.site = server.Site(self.root)
@@ -409,7 +409,8 @@ def getRootResource(config, newStore, resources=None):
#
# Configure the Site and Wrappers
#
credentialFactories = []
wireEncryptedCredentialFactories = []
wireUnencryptedCredentialFactories = []

portal = Portal(auth.DavRealm())

@@ -464,7 +465,9 @@ def getRootResource(config, newStore, resources=None):
log.error("Unknown scheme: %s" % (scheme,))

if credFactory:
credentialFactories.append(credFactory)
wireEncryptedCredentialFactories.append(credFactory)
if schemeConfig.get("AllowedOverWireUnencrypted", False):
wireUnencryptedCredentialFactories.append(credFactory)

#
# Setup Resource hierarchy
@@ -685,9 +688,10 @@ def getRootResource(config, newStore, resources=None):
authWrapper = AuthenticationWrapper(
root,
portal,
credentialFactories,
wireEncryptedCredentialFactories,
wireUnencryptedCredentialFactories,
(auth.IPrincipal,),
overrides=overrides,
overrides=overrides
)

logWrapper = DirectoryLogWrapperResource(
@@ -242,7 +242,7 @@
<key>Basic</key>
<dict>
<key>Enabled</key>
<false/>
<true/>
</dict>

<!-- Digest challenge/response -->
@@ -507,13 +507,17 @@
<dict>
<key>Enabled</key>
<true/>
<key>AllowedOverWireUnencrypted</key> <!-- advertised over non SSL? -->
<true/>
</dict>

<!-- Digest challenge/response -->
<key>Digest</key>
<dict>
<key>Enabled</key>
<true/>
<key>AllowedOverWireUnencrypted</key> <!-- advertised over non SSL? -->
<true/>
<key>Algorithm</key>
<string>md5</string>
<key>Qop</key>
@@ -525,6 +529,8 @@
<dict>
<key>Enabled</key>
<true/>
<key>AllowedOverWireUnencrypted</key> <!-- advertised over non SSL? -->
<true/>
<key>ServicePrincipal</key>
<string></string>
</dict>
@@ -146,6 +146,7 @@ def updateSettings(settings, otherCert):

settings["EnableSSL"] = True
settings["RedirectHTTPToHTTPS"] = True
settings.setdefault("Authentication", {}).setdefault("Basic", {})["Enabled"] = True

def setCert(plistPath, otherCert):
"""
@@ -34,6 +34,7 @@ def test_updateSettings(self):
orig = {
}
expected = {
'Authentication': {'Basic': {'Enabled': True}},
'EnableSSL': True,
'RedirectHTTPToHTTPS': True,
'SSLAuthorityChain': '/test/pchain.pem',
@@ -637,11 +637,7 @@ def abortConnection(self, closeWrite=True):
self._cleanup()

def getHostInfo(self):
t=self.channel.transport
secure = interfaces.ISSLTransport(t, None) is not None
host = t.getHost()
host.host = _cachedGetHostByAddr(host.host)
return host, secure
return self.channel._host, self.channel._secure

def getRemoteHost(self):
return self.channel.transport.getPeer()
@@ -783,6 +779,9 @@ def __init__(self):
self.requests = []

def connectionMade(self):
self._secure = interfaces.ISSLTransport(self.transport, None) is not None
address = self.transport.getHost()
self._host = _cachedGetHostByAddr(address.host)
self.setTimeout(self.inputTimeOut)
self.factory.addConnectedChannel(self)

@@ -37,30 +37,51 @@


class AuthenticationWrapper(WrapperResource):
def __init__(self, resource, portal, credentialFactories, loginInterfaces):
def __init__(self, resource, portal,
wireEncryptedCredentialFactories, wireUnencryptedCredentialFactories,
loginInterfaces):
"""
Wrap the given resource and use the parameters to set up the request
to allow anyone to challenge and handle authentication.
@param resource: L{DAVResource} FIXME: This should get promoted to
twext.web2.auth
@param portal: The cred portal
@param credentialFactories: Sequence of credentialFactories that can
be used to authenticate by resources in this tree.
@param wireEncryptedCredentialFactories: Sequence of credentialFactories
that can be used to authenticate by resources in this tree over a
wire-encrypted channel (SSL).
@param wireUnencryptedCredentialFactories: Sequence of credentialFactories
that can be used to authenticate by resources in this tree over a
wire-unencrypted channel (non-SSL).
@param loginInterfaces: More cred stuff
"""
super(AuthenticationWrapper, self).__init__(resource)

self.portal = portal
self.credentialFactories = dict([(factory.scheme, factory)
for factory in credentialFactories])
self.wireEncryptedCredentialFactories = dict([(factory.scheme, factory)
for factory in wireEncryptedCredentialFactories])
self.wireUnencryptedCredentialFactories = dict([(factory.scheme, factory)
for factory in wireUnencryptedCredentialFactories])
self.loginInterfaces = loginInterfaces

# FIXME: some unit tests access self.credentialFactories, so assigning here
self.credentialFactories = self.wireEncryptedCredentialFactories

def hook(self, req):
req.portal = self.portal
req.credentialFactories = self.credentialFactories
req.loginInterfaces = self.loginInterfaces

# If not using SSL, use the factory list which excludes "Basic"
if req.chanRequest is None: # This is only None in unit tests
secureConnection = True
else:
ignored, secureConnection = req.chanRequest.getHostInfo()
req.credentialFactories = (
self.wireEncryptedCredentialFactories
if secureConnection
else self.wireUnencryptedCredentialFactories
)


class IPrincipal(Interface):
pass
@@ -72,6 +72,7 @@ def createDocumentRoot(self):
rootResource,
portal,
credentialFactories,
credentialFactories,
loginInterfaces
))

@@ -0,0 +1,67 @@
##
# Copyright (c) 2012 Apple Computer, Inc. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# DRI: Wilfredo Sanchez, wsanchez@apple.com
##

import collections
from twext.web2.dav.auth import AuthenticationWrapper
import twext.web2.dav.test.util

class AutoWrapperTestCase(twext.web2.dav.test.util.TestCase):

def test_basicAuthPrevention(self):
"""
Ensure authentication factories which are not safe to use over an
"unencrypted wire" are not advertised when an insecure (i.e. non-SSL
connection is made.
"""
FakeFactory = collections.namedtuple("FakeFactory", ("scheme,"))
wireEncryptedfactories = [FakeFactory("basic"), FakeFactory("digest"), FakeFactory("xyzzy")]
wireUnencryptedfactories = [FakeFactory("digest"), FakeFactory("xyzzy")]

class FakeChannel(object):
def __init__(self, secure):
self.secure = secure
def getHostInfo(self):
return "ignored", self.secure

class FakeRequest(object):
def __init__(self, secure):
self.portal = None
self.loginInterfaces = None
self.credentialFactories = None
self.chanRequest = FakeChannel(secure)

wrapper = AuthenticationWrapper(None, None,
wireEncryptedfactories, wireUnencryptedfactories, None)
req = FakeRequest(True) # Connection is over SSL
wrapper.hook(req)
self.assertEquals(
set(req.credentialFactories.keys()),
set(["basic", "digest", "xyzzy"])
)
req = FakeRequest(False) # Connection is not over SSL
wrapper.hook(req)
self.assertEquals(
set(req.credentialFactories.keys()),
set(["digest", "xyzzy"])
)
@@ -240,6 +240,7 @@ def setUp(self):
self.rootresource,
portal,
credentialFactories,
credentialFactories,
loginInterfaces,
))

@@ -53,6 +53,9 @@ def getpeername(self):
raise SocketError(ENOTCONN, "Transport endpoint not connected")


def getsockname(self):
return ("4.3.2.1", 4321)


class InheritedPortForTesting(sendfdport.InheritedPort):
"""
@@ -3023,11 +3023,13 @@ class AuthenticationWrapper(SuperAuthenticationWrapper):
""" AuthenticationWrapper implementation which allows overriding
credentialFactories on a per-resource-path basis """

def __init__(self, resource, portal, credentialFactories, loginInterfaces,
overrides=None):
def __init__(self, resource, portal,
wireEncryptedCredentialFactories, wireUnencryptedCredentialFactories,
loginInterfaces, overrides=None):

super(AuthenticationWrapper, self).__init__(resource, portal,
credentialFactories, loginInterfaces)
wireEncryptedCredentialFactories, wireUnencryptedCredentialFactories,
loginInterfaces)

self.overrides = {}
if overrides:
@@ -3043,7 +3045,7 @@ def hook(self, req):
super(AuthenticationWrapper, self).hook(req)

factories = self.overrides.get(req.path.rstrip("/"),
self.credentialFactories)
req.credentialFactories)
req.credentialFactories = factories


@@ -406,15 +406,20 @@
# Authentication
#
"Authentication": {
"Basic": { "Enabled": False }, # Clear text; best avoided
"Basic": { # Clear text; best avoided
"Enabled": True,
"AllowedOverWireUnencrypted": False, # Advertised over non-SSL?
},
"Digest": { # Digest challenge/response
"Enabled": True,
"Algorithm": "md5",
"Qop": "",
"AllowedOverWireUnencrypted": True, # Advertised over non-SSL?
},
"Kerberos": { # Kerberos/SPNEGO
"Enabled": False,
"ServicePrincipal": ""
"ServicePrincipal": "",
"AllowedOverWireUnencrypted": True, # Advertised over non-SSL?
},
"Wiki": {
"Enabled": False,

0 comments on commit 5ac86d0

Please sign in to comment.
You can’t perform that action at this time.