Sophie

Sophie

distrib > Scientific%20Linux > 5x > x86_64 > by-pkgid > e59fb70cb4d619d418a1b5d002e398c9 > files > 2

ipa-client-2.1.3-5.el5_9.2.src.rpm

From f29806446e58957c836f67021e51c0598a0688db Mon Sep 17 00:00:00 2001
From: John Dennis <jdennis@redhat.com>
Date: Thu, 15 Nov 2012 14:57:52 -0500
Subject: [PATCH 2/3] Use secure method to acquire IPA CA certificate

Major changes ipa-client-install:

* Use GSSAPI connection to LDAP server to download CA cert (now
  the default method)

* Add --ca-cert-file option to load the CA cert from a disk file.
  Validate the file. If this option is used the supplied CA cert
  is considered definitive.

* The insecure HTTP retrieval method is still supported but it must be
  explicitly forced and a warning will be emitted.

* Remain backward compatible with unattended case (except for aberrant
  condition when preexisting /etc/ipa/ca.crt differs from securely
  obtained CA cert, see below)

* If /etc/ipa/ca.crt CA cert preexists the validate it matches the
  securely acquired CA cert, if not:

  - If --unattended and not --force abort with error

  - If interactive query user to accept new CA cert, if not abort

  In either case warn user.

* If interactive and LDAP retrieval fails prompt user if they want to
  proceed with insecure HTTP method

* If not interactive and LDAP retrieval fails abort unless --force

* Backup preexisting /etc/ipa/ca.crt in FileStore prior to execution,
  if ipa-client-install fails it will be restored.

Other changes:

* ipadiscovery no longer retrieves CA cert via HTTP.

* Add new exception class CertificateInvalidError

* Add utility convert_ldap_error() to ipalib.ipautil

* Replace all hardcoded instances of /etc/ipa/ca.crt in
  ipa-client-install with CACERT constant (matches existing practice
  elsewhere).

Signed-off-by: Simo Sorce <simo@redhat.com>
---
 ipa-client/ipa-install/ipa-client-install |  404 +++++++++++++++++++++++++++--
 ipa-client/ipaclient/ipadiscovery.py      |  131 ++++------
 ipa-client/man/ipa-client-install.1       |    8 +
 ipalib/errors.py                          |   22 ++-
 ipapython/ipautil.py                      |   36 +++
 ipaserver/install/certs.py                |    5 +-
 6 files changed, 506 insertions(+), 100 deletions(-)

diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install
index 3a75bfd0fde82258130cffba0c0ce32434142b14..5bba4e3a3751029ebe2b10c359afaf56bb1c8f97 100755
--- a/ipa-client/ipa-install/ipa-client-install
+++ b/ipa-client/ipa-install/ipa-client-install
@@ -29,22 +29,28 @@ try:
     import os
     import time
     import socket
+    import ldap
+    import ldap.sasl
+    import urlparse
+    import base64
     import logging
     import tempfile
     import getpass
     from ipaclient import ipadiscovery
     import ipaclient.ipachangeconf
     import ipaclient.ntpconf
-    from ipapython.ipautil import run, user_input, CalledProcessError, file_exists, realm_to_suffix
+    from ipapython.ipautil import run, user_input, CalledProcessError, file_exists, realm_to_suffix, convert_ldap_error
     import ipapython.services as ipaservices
     from ipapython import ipautil
     from ipapython import dnsclient
     from ipapython import sysrestore
     from ipapython import version
     from ipapython import certmonger
+    from ipapython import errors
     from ipapython.config import IPAOptionParser
     from ConfigParser import RawConfigParser
-    from optparse import SUPPRESS_HELP, OptionGroup
+    from optparse import SUPPRESS_HELP, OptionGroup, OptionValueError
+    from OpenSSL import crypto
 except ImportError:
     print >> sys.stderr, """\
 There was a problem importing one of the required Python modules. The
@@ -54,14 +60,31 @@ error was:
 """ % sys.exc_value
     sys.exit(1)
 
+SUCCESS = 0
 CLIENT_INSTALL_ERROR = 1
 CLIENT_NOT_CONFIGURED = 2
 CLIENT_ALREADY_CONFIGURED = 3
 CLIENT_UNINSTALL_ERROR = 4 # error after restoring files/state
 
