Dne 23.9.2014 v 20:39 Rob Crittenden napsal(a):
Jan Cholasta wrote:
Hi,

the attached patches fix various bugs and shortcomings in the CA
management and renewal code. Related tickets:
<https://fedorahosted.org/freeipa/ticket/4416>,
<https://fedorahosted.org/freeipa/ticket/4460>.

(Patch 319 was originally posted at
<http://www.redhat.com/archives/freeipa-devel/2014-September/msg00132.html>.)

Only two of the patches includes what ticket(s) they address. Most have
the tersest of commit messages. If got more and more difficult to see
why the changes were needed at all, as you'll see.

Sorry, fixed (hopefully).

Note that most of these patches fix stuff I didn't have time to fix before I posted the original CA management patches, hence the missing tickets.


As a side note, it was rather difficult to review parts of this as
different patches modify exactly the same code in different ways.

General:

These patches don't replace /etc/pki/nssdb, they just add a new
location. Do we need to keep using the shared db because of p11-kit?

It's for backward compatibility.


319:

The post-install script tries to get a cert with nickname 'IPA CA'. It
should also look for '$REALM IPA CA'. I'd use
ipapythhon.certdb.py:CA_NICKNAME_FMT. Having a similar option for
External CA is probably not a bad idea.

I included 'IPA CA' and 'External CA cert', because they are the only nicknames used for the CA certificate in /etc/pki/nssdb before 4.1 (I checked in git). I did not include '$REALM IPA CA', as it is used only in unreleased code.


You don't need the password file to import a cert into an NSS database.

Fixed.


It may be too complex in a spec file but would it be better to try to
fetch things out of LDAP? This nearly screams for a separate script to
do the work.

Sorry, you have to run ipa-certupdate manually for now to update the database. Automatic updates from LDAP will be included in 4.2.

We definitely need a client update script, for other stuff as well.


Are these operations important enough to be logged to
/var/log/ipaupgrade.log? I suspect it's the kind of thing that might
fail and never be noticed.

It couldn't hurt.


At some point we'll upgrade to sql NSS databases. Is this going to
complicate things?

I don't think so. When do we want to do the upgrade?


There are some places you do str(e[2]) that aren't necessary.

Fixed.


Might be worthwhile to clean up the comments regarding XML-RPC while
you're at it and just refer to RPC.

Done.


I'd rename ca_certs_nss to ca_certs_trust for clarity.

OK.


Is the separate helper create_ipa_nssdb() necessary? Should create_db()
be extended to do this extra work?

It is easier to call from the spec file this way.


324:

ACK

325:

The exception from get_cert() is very rough so wouldn't cover the case
of file permissions, missing database, etc, but since the overall
behavior isn't changing, ACK. Might be worth a comment though indicating
a potential source of oddness for the uninitiated.

Comment added.


326:

ACK

327:

@@ -133,6 +116,7 @@ class CertUpdate(admintool.AdminTool):
          criteria = {
              'cert-database': dogtag_constants.ALIAS_DIR,
              'cert-nickname': nickname,
+            'ca-name': 'dogtag-ipa-ca-renew-agent',
          }
          request_id = certmonger.get_request_id(criteria)
          if request_id is not None:

Doesn't seem related to this change.

Moved to patch 340, along with similar change in ipa-cacert-manage in patch 335.


328:

ACK

329:

ACK

330:

I don't understand the reference to ipa.p11-kit (an unowned file, btw).

Added the file to the spec file.


Are you removing any cert that may be left over from some previous install?

Yes. I believe it might what was causing the NSS failures you saw in <http://www.redhat.com/archives/freeipa-devel/2014-July/msg00112.html>.


If the file were to exist it could cause update-ca-trust to be executed
twice. Is that needed?

Where?


I think more clarity in the commit message will clear things up.

OK.


331:

ACK

Updated the patch with a simpler fix and a ticket number.


332:

What is the reason for this refactoring?

It makes it easier to make changes such as the one in patch 333. Also it makes dogtag-ipa-ca-renew-agent more pythonic and easy to read IMHO.


333:

Seems reasonable but why would a CA profile change in the middle of a
request?

334:

Same boat, why?

335:

I kinda get the picture, along with previous patches, but need more info.

Updated commit messages of the above 3 patches with more info.


Some tips on testing would be helpful.

Testing is basically the same as for the original CA management patches - do some server installs, upgrades and uninstalls and run ipa-certupdate and ipa-cacert-manage with different options.


rob


Updated patches attached.

--
Jan Cholasta
>From df518f7f971c2bcdbbdab99244a8185f721945fc Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 18 Sep 2014 16:28:59 +0200
Subject: [PATCH 01/14] Introduce NSS database /etc/ipa/nssdb

This is the new default NSS database for IPA.

/etc/pki/nssdb is still maintained for backward compatibility.
---
 freeipa.spec.in                           |  17 ++++
 ipa-client/ipa-install/ipa-client-install | 159 ++++++++++++++++++------------
 ipa-client/ipaclient/ipa_certupdate.py    |   9 ++
 ipalib/rpc.py                             |   2 +-
 ipaplatform/base/paths.py                 |   2 +-
 ipapython/certdb.py                       |  28 ++++++
 6 files changed, 153 insertions(+), 64 deletions(-)

diff --git a/freeipa.spec.in b/freeipa.spec.in
index 1097c1a..63ff4f1 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -423,6 +423,7 @@ mkdir -p %{buildroot}/%{_localstatedir}/lib/ipa/backup
 mkdir -p %{buildroot}%{_sysconfdir}/ipa/
 /bin/touch %{buildroot}%{_sysconfdir}/ipa/default.conf
 /bin/touch %{buildroot}%{_sysconfdir}/ipa/ca.crt
+mkdir -p %{buildroot}%{_sysconfdir}/ipa/nssdb
 mkdir -p %{buildroot}/%{_localstatedir}/lib/ipa-client/sysrestore
 mkdir -p %{buildroot}%{_sysconfdir}/bash_completion.d
 install -pm 644 contrib/completion/ipa.bash_completion %{buildroot}%{_sysconfdir}/bash_completion.d/ipa
@@ -537,6 +538,17 @@ if [ $1 -gt 1 ] ; then
             /bin/systemctl condrestart ntpd.service 2>&1 || :
         fi
     fi
+
+    if [ ! -f '/etc/ipa/nssdb/cert8.db' -a $restore -ge 2 ]; then
+        python2 -c 'from ipapython.certdb import create_ipa_nssdb; create_ipa_nssdb()' >/dev/null 2>&1
+        tempfile=$(mktemp)
+        if certutil -L -d /etc/pki/nssdb -n 'IPA CA' -a >"$tempfile" 2>/var/log/ipaupgrade.log; then
+            certutil -A -d /etc/ipa/nssdb -n 'IPA CA' -t CT,C,C -a -i "$tempfile" >/var/log/ipaupgrade.log 2>&1
+        elif certutil -L -d /etc/pki/nssdb -n 'External CA cert' -a >"$tempfile" 2>/var/log/ipaupgrade.log; then
+            certutil -A -d /etc/ipa/nssdb -n 'External CA cert' -t C,, -a -i "$tempfile" >/var/log/ipaupgrade.log 2>&1
+        fi
+        rm -f "$tempfile"
+    fi
 fi
 
 %triggerin -n freeipa-client -- openssh-server
@@ -795,6 +807,11 @@ fi
 %dir %attr(0755,root,root) %{_sysconfdir}/ipa/
 %ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/default.conf
 %ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/ca.crt
+%dir %attr(0755,root,root) %{_sysconfdir}/ipa/nssdb
+%ghost %config(noreplace) %{_sysconfdir}/ipa/nssdb/cert8.db
+%ghost %config(noreplace) %{_sysconfdir}/ipa/nssdb/key3.db
+%ghost %config(noreplace) %{_sysconfdir}/ipa/nssdb/secmod.db
+%ghost %config(noreplace) %{_sysconfdir}/ipa/nssdb/pwdfile.txt
 
 %if ! %{ONLY_CLIENT}
 %files tests -f tests-python.list
diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install
index b3da28d..09665e6 100755
--- a/ipa-client/ipa-install/ipa-client-install
+++ b/ipa-client/ipa-install/ipa-client-install
@@ -46,7 +46,7 @@ try:
     from ipaplatform import services
     from ipaplatform.paths import paths
     from ipapython import ipautil, sysrestore, version, certmonger, ipaldap
-    from ipapython import kernel_keyring
+    from ipapython import kernel_keyring, certdb
     from ipapython.config import IPAOptionParser
     from ipalib import api, errors
     from ipalib import x509, certstore
@@ -550,6 +550,15 @@ def uninstall(options, env):
             cmonger.service_name, str(e))
 
     # Remove our host cert and CA cert
+    for filename in (os.path.join(paths.IPA_NSSDB_DIR, 'cert8.db'),
+                     os.path.join(paths.IPA_NSSDB_DIR, 'key3.db'),
+                     os.path.join(paths.IPA_NSSDB_DIR, 'secmod.db'),
+                     os.path.join(paths.IPA_NSSDB_DIR, 'pwdfile.txt')):
+        try:
+            os.remove(filename)
+        except OSError, e:
+            root_logger.error("Failed to remove %s: %s", filename, e)
+
     purge_ipa_certs({client_nss_nickname, 'IPA CA', 'External CA cert'})
 
     try:
@@ -2523,62 +2532,69 @@ def install(options, env, fstore, statestore):
     except ValueError:
         pass
 
-    # Add CA certs to a temporary NSS database
+    tmp_nss_dir = tempfile.mkdtemp()
     try:
-        os.mkdir(paths.IPA_NSSDB_DIR)
-        pwd_file = ipautil.write_tmp_file(ipautil.ipa_generate_password())
-        run([paths.CERTUTIL, '-N',
-             '-d', paths.IPA_NSSDB_DIR,
-             '-f', pwd_file.name])
-
-        ca_certs = x509.load_certificate_list_from_file(CACERT)
-        ca_certs = [cert.der_data for cert in ca_certs]
-        for i, cert in enumerate(ca_certs):
-            run([paths.CERTUTIL, '-A',
-                 '-d', paths.IPA_NSSDB_DIR,
-                 '-n', 'CA certificate %d' % (i + 1),
-                 '-t', 'C,,'],
-                 stdin=cert)
-    except CalledProcessError, e:
-        root_logger.info("Failed to add CA to temporary NSS database.")
-        return CLIENT_INSTALL_ERROR
+        # Add CA certs to a temporary NSS database
+        try:
+            pwd_file = ipautil.write_tmp_file(ipautil.ipa_generate_password())
+            run([paths.CERTUTIL, '-N',
+                 '-d', tmp_nss_dir,
+                 '-f', pwd_file.name])
 
-    # Now, let's try to connect to the server's XML-RPC interface
-    connected = False
-    try:
-        api.Backend.rpcclient.connect(nss_dir=paths.IPA_NSSDB_DIR)
-        connected = True
-        root_logger.debug('Try RPC connection')
-        api.Backend.rpcclient.forward('ping')
-    except errors.KerberosError, e:
-        if connected:
-            api.Backend.rpcclient.disconnect()
-        root_logger.info('Cannot connect to the server due to ' +
-            'Kerberos error: %s. Trying with delegate=True', str(e))
+            ca_certs = x509.load_certificate_list_from_file(CACERT)
+            ca_certs = [cert.der_data for cert in ca_certs]
+            for i, cert in enumerate(ca_certs):
+                run([paths.CERTUTIL, '-A',
+                     '-d', tmp_nss_dir,
+                     '-n', 'CA certificate %d' % (i + 1),
+                     '-t', 'C,,'],
+                     stdin=cert)
+        except CalledProcessError, e:
+            root_logger.info("Failed to add CA to temporary NSS database.")
+            return CLIENT_INSTALL_ERROR
+
+        # Now, let's try to connect to the server's RPC interface
+        connected = False
         try:
-            api.Backend.rpcclient.connect(delegate=True, nss_dir=paths.IPA_NSSDB_DIR)
-            root_logger.debug('Try RPC connection')
+            api.Backend.rpcclient.connect(nss_dir=tmp_nss_dir)
+            connected = True
+            root_logger.debug("Try RPC connection")
             api.Backend.rpcclient.forward('ping')
+        except errors.KerberosError, e:
+            if connected:
+                api.Backend.rpcclient.disconnect()
+            root_logger.info(
+                "Cannot connect to the server due to Kerberos error: %s. "
+                "Trying with delegate=True", e)
+            try:
+                api.Backend.rpcclient.connect(delegate=True,
+                                              nss_dir=tmp_nss_dir)
+                root_logger.debug("Try RPC connection")
+                api.Backend.rpcclient.forward('ping')
 
