summaryrefslogtreecommitdiffstats
path: root/func
diff options
context:
space:
mode:
authorAdrian Likins <alikins@redhat.com>2007-09-25 11:50:28 -0400
committerAdrian Likins <alikins@redhat.com>2007-09-25 11:50:28 -0400
commitfb7df2a5b7834ead29effb5c60bdc33558546631 (patch)
tree6c70ccfb037ab621cc69085ba8d5b540753d81dd /func
parent9c72cbd826528bb64267ba2184ae16099343c7ab (diff)
parent47100aa2f165b47175af1e1aef736c5769a83169 (diff)
downloadfunc-fb7df2a5b7834ead29effb5c60bdc33558546631.tar.gz
func-fb7df2a5b7834ead29effb5c60bdc33558546631.tar.xz
func-fb7df2a5b7834ead29effb5c60bdc33558546631.zip
Merge branch 'master' of ssh://git.fedoraproject.org/git/hosted/func
Diffstat (limited to 'func')
-rw-r--r--func/CommonErrors.py62
-rw-r--r--func/SSLCommon.py122
-rw-r--r--func/SSLConnection.py161
-rwxr-xr-xfunc/certmaster.py171
4 files changed, 516 insertions, 0 deletions
diff --git a/func/CommonErrors.py b/func/CommonErrors.py
new file mode 100644
index 0000000..13ef505
--- /dev/null
+++ b/func/CommonErrors.py
@@ -0,0 +1,62 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# Copyright 2005 Dan Williams <dcbw@redhat.com> and Red Hat, Inc.
+
+import socket
+
+
+def canIgnoreSSLError(e):
+ """
+ Identify common network errors that mean we cannot connect to the server
+ """
+
+ # This is a bit complicated by the fact that different versions of
+ # M2Crypto & OpenSSL seem to return different error codes for the
+ # same type of error
+ s = "%s" % e
+ if e[0] == 104: # Connection refused
+ return True
+ elif e[0] == 111: # Connection reset by peer
+ return True
+ elif e[0] == 61: # Connection refused
+ return True
+ elif e[0] == 54: # Connection reset by peer
+ return True
+ elif s == "no certificate returned":
+ return True
+ elif s == "wrong version number":
+ return True
+ elif s == "unexpected eof":
+ return True
+
+ return False
+
+
+def canIgnoreSocketError(e):
+ """
+ Identify common network errors that mean we cannot connect to the server
+ """
+
+ try:
+ if e[0] == 111: # Connection refused
+ return True
+ elif e[0] == 104: # Connection reset by peer
+ return True
+ elif e[0] == 61: # Connection refused
+ return True
+ except IndexError:
+ return True
+
+ return False
diff --git a/func/SSLCommon.py b/func/SSLCommon.py
new file mode 100644
index 0000000..bdfd719
--- /dev/null
+++ b/func/SSLCommon.py
@@ -0,0 +1,122 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# Copyright 2005 Dan Williams <dcbw@redhat.com> and Red Hat, Inc.
+
+import os, sys
+import CommonErrors
+from OpenSSL import SSL
+import SSLConnection
+import httplib
+import socket
+import SocketServer
+
+def our_verify(connection, x509, errNum, errDepth, preverifyOK):
+ # print "Verify: errNum = %s, errDepth = %s, preverifyOK = %s" % (errNum, errDepth, preverifyOK)
+
+ # preverifyOK should tell us whether or not the client's certificate
+ # correctly authenticates against the CA chain
+ return preverifyOK
+
+
+def CreateSSLContext(pkey, cert, ca_cert):
+ for f in pkey, cert, ca_cert:
+ if f and not os.access(f, os.R_OK):
+ print "%s does not exist or is not readable." % f
+ os._exit(1)
+
+ ctx = SSL.Context(SSL.SSLv3_METHOD) # SSLv3 only
+ ctx.use_certificate_file(cert)
+ ctx.use_privatekey_file(pkey)
+ ctx.load_client_ca(ca_cert)
+ ctx.load_verify_locations(ca_cert)
+ verify = SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT
+ ctx.set_verify(verify, our_verify)
+ ctx.set_verify_depth(10)
+ ctx.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_TLSv1)
+ return ctx
+
+
+
+class BaseServer(SocketServer.TCPServer):
+ allow_reuse_address = 1
+
+ def __init__(self, server_addr, req_handler):
+ self._quit = False
+ self.allow_reuse_address = 1
+ SocketServer.TCPServer.__init__(self, server_addr, req_handler)
+
+ def stop(self):
+ self._quit = True
+
+ def serve_forever(self):
+ while not self._quit:
+ self.handle_request()
+ self.server_close()
+
+
+class BaseSSLServer(BaseServer):
+ """ SSL-enabled variant """
+
+ def __init__(self, server_address, req_handler, pkey, cert, ca_cert, timeout=None):
+ self._timeout = timeout
+ self.ssl_ctx = CreateSSLContext(pkey, cert, ca_cert)
+
+ BaseServer.__init__(self, server_address, req_handler)
+
+ sock = socket.socket(self.address_family, self.socket_type)
+ con = SSL.Connection(self.ssl_ctx, sock)
+ self.socket = SSLConnection.SSLConnection(con)
+ if sys.version_info[:3] >= (2, 3, 0):
+ self.socket.settimeout(self._timeout)
+ self.server_bind()
+ self.server_activate()
+
+ host, port = self.socket.getsockname()[:2]
+ self.server_name = socket.getfqdn(host)
+ self.server_port = port
+
+
+class HTTPSConnection(httplib.HTTPConnection):
+ "This class allows communication via SSL."
+
+ response_class = httplib.HTTPResponse
+
+ def __init__(self, host, port=None, ssl_context=None, strict=None, timeout=None):
+ httplib.HTTPConnection.__init__(self, host, port, strict)
+ self.ssl_ctx = ssl_context
+ self._timeout = timeout
+
+ def connect(self):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ con = SSL.Connection(self.ssl_ctx, sock)
+ self.sock = SSLConnection.SSLConnection(con)
+ if sys.version_info[:3] >= (2, 3, 0):
+ self.sock.settimeout(self._timeout)
+ self.sock.connect((self.host, self.port))
+
+
+class HTTPS(httplib.HTTP):
+ """Compatibility with 1.5 httplib interface
+
+ Python 1.5.2 did not have an HTTPS class, but it defined an
+ interface for sending http requests that is also useful for
+ https.
+ """
+
+ _connection_class = HTTPSConnection
+
+ def __init__(self, host='', port=None, ssl_context=None, strict=None, timeout=None):
+ self._setup(self._connection_class(host, port, ssl_context, strict, timeout))
+
diff --git a/func/SSLConnection.py b/func/SSLConnection.py
new file mode 100644
index 0000000..47529b4
--- /dev/null
+++ b/func/SSLConnection.py
@@ -0,0 +1,161 @@
+#!/usr/bin/python
+#
+# Higher-level SSL objects used by rpclib
+#
+# Copyright (c) 2002 Red Hat, Inc.
+#
+# Author: Mihai Ibanescu <misa@redhat.com>
+# Modifications by Dan Williams <dcbw@redhat.com>
+
+
+from OpenSSL import SSL, crypto
+import os, string, time, socket, select
+
+
+class SSLConnection:
+ """
+ This whole class exists just to filter out a parameter
+ passed in to the shutdown() method in SimpleXMLRPC.doPOST()
+ """
+
+ DEFAULT_TIMEOUT = 20
+
+ def __init__(self, conn):
+ """
+ Connection is not yet a new-style class,
+ so I'm making a proxy instead of subclassing.
+ """
+ self.__dict__["conn"] = conn
+ self.__dict__["close_refcount"] = 0
+ self.__dict__["closed"] = False
+ self.__dict__["timeout"] = self.DEFAULT_TIMEOUT
+
+ def __del__(self):
+ self.__dict__["conn"].close()
+
+ def __getattr__(self,name):
+ return getattr(self.__dict__["conn"], name)
+
+ def __setattr__(self,name, value):
+ setattr(self.__dict__["conn"], name, value)
+
+ def settimeout(self, timeout):
+ if timeout == None:
+ self.__dict__["timeout"] = self.DEFAULT_TIMEOUT
+ else:
+ self.__dict__["timeout"] = timeout
+ self.__dict__["conn"].settimeout(timeout)
+
+ def shutdown(self, how=1):
+ """
+ SimpleXMLRpcServer.doPOST calls shutdown(1),
+ and Connection.shutdown() doesn't take
+ an argument. So we just discard the argument.
+ """
+ self.__dict__["conn"].shutdown()
+
+ def accept(self):
+ """
+ This is the other part of the shutdown() workaround.
+ Since servers create new sockets, we have to infect
+ them with our magic. :)
+ """
+ c, a = self.__dict__["conn"].accept()
+ return (SSLConnection(c), a)
+
+ def makefile(self, mode, bufsize):
+ """
+ We need to use socket._fileobject Because SSL.Connection
+ doesn't have a 'dup'. Not exactly sure WHY this is, but
+ this is backed up by comments in socket.py and SSL/connection.c
+
+ Since httplib.HTTPSResponse/HTTPConnection depend on the
+ socket being duplicated when they close it, we refcount the
+ socket object and don't actually close until its count is 0.
+ """
+ self.__dict__["close_refcount"] = self.__dict__["close_refcount"] + 1
+ return PlgFileObject(self, mode, bufsize)
+
+ def close(self):
+ if self.__dict__["closed"]:
+ return
+ self.__dict__["close_refcount"] = self.__dict__["close_refcount"] - 1
+ if self.__dict__["close_refcount"] == 0:
+ self.shutdown()
+ self.__dict__["conn"].close()
+ self.__dict__["closed"] = True
+
+ def sendall(self, data, flags=0):
+ """
+ - Use select() to simulate a socket timeout without setting the socket
+ to non-blocking mode.
+ - Don't use pyOpenSSL's sendall() either, since it just loops on WantRead
+ or WantWrite, consuming 100% CPU, and never times out.
+ """
+ timeout = self.__dict__["timeout"]
+ con = self.__dict__["conn"]
+ (read, write, excpt) = select.select([], [con], [], timeout)
+ if not con in write:
+ raise socket.timeout((110, "Operation timed out."))
+
+ starttime = time.time()
+ origlen = len(data)
+ sent = -1
+ while len(data):
+ curtime = time.time()
+ if curtime - starttime > timeout:
+ raise socket.timeout((110, "Operation timed out."))
+
+ try:
+ sent = con.send(data, flags)
+ except SSL.SysCallError, e:
+ if e[0] == 32: # Broken Pipe
+ self.close()
+ sent = 0
+ else:
+ raise socket.error(e)
+ except (SSL.WantWriteError, SSL.WantReadError):
+ time.sleep(0.2)
+ continue
+
+ data = data[sent:]
+ return origlen - len(data)
+
+ def recv(self, bufsize, flags=0):
+ """
+ Use select() to simulate a socket timeout without setting the socket
+ to non-blocking mode
+ """
+ timeout = self.__dict__["timeout"]
+ con = self.__dict__["conn"]
+ (read, write, excpt) = select.select([con], [], [], timeout)
+ if not con in read:
+ raise socket.timeout((110, "Operation timed out."))
+
+ starttime = time.time()
+ while True:
+ curtime = time.time()
+ if curtime - starttime > timeout:
+ raise socket.timeout((110, "Operation timed out."))
+
+ try:
+ return con.recv(bufsize, flags)
+ except SSL.ZeroReturnError:
+ return None
+ except SSL.WantReadError:
+ time.sleep(0.2)
+ return None
+
+class PlgFileObject(socket._fileobject):
+ def close(self):
+ """
+ socket._fileobject doesn't actually _close_ the socket,
+ which we want it to do, so we have to override.
+ """
+ try:
+ if self._sock:
+ self.flush()
+ self._sock.close()
+ finally:
+ self._sock = None
+
diff --git a/func/certmaster.py b/func/certmaster.py
new file mode 100755
index 0000000..02c8013
--- /dev/null
+++ b/func/certmaster.py
@@ -0,0 +1,171 @@
+#!/usr/bin/python
+
+"""
+cert master listener
+
+Copyright 2007, Red Hat, Inc
+see AUTHORS
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+# standard modules
+import SimpleXMLRPCServer
+import string
+import socket
+import sys
+import os
+import os.path
+import traceback
+from OpenSSL import crypto
+import sha
+
+#from func.server import codes
+import func
+import func.certs
+
+
+class SimpleConfigFile(object):
+ """simple config file object:
+ reads in key=value pairs from a file and stores each as an attribute"""
+
+ def __init__(self, filename):
+ self.fn = filename
+ fo = open(filename, 'r')
+ for line in fo.readlines():
+ if line.startswith('#'): continue
+ if line.strip() == '': continue
+ (key, val) = line.split('=')
+ key = key.strip().lower()
+ val = val.strip()
+ setattr(self, key, val)
+ fo.close()
+
+
+class CertMaster(object):
+ def __init__(self, conf_file):
+ self.cfg = SimpleConfigFile(conf_file)
+ self.listen_addr = 'localhost'
+ self.listen_port = '51235'
+ self.cadir = '/etc/pki/func/ca'
+ self.certroot = '/etc/pki/func/ca/certs'
+ self.csrroot = '/etc/pki/func/ca/csrs'
+ self.autosign = True
+ for attr in ['listen_addr', 'listen_port', 'cadir', 'certroot',
+ 'csrroot']:
+ if hasattr(self.cfg, attr):
+ setattr(self, attr, getattr(self.cfg, attr))
+ if hasattr(self.cfg, 'autosign'):
+ if getattr(self.cfg, 'autosign').lower() in ['yes', 'true', 1, 'on']:
+ self.autosign = True
+ elif getattr(self.cfg, 'autosign').lower() in ['no', 'false', 0, 'off']:
+ self.autosign = False
+ # open up the cakey and cacert so we have them available
+ ca_key_file = '%s/funcmaster.key' % self.cadir
+ ca_cert_file = '%s/funcmaster.crt' % self.cadir
+ self.cakey = func.certs.retrieve_key_from_file(ca_key_file)
+ self.cacert = func.certs.retrieve_cert_from_file(ca_cert_file)
+
+ for dirpath in [self.cadir, self.certroot, self.csrroot]:
+ if not os.path.exists(dirpath):
+ os.makedirs(dirpath)
+
+ # setup handlers
+ self.handlers = {
+ 'wait_for_cert': self.wait_for_cert,
+ }
+
+ def _dispatch(self, method, params):
+ if method == 'trait_names' or method == '_getAttributeNames':
+ return self.handlers.keys()
+
+ if method in self.handlers.keys():
+ return self.handlers[method](*params)
+ else:
+ pass
+ #raise codes.InvalidMethodException
+
+
+ def wait_for_cert(self, csrbuf):
+ """
+ takes csr as a string
+ returns True, caller_cert, ca_cert
+ returns False, '', ''
+ """
+
+ try:
+ csrreq = crypto.load_certificate_request(crypto.FILETYPE_PEM, csrbuf)
+ except crypto.Error, e:
+ #XXX need to raise a fault here and document it - but false is just as good
+ return False, '', ''
+
+ requesting_host = csrreq.get_subject().CN
+ certfile = '%s/%s.pem' % (self.certroot, requesting_host)
+ csrfile = '%s/%s.csr' % (self.csrroot, requesting_host)
+
+ # check for old csr on disk
+ # if we have it - compare the two - if they are not the same - raise a fault
+ if os.path.exists(csrfile):
+ oldfo = open(csrfile)
+ oldcsrbuf = oldfo.read()
+ oldsha = sha.new()
+ oldsha.update(oldcsrbuf)
+ olddig = oldsha.hexdigest()
+ newsha = sha.new()
+ newsha.update(csrbuf)
+ newdig = newsha.hexdigest()
+ if not newdig == olddig:
+ # XXX raise a proper fault
+ return False, '', ''
+
+ # look for a cert:
+ # if we have it, then return True, etc, etc
+ if os.path.exists(certfile):
+ slavecert = crypto.load_certificate(crypto.FILETYPE_PEM, certfile)
+
+ cert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, slavecert)
+ cacert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cacert)
+ return True, cert_buf, cacert_buf
+
+ # if we don't have a cert then:
+ # if we're autosign then sign it, write out the cert and return True, etc, etc
+ # else write out the csr
+
+ if self.autosign:
+ slavecert = func.certs.create_slave_certificate(csrreq,
+ self.cakey, self.cacert, self.cadir)
+
+ destfo = open(certfile, 'w')
+ destfo.write(crypto.dump_certificate(crypto.FILETYPE_PEM, slavecert))
+ destfo.close()
+ del destfo
+ cert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, slavecert)
+ cacert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cacert)
+ return True, cert_buf, cacert_buf
+
+ else:
+ # write the csr out to a file to be dealt with by the admin
+ destfo = open(csrfile, 'w')
+ destfo.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, csrreq))
+ destfo.close()
+ del destfo
+ return False, '', ''
+
+ return False, '', ''
+
+
+def serve(xmlrpcinstance):
+
+ """
+ Code for starting the XMLRPC service.
+ """
+
+ server =FuncXMLRPCServer((xmlrpcinstance.listen_addr, xmlrpcinstance.list_port))
+ server.logRequests = 0 # don't print stuff to console
+ server.register_instance(xmlrpcinstance)
+ server.serve_forever()