From f2abe05398e5979dfa723dc353d96dc53fd9fc6f Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Thu, 19 Feb 2009 17:20:37 -0500 Subject: Use OpenSSL for SSL instead of the built-in python version. --- ipa-client/ipa-install/ipa-client-install | 3 + ipa.spec.in | 2 + ipalib/rpc.py | 130 +++++++++++++++++++++++++++++- 3 files changed, 132 insertions(+), 3 deletions(-) diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install index 669e2f2b7..24db3f8f1 100644 --- a/ipa-client/ipa-install/ipa-client-install +++ b/ipa-client/ipa-install/ipa-client-install @@ -374,6 +374,9 @@ def main(): print "Caching of users/groups will not be available after reboot" pass + # Get the CA certificate + run(["/usr/bin/wget", "-O", "/etc/ipa/ca.crt", "http://%s/ipa/config/ca.crt" % cli_server]) + print "Client configuration complete." return 0 diff --git a/ipa.spec.in b/ipa.spec.in index f41454aaa..d07023cd3 100644 --- a/ipa.spec.in +++ b/ipa.spec.in @@ -120,6 +120,7 @@ Requires: krb5-libs Requires: authconfig Requires: pam_krb5 Requires: nss_ldap +Requires: wget %description client IPA is an integrated solution to provide centrally managed Identity (machine, @@ -153,6 +154,7 @@ Requires: python-kerberos >= 1.1-3 %endif Requires: authconfig Requires: gnupg +Requires: pyOpenSSL %description python IPA is an integrated solution to provide centrally managed Identity (machine, diff --git a/ipalib/rpc.py b/ipalib/rpc.py index 207276d5a..5c336ca1d 100644 --- a/ipalib/rpc.py +++ b/ipalib/rpc.py @@ -33,12 +33,15 @@ Also see the `ipaserver.rpcserver` module. from types import NoneType import threading import socket -from xmlrpclib import Binary, Fault, dumps, loads, ServerProxy, SafeTransport +import os +from xmlrpclib import Binary, Fault, dumps, loads, ServerProxy, Transport import kerberos from ipalib.backend import Connectible from ipalib.errors2 import public_errors, PublicError, UnknownError, NetworkError from ipalib import errors2 from ipalib.request import context +from OpenSSL import SSL +import httplib def xml_wrap(value): @@ -175,13 +178,134 @@ def xml_loads(data, encoding='UTF-8'): raise decode_fault(e) -class KerbTransport(SafeTransport): +class SSLTransport(Transport): + """Handles an HTTPS transaction to an XML-RPC server.""" + + def make_connection(self, host): + host, extra_headers, x509 = self.get_host_info(host) + return SSLSocket(host, None, **(x509 or {})) + + +class SSLFile(httplib.SSLFile): + """ + Override the _read method so we can handle PyOpenSSL errors + gracefully. + """ + def _read(self): + buf = '' + while True: + try: + buf = self._ssl.read(self._bufsize) + except SSL.ZeroReturnError: + # Nothing more to be read + break + except SSL.SysCallError, e: + print "SSL exception", e.args + break + except SSL.WantWriteError: + break + except SSL.WantReadError: + break + 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 + + +class FakeSocket(httplib.FakeSocket): + """ + Override this class so we can end up using our own SSLFile + implementation. + """ + def makefile(self, mode, bufsize=None): + if mode != 'r' and mode != 'rb': + raise httplib.UnimplementedFileMode() + return SSLFile(self._shared, self._ssl, bufsize) + + +class SSLConnection(httplib.HTTPConnection): + """ + Use OpenSSL as the SSL provider instead of the built-in python SSL + support. The built-in SSL client doesn't do CA validation. + + By default we will attempt to load the ca-bundle.crt and our own + IPA CA for validation purposes. To add an additional CA to verify + against set the x509['ca_file'] to the path of the CA PEM file in + KerbTransport.get_host_info + """ + default_port = httplib.HTTPSConnection.default_port + + def verify_callback(self, conn, cert, errnum, depth, ok): + """ + Verify callback. If we get here then the certificate is ok. + """ + return ok + + def __init__(self, host, port=None, key_file=None, cert_file=None, + ca_file=None, strict=None): + httplib.HTTPConnection.__init__(self, host, port, strict) + self.key_file = key_file + self.cert_file = cert_file + self.ca_file = ca_file + + def connect(self): + ctx = SSL.Context(SSL.SSLv23_METHOD) + ctx.set_verify(SSL.VERIFY_PEER, self.verify_callback) + if self.key_file: + ctx.use_privatekey_file (self.key_file) + if self.cert_file: + ctx.use_certificate_file(self.cert_file) + if os.path.exists("/etc/pki/tls/certs/ca-bundle.crt"): + ctx.load_verify_locations("/etc/pki/tls/certs/ca-bundle.crt") + if os.path.exists("/etc/ipa/ca.crt"): + ctx.load_verify_locations("/etc/ipa/ca.crt") + if self.ca_file is not None and os.path.exists(self.ca_file): + ctx.load_verify_locations(self.ca_file) + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + ssl = SSL.Connection(ctx, sock) + ssl.connect((self.host, self.port)) + ssl.do_handshake() + self.sock = FakeSocket(sock, ssl) + + +class SSLSocket(httplib.HTTP): + """ + This is more or less equivalent to the httplib.HTTPS class, we juse + use our own connection provider. + """ + _connection_class = SSLConnection + + def __init__(self, host='', port=None, key_file=None, cert_file=None, + ca_file=None, strict=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 + + +class KerbTransport(SSLTransport): """ Handles Kerberos Negotiation authentication to an XML-RPC server. """ def get_host_info(self, host): - (host, extra_headers, x509) = SafeTransport.get_host_info(self, host) + (host, extra_headers, x509) = SSLTransport.get_host_info(self, host) # Set the remote host principal service = "HTTP@" + host.split(':')[0] -- cgit