Hi,

the attached patches fix <https://fedorahosted.org/freeipa/ticket/4565>.

Honza

--
Jan Cholasta
>From f4c02ff6105954115c1b46d874aed43bf52aa4c4 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 17 Mar 2015 09:28:47 +0000
Subject: [PATCH 1/4] certstore: Make certificate retrieval more robust

https://fedorahosted.org/freeipa/ticket/4565
---
 ipalib/certstore.py | 74 +++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 52 insertions(+), 22 deletions(-)

diff --git a/ipalib/certstore.py b/ipalib/certstore.py
index 8a9b410..3a5555c 100644
--- a/ipalib/certstore.py
+++ b/ipalib/certstore.py
@@ -239,6 +239,31 @@ def put_ca_cert(ldap, base_dn, dercert, nickname, trusted=None,
         pass
 
 
+def make_compat_ca_certs(certs, realm, ipa_ca_subject):
+    """
+    Make CA certificates and associated key policy from DER certificates.
+    """
+    result = []
+
+    for cert in certs:
+        subject, issuer_serial, public_key_info = _parse_cert(cert)
+        subject = DN(subject)
+
+        if ipa_ca_subject is not None and subject == DN(ipa_ca_subject):
+            nickname = get_ca_nickname(realm)
+            ext_key_usage = {x509.EKU_SERVER_AUTH,
+                             x509.EKU_CLIENT_AUTH,
+                             x509.EKU_EMAIL_PROTECTION,
+                             x509.EKU_CODE_SIGNING}
+        else:
+            nickname = str(subject)
+            ext_key_usage = {x509.EKU_SERVER_AUTH}
+
+        result.append((cert, nickname, True, ext_key_usage))
+
+    return result
+
+
 def get_ca_certs(ldap, base_dn, compat_realm, compat_ipa_ca,
                  filter_subject=None):
     """
@@ -250,6 +275,7 @@ def get_ca_certs(ldap, base_dn, compat_realm, compat_ipa_ca,
         filter_subject = [str(subj).replace('\\;', '\\3b')
                           for subj in filter_subject]
 
+    certs = []
     config_dn = DN(('cn', 'ipa'), ('cn', 'etc'), base_dn)
     container_dn = DN(('cn', 'certificates'), config_dn)
     try:
@@ -265,7 +291,6 @@ def get_ca_certs(ldap, base_dn, compat_realm, compat_ipa_ca,
                         'ipaPublicKey', 'ipaKeyTrust', 'ipaKeyExtUsage',
                         'cACertificate;binary'])
 
-        certs = []
         for entry in result:
             nickname = entry.single_value['cn']
             trusted = entry.single_value.get('ipaKeyTrust', 'unknown').lower()
@@ -281,34 +306,39 @@ def get_ca_certs(ldap, base_dn, compat_realm, compat_ipa_ca,
                 ext_key_usage.discard(x509.EKU_PLACEHOLDER)
 
             for cert in entry.get('cACertificate;binary', []):
+                try:
+                    _parse_cert(cert)
+                except ValueError:
+                    certs = []
+                    break
                 certs.append((cert, nickname, trusted, ext_key_usage))
-
-        return certs
     except errors.NotFound:
         try:
             ldap.get_entry(container_dn, [''])
         except errors.NotFound:
-            pass
-        else:
-            return []
-
-        # Fallback to cn=CAcert,cn=ipa,cn=etc,SUFFIX
-        dn = DN(('cn', 'CAcert'), config_dn)
-        entry = ldap.get_entry(dn, ['cACertificate;binary'])
-
-        cert = entry.single_value['cACertificate;binary']
-        subject, issuer_serial, public_key_info = _parse_cert(cert)
-        if filter_subject is not None and subject not in filter_subject:
-            return []
+            # Fallback to cn=CAcert,cn=ipa,cn=etc,SUFFIX
+            dn = DN(('cn', 'CAcert'), config_dn)
+            entry = ldap.get_entry(dn, ['cACertificate;binary'])
+
+            cert = entry.single_value['cACertificate;binary']
+            try:
+                subject, issuer_serial, public_key_info = _parse_cert(cert)
+            except ValueError:
+                pass
+            else:
+                if filter_subject is not None and subject not in filter_subject:
+                    raise errors.NotFound(reason="no matching entry found")
 
-        nickname = get_ca_nickname(compat_realm)
-        ext_key_usage = {x509.EKU_SERVER_AUTH}
-        if compat_ipa_ca:
-            ext_key_usage |= {x509.EKU_CLIENT_AUTH,
-                              x509.EKU_EMAIL_PROTECTION,
-                              x509.EKU_CODE_SIGNING}
+                if compat_ipa_ca:
+                    ca_subject = subject
+                else:
+                    ca_subject = None
+                certs = make_compat_ca_certs([cert], compat_realm, ca_subject)
 
-        return [(cert, nickname, True, ext_key_usage)]
+    if certs:
+        return certs
+    else:
+        raise errors.NotFound(reason="no such entry")
 
 
 def trust_flags_to_key_policy(trust_flags):
-- 
2.1.0

>From e07493dd4d3a723697d44901b20bc0b7bc4aa53c Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 17 Mar 2015 09:29:21 +0000
Subject: [PATCH 2/4] client-install: Do not crash on invalid CA certificate in
 LDAP

When CA certificates in LDAP are corrupted, use the otherwise acquired CA
certificates from before.

https://fedorahosted.org/freeipa/ticket/4565
---
 ipa-client/ipa-install/ipa-client-install | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install
index 3761794..5117d9a 100755
--- a/ipa-client/ipa-install/ipa-client-install
+++ b/ipa-client/ipa-install/ipa-client-install
@@ -2581,14 +2581,15 @@ def install(options, env, fstore, statestore):
     except ValueError:
         pass
 
+    ca_certs = x509.load_certificate_list_from_file(CACERT)
+    ca_certs = [cert.der_data for cert in ca_certs]
+
     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())
             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):
                 tmp_db.add_cert(cert, 'CA certificate %d' % (i + 1), 'C,,')
         except CalledProcessError, e:
@@ -2661,8 +2662,16 @@ def install(options, env, fstore, statestore):
         return CLIENT_INSTALL_ERROR
 
     # Get CA certificates from the certificate store
-    ca_certs = get_certs_from_ldap(cli_server[0], cli_basedn, cli_realm,
-                                   ca_enabled)
+    try:
+        ca_certs = get_certs_from_ldap(cli_server[0], cli_basedn, cli_realm,
+                                       ca_enabled)
+    except errors.NoCertificateError:
+        if ca_enabled:
+            ca_subject = DN(('CN', 'Certificate Authority'), subject_base)
+        else:
+            ca_subject = None
+        ca_certs = certstore.make_compat_ca_certs(ca_certs, cli_realm,
+                                                  ca_subject)
     ca_certs_trust = [(c, n, certstore.key_policy_to_trust_flags(t, True, u))
                       for (c, n, t, u) in ca_certs]
 
-- 
2.1.0

>From e605a972363259d114626fc1cb36300841b2ef20 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 17 Mar 2015 09:35:49 +0000
Subject: [PATCH 3/4] client: Fix ca_is_enabled calls

The command was added in API version 2.107. Old IPA servers may crash with
NetworkError on ca_is_enabled, handle this case gracefully.

https://fedorahosted.org/freeipa/ticket/4565
---
 ipa-client/ipa-install/ipa-client-install | 4 ++--
 ipa-client/ipaclient/ipa_certupdate.py    | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install
index 5117d9a..f6bd898 100755
--- a/ipa-client/ipa-install/ipa-client-install
+++ b/ipa-client/ipa-install/ipa-client-install
@@ -2641,10 +2641,10 @@ def install(options, env, fstore, statestore):
     try:
         result = api.Backend.rpcclient.forward(
             'ca_is_enabled',
-            version=u'2.0',
+            version=u'2.107',
         )
         ca_enabled = result['result']
-    except errors.CommandError:
+    except (errors.CommandError, errors.NetworkError):
         result = api.Backend.rpcclient.forward(
             'env',
             server=True,
diff --git a/ipa-client/ipaclient/ipa_certupdate.py b/ipa-client/ipaclient/ipa_certupdate.py
index 031a34c..5ec5026 100644
--- a/ipa-client/ipaclient/ipa_certupdate.py
+++ b/ipa-client/ipaclient/ipa_certupdate.py
@@ -63,10 +63,10 @@ class CertUpdate(admintool.AdminTool):
             try:
                 result = api.Backend.rpcclient.forward(
                     'ca_is_enabled',
-                    version=u'2.0',
+                    version=u'2.107',
                 )
                 ca_enabled = result['result']
-            except errors.CommandError:
+            except (errors.CommandError, errors.NetworkError):
                 result = api.Backend.rpcclient.forward(
                     'env',
                     server=True,
-- 
2.1.0

>From aee544f38f60841375972406820ae9a9cc7a3d24 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 17 Mar 2015 08:23:40 +0000
Subject: [PATCH 4/4] upload_cacrt: Fix empty cACertificate in cn=CAcert

https://fedorahosted.org/freeipa/ticket/4565
---
 ipaserver/install/plugins/upload_cacrt.py | 54 +++++++++++++++++--------------
 1 file changed, 30 insertions(+), 24 deletions(-)

diff --git a/ipaserver/install/plugins/upload_cacrt.py b/ipaserver/install/plugins/upload_cacrt.py
index 66270ae..4d5ce52 100644
--- a/ipaserver/install/plugins/upload_cacrt.py
+++ b/ipaserver/install/plugins/upload_cacrt.py
@@ -20,7 +20,7 @@
 from ipaserver.install.plugins import MIDDLE
 from ipaserver.install.plugins.baseupdate import PostUpdate
 from ipaserver.install import certs
-from ipalib import api, certstore
+from ipalib import api, errors, certstore
 from ipapython import certdb
 from ipapython.dn import DN
 
@@ -45,7 +45,7 @@ class update_upload_cacrt(PostUpdate):
                 if ca_chain:
                     ca_nickname = ca_chain[-1]
 
-        updates = {}
+        ldap = self.obj.backend
 
         for nickname, trust_flags in db.list_certs():
             if 'u' in trust_flags:
@@ -53,40 +53,46 @@ class update_upload_cacrt(PostUpdate):
             if nickname == ca_nickname and ca_enabled:
                 trust_flags = 'CT,C,C'
             cert = db.get_cert_from_db(nickname, pem=False)
+            trust, ca, eku = certstore.trust_flags_to_key_policy(trust_flags)
+
+            dn = DN(('cn', nickname), ('cn', 'certificates'), ('cn', 'ipa'),
+                    ('cn','etc'), self.api.env.basedn)
+            entry = ldap.make_entry(dn)
+
             try:
-                dn, entry = self._make_entry(cert, nickname, trust_flags)
+                certstore.init_ca_entry(entry, cert, nickname, trust, eku)
             except Exception, e:
                 self.log.warning("Failed to create entry for %s: %s",
                                  nickname, e)
                 continue
             if nickname == ca_nickname:
                 ca_cert = cert
+                config = entry.setdefault('ipaConfigString', [])
                 if ca_enabled:
-                    entry.append('ipaConfigString:ipaCA')
-                entry.append('ipaConfigString:compatCA')
-            updates[dn] = {'dn': dn, 'default': entry}
+                    config.append('ipaCa')
+                config.append('ipaCa')
+
+            try:
+                ldap.add_entry(entry)
+            except errors.DuplicateEntry:
+                pass
 
         if ca_cert:
             dn = DN(('cn', 'CACert'), ('cn', 'ipa'), ('cn','etc'),
                     self.api.env.basedn)
-            entry = ['objectclass:nsContainer',
-                     'objectclass:pkiCA',
-                     'cn:CAcert',
-                     'cACertificate;binary:%s' % ca_cert,
-                    ]
-            updates[dn] = {'dn': dn, 'default': entry}
-
-        return (False, True, [updates])
-
-    def _make_entry(self, cert, nickname, trust_flags):
-        dn = DN(('cn', nickname), ('cn', 'certificates'), ('cn', 'ipa'),
-                ('cn','etc'), self.api.env.basedn)
-
-        entry = dict()
-        trust, ca, eku = certstore.trust_flags_to_key_policy(trust_flags)
-        certstore.init_ca_entry(entry, cert, nickname, trust, eku)
-        entry = ['%s:%s' % (a, v) for a, vs in entry.iteritems() for v in vs]
+            try:
+                entry = ldap.get_entry(dn)
+            except errors.NotFound:
+                entry = ldap.make_entry(dn)
+                entry['objectclass'] = ['nsContainer', 'pkiCA']
+                entry.single_value['cn'] = 'CAcert'
+                entry.single_value['cACertificate;binary'] = ca_cert
+                ldap.add_entry(entry)
+            else:
+                if '' in entry['cACertificate;binary']:
+                    entry.single_value['cACertificate;binary'] = ca_cert
+                    ldap.update_entry(entry)
 
-        return dn, entry
+        return (False, False, [])
 
 api.register(update_upload_cacrt)
-- 
2.1.0

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to