-            root_logger.info('Connection with delegate=True successful')
+                root_logger.info("Connection with delegate=True successful")
 
-            # The remote server is not capable of Kerberos S4U2Proxy delegation
-            # This features is implemented in IPA server version 2.2 and higher
-            root_logger.warning("Target IPA server has a lower version than " +
-                "the enrolled client")
-            root_logger.warning("Some capabilities including the ipa " +
-                "command capability may not be available")
-        except errors.PublicError, e2:
-            root_logger.warning(
-                'Second connect with delegate=True also failed: %s', str(e2))
+                # The remote server is not capable of Kerberos S4U2Proxy
+                # delegation. This features is implemented in IPA server
+                # version 2.2 and higher
+                root_logger.warning(
+                    "Target IPA server has a lower version than the enrolled "
+                    "client")
+                root_logger.warning(
+                    "Some capabilities including the ipa command capability "
+                    "may not be available")
+            except errors.PublicError, e2:
+                root_logger.warning(
+                    "Second connect with delegate=True also failed: %s", e2)
+                root_logger.error(
+                    "Cannot connect to the IPA server RPC interface: %s", e2)
+                return CLIENT_INSTALL_ERROR
+        except errors.PublicError, e:
             root_logger.error(
-                "Cannot connect to the IPA server XML-RPC interface: %s",
-                str(e2))
+                "Cannot connect to the server due to generic error: %s", e)
             return CLIENT_INSTALL_ERROR
-    except errors.PublicError, e:
-        root_logger.error(
-            'Cannot connect to the server due to generic error: %s', str(e))
-        return CLIENT_INSTALL_ERROR
+    finally:
+        shutil.rmtree(tmp_nss_dir)
 
     # Use the RPC directly so older servers are supported
     result = api.Backend.rpcclient.forward(
@@ -2590,14 +2606,38 @@ def install(options, env, fstore, statestore):
     if not remote_env['enable_ra']:
         disable_ra()
 
+    # Create IPA NSS database
+    try:
+        certdb.create_ipa_nssdb()
+    except ipautil.CalledProcessError, e:
+        root_logger.error("Failed to create IPA NSS database: %s", e)
+        return CLIENT_INSTALL_ERROR
+
     # Get CA certificates from the certificate store
     ca_certs = get_certs_from_ldap(cli_server[0], cli_basedn, cli_realm,
                                    remote_env['enable_ra'])
+    ca_certs_trust = [(c, n, certstore.key_policy_to_trust_flags(t, True, u))
+                      for (c, n, t, u) in ca_certs]
 
-    # Add the CA to the platform-dependant systemwide CA store
+    # Add the CA certificates to the IPA NSS database
+    root_logger.debug("Adding CA certificates to the IPA NSS database.")
+    for cert, nickname, trust_flags in ca_certs_trust:
+        try:
+            run([paths.CERTUTIL,
+                 "-A",
+                 "-d", paths.IPA_NSSDB_DIR,
+                 "-n", nickname,
+                 "-t", trust_flags],
+                stdin=cert)
+        except CalledProcessError, e:
+            root_logger.error(
+                "Failed to add %s to the IPA NSS database.", nickname)
+            return CLIENT_INSTALL_ERROR
+
+    # Add the CA certificates to the platform-dependant systemwide CA store
     tasks.insert_ca_certs_into_systemwide_ca_store(ca_certs)
 
-    # Add the CA to the default NSS database and trust it
+    # Add the CA certificates to the default NSS database
     if not purge_ipa_certs():
         root_logger.info(
             "Failed to remove old IPA certificates from the default NSS "
@@ -2610,12 +2650,10 @@ def install(options, env, fstore, statestore):
         root_logger.error("Failed to open /etc/pki/nssdb/ipa.txt: %s", e)
         return CLIENT_INSTALL_ERROR
 
-    for cert, nickname, trusted, ext_key_usage in ca_certs:
+    root_logger.debug(
+        "Attempting to add CA certificates to the default NSS database.")
+    for cert, nickname, trust_flags in ca_certs_trust:
         try:
-            root_logger.debug("Attempting to add CA directly to the "
-                              "default NSS database.")
-            trust_flags = certstore.key_policy_to_trust_flags(
-                trusted, True, ext_key_usage)
             run([paths.CERTUTIL,
                  "-A",
                  "-d", paths.NSS_DB_DIR,
@@ -2623,12 +2661,13 @@ def install(options, env, fstore, statestore):
                  "-t", trust_flags],
                 stdin=cert)
         except CalledProcessError, e:
-            root_logger.info("Failed to add CA to the default NSS database.")
+            root_logger.error(
+                "Failed to add %s to the default NSS database.", nickname)
             list_file.close()
             return CLIENT_INSTALL_ERROR
         else:
-            root_logger.info('Added the CA to the default NSS database.')
             list_file.write(nickname + '\n')
+    root_logger.info("Added CA certificates to the default NSS database.")
 
     list_file.close()
 
@@ -2854,7 +2893,3 @@ finally:
         os.remove(CCACHE_FILE)
     except Exception:
         pass
-    try:
-        shutil.rmtree(paths.IPA_NSSDB_DIR)
-    except Exception:
-        pass
diff --git a/ipa-client/ipaclient/ipa_certupdate.py b/ipa-client/ipaclient/ipa_certupdate.py
index 8e7fe04..57dbf20 100644
--- a/ipa-client/ipaclient/ipa_certupdate.py
+++ b/ipa-client/ipaclient/ipa_certupdate.py
@@ -70,6 +70,15 @@ class CertUpdate(admintool.AdminTool):
 
     def update_client(self, certs):
         self.update_file(paths.IPA_CA_CRT, certs)
+        self.update_db(paths.IPA_NSSDB_DIR, certs)
+
+        for nickname in ('IPA CA', 'External CA cert'):
+            try:
+                ipautil.run([paths.CERTUTIL, '-D',
+                             '-d', paths.NSS_DB_DIR,
+                             '-n', nickname])
+            except ipautil.CalledProcessError, e:
+                pass
 
         self.update_db(paths.NSS_DB_DIR, certs)
 
diff --git a/ipalib/rpc.py b/ipalib/rpc.py
index e7e60f4..5934f0c 100644
--- a/ipalib/rpc.py
+++ b/ipalib/rpc.py
@@ -483,7 +483,7 @@ class SSLTransport(LanguageAwareTransport):
             if self._connection and host == self._connection[0]:
                 return self._connection[1]
 
-        dbdir = getattr(context, 'nss_dir', paths.NSS_DB_DIR)
+        dbdir = getattr(context, 'nss_dir', paths.IPA_NSSDB_DIR)
         no_init = self.__nss_initialized(dbdir)
         if sys.version_info < (2, 7):
             conn = NSSHTTPS(host, 443, dbdir=dbdir, no_init=no_init)
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index 6f2a29e..1493918 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -63,7 +63,7 @@ class BasePathNamespace(object):
     IPA_DNS_UPDATE_TXT = "/etc/ipa/.dns_update.txt"
     IPA_CA_CRT = "/etc/ipa/ca.crt"
     IPA_DEFAULT_CONF = "/etc/ipa/default.conf"
-    IPA_NSSDB_DIR = "/etc/ipa/.nssdb"
+    IPA_NSSDB_DIR = "/etc/ipa/nssdb"
     KRB5_CONF = "/etc/krb5.conf"
     KRB5_KEYTAB = "/etc/krb5.keytab"
     LDAP_CONF = "/etc/ldap.conf"
diff --git a/ipapython/certdb.py b/ipapython/certdb.py
index a858313..426c809 100644
--- a/ipapython/certdb.py
+++ b/ipapython/certdb.py
@@ -17,6 +17,34 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
+import os
+
+from ipaplatform.paths import paths
+from ipapython import ipautil
+
 CA_NICKNAME_FMT = "%s IPA CA"
+
+
 def get_ca_nickname(realm, format=CA_NICKNAME_FMT):
     return format % realm
+
+
+def create_ipa_nssdb():
+    pwdfile = os.path.join(paths.IPA_NSSDB_DIR, 'pwdfile.txt')
+
+    ipautil.backup_file(pwdfile)
+    ipautil.backup_file(os.path.join(paths.IPA_NSSDB_DIR, 'cert8.db'))
+    ipautil.backup_file(os.path.join(paths.IPA_NSSDB_DIR, 'key3.db'))
+    ipautil.backup_file(os.path.join(paths.IPA_NSSDB_DIR, 'secmod.db'))
+
+    with open(pwdfile, 'w') as f:
+        f.write(ipautil.ipa_generate_password(pwd_len=40))
+    os.chmod(pwdfile, 0600)
+
+    ipautil.run([paths.CERTUTIL,
+         "-N",
+         "-d", paths.IPA_NSSDB_DIR,
+         "-f", pwdfile])
+    os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'cert8.db'), 0644)
+    os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'key3.db'), 0644)
+    os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'secmod.db'), 0644)
-- 
1.9.3

>From 726518ddf819bdb36730635b42ca07e8c5916978 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 18 Sep 2014 11:19:46 +0200
Subject: [PATCH 02/14] Move NSSDatabase from ipaserver.certs to
 ipapython.certdb

https://fedorahosted.org/freeipa/ticket/4416
---
 ipapython/certdb.py        | 256 +++++++++++++++++++++++++++++++++++++++++++++
 ipaserver/install/certs.py | 254 +-------------------------------------------
 2 files changed, 257 insertions(+), 253 deletions(-)

diff --git a/ipapython/certdb.py b/ipapython/certdb.py
index 426c809..9c1dfc4 100644
--- a/ipapython/certdb.py
+++ b/ipapython/certdb.py
@@ -18,8 +18,14 @@
 #
 
 import os
+import re
+import tempfile
+import shutil
+from nss import nss
+from nss.error import NSPRError
 
 from ipaplatform.paths import paths
+from ipapython.ipa_log_manager import root_logger
 from ipapython import ipautil
 
 CA_NICKNAME_FMT = "%s IPA CA"
@@ -48,3 +54,253 @@ def create_ipa_nssdb():
     os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'cert8.db'), 0644)
     os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'key3.db'), 0644)
     os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'secmod.db'), 0644)
