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