From 5301e86fccfde6ab444a2c600a412487318fbd13 Mon Sep 17 00:00:00 2001 From: Jan Cholasta Date: Wed, 3 May 2017 06:14:27 +0000 Subject: [PATCH] server install: fix KDC certificate validation in CA-less Verify that the provided certificate has the extended key usage and subject alternative name required for KDC. https://pagure.io/freeipa/issue/6831 https://pagure.io/freeipa/issue/6869 Reviewed-By: Stanislav Laznicka Reviewed-By: Martin Babinsky --- ipapython/certdb.py | 42 ++++++++++++++++++++++++++++++ ipaserver/install/installutils.py | 24 +++++++++++------ ipaserver/install/server/install.py | 11 ++++++-- ipaserver/install/server/replicainstall.py | 11 ++++++-- 4 files changed, 76 insertions(+), 12 deletions(-) diff --git a/ipapython/certdb.py b/ipapython/certdb.py index 1ee2603653452577476cf413e6af951cd29c273e..114c58340253141706afa461ecaf87797562ca1d 100644 --- a/ipapython/certdb.py +++ b/ipapython/certdb.py @@ -24,14 +24,17 @@ import pwd import grp import re import tempfile +from tempfile import NamedTemporaryFile import shutil import base64 from cryptography.hazmat.primitives import serialization +import cryptography.x509 from nss import nss from nss.error import NSPRError from ipapython.dn import DN from ipapython.ipa_log_manager import root_logger +from ipapython.kerberos import Principal from ipapython import ipautil from ipalib import x509 # pylint: disable=ipa-forbidden-import @@ -182,6 +185,38 @@ def unparse_trust_flags(trust_flags): return trust_flags +def verify_kdc_cert_validity(kdc_cert, ca_certs, realm): + pem_kdc_cert = kdc_cert.public_bytes(serialization.Encoding.PEM) + pem_ca_certs = '\n'.join( + cert.public_bytes(serialization.Encoding.PEM) for cert in ca_certs) + + with NamedTemporaryFile() as kdc_file, NamedTemporaryFile() as ca_file: + kdc_file.write(pem_kdc_cert) + kdc_file.flush() + ca_file.write(pem_ca_certs) + ca_file.flush() + + try: + ipautil.run( + [OPENSSL, 'verify', '-CAfile', ca_file.name, kdc_file.name]) + eku = kdc_cert.extensions.get_extension_for_class( + cryptography.x509.ExtendedKeyUsage) + list(eku.value).index( + cryptography.x509.ObjectIdentifier(x509.EKU_PKINIT_KDC)) + except (ipautil.CalledProcessError, + cryptography.x509.ExtensionNotFound, + ValueError): + raise ValueError("invalid for a KDC") + + principal = str(Principal(['krbtgt', realm], realm)) + gns = x509.process_othernames(x509.get_san_general_names(kdc_cert)) + for gn in gns: + if isinstance(gn, x509.KRB5PrincipalName) and gn.name == principal: + break + else: + raise ValueError("invalid for realm %s" % realm) + + class NSSDatabase(object): """A general-purpose wrapper around a NSS cert database @@ -707,3 +742,10 @@ class NSSDatabase(object): finally: del certdb, cert nss.nss_shutdown() + + def verify_kdc_cert_validity(self, nickname, realm): + nicknames = self.get_trust_chain(nickname) + certs = [self.get_cert(nickname) for nickname in nicknames] + certs = [x509.load_certificate(cert, x509.DER) for cert in certs] + + verify_kdc_cert_validity(certs[-1], certs[:-1], realm) diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py index 5bce9894780bd920db11196b925492a7fe8f22d0..d2283af20485fd5d66bfd3cc49059d08d1802575 100644 --- a/ipaserver/install/installutils.py +++ b/ipaserver/install/installutils.py @@ -1001,7 +1001,7 @@ def handle_error(error, log_file_name=None): def load_pkcs12(cert_files, key_password, key_nickname, ca_cert_files, - host_name): + host_name=None, realm_name=None): """ Load and verify server certificate and private key from multiple files @@ -1066,13 +1066,21 @@ def load_pkcs12(cert_files, key_password, key_nickname, ca_cert_files, "CA certificate %s in %s is not valid: %s" % (subject, ", ".join(cert_files), e)) - # Check server validity - try: - nssdb.verify_server_cert_validity(key_nickname, host_name) - except ValueError as e: - raise ScriptError( - "The server certificate in %s is not valid: %s" % - (", ".join(cert_files), e)) + if host_name is not None: + try: + nssdb.verify_server_cert_validity(key_nickname, host_name) + except ValueError as e: + raise ScriptError( + "The server certificate in %s is not valid: %s" % + (", ".join(cert_files), e)) + + if realm_name is not None: + try: + nssdb.verify_kdc_cert_validity(key_nickname, realm_name) + except ValueError as e: + raise ScriptError( + "The KDC certificate in %s is not valid: %s" % + (", ".join(cert_files), e)) out_file = tempfile.NamedTemporaryFile() out_password = ipautil.ipa_generate_password() diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py index c1bdce6c8459dfeabd0096d105e535ec4ee56a2a..03380b8d0e9150224b014a1a174d7ea81ccdcf00 100644 --- a/ipaserver/install/server/install.py +++ b/ipaserver/install/server/install.py @@ -520,12 +520,12 @@ def install_check(installer): if options.pkinit_pin is None: raise ScriptError( "Kerberos KDC private key unlock password required") - pkinit_pkcs12_file, pkinit_pin, _pkinit_ca_cert = load_pkcs12( + pkinit_pkcs12_file, pkinit_pin, pkinit_ca_cert = load_pkcs12( cert_files=options.pkinit_cert_files, key_password=options.pkinit_pin, key_nickname=options.pkinit_cert_name, ca_cert_files=options.ca_cert_files, - host_name=host_name) + realm_name=realm_name) pkinit_pkcs12_info = (pkinit_pkcs12_file.name, pkinit_pin) if (options.http_cert_files and options.dirsrv_cert_files and @@ -534,6 +534,13 @@ def install_check(installer): "Apache Server SSL certificate and Directory Server SSL " "certificate are not signed by the same CA certificate") + if (options.http_cert_files and + options.pkinit_cert_files and + http_ca_cert != pkinit_ca_cert): + raise ScriptError( + "Apache Server SSL certificate and PKINIT KDC " + "certificate are not signed by the same CA certificate") + if not options.dm_password: dm_password = read_dm_password() diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py index 66d7ba44645aed69b12f0e5ea14f5080492fe5ef..6f71f0b51812943fea3fb1c576a0174c739a070b 100644 --- a/ipaserver/install/server/replicainstall.py +++ b/ipaserver/install/server/replicainstall.py @@ -1069,12 +1069,12 @@ def promote_check(installer): if options.pkinit_pin is None: raise ScriptError( "Kerberos KDC private key unlock password required") - pkinit_pkcs12_file, pkinit_pin, _pkinit_ca_cert = load_pkcs12( + pkinit_pkcs12_file, pkinit_pin, pkinit_ca_cert = load_pkcs12( cert_files=options.pkinit_cert_files, key_password=options.pkinit_pin, key_nickname=options.pkinit_cert_name, ca_cert_files=options.ca_cert_files, - host_name=config.host_name) + realm_name=config.realm_name) pkinit_pkcs12_info = (pkinit_pkcs12_file.name, pkinit_pin) if (options.http_cert_files and options.dirsrv_cert_files and @@ -1083,6 +1083,13 @@ def promote_check(installer): "Server SSL certificate are not signed by the same" " CA certificate") + if (options.http_cert_files and + options.pkinit_cert_files and + http_ca_cert != pkinit_ca_cert): + raise RuntimeError("Apache Server SSL certificate and PKINIT KDC " + "certificate are not signed by the same CA " + "certificate") + installutils.verify_fqdn(config.host_name, options.no_host_dns) installutils.verify_fqdn(config.master_host_name, options.no_host_dns) -- 2.9.4