403b09
From c9428fe0a1230fb9ea9c18c895c0834678e94da8 Mon Sep 17 00:00:00 2001
403b09
From: Fraser Tweedale <ftweedal@redhat.com>
403b09
Date: Fri, 26 Aug 2016 15:31:13 +1000
403b09
Subject: [PATCH] Make host/service cert revocation aware of lightweight CAs
403b09
403b09
Revocation of host/service certs on host/service deletion or other
403b09
operations is broken when cert is issued by a lightweight (sub)CA,
403b09
causing the delete operation to be aborted.  Look up the issuing CA
403b09
and pass it to 'cert_revoke' to fix the issue.
403b09
403b09
Fixes: https://fedorahosted.org/freeipa/ticket/6221
403b09
Reviewed-By: Jan Cholasta <jcholast@redhat.com>
403b09
---
403b09
 ipaserver/plugins/host.py    | 20 +++++++---------
403b09
 ipaserver/plugins/service.py | 56 ++++++++++++++++++++++----------------------
403b09
 2 files changed, 37 insertions(+), 39 deletions(-)
403b09
403b09
diff --git a/ipaserver/plugins/host.py b/ipaserver/plugins/host.py
403b09
index 03c64c637cbba0aee1b6569f3b5dbe200953bff8..2362b6247af87b4ce63c21083e6bc8ac39db0804 100644
403b09
--- a/ipaserver/plugins/host.py
403b09
+++ b/ipaserver/plugins/host.py
403b09
@@ -843,12 +843,8 @@ class host_del(LDAPDelete):
403b09
                 )
403b09
 
403b09
         if self.api.Command.ca_is_enabled()['result']:
403b09
-            try:
403b09
-                entry_attrs = ldap.get_entry(dn, ['usercertificate'])
403b09
-            except errors.NotFound:
403b09
-                self.obj.handle_not_found(*keys)
403b09
-
403b09
-            revoke_certs(entry_attrs.get('usercertificate', []), self.log)
403b09
+            certs = self.api.Command.cert_find(host=keys)['result']
403b09
+            revoke_certs(certs)
403b09
 
403b09
         return dn
403b09
 
403b09
@@ -910,7 +906,9 @@ class host_mod(LDAPUpdate):
403b09
             old_certs = entry_attrs_old.get('usercertificate', [])
403b09
             old_certs_der = [x509.normalize_certificate(c) for c in old_certs]
403b09
             removed_certs_der = set(old_certs_der) - set(certs_der)
403b09
-            revoke_certs(removed_certs_der, self.log)
403b09
+            for der in removed_certs_der:
403b09
+                rm_certs = api.Command.cert_find(certificate=der)['result']
403b09
+                revoke_certs(rm_certs)
403b09
 
403b09
         if certs:
403b09
             entry_attrs['usercertificate'] = certs_der
403b09
@@ -1196,10 +1194,10 @@ class host_disable(LDAPQuery):
403b09
         except errors.NotFound:
403b09
             self.obj.handle_not_found(*keys)
403b09
         if self.api.Command.ca_is_enabled()['result']:
403b09
-            certs = entry_attrs.get('usercertificate', [])
403b09
+            certs = self.api.Command.cert_find(host=keys)['result']
403b09
 
403b09
             if certs:
403b09
-                revoke_certs(certs, self.log)
403b09
+                revoke_certs(certs)
403b09
                 # Remove the usercertificate altogether
403b09
                 entry_attrs['usercertificate'] = None
403b09
                 ldap.update_entry(entry_attrs)
403b09
@@ -1341,8 +1339,8 @@ class host_remove_cert(LDAPRemoveAttributeViaOption):
403b09
     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
403b09
         assert isinstance(dn, DN)
403b09
 
403b09
-        if 'usercertificate' in options:
403b09
-            revoke_certs(options['usercertificate'], self.log)
403b09
+        for cert in options.get('usercertificate', []):
403b09
+            revoke_certs(api.Command.cert_find(certificate=cert)['result'])
403b09
 
403b09
         return dn
403b09
 
403b09
diff --git a/ipaserver/plugins/service.py b/ipaserver/plugins/service.py
403b09
index 04d1916fe989a8651bcc4d44f1914c460be1081c..093525f2e7cb84b18f0658dcb5d7c786e45c6ab6 100644
403b09
--- a/ipaserver/plugins/service.py
403b09
+++ b/ipaserver/plugins/service.py
403b09
@@ -220,37 +220,38 @@ def validate_certificate(ugettext, cert):
403b09
         x509.validate_certificate(cert, datatype=x509.DER)
403b09
 
403b09
 