+
+
+def find_cert_from_txt(cert, start=0):
+    """
+    Given a cert blob (str) which may or may not contian leading and
+    trailing text, pull out just the certificate part. This will return
+    the FIRST cert in a stream of data.
+
+    Returns a tuple (certificate, last position in cert)
+    """
+    s = cert.find('-----BEGIN CERTIFICATE-----', start)
+    e = cert.find('-----END CERTIFICATE-----', s)
+    if e > 0:
+        e = e + 25
+
+    if s < 0 or e < 0:
+        raise RuntimeError("Unable to find certificate")
+
+    cert = cert[s:e]
+    return (cert, e)
+
+
+class NSSDatabase(object):
+    """A general-purpose wrapper around a NSS cert database
+
+    For permanent NSS databases, pass the cert DB directory to __init__
+
+    For temporary databases, do not pass nssdir, and call close() when done
+    to remove the DB. Alternatively, a NSSDatabase can be used as a
+    context manager that calls close() automatically.
+    """
+    # Traditionally, we used CertDB for our NSS DB operations, but that class
+    # got too tied to IPA server details, killing reusability.
+    # BaseCertDB is a class that knows nothing about IPA.
+    # Generic NSS DB code should be moved here.
+    def __init__(self, nssdir=None):
+        if nssdir is None:
+            self.secdir = tempfile.mkdtemp()
+            self._is_temporary = True
+        else:
+            self.secdir = nssdir
+            self._is_temporary = False
+
+    def close(self):
+        if self._is_temporary:
+            shutil.rmtree(self.secdir)
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, type, value, tb):
+        self.close()
+
+    def run_certutil(self, args, stdin=None):
+        new_args = [paths.CERTUTIL, "-d", self.secdir]
+        new_args = new_args + args
+        return ipautil.run(new_args, stdin)
+
+    def create_db(self, password_filename):
+        """Create cert DB
+
+        :param password_filename: Name of file containing the database password
+        """
+        self.run_certutil(["-N", "-f", password_filename])
+
+    def list_certs(self):
+        """Return nicknames and cert flags for all certs in the database
+
+        :return: List of (name, trust_flags) tuples
+        """
+        certs, stderr, returncode = self.run_certutil(["-L"])
+        certs = certs.splitlines()
+
+        # FIXME, this relies on NSS never changing the formatting of certutil
+        certlist = []
+        for cert in certs:
+            match = re.match(r'^(.+?)\s+(\w*,\w*,\w*)\s*$', cert)
+            if match:
+                certlist.append(match.groups())
+
+        return tuple(certlist)
+
+    def find_server_certs(self):
+        """Return nicknames and cert flags for server certs in the database
+
+        Server certs have an "u" character in the trust flags.
+
+        :return: List of (name, trust_flags) tuples
+        """
+        server_certs = []
+        for name, flags in self.list_certs():
+            if 'u' in flags:
+                server_certs.append((name, flags))
+
+        return server_certs
+
+    def get_trust_chain(self, nickname):
+        """Return names of certs in a given cert's trust chain
+
+        :param nickname: Name of the cert
+        :return: List of certificate names
+        """
+        root_nicknames = []
+        chain, stderr, returncode = self.run_certutil([
+            "-O", "-n", nickname])
+        chain = chain.splitlines()
+
+        for c in chain:
+            m = re.match('\s*"(.*)" \[.*', c)
+            if m:
+                root_nicknames.append(m.groups()[0])
+
+        return root_nicknames
+
+    def import_pkcs12(self, pkcs12_filename, db_password_filename,
+                      pkcs12_passwd=None):
+        args = [paths.PK12UTIL, "-d", self.secdir,
+                "-i", pkcs12_filename,
+                "-k", db_password_filename, '-v']
+        if pkcs12_passwd is not None:
+            pkcs12_passwd = pkcs12_passwd + '\n'
+            args = args + ["-w", paths.DEV_STDIN]
+        try:
+            ipautil.run(args, stdin=pkcs12_passwd)
+        except ipautil.CalledProcessError, e:
+            if e.returncode == 17:
+                raise RuntimeError("incorrect password for pkcs#12 file %s" %
+                                   pkcs12_filename)
+            elif e.returncode == 10:
+                raise RuntimeError("Failed to open %s" % pkcs12_filename)
+            else:
+                raise RuntimeError("unknown error import pkcs#12 file %s" %
+                                   pkcs12_filename)
+
+    def trust_root_cert(self, root_nickname, trust_flags=None):
+        if root_nickname[:7] == "Builtin":
+            root_logger.debug(
+                "No need to add trust for built-in root CAs, skipping %s" %
+                root_nickname)
+        else:
+            if trust_flags is None:
+                trust_flags = 'C,,'
+            try:
+                self.run_certutil(["-M", "-n", root_nickname,
+                                   "-t", trust_flags])
+            except ipautil.CalledProcessError, e:
+                raise RuntimeError(
+                    "Setting trust on %s failed" % root_nickname)
+
+    def get_cert(self, nickname, pem=False):
+        args = ['-L', '-n', nickname]
+        if pem:
+            args.append('-a')
+        else:
+            args.append('-r')
+        try:
+            cert, err, returncode = self.run_certutil(args)
+        except ipautil.CalledProcessError:
+            raise RuntimeError("Failed to get %s" % nickname)
+        return cert
+
+    def export_pem_cert(self, nickname, location):
+        """Export the given cert to PEM file in the given location"""
+        cert = self.get_cert(nickname)
+        with open(location, "w+") as fd:
+            fd.write(cert)
+        os.chmod(location, 0444)
+
+    def import_pem_cert(self, nickname, flags, location):
+        """Import a cert form the given PEM file.
+
+        The file must contain exactly one certificate.
+        """
+        try:
+            with open(location) as fd:
+                certs = fd.read()
+        except IOError as e:
+            raise RuntimeError(
+                "Failed to open %s: %s" % (location, e.strerror)
+            )
+
+        cert, st = find_cert_from_txt(certs)
+        self.add_cert(cert, nickname, flags, pem=True)
+
+        try:
+            find_cert_from_txt(certs, st)
+        except RuntimeError:
+            pass
+        else:
+            raise ValueError('%s contains more than one certificate' %
+                             location)
+
+    def add_cert(self, cert, nick, flags, pem=False):
+        args = ["-A", "-n", nick, "-t", flags]
+        if pem:
+            args.append("-a")
+        self.run_certutil(args, stdin=cert)
+
+    def delete_cert(self, nick):
+        self.run_certutil(["-D", "-n", nick])
+
+    def verify_server_cert_validity(self, nickname, hostname):
+        """Verify a certificate is valid for a SSL server with given hostname
+
+        Raises a ValueError if the certificate is invalid.
+        """
+        certdb = cert = None
+        if nss.nss_is_initialized():
+            nss.nss_shutdown()
+        nss.nss_init(self.secdir)
+        try:
+            certdb = nss.get_default_certdb()
+            cert = nss.find_cert_from_nickname(nickname)
+            intended_usage = nss.certificateUsageSSLServer
+            try:
+                approved_usage = cert.verify_now(certdb, True, intended_usage)
+            except NSPRError, e:
+                if e.errno != -8102:
+                    raise ValueError(e.strerror)
+                approved_usage = 0
+            if not approved_usage & intended_usage:
+                raise ValueError('invalid for a SSL server')
+            if not cert.verify_hostname(hostname):
+                raise ValueError('invalid for server %s' % hostname)
+        finally:
+            del certdb, cert
+            nss.nss_shutdown()
+
+        return None
+
+    def verify_ca_cert_validity(self, nickname):
+        certdb = cert = None
+        if nss.nss_is_initialized():
+            nss.nss_shutdown()
+        nss.nss_init(self.secdir)
+        try:
+            certdb = nss.get_default_certdb()
+            cert = nss.find_cert_from_nickname(nickname)
+            intended_usage = nss.certificateUsageSSLCA
+            try:
+                approved_usage = cert.verify_now(certdb, True, intended_usage)
+            except NSPRError, e:
+                if e.errno != -8102:    # SEC_ERROR_INADEQUATE_KEY_USAGE
+                    raise ValueError(e.strerror)
+                approved_usage = 0
+            if approved_usage & intended_usage != intended_usage:
+                raise ValueError('invalid for a CA')
+        finally:
+            del certdb, cert
+            nss.nss_shutdown()
diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py
index 4d508cd..c2ab59e 100644
--- a/ipaserver/install/certs.py
+++ b/ipaserver/install/certs.py
@@ -19,7 +19,6 @@
 
 import os
 import stat
-import re
 import sys
 import tempfile
 import shutil
@@ -28,15 +27,12 @@ import pwd
 import base64
 from hashlib import sha1
 
-from nss import nss
-from nss.error import NSPRError
-
 from ipapython.ipa_log_manager import root_logger
 from ipapython import dogtag
 from ipapython import sysrestore
 from ipapython import ipautil
 from ipapython import certmonger
-from ipapython.certdb import get_ca_nickname
+from ipapython.certdb import get_ca_nickname, find_cert_from_txt, NSSDatabase
 from ipapython.dn import DN
 from ipalib import pkcs10, x509, api
 from ipalib.errors import CertificateOperationError
@@ -48,24 +44,6 @@ from ipaplatform.paths import paths
 # where apache can reach
 NSS_DIR = paths.HTTPD_ALIAS_DIR
 
-def find_cert_from_txt(cert, start=0):
-    """
-    Given a cert blob (str) which may or may not contian leading and
-    trailing text, pull out just the certificate part. This will return
-    the FIRST cert in a stream of data.
-
-    Returns a tuple (certificate, last position in cert)
-    """
-    s = cert.find('-----BEGIN CERTIFICATE-----', start)
-    e = cert.find('-----END CERTIFICATE-----', s)
-    if e > 0: e = e + 25
-
-    if s < 0 or e < 0:
-        raise RuntimeError("Unable to find certificate")
-
-    cert = cert[s:e]
-    return (cert, e)
-
 def get_cert_nickname(cert):
     """
     Using the subject from cert come up with a nickname suitable
@@ -83,236 +61,6 @@ def get_cert_nickname(cert):
     return (str(dn[0]), dn)
 
 
-class NSSDatabase(object):
-    """A general-purpose wrapper around a NSS cert database
-
-    For permanent NSS databases, pass the cert DB directory to __init__
-
-    For temporary databases, do not pass nssdir, and call close() when done
-    to remove the DB. Alternatively, a NSSDatabase can be used as a
-    context manager that calls close() automatically.
-    """
-    # Traditionally, we used CertDB for our NSS DB operations, but that class
-    # got too tied to IPA server details, killing reusability.
-    # BaseCertDB is a class that knows nothing about IPA.
-    # Generic NSS DB code should be moved here.
-    def __init__(self, nssdir=None):
-        if nssdir is None:
-            self.secdir = tempfile.mkdtemp()
-            self._is_temporary = True
-        else:
-            self.secdir = nssdir
-            self._is_temporary = False
-
-    def close(self):
-        if self._is_temporary:
-            shutil.rmtree(self.secdir)
-
-    def __enter__(self):
-        return self
-
-    def __exit__(self, type, value, tb):
-        self.close()
-
-    def run_certutil(self, args, stdin=None):
-        new_args = [paths.CERTUTIL, "-d", self.secdir]
-        new_args = new_args + args
-        return ipautil.run(new_args, stdin)
-
-    def create_db(self, password_filename):
-        """Create cert DB
-
-        :param password_filename: Name of file containing the database password
-        """
-        self.run_certutil(["-N", "-f", password_filename])
-
-    def list_certs(self):
-        """Return nicknames and cert flags for all certs in the database
-
-        :return: List of (name, trust_flags) tuples
-        """
-        certs, stderr, returncode = self.run_certutil(["-L"])
-        certs = certs.splitlines()
-
-        # FIXME, this relies on NSS never changing the formatting of certutil
-        certlist = []
-        for cert in certs:
-            match = re.match(r'^(.+?)\s+(\w*,\w*,\w*)\s*$', cert)
-            if match:
-                certlist.append(match.groups())
-
-        return tuple(certlist)
-
-    def find_server_certs(self):
-        """Return nicknames and cert flags for server certs in the database
-
-        Server certs have an "u" character in the trust flags.
-
-        :return: List of (name, trust_flags) tuples
-        """
-        server_certs = []
-        for name, flags in self.list_certs():
-            if 'u' in flags:
-                server_certs.append((name, flags))
-
-        return server_certs
-
-    def get_trust_chain(self, nickname):
-        """Return names of certs in a given cert's trust chain
-
-        :param nickname: Name of the cert
-        :return: List of certificate names
-        """
-        root_nicknames = []
-        chain, stderr, returncode = self.run_certutil([
-            "-O", "-n", nickname])
-        chain = chain.splitlines()
-
-        for c in chain:
-            m = re.match('\s*"(.*)" \[.*', c)
-            if m:
-                root_nicknames.append(m.groups()[0])
-
-        return root_nicknames
-
-    def import_pkcs12(self, pkcs12_filename, db_password_filename,
-                      pkcs12_passwd=None):
-        args = [paths.PK12UTIL, "-d", self.secdir,
-                "-i", pkcs12_filename,
-                "-k", db_password_filename, '-v']
-        if pkcs12_passwd is not None:
-            pkcs12_passwd = pkcs12_passwd + '\n'
-            args = args + ["-w", paths.DEV_STDIN]
-        try:
-            ipautil.run(args, stdin=pkcs12_passwd)
-        except ipautil.CalledProcessError, e:
-            if e.returncode == 17:
-                raise RuntimeError("incorrect password for pkcs#12 file %s" %
-                    pkcs12_filename)
-            elif e.returncode == 10:
-                raise RuntimeError("Failed to open %s" % pkcs12_filename)
-            else:
-                raise RuntimeError("unknown error import pkcs#12 file %s" %
-                    pkcs12_filename)
-
-    def trust_root_cert(self, root_nickname, trust_flags=None):
-        if root_nickname[:7] == "Builtin":
-            root_logger.debug(
-                "No need to add trust for built-in root CAs, skipping %s" %
-                root_nickname)
-        else:
-            if trust_flags is None:
-                trust_flags = 'C,,'
-            try:
-                self.run_certutil(["-M", "-n", root_nickname,
-                                   "-t", trust_flags])
-            except ipautil.CalledProcessError, e:
-                raise RuntimeError(
-                    "Setting trust on %s failed" % root_nickname)
-
-    def get_cert(self, nickname, pem=False):
-        args = ['-L', '-n', nickname]
-        if pem:
-            args.append('-a')
-        else:
-            args.append('-r')
-        try:
-            cert, err, returncode = self.run_certutil(args)
-        except ipautil.CalledProcessError:
-            raise RuntimeError("Failed to get %s" % nickname)
-        return cert
-
-    def export_pem_cert(self, nickname, location):
-        """Export the given cert to PEM file in the given location"""
-        cert = self.get_cert(nickname)
-        with open(location, "w+") as fd:
-            fd.write(cert)
-        os.chmod(location, 0444)
-
-    def import_pem_cert(self, nickname, flags, location):
-        """Import a cert form the given PEM file.
-
-        The file must contain exactly one certificate.
-        """
-        try:
-            with open(location) as fd:
-                certs = fd.read()
-        except IOError as e:
-            raise RuntimeError(
-                "Failed to open %s: %s" % (location, e.strerror)
-            )
-
-        cert, st = find_cert_from_txt(certs)
-        self.add_cert(cert, nickname, flags, pem=True)
-
-        try:
-            find_cert_from_txt(certs, st)
-        except RuntimeError:
-            pass
-        else:
-            raise ValueError('%s contains more than one certificate' %
-                             location)
-
-    def add_cert(self, cert, nick, flags, pem=False):
-        args = ["-A", "-n", nick, "-t", flags]
-        if pem:
-            args.append("-a")
-        self.run_certutil(args, stdin=cert)
-
-    def delete_cert(self, nick):
-        self.run_certutil(["-D", "-n", nick])
-
-    def verify_server_cert_validity(self, nickname, hostname):
-        """Verify a certificate is valid for a SSL server with given hostname
-
-        Raises a ValueError if the certificate is invalid.
-        """
-        certdb = cert = None
-        if nss.nss_is_initialized():
-            nss.nss_shutdown()
-        nss.nss_init(self.secdir)
-        try:
-            certdb = nss.get_default_certdb()
-            cert = nss.find_cert_from_nickname(nickname)
-            intended_usage = nss.certificateUsageSSLServer
-            try:
-                approved_usage = cert.verify_now(certdb, True, intended_usage)
-            except NSPRError, e:
-                if e.errno != -8102:
-                    raise ValueError(e.strerror)
-                approved_usage = 0
-            if not approved_usage & intended_usage:
-                raise ValueError('invalid for a SSL server')
-            if not cert.verify_hostname(hostname):
-                raise ValueError('invalid for server %s' % hostname)
-        finally:
-            del certdb, cert
-            nss.nss_shutdown()
-
-        return None
-
-    def verify_ca_cert_validity(self, nickname):
-        certdb = cert = None
-        if nss.nss_is_initialized():
-            nss.nss_shutdown()
-        nss.nss_init(self.secdir)
-        try:
-            certdb = nss.get_default_certdb()
-            cert = nss.find_cert_from_nickname(nickname)
-            intended_usage = nss.certificateUsageSSLCA
-            try:
-                approved_usage = cert.verify_now(certdb, True, intended_usage)
-            except NSPRError, e:
-                if e.errno != -8102:    # SEC_ERROR_INADEQUATE_KEY_USAGE
-                    raise ValueError(e.strerror)
-                approved_usage = 0
-            if approved_usage & intended_usage != intended_usage:
-                raise ValueError('invalid for a CA')
-        finally:
-            del certdb, cert
-            nss.nss_shutdown()
-
-
 class CertDB(object):
     """An IPA-server-specific wrapper around NSS
 
