summaryrefslogtreecommitdiffstats
path: root/ipapython
diff options
context:
space:
mode:
authorJohn Dennis <jdennis@redhat.com>2010-05-31 07:40:17 -0400
committerRob Crittenden <rcritten@redhat.com>2010-06-15 15:03:36 -0400
commit31027c6183e3df927b08f0f0b7f84ae7420c3e88 (patch)
tree80c6f3d84f18b02b1afb52ce364ec57bdeb06802 /ipapython
parent1dd7b11b0b5697f86f4d486fbe9509484ae2065a (diff)
downloadfreeipa-31027c6183e3df927b08f0f0b7f84ae7420c3e88.tar.gz
freeipa-31027c6183e3df927b08f0f0b7f84ae7420c3e88.tar.xz
freeipa-31027c6183e3df927b08f0f0b7f84ae7420c3e88.zip
use NSS for SSL operations
Diffstat (limited to 'ipapython')
-rw-r--r--ipapython/dogtag.py13
-rw-r--r--ipapython/ipasslfile.py185
-rw-r--r--ipapython/nsslib.py228
3 files changed, 167 insertions, 259 deletions
diff --git a/ipapython/dogtag.py b/ipapython/dogtag.py
index c6b3a5dc4..96d9469d0 100644
--- a/ipapython/dogtag.py
+++ b/ipapython/dogtag.py
@@ -22,9 +22,9 @@ import httplib
import xml.dom.minidom
from ipapython import nsslib
import nss.nss as nss
+from nss.error import NSPRError
from ipalib.errors import NetworkError, CertificateOperationError
from urllib import urlencode
-import socket
import logging
def get_ca_certchain(ca_host=None):
@@ -76,10 +76,11 @@ def https_request(host, port, url, secdir, password, nickname, **kw):
"Accept": "text/plain"}
try:
conn = nsslib.NSSConnection(host, port, dbdir=secdir)
- conn.sslsock.set_client_auth_data_callback(nsslib.client_auth_data_callback,
- nickname,
- password, nss.get_default_certdb())
+ conn.sock.set_client_auth_data_callback(nsslib.client_auth_data_callback,
+ nickname,
+ password, nss.get_default_certdb())
conn.set_debuglevel(0)
+ conn.connect()
conn.request("POST", url, post, request_headers)
res = conn.getresponse()
@@ -122,8 +123,8 @@ def http_request(host, port, url, **kw):
http_headers = res.msg.dict
http_body = res.read()
conn.close()
- except socket.error, e:
- raise NetworkError(uri=uri, error=e.args[1])
+ except NSPRError, e:
+ raise NetworkError(uri=uri, error=str(e))
logging.debug('request status %d', http_status)
logging.debug('request reason_phrase %r', http_reason_phrase)
diff --git a/ipapython/ipasslfile.py b/ipapython/ipasslfile.py
deleted file mode 100644
index 2082e2683..000000000
--- a/ipapython/ipasslfile.py
+++ /dev/null
@@ -1,185 +0,0 @@
-# This is a forward backport of the Python2.5 uuid module. It isn't available
-# in Python 2.6
-
-# The next several classes are used to define FakeSocket, a socket-like
-# interface to an SSL connection.
-
-# The primary complexity comes from faking a makefile() method. The
-# standard socket makefile() implementation calls dup() on the socket
-# file descriptor. As a consequence, clients can call close() on the
-# parent socket and its makefile children in any order. The underlying
-# socket isn't closed until they are all closed.
-
-# The implementation uses reference counting to keep the socket open
-# until the last client calls close(). SharedSocket keeps track of
-# the reference counting and SharedSocketClient provides an constructor
-# and close() method that call incref() and decref() correctly.
-
-import socket
-import errno
-from httplib import UnimplementedFileMode, HTTPException
-
-error = HTTPException
-
-class SharedSocket:
- def __init__(self, sock):
- self.sock = sock
- self._refcnt = 0
-
- def incref(self):
- self._refcnt += 1
-
- def decref(self):
- self._refcnt -= 1
- assert self._refcnt >= 0
- if self._refcnt == 0:
- self.sock.close()
-
- def __del__(self):
- self.sock.close()
-
-class SharedSocketClient:
-
- def __init__(self, shared):
- self._closed = 0
- self._shared = shared
- self._shared.incref()
- self._sock = shared.sock
-
- def close(self):
- if not self._closed:
- self._shared.decref()
- self._closed = 1
- self._shared = None
-
-class SSLFile(SharedSocketClient):
- """File-like object wrapping an SSL socket."""
-
- BUFSIZE = 8192
-
- def __init__(self, sock, ssl, bufsize=None):
- SharedSocketClient.__init__(self, sock)
- self._ssl = ssl
- self._buf = ''
- self._bufsize = bufsize or self.__class__.BUFSIZE
-
- def _read(self):
- buf = ''
- # put in a loop so that we retry on transient errors
- while True:
- try:
- buf = self._ssl.read(self._bufsize)
- except socket.sslerror, err:
- if (err[0] == socket.SSL_ERROR_WANT_READ
- or err[0] == socket.SSL_ERROR_WANT_WRITE):
- continue
- if (err[0] == socket.SSL_ERROR_ZERO_RETURN
- or err[0] == socket.SSL_ERROR_EOF):
- break
- raise
- except socket.error, err:
- if err[0] == errno.EINTR:
- continue
- if err[0] == errno.EBADF:
- # XXX socket was closed?
- break
- raise
- else:
- break
- return buf
-
- def read(self, size=None):
- L = [self._buf]
- avail = len(self._buf)
- while size is None or avail < size:
- s = self._read()
- if s == '':
- break
- L.append(s)
- avail += len(s)
- alldata = "".join(L)
- if size is None:
- self._buf = ''
- return alldata
- else:
- self._buf = alldata[size:]
- return alldata[:size]
-
- def readline(self):
- L = [self._buf]
- self._buf = ''
- while 1:
- i = L[-1].find("\n")
- if i >= 0:
- break
- s = self._read()
- if s == '':
- break
- L.append(s)
- if i == -1:
- # loop exited because there is no more data
- return "".join(L)
- else:
- alldata = "".join(L)
- # XXX could do enough bookkeeping not to do a 2nd search
- i = alldata.find("\n") + 1
- line = alldata[:i]
- self._buf = alldata[i:]
- return line
-
- def readlines(self, sizehint=0):
- total = 0
- inlist = []
- while True:
- line = self.readline()
- if not line:
- break
- inlist.append(line)
- total += len(line)
- if sizehint and total >= sizehint:
- break
- return inlist
-
- def fileno(self):
- return self._sock.fileno()
-
- def __iter__(self):
- return self
-
- def next(self):
- line = self.readline()
- if not line:
- raise StopIteration
- return line
-
-class FakeSocket(SharedSocketClient):
-
- class _closedsocket:
- def __getattr__(self, name):
- raise error(9, 'Bad file descriptor')
-
- def __init__(self, sock, ssl):
- sock = SharedSocket(sock)
- SharedSocketClient.__init__(self, sock)
- self._ssl = ssl
-
- def close(self):
- SharedSocketClient.close(self)
- self._sock = self.__class__._closedsocket()
-
- def makefile(self, mode, bufsize=None):
- if mode != 'r' and mode != 'rb':
- raise UnimplementedFileMode()
- return SSLFile(self._shared, self._ssl, bufsize)
-
- def send(self, stuff, flags = 0):
- return self._ssl.write(stuff)
-
- sendall = send
-
- def recv(self, len = 1024, flags = 0):
- return self._ssl.read(len)
-
- def __getattr__(self, attr):
- return getattr(self._sock, attr)
-
diff --git a/ipapython/nsslib.py b/ipapython/nsslib.py
index 2052843e2..1710a7d74 100644
--- a/ipapython/nsslib.py
+++ b/ipapython/nsslib.py
@@ -1,4 +1,5 @@
# Authors: Rob Crittenden <rcritten@redhat.com>
+# John Dennis <jdennis@redhat.com>
#
# Copyright (C) 2009 Red Hat
# see file 'COPYING' for use and warranty information
@@ -19,19 +20,75 @@
import httplib
import getpass
-import socket
+import logging
from nss.error import NSPRError
import nss.io as io
import nss.nss as nss
import nss.ssl as ssl
-try:
- from httplib import SSLFile
- from httplib import FakeSocket
-except ImportError:
- from ipapython.ipasslfile import SSLFile
- from ipapython.ipasslfile import FakeSocket
+def auth_certificate_callback(sock, check_sig, is_server, certdb):
+ cert_is_valid = False
+
+ cert = sock.get_peer_certificate()
+
+ logging.debug("auth_certificate_callback: check_sig=%s is_server=%s\n%s",
+ check_sig, is_server, str(cert))
+
+ pin_args = sock.get_pkcs11_pin_arg()
+ if pin_args is None:
+ pin_args = ()
+
+ # Define how the cert is being used based upon the is_server flag. This may
+ # seem backwards, but isn't. If we're a server we're trying to validate a
+ # client cert. If we're a client we're trying to validate a server cert.
+ if is_server:
+ intended_usage = nss.certificateUsageSSLClient
+ else:
+ intended_usage = nss.certificateUsageSSLServer
+
+ try:
+ # If the cert fails validation it will raise an exception, the errno attribute
+ # will be set to the error code matching the reason why the validation failed
+ # and the strerror attribute will contain a string describing the reason.
+ approved_usage = cert.verify_now(certdb, check_sig, intended_usage, *pin_args)
+ except Exception, e:
+ logging.error('cert validation failed for "%s" (%s)', cert.subject, e.strerror)
+ cert_is_valid = False
+ return cert_is_valid
+
+ logging.debug("approved_usage = %s intended_usage = %s",
+ ', '.join(nss.cert_usage_flags(approved_usage)),
+ ', '.join(nss.cert_usage_flags(intended_usage)))
+
+ # Is the intended usage a proper subset of the approved usage
+ if approved_usage & intended_usage:
+ cert_is_valid = True
+ else:
+ cert_is_valid = False
+
+ # If this is a server, we're finished
+ if is_server or not cert_is_valid:
+ logging.debug('cert valid %s for "%s"', cert_is_valid, cert.subject)
+ return cert_is_valid
+
+ # Certificate is OK. Since this is the client side of an SSL
+ # connection, we need to verify that the name field in the cert
+ # matches the desired hostname. This is our defense against
+ # man-in-the-middle attacks.
+
+ hostname = sock.get_hostname()
+ try:
+ # If the cert fails validation it will raise an exception
+ cert_is_valid = cert.verify_hostname(hostname)
+ except Exception, e:
+ logging.error('failed verifying socket hostname "%s" matches cert subject "%s" (%s)',
+ hostname, cert.subject, e.strerror)
+ cert_is_valid = False
+ return cert_is_valid
+
+ logging.debug('cert valid %s for "%s"', cert_is_valid, cert.subject)
+ return cert_is_valid
def client_auth_data_callback(ca_names, chosen_nickname, password, certdb):
cert = None
@@ -55,56 +112,32 @@ def client_auth_data_callback(ca_names, chosen_nickname, password, certdb):
return False
return False
-class SSLFile(SSLFile):
- """
- Override the _read method so we can use the NSS recv method.
- """
- def _read(self):
- buf = ''
- while True:
- try:
- buf = self._ssl.recv(self._bufsize)
- except NSPRError, e:
- raise e
- else:
- break
- return buf
-
-class NSSFakeSocket(FakeSocket):
- def makefile(self, mode, bufsize=None):
- if mode != 'r' and mode != 'rb':
- raise httplib.UnimplementedFileMode()
- return SSLFile(self._shared, self._ssl, bufsize)
-
- def send(self, stuff, flags = 0):
- return self._ssl.send(stuff)
-
- sendall = send
-
class NSSConnection(httplib.HTTPConnection):
default_port = httplib.HTTPSConnection.default_port
- def __init__(self, host, port=None, key_file=None, cert_file=None,
- ca_file='/etc/pki/tls/certs/ca-bundle.crt', strict=None,
- dbdir=None):
+ def __init__(self, host, port=None, strict=None, dbdir=None):
httplib.HTTPConnection.__init__(self, host, port, strict)
- self.key_file = key_file
- self.cert_file = cert_file
- self.ca_file = ca_file
if not dbdir:
raise RuntimeError("dbdir is required")
+ logging.debug('%s init %s', self.__class__.__name__, host)
nss.nss_init(dbdir)
ssl.set_domestic_policy()
nss.set_password_callback(self.password_callback)
# Create the socket here so we can do things like let the caller
# override the NSS callbacks
- self.sslsock = ssl.SSLSocket()
- self.sslsock.set_ssl_option(ssl.SSL_SECURITY, True)
- self.sslsock.set_ssl_option(ssl.SSL_HANDSHAKE_AS_CLIENT, True)
- self.sslsock.set_handshake_callback(self.handshake_callback)
+ self.sock = ssl.SSLSocket()
+ self.sock.set_ssl_option(ssl.SSL_SECURITY, True)
+ self.sock.set_ssl_option(ssl.SSL_HANDSHAKE_AS_CLIENT, True)
+
+ # Provide a callback which notifies us when the SSL handshake is complete
+ self.sock.set_handshake_callback(self.handshake_callback)
+
+ # Provide a callback to verify the servers certificate
+ self.sock.set_auth_certificate_callback(auth_certificate_callback,
+ nss.get_default_certdb())
def password_callback(self, slot, retry, password):
if not retry and password: return password
@@ -114,43 +147,102 @@ class NSSConnection(httplib.HTTPConnection):
"""
Verify callback. If we get here then the certificate is ok.
"""
- if self.debuglevel > 0:
- print "handshake complete, peer = %s" % (sock.get_peer_name())
+ logging.debug("handshake complete, peer = %s", sock.get_peer_name())
pass
def connect(self):
- self.sslsock.set_hostname(self.host)
-
+ logging.debug("connect: host=%s port=%s", self.host, self.port)
+ self.sock.set_hostname(self.host)
net_addr = io.NetworkAddress(self.host, self.port)
-
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.sslsock.connect(net_addr)
- self.sock = NSSFakeSocket(sock, self.sslsock)
+ logging.debug("connect: %s", net_addr)
+ self.sock.connect(net_addr)
class NSSHTTPS(httplib.HTTP):
+ # We would like to use HTTP 1.1 not the older HTTP 1.0 but xmlrpclib
+ # and httplib do not play well together. httplib when the protocol
+ # is 1.1 will add a host header in the request. But xmlrpclib
+ # always adds a host header irregardless of the HTTP protocol
+ # version. That means the request ends up with 2 host headers,
+ # but Apache freaks out if it sees 2 host headers, a known Apache
+ # issue. httplib has a mechanism to skip adding the host header
+ # (i.e. skip_host in HTTPConnection.putrequest()) but xmlrpclib
+ # doesn't use it. Oh well, back to 1.0 :-(
+ #
+ #_http_vsn = 11
+ #_http_vsn_str = 'HTTP/1.1'
+
_connection_class = NSSConnection
- def __init__(self, host='', port=None, key_file=None, cert_file=None,
- ca_file='/etc/pki/tls/certs/ca-bundle.crt', strict=None):
+ def __init__(self, host='', port=None, strict=None, dbdir=None):
# provide a default host, pass the X509 cert info
# urf. compensate for bad input.
if port == 0:
port = None
- self._setup(self._connection_class(host, port, key_file,
- cert_file, ca_file, strict))
- # we never actually use these for anything, but we keep them
- # here for compatibility with post-1.5.2 CVS.
- self.key_file = key_file
- self.cert_file = cert_file
- self.ca_file = ca_file
+ self._setup(self._connection_class(host, port, strict, dbdir=dbdir))
+
+class NSPRConnection(httplib.HTTPConnection):
+ default_port = httplib.HTTPConnection.default_port
+
+ def __init__(self, host, port=None, strict=None):
+ httplib.HTTPConnection.__init__(self, host, port, strict)
+
+ logging.debug('%s init %s', self.__class__.__name__, host)
+ nss.nss_init_nodb()
+
+ self.sock = io.Socket()
+ def connect(self):
+ logging.debug("connect: host=%s port=%s", self.host, self.port)
+ net_addr = io.NetworkAddress(self.host, self.port)
+ logging.debug("connect: %s", net_addr)
+ self.sock.connect(net_addr)
+
+class NSPRHTTP(httplib.HTTP):
+ _http_vsn = 11
+ _http_vsn_str = 'HTTP/1.1'
+
+ _connection_class = NSPRConnection
+
+#------------------------------------------------------------------------------
if __name__ == "__main__":
- h = NSSConnection("www.verisign.com", 443, dbdir="/etc/pki/nssdb")
- h.set_debuglevel(1)
- h.request("GET", "/")
- res = h.getresponse()
- print res.status
- data = res.read()
- print data
- h.close()
+ logging.basicConfig(level=logging.DEBUG,
+ format='%(asctime)s %(levelname)-8s %(message)s',
+ datefmt='%m-%d %H:%M',
+ filename='nsslib.log',
+ filemode='a')
+ # Create a seperate logger for the console
+ console_logger = logging.StreamHandler()
+ console_logger.setLevel(logging.DEBUG)
+ # set a format which is simpler for console use
+ formatter = logging.Formatter('%(levelname)s %(message)s')
+ console_logger.setFormatter(formatter)
+ # add the handler to the root logger
+ logging.getLogger('').addHandler(console_logger)
+ logging.info("Start")
+
+ if False:
+ conn = NSSConnection("www.verisign.com", 443, dbdir="/etc/pki/nssdb")
+ conn.set_debuglevel(1)
+ conn.connect()
+ conn.request("GET", "/")
+ response = conn.getresponse()
+ print response.status
+ #print response.msg
+ print response.getheaders()
+ data = response.read()
+ #print data
+ conn.close()
+
+ if True:
+ h = NSSHTTPS("www.verisign.com", 443, dbdir="/etc/pki/nssdb")
+ h.connect()
+ h.putrequest('GET', '/')
+ h.endheaders()
+ http_status, http_reason, headers = h.getreply()
+ print "status = %s %s" % (http_status, http_reason)
+ print "headers:\n%s" % headers
+ f = h.getfile()
+ data = f.read() # Get the raw HTML
+ f.close()
+ #print data