+CACERT = "/etc/ipa/ca.crt"
 client_nss_nickname_format = 'IPA Machine Certificate - %s'
 
 def parse_options():
+    def validate_ca_cert_file_option(option, opt, value, parser):
+        if not os.path.exists(value):
+            raise OptionValueError("%s option '%s' does not exist" % (opt, value))
+        if not os.path.isfile(value):
+            raise OptionValueError("%s option '%s' is not a file" % (opt, value))
+        if not os.path.isabs(value):
+            raise OptionValueError("%s option '%s' is not an absolute file path" % (opt, value))
+
+        try:
+            cert = load_certificate_from_file(value)
+        except Exception, e:
+            raise OptionValueError("%s option '%s' is not a valid certificate file" % (opt, value))
+
+        parser.values.ca_cert_file = value
+
     parser = IPAOptionParser(version=version.VERSION)
 
     basic_group = OptionGroup(parser, "basic options")
@@ -92,6 +115,9 @@ def parse_options():
     basic_group.add_option("-U", "--unattended", dest="unattended",
                       action="store_true",
                       help="unattended (un)installation never prompts the user")
+    basic_group.add_option("--ca-cert-file", dest="ca_cert_file",
+                           type="string", action="callback", callback=validate_ca_cert_file_option,
+                           help="load the CA certificate from this file")
     # --on-master is used in ipa-server-install and ipa-replica-install
     # only, it isn't meant to be used on clients.
     basic_group.add_option("--on-master", dest="on_master", action="store_true",
@@ -167,6 +193,49 @@ def nickname_exists(nickname):
         else:
             return False
 
+def load_certificate_from_file(filename):
+    """
+    Using PyOpenSSL load a PEM certificate file and return an x509 object.
+    """
+    fd = open(filename, 'r')
+    data = fd.read()
+    fd.close()
+
+    return crypto.load_certificate(crypto.FILETYPE_PEM, data)
+
+def make_pem(data):
+    """
+    Convert a raw base64-encoded blob into something that looks like a PE
+    file with lines split to 64 characters and proper headers.
+    """
+    pemcert = '\n'.join([data[x:x+64] for x in range(0, len(data), 64)])
+    return '-----BEGIN CERTIFICATE-----\n' + \
+    pemcert + \
+    '\n-----END CERTIFICATE-----'
+
+def write_certificate(dercert, filename):
+    """
+    Write the certificate to a file in PEM format.
+
+    The incoming cert value must be DER-encoded.
+    """
+    try:
+        fp = open(filename, 'w')
+        fp.write(make_pem(base64.b64encode(dercert)))
+        fp.close()
+    except (IOError, OSError), e:
+        raise errors.FileError(reason=str(e))
+
+def cert_summary(msg, cert, indent='    '):
+    if msg:
+        s = '%s\n' % msg
+    else:
+        s = ''
+    s += '%sSubject:     %s\n' % (indent, str(cert.get_subject())[18:-2])
+    s += '%sIssuer:      %s\n' % (indent, str(cert.get_issuer())[18:-2])
+
+    return s
+
 def emit_quiet(quiet, message):
     if not quiet:
         print message
@@ -586,7 +655,7 @@ def configure_krb5_conf(fstore, cli_basedn, cli_realm, cli_domain, cli_server, c
                  {'name':'default_domain', 'type':'option', 'value':cli_domain}]
     else:
         kropts = []
-    kropts.append({'name':'pkinit_anchors', 'type':'option', 'value':'FILE:/etc/ipa/ca.crt'})
+    kropts.append({'name':'pkinit_anchors', 'type':'option', 'value':'FILE:%s' % CACERT})
     ropts = [{'name':cli_realm, 'type':'subsection', 'value':kropts}]
 
     opts.append({'name':'realms', 'type':'section', 'value':ropts})
@@ -730,7 +799,7 @@ def configure_sssd_conf(fstore, cli_realm, cli_domain, cli_server, options):
     # Note that SSSD will force StartTLS because the channel is later used for
     # authentication as well if password migration is enabled. Thus set the option
     # unconditionally.
-    domain.set_option('ldap_tls_cacert', '/etc/ipa/ca.crt')
+    domain.set_option('ldap_tls_cacert', CACERT)
 
     if options.dns_updates:
         domain.set_option('ipa_dyndns_update', True)
@@ -849,6 +918,308 @@ def client_dns(server, hostname, dns_updates=False):
     if dns_updates or not dns_ok:
         update_dns(server, hostname)
 
+def get_ca_cert_from_file(url):
+    '''
+    Get the CA cert from a user supplied file and write it into the
+    CACERT file.
+
+    Raises errors.NoCertificateError if unable to read cert.
+    Raises errors.FileError if unable to write cert.
+    '''
+
+    # pylint: disable=E1101
+    try:
+        (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url, 'file')
+    except Exception, e:
+        raise errors.FileError("unable to parse file url '%s'" % (url))
+
+    if scheme != 'file':
+        raise errors.FileError("url is not a file scheme '%s'" % (url))
+
+    filename = path
+
+    if not os.path.exists(filename):
+        raise errors.FileError("file '%s' does not exist" % (filename))
+
+    if not os.path.isfile(filename):
+        raise errors.FileError("file '%s' is not a file" % (filename))
+
+    logging.debug("trying to retrieve CA cert from file %s", filename)
+    try:
+        cert = load_certificate_from_file(filename)
+    except Exception, e:
+        raise errors.NoCertificateError(entry=filename)
+
+    try:
+        write_certificate(crypto.dump_certificate(crypto.FILETYPE_ASN1, cert), CACERT)
+    except Exception, e:
+        raise errors.FileError(reason =
+            u"cannot write certificate file '%s': %s" % (CACERT, e))
+
+def get_ca_cert_from_http(url, ca_file, warn=True):
+    '''
+    Use HTTP to retrieve the CA cert and write it into the CACERT file.
+    This is insecure and should be avoided.
+
+    Raises errors.NoCertificateError if unable to retrieve and write cert.
+    '''
+
+    if warn:
+        logging.warning("Downloading the CA certificate via HTTP, " +
+                            "this is INSECURE")
+
+    logging.debug("trying to retrieve CA cert via HTTP from %s", url)
+    try:
+
+        run(["/usr/bin/wget", "-O", ca_file, url])
+    except CalledProcessError, e:
+        raise errors.NoCertificateError(entry=url)
+
+def get_ca_cert_from_ldap(url, basedn, ca_file):
+    '''
+    Retrieve th CA cert from the LDAP server by binding to the
+    server with GSSAPI using the current Kerberos credentials.
+    Write the retrieved cert into the CACERT file.
+
+    Raises errors.NoCertificateError if cert is not found.
+    Raises errors.NetworkError if LDAP connection can't be established.
+    Raises errors.LDAPError for any other generic LDAP error.
+    Raises errors.OnlyOneValueAllowed if more than one cert is found.
+    Raises errors.FileError if unable to write cert.
+    '''
+
+    ca_cert_attr = 'cAcertificate;binary'
+    dn = 'CN=CAcert, CN=ipa, CN=etc, %s' % basedn
+
+    SASL_GSSAPI = ldap.sasl.sasl({},'GSSAPI')
+
+    logging.debug("trying to retrieve CA cert via LDAP from %s", url)
+
+    conn = ldap.initialize(url)
+    try:
+        conn.sasl_interactive_bind_s('', SASL_GSSAPI)
+        result = conn.search_st(str(dn), ldap.SCOPE_BASE, 'objectclass=pkiCA',
+                                [ca_cert_attr], timeout=10)
+    except ldap.NO_SUCH_OBJECT, e:
+        logging.debug("get_ca_cert_from_ldap() error: %s",
+                          convert_ldap_error(e))
+        raise errors.NoCertificateError(entry=url)
+
+    except ldap.SERVER_DOWN, e:
+        logging.debug("get_ca_cert_from_ldap() error: %s",
+                          convert_ldap_error(e))
+        raise errors.NetworkError(uri=url, error=str(e))
+    except Exception, e:
+        logging.debug("get_ca_cert_from_ldap() error: %s",
+                          convert_ldap_error(e))
+        raise errors.LDAPError(str(e))
+
+    if len(result) != 1:
+        raise errors.OnlyOneValueAllowed(attr=ca_cert_attr)
+
+    attrs = result[0][1]
+    try:
+        der_cert = attrs[ca_cert_attr][0]
+    except KeyError:
+        raise errors.NoCertificateError(entry=ca_cert_attr)
+
+    try:
+        write_certificate(der_cert, ca_file)
+    except Exception, e:
+        raise errors.FileError(reason =
+            u"cannot write certificate file '%s': %s" % (ca_file, e))
+
+def validate_new_ca_cert(existing_ca_cert, ca_file, ask, override=False):
+
+    try:
+        new_ca_cert = load_certificate_from_file(ca_file)
+    except Exception, e:
+        raise errors.FileError(
+            "Unable to read new ca cert '%s': %s" % (ca_file, e))
+
+    if existing_ca_cert is None:
+        logging.info(
+            cert_summary("Successfully retrieved CA cert", new_ca_cert))
+        return
+
+    if crypto.dump_certificate(crypto.FILETYPE_ASN1, existing_ca_cert) != crypto.dump_certificate(crypto.FILETYPE_ASN1, existing_ca_cert):
+        logging.warning(
+            "The CA cert available from the IPA server does not match the\n"
+            "local certificate available at %s" % CACERT)
+        logging.warning(
+            cert_summary("Existing CA cert:", existing_ca_cert))
+        logging.warning(
+            cert_summary("Retrieved CA cert:", new_ca_cert))
+        if override:
+            logging.warning("Overriding existing CA cert\n")
+        elif not ask or not user_input(
+                "Do you want to replace the local certificate with the CA\n"
+                "certificate retrieved from the IPA server?", True):
+            raise errors.CertificateInvalidError(name='Retrieved CA')
+    else:
+        logging.debug(
+                "Existing CA cert and Retrieved CA cert are identical")
+        os.remove(ca_file)
+
+
+def get_ca_cert(fstore, options, server, basedn):
+    '''
+    Examine the different options and determine a method for obtaining
+    the CA cert.
+
+    If successful the CA cert will have been written into CACERT.
+
+    Raises errors.NoCertificateError if not successful.
+
+    The logic for determining how to load the CA cert is as follow:
+
+    In the OTP case (not -p and -w):
+
+    1. load from user supplied cert file
+    2. else load from HTTP
+
+    In the 'user_auth' case ((-p and -w) or interactive):
+
+    1. load from user supplied cert file
+    2. load from LDAP using SASL/GSS/Krb5 auth
+       (provides mutual authentication, integrity and security)
+    3. if LDAP failed and interactive ask for permission to
+       use insecure HTTP (default: No)
+
+    In the unattended case:
+
+    1. load from user supplied cert file
+    2. load from HTTP if --force specified else fail
+
+    In all cases if HTTP is used emit warning message
+    '''
+
+    ca_file = CACERT + ".new"
+
+    def ldap_url():
+        return urlparse.urlunparse(('ldap', ipautil.format_netloc(server),
+                                   '', '', '', ''))
+
+    def file_url():
+        return urlparse.urlunparse(('file', '', options.ca_cert_file,
+                                   '', '', ''))
+
+    def http_url():
+        return urlparse.urlunparse(('http', ipautil.format_netloc(server),
+                                   '/ipa/config/ca.crt', '', '', ''))
+
+
+    interactive = not options.unattended
+    otp_auth = options.principal is None and options.password is not None
+    existing_ca_cert = None
+
+    if options.ca_cert_file:
+        url = file_url()
+        try:
+            get_ca_cert_from_file(url)
+        except Exception, e:
+            logging.debug(e)
+            raise errors.NoCertificateError(entry=url)
+        logging.debug("CA cert provided by user, use it!")
+    else:
+        if os.path.exists(CACERT):
+            if os.path.isfile(CACERT):
+                try:
+                    existing_ca_cert = load_certificate_from_file(CACERT)
+                except Exception, e:
+                    raise errors.FileError(reason=u"Unable to load existing" +
+                                           " CA cert '%s': %s" % (CACERT, e))
+            else:
+                raise errors.FileError(reason=u"Existing ca cert '%s' is " +
+                                       "not a plain file" % (CACERT))
+
+        if otp_auth:
+            if existing_ca_cert:
+                logging.info("OTP case, CA cert preexisted, use it")
+            else:
+                url = http_url()
+                override = not interactive
+                if interactive and not user_input(
+                    "Do you want download the CA cert from " + url + " ?\n"
+                    "(this is INSECURE)", False):
+                    raise errors.NoCertificateError(message=u"HTTP certificate"
+                            " download declined by user")
+                try:
+                    get_ca_cert_from_http(url, ca_file, override)
+                except Exception, e:
+                    logging.debug(e)
+                    raise errors.NoCertificateError(entry=url)
+
+                try:
+                    validate_new_ca_cert(existing_ca_cert, ca_file,
+                                         False, override)
+                except Exception, e:
+                    os.unlink(ca_file)
+                    raise
+        else:
+            # Auth with user credentials
+            url = ldap_url()
+            try:
+                get_ca_cert_from_ldap(url, basedn, ca_file)
+                try:
+                    validate_new_ca_cert(existing_ca_cert,
+                                         ca_file, interactive)
+                except Exception, e:
+                    os.unlink(ca_file)
+                    raise
+            except errors.NoCertificateError, e:
+                logging.debug(str(e))
+                url = http_url()
+                if existing_ca_cert:
+                    logging.warning(
+                        "Unable to download CA cert from LDAP\n"
+                        "but found preexisting cert, using it.\n")
+                elif interactive and not user_input(
+                    "Unable to download CA cert from LDAP.\n"
+                    "Do you want download the CA cert form " + url + "?\n"
+                    "(this is INSECURE)", False):
+                    raise errors.NoCertificateError(message=u"HTTP "
+                                "certificate download declined by user")
+                elif not interactive and not options.force:
+                    logging.error(
+                        "In unattended mode without a One Time Password "
+                        "(OTP) or without --ca-cert-file\nYou must specify"
+                        " --force to retrieve the CA cert using HTTP")
+                    raise errors.NoCertificateError(message=u"HTTP "
+                                "certificate download requires --force")
+                else:
+                    try:
+                        get_ca_cert_from_http(url, ca_file)
+                    except Exception, e:
+                        logging.debug(e)
+                        raise errors.NoCertificateError(entry=url)
+                    try:
+                        validate_new_ca_cert(existing_ca_cert,
+                                             ca_file, interactive)
+                    except Exception, e:
+                        os.unlink(ca_file)
+                        raise
+            except Exception, e:
+                logging.debug(str(e))
+                raise errors.NoCertificateError(entry=url)
+
+
+        # We should have a cert now, move it to the canonical place
+        if os.path.exists(ca_file):
+            os.rename(ca_file, CACERT)
+        elif existing_ca_cert is None:
+            raise errors.InternalError(u"expected CA cert file '%s' to "
+                                       u"exist, but it's absent" % (ca_file))
+
+
+    # Make sure the file permissions are correct
+    try:
+        os.chmod(CACERT, 0644)
+    except Exception, e:
+        raise errors.FileError(reason=u"Unable set permissions on ca "
+                               u"cert '%s': %s" % (CACERT, e))
+
+
 def install(options, env, fstore, statestore):
     dnsok = False
 
@@ -986,20 +1357,6 @@ def install(options, env, fstore, statestore):
             options.principal = user_input("User authorized to enroll computers", allow_empty=False)
             logging.debug("will use principal: %s\n", options.principal)
 
-    # Get the CA certificate
-    try:
-        # Remove anything already there so that wget doesn't use its
-        # too-clever renaming feature
-        os.remove("/etc/ipa/ca.crt")
-    except:
-        pass
-
-    try:
-        run(["/usr/bin/wget", "-O", "/etc/ipa/ca.crt", "http://%s/ipa/config/ca.crt" % ipautil.format_netloc(cli_server)])
-    except CalledProcessError, e:
-        print 'Retrieving CA from %s failed.\n%s' % (cli_server, str(e))
-        return CLIENT_INSTALL_ERROR
-
     if not options.on_master:
         nolog = tuple()
         # First test out the kerberos configuration
@@ -1080,6 +1437,15 @@ def install(options, env, fstore, statestore):
                 join_args.append(password)
                 nolog = (password,)
 
+            # Get the CA certificate
+            try:
+                os.environ['KRB5_CONFIG'] = env['KRB5_CONFIG']
+                get_ca_cert(fstore, options, cli_server, cli_basedn)
+                del os.environ['KRB5_CONFIG']
+            except Exception, e:
+                logging.error("Cannot obtain CA certificate\n%s", e)
+                return CLIENT_INSTALL_ERROR
+
             # Now join the domain
             (stdout, stderr, returncode) = run(join_args, raiseonerr=False, env=env, nolog=nolog)
 
@@ -1116,7 +1482,7 @@ def install(options, env, fstore, statestore):
         print "Configured /etc/sssd/sssd.conf"
 
     # Add the CA to the default NSS database and trust it
-    run(["/usr/bin/certutil", "-A", "-d", "/etc/pki/nssdb", "-n", "IPA CA", "-t", "CT,C,C", "-a", "-i", "/etc/ipa/ca.crt"])
+    run(["/usr/bin/certutil", "-A", "-d", "/etc/pki/nssdb", "-n", "IPA CA", "-t", "CT,C,C", "-a", "-i", CACERT])
 
     # If on master assume kerberos is already configured properly.
     if not options.on_master:
diff --git a/ipa-client/ipaclient/ipadiscovery.py b/ipa-client/ipaclient/ipadiscovery.py
index 9e446b0174718ad8bf1b74495cd2f97969bbc91b..10ffea36c16ddce034fe47b6c5391a75f6e79da2 100644
--- a/ipa-client/ipaclient/ipadiscovery.py
+++ b/ipa-client/ipaclient/ipadiscovery.py
@@ -194,9 +194,7 @@ class IPADiscovery:
     def ipacheckldap(self, thost, trealm):
         """
         Given a host and kerberos realm verify that it is an IPA LDAP
-        server hosting the realm. The connection is an SSL connection
-        so the remote IPA CA cert must be available at
-        http://HOST/ipa/config/ca.crt
+        server hosting the realm.
 
         Returns a list [errno, host, realm] or an empty list on error.
         Errno is an error number:
@@ -214,87 +212,64 @@ class IPADiscovery:
 
         i = 0
 
-        # Get the CA certificate
-        try:
-            # Create TempDir
-            temp_ca_dir = tempfile.mkdtemp()
-        except OSError, e:
-            raise RuntimeError("Creating temporary directory failed: %s" % str(e))
-
-        try:
-            run(["/usr/bin/wget", "-O", "%s/ca.crt" % temp_ca_dir, "-T", "15", "-t", "2",
-                 "http://%s/ipa/config/ca.crt" % format_netloc(thost)])
-        except CalledProcessError, e:
-            logging.debug('Retrieving CA from %s failed.\n%s' % (thost, str(e)))
-            return [NOT_IPA_SERVER]
-
         #now verify the server is really an IPA server
         try:
-            try:
-                logging.debug("Init ldap with: ldap://"+format_netloc(thost, 389))
-                lh = ldap.initialize("ldap://"+format_netloc(thost, 389))
-                ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, True)
-                ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, "%s/ca.crt" % temp_ca_dir)
-                lh.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
-                lh.set_option(ldap.OPT_X_TLS_DEMAND, True)
-                lh.start_tls_s()
-                lh.simple_bind_s("","")
-
-                # get IPA base DN
-                logging.debug("Search LDAP server for IPA base DN")
-                basedn = get_ipa_basedn(lh)
-
-                if basedn is None:
-                    return [NOT_IPA_SERVER]
-
-                self.basedn = basedn
-
-                #search and return known realms
-                logging.debug("Search for (objectClass=krbRealmContainer) in "+self.basedn+"(sub)")
-                lret = lh.search_s("cn=kerberos,"+self.basedn, ldap.SCOPE_SUBTREE, "(objectClass=krbRealmContainer)")
-                if not lret:
-                    #something very wrong
-                    return [REALM_NOT_FOUND]
-                logging.debug("Found: "+str(lret))
-
-                for lres in lret:
-                    for lattr in lres[1]:
-                        if lattr.lower() == "cn":
-                            lrealms.append(lres[1][lattr][0])
-
-
-                if trealm:
-                    for r in lrealms:
-                        if trealm == r:
-                            return [0, thost, trealm]
-                    # must match or something is very wrong
+            logging.debug("Init ldap with: ldap://"+format_netloc(thost, 389))
+            lh = ldap.initialize("ldap://"+format_netloc(thost, 389))
+            lh.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
+            lh.simple_bind_s("","")
+
+            # get IPA base DN
+            logging.debug("Search LDAP server for IPA base DN")
+            basedn = get_ipa_basedn(lh)
+
+            if basedn is None:
+                return [NOT_IPA_SERVER]
+
+            self.basedn = basedn
+
+            #search and return known realms
+            logging.debug("Search for (objectClass=krbRealmContainer) in "+self.basedn+"(sub)")
+            lret = lh.search_s("cn=kerberos,"+self.basedn, ldap.SCOPE_SUBTREE, "(objectClass=krbRealmContainer)")
+            if not lret:
+                #something very wrong
+                return [REALM_NOT_FOUND]
+            logging.debug("Found: "+str(lret))
+
+            for lres in lret:
+                for lattr in lres[1]:
+                    if lattr.lower() == "cn":
+                        lrealms.append(lres[1][lattr][0])
+
+
+            if trealm:
+                for r in lrealms:
+                    if trealm == r:
+                        return [0, thost, trealm]
+                # must match or something is very wrong
+                return [REALM_NOT_FOUND]
+            else:
+                if len(lrealms) != 1:
+                    #which one? we can't attach to a multi-realm server without DNS working
                     return [REALM_NOT_FOUND]
                 else:
-                    if len(lrealms) != 1:
-                        #which one? we can't attach to a multi-realm server without DNS working
-                        return [REALM_NOT_FOUND]
-                    else:
-                        return [0, thost, lrealms[0]]
-
-                #we shouldn't get here
-                return [UNKNOWN_ERROR]
+                    return [0, thost, lrealms[0]]
 
-            except LDAPError, err:
-                if isinstance(err, ldap.TIMEOUT):
-                    logging.error("LDAP Error: timeout")
-                    return [NO_LDAP_SERVER]
+            #we shouldn't get here
+            return [UNKNOWN_ERROR]
 
-                if isinstance(err, ldap.INAPPROPRIATE_AUTH):
-                    logging.debug("LDAP Error: Anonymous acces not allowed")
-                    return [NO_ACCESS_TO_LDAP]
+        except LDAPError, err:
+            if isinstance(err, ldap.TIMEOUT):
+                logging.error("LDAP Error: timeout")
+                return [NO_LDAP_SERVER]
 
-                logging.error("LDAP Error: %s: %s" %
-                   (err.args[0]['desc'], err.args[0].get('info', '')))
-                return [UNKNOWN_ERROR]
+            if isinstance(err, ldap.INAPPROPRIATE_AUTH):
+                logging.debug("LDAP Error: Anonymous acces not allowed")
+                return [NO_ACCESS_TO_LDAP]
 
-        finally:
-            os.remove("%s/ca.crt" % temp_ca_dir)
-            os.rmdir(temp_ca_dir)
+            logging.error("LDAP Error: %s: %s" %
+               (err.args[0]['desc'], err.args[0].get('info', '')))
+            return [UNKNOWN_ERROR]
 
 
     def ipadnssearchldap(self, tdomain):
diff --git a/ipa-client/man/ipa-client-install.1 b/ipa-client/man/ipa-client-install.1
index 6f5e0fc4590f69163573798ae80f839179e7df77..327e83776ac6298fae2b0eb328fb3d220860156e 100644
--- a/ipa-client/man/ipa-client-install.1
+++ b/ipa-client/man/ipa-client-install.1
@@ -71,6 +71,14 @@ Print debugging information to stdout
 .TP
 \fB\-U\fR, \fB\-\-unattended\fR
 Unattended installation. The user will not be prompted.
+.TP
+\fB\-\-ca-cert-file\fR=\fICA_FILE\fR
+Do not attempt to acquire the IPA CA certificate via automated means,
+instead use the CA certificate found locally in in \fICA_FILE\fR.  The
+\fICA_FILE\fR must be an absolute path to a PEM formatted certificate
+file. The CA certificate found in \fICA_FILE\fR is considered
+authoritative and will be installed without checking to see if it's
+valid for the IPA domain.
 
 .SS "SSSD OPTIONS"
 .TP
diff --git a/ipalib/errors.py b/ipalib/errors.py
index 3013236bbab713e2a6be24a8c229c6e9f4d51e48..36a098a138bd79363c984104a99682b78f1a9513 100644
--- a/ipalib/errors.py
+++ b/ipalib/errors.py
@@ -101,9 +101,11 @@ current block assignments:
 """
 
 from inspect import isclass
-from text import _ as ugettext, ngettext as ungettext
-from constants import TYPE_ERROR
 
+def ugettext(data):
+    return data
+
+TYPE_ERROR = '%s: need a %r; got %r (a %r)'
 
 class PrivateError(StandardError):
     """
@@ -1525,6 +1527,22 @@ class NotRegisteredError(ExecutionError):
     errno = 4306
     format = _('Not registered yet')
 
+class CertificateInvalidError(CertificateError):
+    """
+    **4310** Raised when a certificate is not valid
+
+    For example:
+
+    >>> raise CertificateInvalidError(name=_(u'CA'))
+    Traceback (most recent call last):
+      ...
+    CertificateInvalidError: CA certificate is not valid
+
+    """
+
+    errno = 4310
+    format = _('%(name)s certificate is not valid')
+
 
 ##############################################################################
 # 5000 - 5999: Generic errors
diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
index e0d6cb4c5426f4e893c9ac3eaad618928448ac9b..1bf671d3d096c2b30d4fc900a3abbd942ef8d57e 100644
--- a/ipapython/ipautil.py
+++ b/ipapython/ipautil.py
@@ -1193,3 +1193,39 @@ def backup_config_and_replace_variables(fstore, filepath, replacevars=dict(), ap
     old_values = config_replace_variables(filepath, replacevars, appendvars)
 
     return old_values
+
+def convert_ldap_error(exc):
+    """
+    Make LDAP exceptions prettier.
+
+    Some LDAP exceptions have a dict with descriptive information, if
+    this exception has a dict extract useful information from it and
+    format it into something usable and return that. If the LDAP
+    exception does not have an information dict then return the name
+    of the LDAP exception.
+
+    If the exception is not an LDAP exception then convert the
+    exception to a string and return that instead.
+    """
+    if isinstance(exc, ldap.LDAPError):
+        name = exc.__class__.__name__
+
+        if len(exc.args):
+            d = exc.args[0]
+            if isinstance(d, dict):
+                desc = d.get('desc', '').strip()
+                info = d.get('info', '').strip()
+                if desc and info:
+                    return '%s %s' % (desc, info)
+                elif desc:
+                    return desc
+                elif info:
+                    return info
+                else:
+                    return name
+            else:
+                return name
+        else:
+            return name
+    else:
+        return str(exc)
diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py
index feac48a89c9acc961c8490ae0559619da221b041..52d4b9814f3ba063a14889e06ceb7034e1704470 100644
--- a/ipaserver/install/certs.py
+++ b/ipaserver/install/certs.py
@@ -226,7 +226,10 @@ class CertDB(object):
             self.subject_base = "O=IPA"
         self.subject_format = "CN=%%s,%s" % self.subject_base
 
-        self.cacert_name = get_ca_nickname(self.realm)
+        if self.self_signed_ca:
+            self.cacert_name = get_ca_nickname(self.realm, 'CN=%s Certificate Authority')
+        else:
+            self.cacert_name = get_ca_nickname(self.realm)
         self.valid_months = "120"
         self.keysize = "1024"
 
-- 
1.7.4.1