-- 
1.9.3

>From c6747190659f1d12088a52b3c0df5e205cfa2b98 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 18 Sep 2014 11:42:14 +0200
Subject: [PATCH 03/14] Add NSSDatabase.has_nickname for checking nickname
 presence in a NSS DB

---
 ipapython/certdb.py | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/ipapython/certdb.py b/ipapython/certdb.py
index 9c1dfc4..a6f00d3 100644
--- a/ipapython/certdb.py
+++ b/ipapython/certdb.py
@@ -215,6 +215,15 @@ class NSSDatabase(object):
             raise RuntimeError("Failed to get %s" % nickname)
         return cert
 
+    def has_nickname(self, nickname):
+        try:
+            self.get_cert(nickname)
+        except RuntimeError:
+            # This might be error other than "nickname not found". Beware.
+            return False
+        else:
+            return True
+
     def export_pem_cert(self, nickname, location):
         """Export the given cert to PEM file in the given location"""
         cert = self.get_cert(nickname)
-- 
1.9.3

>From 335a1597cabdf5760c563d339e6037c4b2482cb7 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 18 Sep 2014 12:00:15 +0200
Subject: [PATCH 04/14] Use NSSDatabase instead of direct certutil calls in
 client code

---
 ipa-client/ipa-install/ipa-client-install | 50 ++++++++-----------------------
 ipa-client/ipaclient/ipa_certupdate.py    | 14 ++++-----
 ipapython/certdb.py                       | 20 ++++++-------
 3 files changed, 26 insertions(+), 58 deletions(-)

diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install
index 09665e6..ed7ad87 100755
--- a/ipa-client/ipa-install/ipa-client-install
+++ b/ipa-client/ipa-install/ipa-client-install
@@ -226,14 +226,6 @@ def logging_setup(options):
 def log_service_error(name, action, error):
     root_logger.error("%s failed to %s: %s", name, action, str(error))
 
-def nickname_exists(nickname):
-        (sout, serr, returncode) = run([paths.CERTUTIL, "-L", "-d", paths.NSS_DB_DIR, "-n", nickname], raiseonerr=False)
-
-        if returncode == 0:
-            return True
-        else:
-            return False
-
 def purge_ipa_certs(additional=[]):
     filename = paths.NSSDB_IPA_TXT
     if file_exists(filename):
@@ -258,12 +250,11 @@ def purge_ipa_certs(additional=[]):
         if nickname:
             nicknames.add(nickname)
 
+    sys_db = certdb.NSSDatabase(paths.NSS_DB_DIR)
     for nickname in nicknames:
-        while nickname_exists(nickname):
+        while sys_db.has_nickname(nickname):
             try:
-                run([paths.CERTUTIL, "-D",
-                     "-d", paths.NSS_DB_DIR,
-                     "-n", nickname])
+                sys_db.delete_cert(nickname)
             except Exception, e:
                 root_logger.error(
                     "Failed to remove %s from /etc/pki/nssdb: %s", nickname, e)
@@ -2532,23 +2523,16 @@ def install(options, env, fstore, statestore):
     except ValueError:
         pass
 
-    tmp_nss_dir = tempfile.mkdtemp()
-    try:
+    with certdb.NSSDatabase() as tmp_db:
         # Add CA certs to a temporary NSS database
         try:
             pwd_file = ipautil.write_tmp_file(ipautil.ipa_generate_password())
-            run([paths.CERTUTIL, '-N',
-                 '-d', tmp_nss_dir,
-                 '-f', pwd_file.name])
+            tmp_db.create_db(pwd_file.name)
 
             ca_certs = x509.load_certificate_list_from_file(CACERT)
             ca_certs = [cert.der_data for cert in ca_certs]
             for i, cert in enumerate(ca_certs):
-                run([paths.CERTUTIL, '-A',
-                     '-d', tmp_nss_dir,
-                     '-n', 'CA certificate %d' % (i + 1),
-                     '-t', 'C,,'],
-                     stdin=cert)
+                tmp_db.add_cert(cert, 'CA certificate %d' % (i + 1), 'C,,')
         except CalledProcessError, e:
             root_logger.info("Failed to add CA to temporary NSS database.")
             return CLIENT_INSTALL_ERROR
@@ -2556,7 +2540,7 @@ def install(options, env, fstore, statestore):
         # Now, let's try to connect to the server's RPC interface
         connected = False
         try:
-            api.Backend.rpcclient.connect(nss_dir=tmp_nss_dir)
+            api.Backend.rpcclient.connect(nss_dir=tmp_db.secdir)
             connected = True
             root_logger.debug("Try RPC connection")
             api.Backend.rpcclient.forward('ping')
@@ -2568,7 +2552,7 @@ def install(options, env, fstore, statestore):
                 "Trying with delegate=True", e)
             try:
                 api.Backend.rpcclient.connect(delegate=True,
-                                              nss_dir=tmp_nss_dir)
+                                              nss_dir=tmp_db.secdir)
                 root_logger.debug("Try RPC connection")
                 api.Backend.rpcclient.forward('ping')
 
@@ -2593,8 +2577,6 @@ def install(options, env, fstore, statestore):
             root_logger.error(
                 "Cannot connect to the server due to generic error: %s", e)
             return CLIENT_INSTALL_ERROR
-    finally:
-        shutil.rmtree(tmp_nss_dir)
 
     # Use the RPC directly so older servers are supported
     result = api.Backend.rpcclient.forward(
@@ -2621,14 +2603,10 @@ def install(options, env, fstore, statestore):
 
     # Add the CA certificates to the IPA NSS database
     root_logger.debug("Adding CA certificates to the IPA NSS database.")
+    ipa_db = certdb.NSSDatabase(paths.IPA_NSSDB_DIR)
     for cert, nickname, trust_flags in ca_certs_trust:
         try:
-            run([paths.CERTUTIL,
-                 "-A",
-                 "-d", paths.IPA_NSSDB_DIR,
-                 "-n", nickname,
-                 "-t", trust_flags],
-                stdin=cert)
+            ipa_db.add_cert(cert, nickname, trust_flags)
         except CalledProcessError, e:
             root_logger.error(
                 "Failed to add %s to the IPA NSS database.", nickname)
@@ -2652,14 +2630,10 @@ def install(options, env, fstore, statestore):
 
     root_logger.debug(
         "Attempting to add CA certificates to the default NSS database.")
+    sys_db = certdb.NSSDatabase(paths.NSS_DB_DIR)
     for cert, nickname, trust_flags in ca_certs_trust:
         try:
-            run([paths.CERTUTIL,
-                 "-A",
-                 "-d", paths.NSS_DB_DIR,
-                 "-n", nickname,
-                 "-t", trust_flags],
-                stdin=cert)
+            sys_db.add_cert(cert, nickname, trust_flags)
         except CalledProcessError, e:
             root_logger.error(
                 "Failed to add %s to the default NSS database.", nickname)
diff --git a/ipa-client/ipaclient/ipa_certupdate.py b/ipa-client/ipaclient/ipa_certupdate.py
index 57dbf20..f7b0e29 100644
--- a/ipa-client/ipaclient/ipa_certupdate.py
+++ b/ipa-client/ipaclient/ipa_certupdate.py
@@ -22,7 +22,7 @@ import tempfile
 import shutil
 
 from ipapython import (admintool, ipautil, ipaldap, sysrestore, dogtag,
-                       certmonger)
+                       certmonger, certdb)
 from ipaplatform import services
 from ipaplatform.paths import paths
 from ipaplatform.tasks import tasks
@@ -72,11 +72,10 @@ class CertUpdate(admintool.AdminTool):
         self.update_file(paths.IPA_CA_CRT, certs)
         self.update_db(paths.IPA_NSSDB_DIR, certs)
 
+        sys_db = certdb.NSSDatabase(paths.NSS_DB_DIR)
         for nickname in ('IPA CA', 'External CA cert'):
             try:
-                ipautil.run([paths.CERTUTIL, '-D',
-                             '-d', paths.NSS_DB_DIR,
-                             '-n', nickname])
+                sys_db.delete_cert(nickname)
             except ipautil.CalledProcessError, e:
                 pass
 
@@ -165,15 +164,12 @@ class CertUpdate(admintool.AdminTool):
             self.log.error("failed to update %s: %s", filename, e)
 
     def update_db(self, path, certs):
+        db = certdb.NSSDatabase(path)
         for cert, nickname, trusted, eku in certs:
             trust_flags = certstore.key_policy_to_trust_flags(
                 trusted, True, eku)
             try:
-                ipautil.run([paths.CERTUTIL, '-A',
-                             '-d', path,
-                             '-n', nickname,
-                             '-t', trust_flags],
-                             stdin=cert)
+                db.add_cert(cert, nickname, trust_flags)
             except ipautil.CalledProcessError, e:
                 self.log.error(
                     "failed to update %s in %s: %s", nickname, path, e)
diff --git a/ipapython/certdb.py b/ipapython/certdb.py
index a6f00d3..56cbe36 100644
--- a/ipapython/certdb.py
+++ b/ipapython/certdb.py
@@ -36,24 +36,22 @@ def get_ca_nickname(realm, format=CA_NICKNAME_FMT):
 
 
 def create_ipa_nssdb():
-    pwdfile = os.path.join(paths.IPA_NSSDB_DIR, 'pwdfile.txt')
+    db = NSSDatabase(paths.IPA_NSSDB_DIR)
+    pwdfile = os.path.join(db.secdir, 'pwdfile.txt')
 
     ipautil.backup_file(pwdfile)
-    ipautil.backup_file(os.path.join(paths.IPA_NSSDB_DIR, 'cert8.db'))
-    ipautil.backup_file(os.path.join(paths.IPA_NSSDB_DIR, 'key3.db'))
-    ipautil.backup_file(os.path.join(paths.IPA_NSSDB_DIR, 'secmod.db'))
+    ipautil.backup_file(os.path.join(db.secdir, 'cert8.db'))
+    ipautil.backup_file(os.path.join(db.secdir, 'key3.db'))
+    ipautil.backup_file(os.path.join(db.secdir, 'secmod.db'))
 
     with open(pwdfile, 'w') as f:
         f.write(ipautil.ipa_generate_password(pwd_len=40))
     os.chmod(pwdfile, 0600)
 