403b09
-def revoke_certs(certs, logger=None):
403b09
+def revoke_certs(certs):
403b09
     """
403b09
     revoke the certificates removed from host/service entry
403b09
-    """
403b09
-    for cert in certs:
403b09
-        try:
403b09
-            cert = x509.normalize_certificate(cert)
403b09
-        except errors.CertificateFormatError as e:
403b09
-            if logger is not None:
403b09
-                logger.info("Problem decoding certificate: %s" % e)
403b09
 
403b09
-        serial = unicode(x509.get_serial_number(cert, x509.DER))
403b09
+    :param certs: Output of a 'cert_find' command.
403b09
 
403b09
-        try:
403b09
-            result = api.Command['cert_show'](unicode(serial))['result']
403b09
-        except errors.CertificateOperationError:
403b09
-            continue
403b09
-        if 'revocation_reason' in result:
403b09
+    """
403b09
+    for cert in certs:
403b09
+        if 'cacn' not in cert:
403b09
+            # Cert is known to IPA, but has no associated CA.
403b09
+            # If it was issued by 3rd-party CA, we can't revoke it.
403b09
+            # If it was issued by a Dogtag lightweight CA that was
403b09
+            # subsequently deleted, we can't revoke it via IPA.
403b09
+            # We could go directly to Dogtag to revoke it, but the
403b09
+            # issuer's cert should have been revoked so never mind.
403b09
             continue
403b09
-        if x509.normalize_certificate(result['certificate']) != cert:
403b09
+
403b09
+        if cert['revoked']:
403b09
+            # cert is already revoked
403b09
             continue
403b09
 
403b09
         try:
403b09
-            api.Command['cert_revoke'](unicode(serial),
403b09
-                                       revocation_reason=4)
403b09
+            api.Command['cert_revoke'](
403b09
+                cert['serial_number'],
403b09
+                cacn=cert['cacn'],
403b09
+                revocation_reason=4,
403b09
+            )
403b09
         except errors.NotImplementedError:
403b09
             # some CA's might not implement revoke
403b09
             pass
403b09
 
403b09
 
403b09
-
403b09
 def set_certificate_attrs(entry_attrs):
403b09
     """
403b09
     Set individual attributes from some values from a certificate.
403b09
@@ -674,11 +675,8 @@ class service_del(LDAPDelete):
403b09
         # custom services allow them to manage them.
403b09
         check_required_principal(ldap, keys[-1])
403b09
         if self.api.Command.ca_is_enabled()['result']:
403b09
-            try:
403b09
-                entry_attrs = ldap.get_entry(dn, ['usercertificate'])
403b09
-            except errors.NotFound:
403b09
-                self.obj.handle_not_found(*keys)
403b09
-            revoke_certs(entry_attrs.get('usercertificate', []), self.log)
403b09
+            certs = self.api.Command.cert_find(service=keys)['result']
403b09
+            revoke_certs(certs)
403b09
 
403b09
         return dn
403b09
 
403b09
@@ -711,7 +709,9 @@ class service_mod(LDAPUpdate):
403b09
             old_certs = entry_attrs_old.get('usercertificate', [])
403b09
             old_certs_der = [x509.normalize_certificate(c) for c in old_certs]
403b09
             removed_certs_der = set(old_certs_der) - set(certs_der)
403b09
-            revoke_certs(removed_certs_der, self.log)
403b09
+            for der in removed_certs_der:
403b09
+                rm_certs = api.Command.cert_find(certificate=der)['result']
403b09
+                revoke_certs(rm_certs)
403b09
 
403b09
         if certs:
403b09
             entry_attrs['usercertificate'] = certs_der
403b09
@@ -950,10 +950,10 @@ class service_disable(LDAPQuery):
403b09
         done_work = False
403b09
 
403b09
         if self.api.Command.ca_is_enabled()['result']:
403b09
-            certs = entry_attrs.get('usercertificate', [])
403b09
+            certs = self.api.Command.cert_find(service=keys)['result']
403b09
 
403b09
             if len(certs) > 0:
403b09
-                revoke_certs(certs, self.log)
403b09
+                revoke_certs(certs)
403b09
                 # Remove the usercertificate altogether
403b09
                 entry_attrs['usercertificate'] = None
403b09
                 ldap.update_entry(entry_attrs)
403b09
@@ -989,8 +989,8 @@ class service_remove_cert(LDAPRemoveAttributeViaOption):
403b09
     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
403b09
         assert isinstance(dn, DN)
403b09
 
403b09
-        if 'usercertificate' in options:
403b09
-            revoke_certs(options['usercertificate'], self.log)
403b09
+        for cert in options.get('usercertificate', []):
403b09
+            revoke_certs(api.Command.cert_find(certificate=cert)['result'])
403b09
 
403b09
         return dn
403b09
 
403b09
-- 
403b09
2.7.4
403b09