-    ipautil.run([paths.CERTUTIL,
-         "-N",
-         "-d", paths.IPA_NSSDB_DIR,
-         "-f", pwdfile])
-    os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'cert8.db'), 0644)
-    os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'key3.db'), 0644)
-    os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'secmod.db'), 0644)
+    db.create_db(pwdfile)
+    os.chmod(os.path.join(db.secdir, 'cert8.db'), 0644)
+    os.chmod(os.path.join(db.secdir, 'key3.db'), 0644)
+    os.chmod(os.path.join(db.secdir, 'secmod.db'), 0644)
 
 
 def find_cert_from_txt(cert, start=0):
-- 
1.9.3

>From 3f095bb25558c208af99806171e5be13587632b2 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Mon, 22 Sep 2014 11:13:15 +0200
Subject: [PATCH 05/14] Use /etc/ipa/nssdb to get nicknames of IPA certs
 installed in /etc/pki/nssdb

Previously a list of nicknames was kept in /etc/pki/nssdb/ipa.txt. The file
is removed now.
---
 ipa-client/ipa-install/ipa-client-install | 78 +++++++++----------------------
 ipa-client/ipaclient/ipa_certupdate.py    | 59 +++++++++--------------
 ipaplatform/base/paths.py                 |  1 -
 3 files changed, 42 insertions(+), 96 deletions(-)

diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install
index ed7ad87..6bf6095 100755
--- a/ipa-client/ipa-install/ipa-client-install
+++ b/ipa-client/ipa-install/ipa-client-install
@@ -226,41 +226,6 @@ def logging_setup(options):
 def log_service_error(name, action, error):
     root_logger.error("%s failed to %s: %s", name, action, str(error))
 
-def purge_ipa_certs(additional=[]):
-    filename = paths.NSSDB_IPA_TXT
-    if file_exists(filename):
-        try:
-            with open(filename, 'r') as f:
-                lines = f.readlines()
-        except IOError, e:
-            root_logger.error("Failed to open %s: %s", filename, e)
-            return False
-        finally:
-            try:
-                os.unlink(filename)
-            except OSError, e:
-                root_logger.error("Failed to remove %s: %s", filename, e)
-                return False
-    else:
-        lines = []
-
-    nicknames = set(additional)
-    for line in lines:
-        nickname = line.strip()
-        if nickname:
-            nicknames.add(nickname)
-
-    sys_db = certdb.NSSDatabase(paths.NSS_DB_DIR)
-    for nickname in nicknames:
-        while sys_db.has_nickname(nickname):
-            try:
-                sys_db.delete_cert(nickname)
-            except Exception, e:
-                root_logger.error(
-                    "Failed to remove %s from /etc/pki/nssdb: %s", nickname, e)
-
-    return True
-
 def cert_summary(msg, certs, indent='    '):
     if msg:
         s = '%s\n' % msg
@@ -541,16 +506,32 @@ def uninstall(options, env):
             cmonger.service_name, str(e))
 
     # Remove our host cert and CA cert
-    for filename in (os.path.join(paths.IPA_NSSDB_DIR, 'cert8.db'),
-                     os.path.join(paths.IPA_NSSDB_DIR, 'key3.db'),
-                     os.path.join(paths.IPA_NSSDB_DIR, 'secmod.db'),
-                     os.path.join(paths.IPA_NSSDB_DIR, 'pwdfile.txt')):
+    ipa_db = certdb.NSSDatabase(paths.IPA_NSSDB_DIR)
+    try:
+        ipa_certs = ipa_db.list_certs()
+    except CalledProcessError, e:
+        root_logger.error(
+            "Failed to list certificates in %s: %s", ipa_db.secdir, e)
+        ipa_certs = []
+
+    for filename in (os.path.join(ipa_db.secdir, 'cert8.db'),
+                     os.path.join(ipa_db.secdir, 'key3.db'),
+                     os.path.join(ipa_db.secdir, 'secmod.db'),
+                     os.path.join(ipa_db.secdir, 'pwdfile.txt')):
         try:
             os.remove(filename)
         except OSError, e:
             root_logger.error("Failed to remove %s: %s", filename, e)
 
-    purge_ipa_certs({client_nss_nickname, 'IPA CA', 'External CA cert'})
+    sys_db = certdb.NSSDatabase(paths.NSS_DB_DIR)
+    for nickname, trust_flags in ipa_certs:
+        while sys_db.has_nickname(nickname):
+            try:
+                sys_db.delete_cert(nickname)
+            except Exception, e:
+                root_logger.error("Failed to remove %s from %s: %s",
+                                  nickname, sys_db.secdir, e)
+                break
 
     try:
         cmonger.stop()
@@ -2616,18 +2597,6 @@ def install(options, env, fstore, statestore):
     tasks.insert_ca_certs_into_systemwide_ca_store(ca_certs)
 
     # Add the CA certificates to the default NSS database
-    if not purge_ipa_certs():
-        root_logger.info(
-            "Failed to remove old IPA certificates from the default NSS "
-            "database.")
-        return CLIENT_INSTALL_ERROR
-
-    try:
-        list_file = open(paths.NSSDB_IPA_TXT, 'w')
-    except IOError, e:
-        root_logger.error("Failed to open /etc/pki/nssdb/ipa.txt: %s", e)
-        return CLIENT_INSTALL_ERROR
-
     root_logger.debug(
         "Attempting to add CA certificates to the default NSS database.")
     sys_db = certdb.NSSDatabase(paths.NSS_DB_DIR)
@@ -2637,14 +2606,9 @@ def install(options, env, fstore, statestore):
         except CalledProcessError, e:
             root_logger.error(
                 "Failed to add %s to the default NSS database.", nickname)
-            list_file.close()
             return CLIENT_INSTALL_ERROR
-        else:
-            list_file.write(nickname + '\n')
     root_logger.info("Added CA certificates to the default NSS database.")
 
-    list_file.close()
-
     if not options.on_master:
         client_dns(cli_server[0], hostname, options.dns_updates)
 
diff --git a/ipa-client/ipaclient/ipa_certupdate.py b/ipa-client/ipaclient/ipa_certupdate.py
index f7b0e29..8259755 100644
--- a/ipa-client/ipaclient/ipa_certupdate.py
+++ b/ipa-client/ipaclient/ipa_certupdate.py
@@ -70,49 +70,32 @@ class CertUpdate(admintool.AdminTool):
 
     def update_client(self, certs):
         self.update_file(paths.IPA_CA_CRT, certs)
-        self.update_db(paths.IPA_NSSDB_DIR, certs)
 
+        ipa_db = certdb.NSSDatabase(paths.IPA_NSSDB_DIR)
         sys_db = certdb.NSSDatabase(paths.NSS_DB_DIR)
-        for nickname in ('IPA CA', 'External CA cert'):
-            try:
-                sys_db.delete_cert(nickname)
-            except ipautil.CalledProcessError, e:
-                pass
-
-        self.update_db(paths.NSS_DB_DIR, certs)
 
-        new_nicknames = set(c[1] for c in certs)
-        old_nicknames = set()
-        if ipautil.file_exists(paths.NSSDB_IPA_TXT):
-            try:
-                list_file = open(paths.NSSDB_IPA_TXT, 'r')
-            except IOError, e:
-                self.log.error("failed to open %s: %s", paths.NSSDB_IPA_TXT, e)
-            else:
+        # Remove IPA certs from /etc/pki/nssdb
+        for nickname, trust_flags in ipa_db.list_certs():
+            while sys_db.has_nickname(nickname):
                 try:
-                    lines = list_file.readlines()
-                except IOError, e:
-                    self.log.error(
-                        "failed to read %s: %s", paths.NSSDB_IPA_TXT, e)
-                else:
-                    for line in lines:
-                        nickname = line.strip()
-                        if nickname:
-                            old_nicknames.add(nickname)
-                list_file.close()
-        if new_nicknames != old_nicknames:
-            try:
-                list_file = open(paths.NSSDB_IPA_TXT, 'w')
-            except IOError, e:
-                self.log.error("failed to open %s: %s", paths.NSSDB_IPA_TXT, e)
-            else:
+                    sys_db.delete_cert(nickname)
+                except ipautil.CalledProcessError, e:
+                    self.log.error("Failed to remove %s from %s: %s",
+                                   nickname, sys_db.secdir, e)
+                    break
+
+        # Remove old IPA certs from /etc/ipa/nssdb
+        for nickname in ('IPA CA', 'External CA cert'):
+            while ipa_db.has_nickname(nickname):
                 try:
-                    for nickname in new_nicknames:
-                        list_file.write(nickname + '\n')
-                except IOError, e:
-                    self.log.error(
-                        "failed to write %s: %s", paths.NSSDB_IPA_TXT, e)
-                list_file.close()
+                    ipa_db.delete_cert(nickname)
+                except ipautil.CalledProcessError, e:
+                    self.log.error("Failed to remove %s from %s: %s",
+                                   nickname, ipa_db.secdir, e)
+                    break
+
+        self.update_db(ipa_db.secdir, certs)
+        self.update_db(sys_db.secdir, certs)
 
         tasks.remove_ca_certs_from_systemwide_ca_store()
         tasks.insert_ca_certs_into_systemwide_ca_store(certs)
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index 1493918..5f73d85 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -85,7 +85,6 @@ class BasePathNamespace(object):
     NSSDB_CERT8_DB = "/etc/pki/nssdb/cert8.db"
     NSSDB_KEY3_DB = "/etc/pki/nssdb/key3.db"
     NSSDB_SECMOD_DB = "/etc/pki/nssdb/secmod.db"
-    NSSDB_IPA_TXT = "/etc/pki/nssdb/ipa.txt"
     PKI_TOMCAT = "/etc/pki/pki-tomcat"
     PKI_TOMCAT_ALIAS_DIR = "/etc/pki/pki-tomcat/alias/"
     PKI_TOMCAT_PASSWORD_CONF = "/etc/pki/pki-tomcat/password.conf"
-- 
1.9.3

>From f0ad7deea10203b49f7361ae21a64c0f7d5f5244 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 18 Sep 2014 10:52:56 +0200
Subject: [PATCH 06/14] Check if IPA client is configured in ipa-certupdate

https://fedorahosted.org/freeipa/ticket/4460
---
 ipa-client/ipaclient/ipa_certupdate.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/ipa-client/ipaclient/ipa_certupdate.py b/ipa-client/ipaclient/ipa_certupdate.py
index 8259755..c25dcae 100644
--- a/ipa-client/ipaclient/ipa_certupdate.py
+++ b/ipa-client/ipaclient/ipa_certupdate.py
@@ -41,6 +41,12 @@ class CertUpdate(admintool.AdminTool):
         super(CertUpdate, self).validate_options(needs_root=True)
 
     def run(self):
+        fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
+        if (not fstore.has_files() and
+            not os.path.exists(paths.IPA_DEFAULT_CONF)):
+            raise admintool.ScriptError(
+                "IPA client is not configured on this system.")
+
         api.bootstrap(context='cli_installer')
         api.finalize()
 
-- 
1.9.3

>From 07f3926fe3d08397b0fd37b5b0f2281729d2066f Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 18 Sep 2014 11:15:49 +0200
Subject: [PATCH 07/14] Get server hostname from jsonrpc_uri in ipa-certupdate

---
 ipa-client/ipaclient/ipa_certupdate.py | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/ipa-client/ipaclient/ipa_certupdate.py b/ipa-client/ipaclient/ipa_certupdate.py
index c25dcae..fd6c80d 100644
--- a/ipa-client/ipaclient/ipa_certupdate.py
+++ b/ipa-client/ipaclient/ipa_certupdate.py
@@ -20,6 +20,7 @@
 import os
 import tempfile
 import shutil
+import urlparse
 
 from ipapython import (admintool, ipautil, ipaldap, sysrestore, dogtag,
                        certmonger, certdb)
@@ -50,10 +51,7 @@ class CertUpdate(admintool.AdminTool):
         api.bootstrap(context='cli_installer')
         api.finalize()
 
-        try:
-            server = api.env.server
-        except AttributeError:
-            server = api.env.host
+        server = urlparse.urlsplit(api.env.jsonrpc_uri).hostname
         ldap = ipaldap.IPAdmin(server)
 
         tmpdir = tempfile.mkdtemp(prefix="tmp-")
-- 
1.9.3

>From f924ab3f633a1da4d54ce2228863825ca8898ffd Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 17 Sep 2014 15:04:11 +0200
Subject: [PATCH 08/14] Remove ipa-ca.crt from systemwide CA store on client
 uninstall and cert update

The file was used by previous versions of IPA to provide the IPA CA certificate
to p11-kit and has since been obsoleted by ipa.p11-kit, a file which contains
all the CA certificates and associated trust policy from the LDAP certificate
store.

Since p11-kit is hooked into /etc/httpd/alias, ipa-ca.crt must be removed to
prevent certificate import failures in installer code.

Also add ipa.p11-kit to the files owned by the freeipa-python package.
---
 freeipa.spec.in             |  1 +
 ipaplatform/base/paths.py   |  1 +
 ipaplatform/fedora/tasks.py | 38 ++++++++++++++++++++++++++++----------
 3 files changed, 30 insertions(+), 10 deletions(-)

diff --git a/freeipa.spec.in b/freeipa.spec.in
index 63ff4f1..190030b 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -812,6 +812,7 @@ fi
 %ghost %config(noreplace) %{_sysconfdir}/ipa/nssdb/key3.db
 %ghost %config(noreplace) %{_sysconfdir}/ipa/nssdb/secmod.db
 %ghost %config(noreplace) %{_sysconfdir}/ipa/nssdb/pwdfile.txt
+%ghost %config(noreplace) %{_sysconfdir}/pki/ca-trust/source/ipa.p11-kit
 
 %if ! %{ONLY_CLIENT}
 %files tests -f tests-python.list
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index 5f73d85..baaa109 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -80,6 +80,7 @@ class BasePathNamespace(object):
     PAM_LDAP_CONF = "/etc/pam_ldap.conf"
     PASSWD = "/etc/passwd"
     ETC_PKI_CA_DIR = "/etc/pki-ca"
+    SYSTEMWIDE_CA_STORE = "/etc/pki/ca-trust/source/anchors/"
     IPA_P11_KIT = "/etc/pki/ca-trust/source/ipa.p11-kit"
     NSS_DB_DIR = "/etc/pki/nssdb"
     NSSDB_CERT8_DB = "/etc/pki/nssdb/cert8.db"
diff --git a/ipaplatform/fedora/tasks.py b/ipaplatform/fedora/tasks.py
index 926c0ea..9de6360 100644
--- a/ipaplatform/fedora/tasks.py
+++ b/ipaplatform/fedora/tasks.py
@@ -155,6 +155,16 @@ class FedoraTaskNamespace(BaseTaskNamespace):
         auth_config.execute()
 
     def insert_ca_certs_into_systemwide_ca_store(self, ca_certs):
+        new_cacert_path = os.path.join(paths.SYSTEMWIDE_CA_STORE, 'ipa-ca.crt')
+
+        if os.path.exists(new_cacert_path):
+            try:
+                os.remove(new_cacert_path)
+            except OSError, e:
+                root_logger.error(
+                    "Could not remove %s: %s", new_cacert_path, e)
+                return False
+
         new_cacert_path = paths.IPA_P11_KIT
 
         try:
@@ -247,25 +257,33 @@ class FedoraTaskNamespace(BaseTaskNamespace):
         return False
 
     def remove_ca_certs_from_systemwide_ca_store(self):
-        new_cacert_path = paths.IPA_P11_KIT
+        ipa_ca_crt = os.path.join(paths.SYSTEMWIDE_CA_STORE, 'ipa-ca.crt')
+        update = False
 
         # Remove CA cert from systemwide store
-        if os.path.exists(new_cacert_path):
+        for new_cacert_path in (paths.IPA_P11_KIT, ipa_ca_crt):
+            if not os.path.exists(new_cacert_path):
+                continue
             try:
                 os.remove(new_cacert_path)
-                ipautil.run([paths.UPDATE_CA_TRUST])
             except OSError, e:
-                root_logger.error('Could not remove: %s, %s'
-                                   % (new_cacert_path, str(e)))
-                return False
+                root_logger.error(
+                    "Could not remove %s: %s", new_cacert_path, e)
+            else:
+                update = True
+
+        if update:
+            try:
+                ipautil.run([paths.UPDATE_CA_TRUST])
             except CalledProcessError, e:
-                root_logger.error('Could not update systemwide CA trust '
-                                  'database: %s' % str(e))
+                root_logger.error(
+                    "Could not update systemwide CA trust database: %s", e)
                 return False
             else:
-                root_logger.info('Systemwide CA database updated.')
+                root_logger.info("Systemwide CA database updated.")
+                return True
 
-        return True
+        return False
 
     def backup_and_replace_hostname(self, fstore, statestore, hostname):
         old_hostname = socket.gethostname()
-- 
1.9.3

>From a2c70a9a7f9c9eeb750408b732435a5357e5e27b Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 16 Sep 2014 10:34:57 +0200
Subject: [PATCH 09/14] Fix certmonger.wait_for_request

https://fedorahosted.org/freeipa/ticket/4558
---
 ipapython/certmonger.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ipapython/certmonger.py b/ipapython/certmonger.py
index 85b0e9a..bcfafda 100644
--- a/ipapython/certmonger.py
+++ b/ipapython/certmonger.py
@@ -474,7 +474,7 @@ def check_state(dirs):
 
 def wait_for_request(request_id, timeout=120):
     for i in range(0, timeout, 5):
-        state = get_request_value(request_id, 'state').strip()
+        state = get_request_value(request_id, 'status')
         root_logger.debug("certmonger request is in state %r", state)
         if state in ('CA_REJECTED', 'CA_UNREACHABLE', 'CA_UNCONFIGURED',
                      'NEED_GUIDANCE', 'NEED_CA', 'MONITORING'):
-- 
1.9.3

>From 6e632df843be1a0c5f37e1dff4fba74b11001592 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 16 Sep 2014 10:38:31 +0200
Subject: [PATCH 10/14] Refactor dogtag-ipa-ca-renew-agent

---
 .../certmonger/dogtag-ipa-ca-renew-agent-submit    | 454 +++++++++++++--------
 1 file changed, 287 insertions(+), 167 deletions(-)

diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
index 4f0b78a..81aaf0a 100755
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
@@ -31,6 +31,7 @@ import tempfile
 import shutil
 import base64
 import contextlib
+import json
 
 from ipapython import ipautil
 from ipapython.dn import DN
@@ -53,6 +54,119 @@ UNCONFIGURED = 4
 WAIT_WITH_DELAY = 5
 OPERATION_NOT_SUPPORTED_BY_HELPER = 6
 
+
+class RequestException(Exception):
+    code = None
+    body = ''
+
+
+class RequestGenericException(RequestException):
+    def __init__(self, code, body):
+        super(RequestGenericException, self).__init__(
+            "Unknown error {0}".format(code))
+        self.code = code
+        self.body = body
+
+
+class RequestWaitException(RequestException):
+    def __init__(self, cookie, message):
+        super(RequestWaitException, self).__init__(message)
+        self.cookie = cookie
+
+    @property
+    def body(self):
+        return '{0}\n'.format(self.cookie)
+
+
+class Error(RequestException):
+    @property
+    def body(self):
+        return '{0}\n'.format(self)
+
+
+class Wait(RequestWaitException):
+    def __init__(self, cookie=None):
+        super(Wait, self).__init__(
+            cookie, "Waiting for the request to complete")
+
+    code = WAIT
+
+
+class Rejected(Error):
+    code = REJECTED
+
+
+class Unreachable(Error):
+    code = UNREACHABLE
+
+
+class Unconfigured(Error):
+    code = UNCONFIGURED
+
+
+class WaitWithDelay(RequestWaitException):
+    code = WAIT_WITH_DELAY
+
+    def __init__(self, delay, cookie=None):
+        super(WaitWithDelay, self).__init__(
+            cookie, "Waiting {0}s for the request to complete".format(delay))
+        self.delay = delay
+
+    @property
+    def body(self):
+        return '{0}\n{1}'.format(
+            self.delay, super(WaitWithDelay, self).body)
+
+
+class OperationNotSupported(Error):
+    code = OPERATION_NOT_SUPPORTED_BY_HELPER
+
+
+def request_handler(fn):
+    def decorated(operation, csr, cookie=None, profile=None, cert=None):
+        if operation == 'POLL':
+            if not cookie:
+                raise Unconfigured("Cookie not provided")
+
+            try:
+                context = json.loads(cookie)
+                if not isinstance(context, dict):
+                    raise TypeError
+            except (TypeError, ValueError):
+                raise Unconfigured("Invalid cookie: {0!r}".format(cookie))
+
+            try:
+                child_cookie = context.get('cookie')
+                if isinstance(child_cookie, unicode):
+                    child_cookie = child_cookie.encode('raw_unicode_escape')
+                elif child_cookie is not None:
+                    raise TypeError
+            except (TypeError, UnicodeEncodeError):
+                raise Unconfigured(
+                    "Invalid child cookie in cookie: {0!r}".format(
+                        child_cookie))
+        else:
+            context = {}
+            child_cookie = None
+
+        if profile is not None:
+            context['profile'] = profile.decode('raw_unicode_escape')
+        else:
+            context['profile'] = None
+
+        try:
+            return fn(context, operation, csr, child_cookie, profile, cert)
+        except RequestWaitException, e:
+            if e.cookie is not None:
+                context['cookie'] = e.cookie.decode('raw_unicode_escape')
+            else:
+                context.pop('cookie', None)
+            e.cookie = json.dumps(context)
+            raise e
+
+    return decorated
+
+
 @contextlib.contextmanager
 def ldap_connect():
     conn = None
@@ -64,10 +178,28 @@ def ldap_connect():
         if conn is not None and conn.isconnected():
             conn.disconnect()
 
-def request_cert():
+
+@request_handler
+def request_cert(context, operation, csr, cookie=None, profile=None,
+                 cert=None):
     """
     Request certificate from IPA CA.
     """
+    os.environ['CERTMONGER_OPERATION'] = operation
+    os.environ['CERTMONGER_CSR'] = csr
+    if cookie is not None:
+        os.environ['CERTMONGER_CA_COOKIE'] = cookie
+    else:
+        os.environ.pop('CERTMONGER_CA_COOKIE', None)
+    if profile is not None:
+        os.environ['CERTMONGER_CA_PROFILE'] = profile
+    else:
+        os.environ.pop('CERTMONGER_CA_PROFILE', None)
+    if cert is not None:
+        os.environ['CERTMONGER_CERTIFICATE'] = cert
+    else:
+        os.environ.pop('CERTMONGER_CERTIFICATE', None)
+
     syslog.syslog(syslog.LOG_NOTICE,
                   "Forwarding request to dogtag-ipa-renew-agent")
 
@@ -79,45 +211,51 @@ def request_cert():
 
     syslog.syslog(syslog.LOG_NOTICE, "dogtag-ipa-renew-agent returned %d" % rc)
 
-    if stdout.endswith('\n'):
-        stdout = stdout[:-1]
-
-    if rc == WAIT_WITH_DELAY:
-        delay, sep, cookie = stdout.partition('\n')
-        return (rc, delay, cookie)
+    body = stdout
+    if body.endswith('\n'):
+        body = body[:-1]
+
+    if rc == ISSUED:
+        return body
+    elif rc == WAIT:
+        raise Wait(body)
+    elif rc == REJECTED:
+        raise Rejected(body)
+    elif rc == UNREACHABLE:
+        raise Unreachable(body)
+    elif rc == UNCONFIGURED:
+        raise Unconfigured(body)
+    elif rc == WAIT_WITH_DELAY:
+        delay, cookie = body.split('\n', 1)
+        raise WaitWithDelay(delay, cookie)
+    elif rc == OPERATION_NOT_SUPPORTED_BY_HELPER:
+        raise OperationNotSupported()
     else:
-        return (rc, stdout)
+        raise RequestGenericException(rc, stdout)
+
 
-def store_cert():
+@request_handler
+def store_cert(context, operation, csr, cookie=None, profile=None, cert=None):
     """
     Store certificate in LDAP.
     """
-    operation = os.environ.get('CERTMONGER_OPERATION')
     if operation == 'SUBMIT':
-        attempts = 0
+        context['counter'] = 0
     elif operation == 'POLL':
-        cookie = os.environ.get('CERTMONGER_CA_COOKIE')
-        if not cookie:
-            return (UNCONFIGURED, "Cookie not provided")
-
-        try:
-            attempts = int(cookie)
-        except ValueError:
-            return (UNCONFIGURED, "Invalid cookie: %r" % cookie)
+        if 'counter' not in context:
+            raise Unconfigured("No counter in cookie")
+        if not isinstance(context['counter'], (int, long)):
+            raise Unconfigured(
+                "Invalid counter in cookie: {0!r}".format(context['counter']))
     else:
-        return (OPERATION_NOT_SUPPORTED_BY_HELPER,)
-
-    csr = os.environ.get('CERTMONGER_CSR')
-    if not csr:
-        return (UNCONFIGURED, "Certificate request not provided")
+        raise OperationNotSupported()
 
     nickname = pkcs10.get_friendlyname(csr)
     if not nickname:
-        return (REJECTED, "No friendly name in the certificate request")
+        raise Rejected("No friendly name in the certificate request")
 
-    cert = os.environ.get('CERTMONGER_CERTIFICATE')
     if not cert:
-        return (REJECTED, "New certificate requests not supported")
+        raise Rejected("New certificate requests not supported")
 
     dercert = x509.normalize_certificate(cert)
 
@@ -139,104 +277,88 @@ def store_cert():
             except errors.EmptyModlist:
                 pass
     except Exception, e:
-        attempts += 1
-        if attempts < 10:
+        context['counter'] += 1
+        if context['counter'] < 10:
             syslog.syslog(
                 syslog.LOG_ERR,
                 "Updating renewal certificate failed: %s. Sleeping 30s" % e)
-            return (WAIT_WITH_DELAY, 30, attempts)
+            raise WaitWithDelay(30)
         else:
             syslog.syslog(
                 syslog.LOG_ERR,
                 "Giving up. To retry storing the certificate, resubmit the "
                 "request with profile \"ipaStorage\"")
 
-    return (ISSUED, cert)
+    return cert
+
 
-def request_and_store_cert():
+@request_handler
+def request_and_store_cert(context, operation, csr, cookie=None, profile=None,
+                           cert=None):
     """
     Request certificate from IPA CA and store it in LDAP.
     """
-    operation = os.environ.get('CERTMONGER_OPERATION')
     if operation == 'SUBMIT':
-        state = 'request'
-        cookie = None
+        context['state'] = u'request'
     elif operation == 'POLL':
-        cookie = os.environ.get('CERTMONGER_CA_COOKIE')
-        if not cookie:
-            return (UNCONFIGURED, "Cookie not provided")
-
-        state, sep, cookie = cookie.partition(':')
-        if state not in ('request', 'store'):
-            return (UNCONFIGURED,
-                    "Invalid cookie: %r" % os.environ['CERTMONGER_CA_COOKIE'])
-    else:
-        return (OPERATION_NOT_SUPPORTED_BY_HELPER,)
+        if 'state' not in context:
+            raise Unconfigured("No state name in cookie")
+        if context['state'] not in (u'request', u'store'):
+            raise Unconfigured(
+                "Invalid state name in cookie: {0!r}".format(context['state']))
 
-    if state == 'request':
-        if cookie is None:
-            os.environ['CERTMONGER_OPERATION'] = 'SUBMIT'
-        else:
-            os.environ['CERTMONGER_CA_COOKIE'] = cookie
-
-        result = request_cert()
-        if result[0] == WAIT:
-            return (result[0], 'request:%s' % result[1])
-        elif result[0] == WAIT_WITH_DELAY:
-            return (result[0], result[1], 'request:%s' % result[2])
-        elif result[0] != ISSUED:
-            return result
-        else:
-            cert = result[1]
-            cookie = None
+        if context['state'] == u'store':
+            try:
+                cert = context['certificate']
+                if isinstance(cert, unicode):
+                    cert = cert.encode('raw_unicode_escape')
+                else:
+                    raise TypeError
+            except KeyError:
+                raise Unconfigured("No certificate in cookie")
+            except (TypeError, UnicodeEncodeError):
+                raise Unconfigured(
+                    "Invalid certificate in cookie: {0!r}".format(cert))
     else:
-        cert, sep, cookie = cookie.partition(':')
+        raise OperationNotSupported()
 
-    if cookie is None:
-        os.environ['CERTMONGER_OPERATION'] = 'SUBMIT'
-    else:
-        os.environ['CERTMONGER_CA_COOKIE'] = cookie
-    os.environ['CERTMONGER_CERTIFICATE'] = cert
+    if context['state'] == u'request':
+        cert = request_cert(operation, csr, cookie, profile, cert)
+        context['state'] = u'store'
+        context['certificate'] = cert.decode('raw_unicode_escape')
+        operation = 'SUBMIT'
+        cookie = None
 
-    result = store_cert()
-    if result[0] == WAIT:
-        return (result[0], 'store:%s:%s' % (cert, result[1]))
-    elif result[0] == WAIT_WITH_DELAY:
-        return (result[0], result[1], 'store:%s:%s' % (cert, result[2]))
-    else:
-        return result
+    if context['state'] == u'store':
+        cert = store_cert(operation, csr, cookie, profile, cert)
 
-def retrieve_cert():
+    return cert
+
+
+@request_handler
+def retrieve_cert(context, operation, csr, cookie=None, profile=None,
+                  cert=None):
     """
     Retrieve new certificate from LDAP.
     """
-    operation = os.environ.get('CERTMONGER_OPERATION')
     if operation == 'SUBMIT':
-        attempts = 0
+        context['counter'] = 0
     elif operation == 'POLL':
-        cookie = os.environ.get('CERTMONGER_CA_COOKIE')
-        if not cookie:
-            return (UNCONFIGURED, "Cookie not provided")
-
-        try:
-            attempts = int(cookie)
-        except ValueError:
-            return (UNCONFIGURED, "Invalid cookie: %r" % cookie)
+        if 'counter' not in context:
+            raise Unconfigured("No counter in cookie")
+        if not isinstance(context['counter'], (int, long)):
+            raise Unconfigured(
+                "Invalid counter in cookie: {0!r}".format(context['counter']))
     else:
-        return (OPERATION_NOT_SUPPORTED_BY_HELPER,)
-
-    csr = os.environ.get('CERTMONGER_CSR')
-    if not csr:
-        return (UNCONFIGURED, "Certificate request not provided")
+        raise OperationNotSupported()
 
     nickname = pkcs10.get_friendlyname(csr)
     if not nickname:
-        return (REJECTED, "No friendly name in the certificate request")
+        raise Rejected("No friendly name in the certificate request")
 
-    old_cert = os.environ.get('CERTMONGER_CERTIFICATE')
-    if not old_cert:
-        return (REJECTED, "New certificate requests not supported")
-    old_cert = x509.normalize_certificate(old_cert)
+    if not cert:
+        raise Rejected("New certificate requests not supported")
+    old_cert = x509.normalize_certificate(cert)
 
     syslog.syslog(syslog.LOG_NOTICE, "Updating certificate for %s" % nickname)
 
@@ -252,132 +374,130 @@ def retrieve_cert():
             cert = entry.single_value['usercertificate']
 
         if cert == old_cert:
-            attempts += 1
-            if attempts < 4:
+            context['counter'] += 1
+            if context['counter'] < 4:
                 syslog.syslog(
                     syslog.LOG_INFO,
                     "Updated certificate for %s not available" % nickname)
-                # No cert available yet, tell certmonger to wait another 8 hours
-                return (WAIT_WITH_DELAY, 8 * 60 * 60, attempts)
+                # No cert available yet, tell certmonger to wait another 8 hrs
+                raise WaitWithDelay(8 * 60 * 60)
 
         cert = base64.b64encode(cert)
         cert = x509.make_pem(cert)
 
-    return (ISSUED, cert)
+    return cert
+
 
-def export_csr():
+@request_handler
+def export_csr(context, operation, csr, cookie=None, profile=None, cert=None):
     """
     This does not actually renew the cert, it just writes the CSR provided
     by certmonger to /var/lib/ipa/ca.csr and returns the existing cert.
     """
-    operation = os.environ.get('CERTMONGER_OPERATION')
     if operation != 'SUBMIT':
-        return (OPERATION_NOT_SUPPORTED_BY_HELPER,)
+        raise OperationNotSupported()
 
-    csr = os.environ.get('CERTMONGER_CSR')
-    if not csr:
-        return (UNCONFIGURED, "Certificate request not provided")
-
-    cert = os.environ.get('CERTMONGER_CERTIFICATE')
     if not cert:
-        return (REJECTED, "New certificate requests not supported")
+        raise Rejected("New certificate requests not supported")
 
     csr_file = paths.IPA_CA_CSR
     try:
         with open(csr_file, 'wb') as f:
             f.write(csr)
     except Exception, e:
-        return (UNREACHABLE, "Failed to write %s: %s" % (csr_file, e))
+        raise Unreachable("Failed to write {0}: {1}".format(csr_file, e))
 
-    return (ISSUED, cert)
+    return cert
 
-def renew_ca_cert():
+
+@request_handler
+def renew_ca_cert(context, operation, csr, cookie=None, profile=None,
+                  cert=None):
     """
     This is used for automatic CA certificate renewal.
     """
     cert = os.environ.get('CERTMONGER_CERTIFICATE')
     if not cert:
-        return (REJECTED, "New certificate requests not supported")
+        raise Rejected("New certificate requests not supported")
     is_self_signed = x509.is_self_signed(cert)
 
-    operation = os.environ.get('CERTMONGER_OPERATION')
     if operation == 'SUBMIT':
-        state = 'retrieve'
-
+        context['state'] = u'retrieve'
         if is_self_signed:
             ca = cainstance.CAInstance(host_name=api.env.host, ldapi=False)
             if ca.is_renewal_master():
-                state = 'request'
+                context['state'] = u'request'
     elif operation == 'POLL':
-        cookie = os.environ.get('CERTMONGER_CA_COOKIE')
-        if not cookie:
-            return (UNCONFIGURED, "Cookie not provided")
+        if 'state' not in context:
+            raise Unconfigured("No state name in cookie")
+        if context['state'] not in (u'retrieve', u'request'):
+            raise Unconfigured(
+                "Invalid state name in cookie: {0!r}".format(context['state']))
+    else:
+        raise OperationNotSupported()
 
-        state, sep, cookie = cookie.partition(':')
-        if state not in ('retrieve', 'request'):
-            return (UNCONFIGURED,
-                    "Invalid cookie: %r" % os.environ['CERTMONGER_CA_COOKIE'])
+    if context['state'] == u'retrieve':
+        try:
+            cert = retrieve_cert(operation, csr, cookie, profile, cert)
+        except WaitWithDelay:
+            if not is_self_signed:
+                syslog.syslog(syslog.LOG_ALERT,
+                              "IPA CA certificate is about to expire, "
+                              "use ipa-cacert-manage to renew it")
+            raise
+    elif context['state'] == u'request':
+        profile = 'caCACert'
+        cert = request_and_store_cert(operation, csr, cookie, profile, cert)
+
+    return cert
 
-        os.environ['CERTMONGER_CA_COOKIE'] = cookie
-    else:
-        return (OPERATION_NOT_SUPPORTED_BY_HELPER,)
-
-    if state == 'retrieve':
-        result = retrieve_cert()
-        if result[0] == WAIT_WITH_DELAY and not is_self_signed:
-            syslog.syslog(syslog.LOG_ALERT,
-                          "IPA CA certificate is about to expire, "
-                          "use ipa-cacert-manage to renew it")
-    elif state == 'request':
-        os.environ['CERTMONGER_CA_PROFILE'] = 'caCACert'
-        result = request_and_store_cert()
-
-    if result[0] == WAIT:
-        return (result[0], '%s:%s' % (state, result[1]))
-    elif result[0] == WAIT_WITH_DELAY:
-        return (result[0], result[1], '%s:%s' % (state, result[2]))
-    else:
-        return result
 
 def main():
-    handlers = {
-        'ipaStorage':       store_cert,
-        'ipaRetrieval':     retrieve_cert,
-        'ipaCSRExport':     export_csr,
-        'ipaCACertRenewal': renew_ca_cert,
-    }
+    operation = os.environ.get('CERTMONGER_OPERATION') or None
+    if operation not in ('SUBMIT', 'POLL'):
+        raise OperationNotSupported()
+
+    csr = os.environ.get('CERTMONGER_CSR') or None
+    if not csr:
+        raise Unconfigured("Certificate request not provided")
+
+    cookie = os.environ.get('CERTMONGER_CA_COOKIE') or None
+    profile = os.environ.get('CERTMONGER_CA_PROFILE') or None
+    cert = os.environ.get('CERTMONGER_CERTIFICATE') or None
 
     api.bootstrap(context='renew')
     api.finalize()
 
-    operation = os.environ.get('CERTMONGER_OPERATION')
-    if operation not in ('SUBMIT', 'POLL'):
-        return OPERATION_NOT_SUPPORTED_BY_HELPER
-
     tmpdir = tempfile.mkdtemp(prefix="tmp-")
     try:
         principal = str('host/%s@%s' % (api.env.host, api.env.realm))
         ipautil.kinit_hostprincipal(paths.KRB5_KEYTAB, tmpdir, principal)
 
-        profile = os.environ.get('CERTMONGER_CA_PROFILE')
-        if profile:
-            handler = handlers.get(profile, request_and_store_cert)
-        else:
+        if not profile:
             ca = cainstance.CAInstance(host_name=api.env.host, ldapi=False)
-            if ca.is_renewal_master():
-                handler = request_and_store_cert
-            else:
-                handler = retrieve_cert
+            if not ca.is_renewal_master():
+                profile = 'ipaRetrieval'
 
-        res = handler()
-        for item in res[1:]:
-            print item
-        return res[0]
+        handler = {
+            'ipaStorage':       store_cert,
+            'ipaRetrieval':     retrieve_cert,
+            'ipaCSRExport':     export_csr,
+            'ipaCACertRenewal': renew_ca_cert,
+        }.get(profile, request_and_store_cert)
+
+        cert = handler(operation, csr, cookie, profile, cert)
     finally:
         shutil.rmtree(tmpdir)
 
+    print cert
+
 try:
-    sys.exit(main())
+    main()
+    sys.exit(ISSUED)
+except RequestException, e:
+    syslog.syslog(syslog.LOG_INFO, str(e))
+    sys.stdout.write(e.body)
+    sys.exit(e.code)
 except Exception, e:
     syslog.syslog(syslog.LOG_ERR, traceback.format_exc())
     print "Internal error"
-- 
1.9.3

>From 635c36e206550bab0a239c4ba168b97ebd2ac7e1 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 16 Sep 2014 10:38:48 +0200
Subject: [PATCH 11/14] Restart request in dogtag-ipa-ca-renew-agent if profile
 changes

The profile can be changed during ipa-cacert-manage and ipa-certupdate. If
the request was not restarted, it could end up with an invalid cookie error.
---
 install/certmonger/dogtag-ipa-ca-renew-agent-submit | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
index 81aaf0a..8690fb8 100755
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
@@ -136,6 +136,20 @@ def request_handler(fn):
                 raise Unconfigured("Invalid cookie: {0!r}".format(cookie))
 
             try:
+                context_profile = context['profile']
+                if isinstance(context_profile, unicode):
+                    context_profile = context_profile.encode(
+                        'raw_unicode_escape')
+                elif context_profile is not None:
+                    raise TypeError
+            except KeyError:
+                raise Unconfigured("No profile name in cookie")
+            except (TypeError, UnicodeEncodeError):
+                raise Unconfigured(
+                    "Invalid profile name {0!r} in cookie".format(
+                        context_profile))
+
+            try:
                 child_cookie = context.get('cookie')
                 if isinstance(child_cookie, unicode):
                     child_cookie = child_cookie.encode('raw_unicode_escape')
@@ -145,6 +159,12 @@ def request_handler(fn):
                 raise Unconfigured(
                     "Invalid child cookie in cookie: {0!r}".format(
                         child_cookie))
+
+            # If profile has changed between SUBMIT and POLL, restart request
+            if context_profile != profile:
+                operation = 'SUBMIT'
+                context = {}
+                child_cookie = None
         else:
             context = {}
             child_cookie = None
-- 
1.9.3

>From 6a1e446e42c81589c47192588f9638041dcdc618 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 18 Sep 2014 10:28:03 +0200
Subject: [PATCH 12/14] Do not wait for new CA certificate to appear in LDAP in
 ipa-certupdate

If new certificate is not available, continue using the old one, instead of
waiting indefinitely for a new certificate to appear.
---
 .../certmonger/dogtag-ipa-ca-renew-agent-submit    | 66 ++++++++++++++--------
 ipa-client/ipaclient/ipa_certupdate.py             |  2 +-
 2 files changed, 43 insertions(+), 25 deletions(-)

diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
index 8690fb8..f6a695b 100755
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
@@ -356,20 +356,13 @@ def request_and_store_cert(context, operation, csr, cookie=None, profile=None,
 
 
 @request_handler
-def retrieve_cert(context, operation, csr, cookie=None, profile=None,
-                  cert=None):
+def try_retrieve_cert(context, operation, csr, cookie=None, profile=None,
+                      cert=None):
     """
-    Retrieve new certificate from LDAP.
+    Retrieve new certificate from LDAP. If new certificate is not available,
+    use the old one.
     """
-    if operation == 'SUBMIT':
-        context['counter'] = 0
-    elif operation == 'POLL':
-        if 'counter' not in context:
-            raise Unconfigured("No counter in cookie")
-        if not isinstance(context['counter'], (int, long)):
-            raise Unconfigured(
-                "Invalid counter in cookie: {0!r}".format(context['counter']))
-    else:
+    if operation != 'SUBMIT':
         raise OperationNotSupported()
 
     nickname = pkcs10.get_friendlyname(csr)
@@ -378,7 +371,6 @@ def retrieve_cert(context, operation, csr, cookie=None, profile=None,
 
     if not cert:
         raise Rejected("New certificate requests not supported")
-    old_cert = x509.normalize_certificate(cert)
 
     syslog.syslog(syslog.LOG_NOTICE, "Updating certificate for %s" % nickname)
 
@@ -389,21 +381,46 @@ def retrieve_cert(context, operation, csr, cookie=None, profile=None,
                    ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn),
                 ['usercertificate'])
         except errors.NotFound:
-            cert = old_cert
+            pass
         else:
             cert = entry.single_value['usercertificate']
+            cert = base64.b64encode(cert)
+            cert = x509.make_pem(cert)
+
+    return cert
+
+
+@request_handler
+def retrieve_cert(context, operation, csr, cookie=None, profile=None,
+                  cert=None):
+    """
+    Retrieve new certificate from LDAP.
+    """
+    if operation == 'SUBMIT':
+        context['counter'] = 0
+    elif operation == 'POLL':
+        if 'counter' not in context:
+            raise Unconfigured("No counter in cookie")
+        if not isinstance(context['counter'], (int, long)):
+            raise Unconfigured(
+                "Invalid counter in cookie: {0!r}".format(context['counter']))
+    else:
+        raise OperationNotSupported()
+
+    if cert:
+        old_cert = x509.normalize_certificate(cert)
+    else:
+        old_cert = None
 
-        if cert == old_cert:
-            context['counter'] += 1
-            if context['counter'] < 4:
-                syslog.syslog(
-                    syslog.LOG_INFO,
-                    "Updated certificate for %s not available" % nickname)
-                # No cert available yet, tell certmonger to wait another 8 hrs
-                raise WaitWithDelay(8 * 60 * 60)
+    cert = try_retrieve_cert('SUBMIT', csr, cookie, profile, cert)
+    new_cert = x509.normalize_certificate(cert)
 
-        cert = base64.b64encode(cert)
-        cert = x509.make_pem(cert)
+    if new_cert == old_cert:
+        context['counter'] += 1
+        if context['counter'] < 4:
+            syslog.syslog(syslog.LOG_INFO, "Updated certificate not available")
+            # No cert available yet, tell certmonger to wait another 8 hours
+            raise WaitWithDelay(8 * 60 * 60)
 
     return cert
 
@@ -500,6 +517,7 @@ def main():
 
         handler = {
             'ipaStorage':       store_cert,
+            'ipaTryRetrieval':  try_retrieve_cert,
             'ipaRetrieval':     retrieve_cert,
             'ipaCSRExport':     export_csr,
             'ipaCACertRenewal': renew_ca_cert,
diff --git a/ipa-client/ipaclient/ipa_certupdate.py b/ipa-client/ipaclient/ipa_certupdate.py
index fd6c80d..f23bfbd 100644
--- a/ipa-client/ipaclient/ipa_certupdate.py
+++ b/ipa-client/ipaclient/ipa_certupdate.py
@@ -126,7 +126,7 @@ class CertUpdate(admintool.AdminTool):
             timeout = api.env.startup_timeout + 60
 
             self.log.debug("resubmitting certmonger request '%s'", request_id)
-            certmonger.resubmit_request(request_id, profile='ipaRetrieval')
+            certmonger.resubmit_request(request_id, profile='ipaTryRetrieval')
             try:
                 state = certmonger.wait_for_request(request_id, timeout)
             except RuntimeError:
-- 
1.9.3

>From 90fd6d240bf9b1c86e08252960b080d35c17055e Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 18 Sep 2014 10:29:26 +0200
Subject: [PATCH 13/14] Fail if certmonger can't see new CA certificate in LDAP
 in ipa-cacert-manage

This should not normally happen, but if it does, report an error instead of
waiting idefinitely for the certificate to appear.
---
 .../certmonger/dogtag-ipa-ca-renew-agent-submit    | 25 ++++++++++++++++++++++
 ipaserver/install/ipa_cacert_manage.py             |  2 +-
 2 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
index f6a695b..32c5cab 100755
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
@@ -391,6 +391,30 @@ def try_retrieve_cert(context, operation, csr, cookie=None, profile=None,
 
 
 @request_handler
+def do_retrieve_cert(context, operation, csr, cookie=None, profile=None,
+                     cert=None):
+    """
+    Retrieve new certificate from LDAP. If new certificate is not available,
+    fail.
+    """
+    if operation != 'SUBMIT':
+        raise OperationNotSupported()
+
+    if cert:
+        old_cert = x509.normalize_certificate(cert)
+    else:
+        old_cert = None
+
+    cert = try_retrieve_cert('SUBMIT', csr, cookie, profile, cert)
+    new_cert = x509.normalize_certificate(cert)
+
+    if new_cert == old_cert:
+        raise Rejected("Updated certificate not available")
+
+    return cert
+
+
+@request_handler
 def retrieve_cert(context, operation, csr, cookie=None, profile=None,
                   cert=None):
     """
@@ -518,6 +542,7 @@ def main():
         handler = {
             'ipaStorage':       store_cert,
             'ipaTryRetrieval':  try_retrieve_cert,
+            'ipaDoRetrieval':   do_retrieve_cert,
             'ipaRetrieval':     retrieve_cert,
             'ipaCSRExport':     export_csr,
             'ipaCACertRenewal': renew_ca_cert,
diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py
index c681261..cd888ce 100644
--- a/ipaserver/install/ipa_cacert_manage.py
+++ b/ipaserver/install/ipa_cacert_manage.py
@@ -291,7 +291,7 @@ class CACertManage(admintool.AdminTool):
         except errors.NotFound:
             raise admintool.ScriptError("CA renewal master not found")
 
-        self.resubmit_request(ca, 'ipaRetrieval')
+        self.resubmit_request(ca, 'ipaDoRetrieval')
 
         print "CA certificate successfully renewed"
 
-- 
1.9.3

>From 3779e13e86a1bebf81fa7478d47919460eb27580 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 24 Sep 2014 19:22:59 +0200
Subject: [PATCH 14/14] Fix certmonger search for the CA cert in ipa-certupdate
 and ipa-cacert-manage

The search criteria did not include the CA agent name.
---
 ipa-client/ipaclient/ipa_certupdate.py | 1 +
 ipaserver/install/ipa_cacert_manage.py | 7 +++++--
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/ipa-client/ipaclient/ipa_certupdate.py b/ipa-client/ipaclient/ipa_certupdate.py
index f23bfbd..cdf9d48 100644
--- a/ipa-client/ipaclient/ipa_certupdate.py
+++ b/ipa-client/ipaclient/ipa_certupdate.py
@@ -120,6 +120,7 @@ class CertUpdate(admintool.AdminTool):
         criteria = {
             'cert-database': dogtag_constants.ALIAS_DIR,
             'cert-nickname': nickname,
+            'ca-name': 'dogtag-ipa-ca-renew-agent',
         }
         request_id = certmonger.get_request_id(criteria)
         if request_id is not None:
diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py
index cd888ce..67103a6e 100644
--- a/ipaserver/install/ipa_cacert_manage.py
+++ b/ipaserver/install/ipa_cacert_manage.py
@@ -153,8 +153,11 @@ class CACertManage(admintool.AdminTool):
             raise admintool.ScriptError("CA is not configured on this system")
 
         nss_dir = ca.dogtag_constants.ALIAS_DIR
-        criteria = {'cert-database': nss_dir,
-                    'cert-nickname': self.cert_nickname}
+        criteria = {
+            'cert-database': nss_dir,
+            'cert-nickname': self.cert_nickname,
+            'ca-name': 'dogtag-ipa-ca-renew-agent',
+        }
         self.request_id = certmonger.get_request_id(criteria)
         if self.request_id is None:
             raise admintool.ScriptError(
-- 
1.9.3

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to