URL: https://github.com/freeipa/freeipa/pull/758 Author: HonzaCholasta Title: #758: install: fix CA-less PKINIT Action: synchronized
To pull the PR as Git branch: git remote add ghfreeipa https://github.com/freeipa/freeipa git fetch ghfreeipa pull/758/head:pr758 git checkout pr758
From 94035206637152fce07a491d645c796121e6b984 Mon Sep 17 00:00:00 2001 From: Jan Cholasta <jchol...@redhat.com> Date: Thu, 27 Apr 2017 09:33:25 +0200 Subject: [PATCH 01/14] certdb: add named trust flag constants Add named constants for common trust flag combinations. Use the named constants instead of trust flags strings in the code. https://pagure.io/freeipa/issue/6831 --- install/restart_scripts/restart_httpd | 3 ++- install/tools/ipa-replica-conncheck | 4 +++- ipaclient/install/client.py | 9 ++++++--- ipapython/certdb.py | 9 +++++++-- ipaserver/install/ca.py | 2 +- ipaserver/install/certs.py | 5 +++-- ipaserver/install/dsinstance.py | 5 +++-- ipaserver/install/httpinstance.py | 5 +++-- ipaserver/install/ipa_cacert_manage.py | 16 +++++++++++----- ipaserver/install/plugins/upload_cacrt.py | 2 +- ipaserver/install/server/replicainstall.py | 3 ++- ipaserver/install/server/upgrade.py | 4 ++-- 12 files changed, 44 insertions(+), 23 deletions(-) diff --git a/install/restart_scripts/restart_httpd b/install/restart_scripts/restart_httpd index b661b82..cd7f120 100644 --- a/install/restart_scripts/restart_httpd +++ b/install/restart_scripts/restart_httpd @@ -24,6 +24,7 @@ import traceback from ipalib import api from ipaplatform import services from ipaplatform.paths import paths +from ipapython.certdb import TRUSTED_PEER_TRUST_FLAGS from ipaserver.install import certs, installutils @@ -36,7 +37,7 @@ def _main(): nickname = installutils.get_directive(paths.HTTPD_NSS_CONF, "NSSNickname") # Add trust flag which set certificate trusted for SSL connections. - db.trust_root_cert(nickname, "P,,") + db.trust_root_cert(nickname, TRUSTED_PEER_TRUST_FLAGS) syslog.syslog(syslog.LOG_NOTICE, 'certmonger restarted httpd') diff --git a/install/tools/ipa-replica-conncheck b/install/tools/ipa-replica-conncheck index fdbd4f3..5282422 100755 --- a/install/tools/ipa-replica-conncheck +++ b/install/tools/ipa-replica-conncheck @@ -549,7 +549,9 @@ def main(): data = ca_cert.public_bytes( serialization.Encoding.DER) nss_db.add_cert( - data, str(DN(ca_cert.subject)), 'C,,') + data, + str(DN(ca_cert.subject)), + certdb.EXTERNAL_CA_TRUST_FLAGS) api.bootstrap(context='client', confdir=paths.ETC_IPA, diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py index abca692..e78be90 100644 --- a/ipaclient/install/client.py +++ b/ipaclient/install/client.py @@ -2318,8 +2318,9 @@ def update_ipa_nssdb(): if not os.path.exists(os.path.join(ipa_db.secdir, 'cert8.db')): create_ipa_nssdb() - for nickname, trust_flags in (('IPA CA', 'CT,C,C'), - ('External CA cert', 'C,,')): + for nickname, trust_flags in ( + ('IPA CA', certdb.IPA_CA_TRUST_FLAGS), + ('External CA cert', certdb.EXTERNAL_CA_TRUST_FLAGS)): try: cert = sys_db.get_cert(nickname) except RuntimeError: @@ -2680,7 +2681,9 @@ def _install(options): tmp_db.create_db() for i, cert in enumerate(ca_certs): - tmp_db.add_cert(cert, 'CA certificate %d' % (i + 1), 'C,,') + tmp_db.add_cert(cert, + 'CA certificate %d' % (i + 1), + certdb.EXTERNAL_CA_TRUST_FLAGS) except CalledProcessError: raise ScriptError( "Failed to add CA to temporary NSS database.", diff --git a/ipapython/certdb.py b/ipapython/certdb.py index 4d7f6e7..38f3bf0 100644 --- a/ipapython/certdb.py +++ b/ipapython/certdb.py @@ -52,6 +52,11 @@ NSS_FILES = ("cert8.db", "key3.db", "secmod.db", "pwdfile.txt") +EMPTY_TRUST_FLAGS = ',,' +IPA_CA_TRUST_FLAGS = 'CT,C,C' +EXTERNAL_CA_TRUST_FLAGS = 'C,,' +TRUSTED_PEER_TRUST_FLAGS = 'P,,' + def get_ca_nickname(realm, format=CA_NICKNAME_FMT): return format % realm @@ -436,7 +441,7 @@ def import_files(self, files, import_keys=False, key_password=None, cert = x509.load_certificate(cert_pem) nickname = str(DN(cert.subject)) data = cert.public_bytes(serialization.Encoding.DER) - self.add_cert(data, nickname, ',,') + self.add_cert(data, nickname, EMPTY_TRUST_FLAGS) if extracted_key: in_file = ipautil.write_tmp_file( @@ -468,7 +473,7 @@ def trust_root_cert(self, root_nickname, trust_flags=None): root_nickname) else: if trust_flags is None: - trust_flags = 'C,,' + trust_flags = EXTERNAL_CA_TRUST_FLAGS try: self.run_certutil(["-M", "-n", root_nickname, "-t", trust_flags]) diff --git a/ipaserver/install/ca.py b/ipaserver/install/ca.py index 8ee0fda..52cb20f 100644 --- a/ipaserver/install/ca.py +++ b/ipaserver/install/ca.py @@ -320,7 +320,7 @@ def install_step_1(standalone, replica_config, options): realm_name, nssdir=dirname, subject_base=subject_base) cacert = cadb.get_cert_from_db('caSigningCert cert-pki-ca', pem=False) nickname = certdb.get_ca_nickname(realm_name) - trust_flags = 'CT,C,C' + trust_flags = certdb.IPA_CA_TRUST_FLAGS dsdb.add_cert(cacert, nickname, trust_flags) certstore.put_ca_cert_nss(api.Backend.ldap2, api.env.basedn, cacert, nickname, trust_flags, diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py index 89e5713..f87e00e 100644 --- a/ipaserver/install/certs.py +++ b/ipaserver/install/certs.py @@ -37,6 +37,7 @@ from ipapython.ipa_log_manager import root_logger from ipapython import dogtag from ipapython import ipautil +from ipapython.certdb import EMPTY_TRUST_FLAGS, IPA_CA_TRUST_FLAGS from ipapython.certdb import get_ca_nickname, find_cert_from_txt, NSSDatabase from ipapython.dn import DN from ipalib import pkcs10, x509, api @@ -597,7 +598,7 @@ def create_from_cacert(self): # a new certificate database. self.create_passwd_file() self.create_certdbs() - self.load_cacert(cacert_fname, 'CT,C,C') + self.load_cacert(cacert_fname, IPA_CA_TRUST_FLAGS) def create_from_pkcs12(self, pkcs12_fname, pkcs12_passwd, passwd=None, ca_file=None, trust_flags=None): @@ -643,7 +644,7 @@ def init_from_pkcs12(self, pkcs12_fname, pkcs12_passwd, cert, st = find_cert_from_txt(certs, st) except RuntimeError: break - self.add_cert(cert, 'CA %s' % num, ',,', pem=True) + self.add_cert(cert, 'CA %s' % num, EMPTY_TRUST_FLAGS, pem=True) num += 1 # We only handle one server cert diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index 403fe84..0db0368 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -32,6 +32,7 @@ import ldap from ipalib.install import certmonger, certstore +from ipapython.certdb import IPA_CA_TRUST_FLAGS, EXTERNAL_CA_TRUST_FLAGS from ipapython.ipa_log_manager import root_logger from ipapython import ipautil, ipaldap from ipapython import dogtag @@ -766,7 +767,7 @@ def __enable_ssl(self): ) if self.pkcs12_info: if self.ca_is_configured: - trust_flags = 'CT,C,C' + trust_flags = IPA_CA_TRUST_FLAGS else: trust_flags = None dsdb.create_from_pkcs12(self.pkcs12_info[0], self.pkcs12_info[1], @@ -1065,7 +1066,7 @@ def add_ca_cert(self, cacert_fname, cacert_name=''): certdb.cacert_name = cacert_name status = True try: - certdb.load_cacert(cacert_fname, 'C,,') + certdb.load_cacert(cacert_fname, EXTERNAL_CA_TRUST_FLAGS) except ipautil.CalledProcessError as e: root_logger.critical("Error importing CA cert file named [%s]: %s" % (cacert_fname, str(e))) diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py index ab688a8..a6aeb21 100644 --- a/ipaserver/install/httpinstance.py +++ b/ipaserver/install/httpinstance.py @@ -32,6 +32,7 @@ from augeas import Augeas from ipalib.install import certmonger +from ipapython.certdb import IPA_CA_TRUST_FLAGS, TRUSTED_PEER_TRUST_FLAGS from ipaserver.install import service from ipaserver.install import certs from ipaserver.install import installutils @@ -381,7 +382,7 @@ def __setup_ssl(self): if self.pkcs12_info: if self.ca_is_configured: - trust_flags = 'CT,C,C' + trust_flags = IPA_CA_TRUST_FLAGS else: trust_flags = None db.init_from_pkcs12(self.pkcs12_info[0], self.pkcs12_info[1], @@ -403,7 +404,7 @@ def __setup_ssl(self): self.__set_mod_nss_nickname(nickname) self.add_cert_to_service() - db.trust_root_cert(nickname, "P,,") + db.trust_root_cert(nickname, TRUSTED_PEER_TRUST_FLAGS) else: if not self.promote: diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py index 3b732e4..88b40d4 100644 --- a/ipaserver/install/ipa_cacert_manage.py +++ b/ipaserver/install/ipa_cacert_manage.py @@ -26,6 +26,7 @@ from ipalib.install import certmonger, certstore from ipapython import admintool, ipautil +from ipapython.certdb import EMPTY_TRUST_FLAGS, EXTERNAL_CA_TRUST_FLAGS from ipapython.dn import DN from ipaplatform.paths import paths from ipalib import api, errors, x509 @@ -242,10 +243,10 @@ def renew_external_step_2(self, ca, old_cert_der): with certs.NSSDatabase() as tmpdb: tmpdb.create_db() - tmpdb.add_cert(old_cert_der, 'IPA CA', 'C,,') + tmpdb.add_cert(old_cert_der, 'IPA CA', EXTERNAL_CA_TRUST_FLAGS) try: - tmpdb.add_cert(new_cert_der, 'IPA CA', 'C,,') + tmpdb.add_cert(new_cert_der, 'IPA CA', EXTERNAL_CA_TRUST_FLAGS) except ipautil.CalledProcessError as e: raise admintool.ScriptError( "Not compatible with the current CA certificate: %s" % e) @@ -253,7 +254,8 @@ def renew_external_step_2(self, ca, old_cert_der): ca_certs = x509.load_certificate_list_from_file(ca_file.name) for ca_cert in ca_certs: data = ca_cert.public_bytes(serialization.Encoding.DER) - tmpdb.add_cert(data, str(DN(ca_cert.subject)), 'C,,') + tmpdb.add_cert( + data, str(DN(ca_cert.subject)), EXTERNAL_CA_TRUST_FLAGS) try: tmpdb.verify_ca_cert_validity('IPA CA') @@ -270,7 +272,11 @@ def renew_external_step_2(self, ca, old_cert_der): except RuntimeError: break certstore.put_ca_cert_nss( - conn, api.env.basedn, ca_cert, nickname, ',,') + conn, + api.env.basedn, + ca_cert, + nickname, + EMPTY_TRUST_FLAGS) dn = DN(('cn', self.cert_nickname), ('cn', 'ca_renewal'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) @@ -343,7 +349,7 @@ def install(self): with certs.NSSDatabase() as tmpdb: tmpdb.create_db() - tmpdb.add_cert(cert, nickname, 'C,,') + tmpdb.add_cert(cert, nickname, EXTERNAL_CA_TRUST_FLAGS) for ca_cert, ca_nickname, ca_trust_flags in ca_certs: tmpdb.add_cert(ca_cert, ca_nickname, ca_trust_flags) diff --git a/ipaserver/install/plugins/upload_cacrt.py b/ipaserver/install/plugins/upload_cacrt.py index 425ea63..7d294ff 100644 --- a/ipaserver/install/plugins/upload_cacrt.py +++ b/ipaserver/install/plugins/upload_cacrt.py @@ -55,7 +55,7 @@ def execute(self, **options): if 'u' in trust_flags: continue if nickname == ca_nickname and ca_enabled: - trust_flags = 'CT,C,C' + trust_flags = certdb.IPA_CA_TRUST_FLAGS cert = db.get_cert_from_db(nickname, pem=False) trust, _ca, eku = certstore.trust_flags_to_key_policy(trust_flags) diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py index aa8e67f..5e78e6f 100644 --- a/ipaserver/install/server/replicainstall.py +++ b/ipaserver/install/server/replicainstall.py @@ -23,6 +23,7 @@ from ipalib.install import certstore, sysrestore from ipalib.install.kinit import kinit_keytab from ipapython import ipaldap, ipautil +from ipapython.certdb import IPA_CA_TRUST_FLAGS from ipapython.dn import DN from ipapython.ipa_log_manager import root_logger from ipapython.admintool import ScriptError @@ -737,7 +738,7 @@ def install_check(installer): nssdir=tmp_db_dir, subject_base=config.subject_base) if ca_enabled: - trust_flags = 'CT,C,C' + trust_flags = IPA_CA_TRUST_FLAGS else: trust_flags = None tmp_db.create_from_pkcs12(pkcs12_info[0], pkcs12_info[1], diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py index 5e5c837..73a4f11 100644 --- a/ipaserver/install/server/upgrade.py +++ b/ipaserver/install/server/upgrade.py @@ -1389,7 +1389,7 @@ def fix_trust_flags(): nickname = certdb.get_ca_nickname(api.env.realm) cert = db.get_cert_from_db(nickname) if cert: - db.trust_root_cert(nickname, 'CT,C,C') + db.trust_root_cert(nickname, certdb.IPA_CA_TRUST_FLAGS) sysupgrade.set_upgrade_state('http', 'fix_trust_flags', True) @@ -1407,7 +1407,7 @@ def fix_server_cert_trust_flags(): sc_nickname = installutils.get_directive(paths.HTTPD_NSS_CONF, "NSSNickname") # Add trust flag which set certificate trusted for SSL connections. - db.trust_root_cert(sc_nickname, "P,,") + db.trust_root_cert(sc_nickname, certdb.TRUSTED_PEER_TRUST_FLAGS) sysupgrade.set_upgrade_state('http', 'fix_serv_cert_trust_flags', True) From aa17b3732721deb3e9c6993137064cd2bfd30c3a Mon Sep 17 00:00:00 2001 From: Jan Cholasta <jchol...@redhat.com> Date: Thu, 27 Apr 2017 09:57:45 +0200 Subject: [PATCH 02/14] certdb, certs: make trust flags argument mandatory Make the trust flags argument mandatory in all functions in `certdb` and `certs`. https://pagure.io/freeipa/issue/6831 --- ipapython/certdb.py | 4 +--- ipaserver/install/certs.py | 11 +++++------ ipaserver/install/dsinstance.py | 2 +- ipaserver/install/httpinstance.py | 6 ++++-- ipaserver/install/installutils.py | 5 +++-- ipaserver/install/server/replicainstall.py | 4 ++-- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/ipapython/certdb.py b/ipapython/certdb.py index 38f3bf0..d923dc3 100644 --- a/ipapython/certdb.py +++ b/ipapython/certdb.py @@ -466,14 +466,12 @@ def import_files(self, files, import_keys=False, key_password=None, self.import_pkcs12(out_file.name, out_password) - def trust_root_cert(self, root_nickname, trust_flags=None): + def trust_root_cert(self, root_nickname, trust_flags): 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 = EXTERNAL_CA_TRUST_FLAGS try: self.run_certutil(["-M", "-n", root_nickname, "-t", trust_flags]) diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py index f87e00e..17b9eba 100644 --- a/ipaserver/install/certs.py +++ b/ipaserver/install/certs.py @@ -550,7 +550,7 @@ def find_root_cert(self, nickname): return root_nicknames - def trust_root_cert(self, root_nickname, trust_flags=None): + def trust_root_cert(self, root_nickname, trust_flags): if root_nickname is None: root_logger.debug("Unable to identify root certificate to trust. Continuing but things are likely to fail.") return @@ -600,14 +600,13 @@ def create_from_cacert(self): self.create_certdbs() self.load_cacert(cacert_fname, IPA_CA_TRUST_FLAGS) - def create_from_pkcs12(self, pkcs12_fname, pkcs12_passwd, passwd=None, - ca_file=None, trust_flags=None): + def create_from_pkcs12(self, pkcs12_fname, pkcs12_passwd, + ca_file, trust_flags): """Create a new NSS database using the certificates in a PKCS#12 file. pkcs12_fname: the filename of the PKCS#12 file pkcs12_pwd_fname: the file containing the pin for the PKCS#12 file nickname: the nickname/friendly-name of the cert we are loading - passwd: The password to use for the new NSS database we are creating The global CA may be added as well in case it wasn't included in the PKCS#12 file. Extra certs won't hurt in any case. @@ -615,7 +614,7 @@ def create_from_pkcs12(self, pkcs12_fname, pkcs12_passwd, passwd=None, The global CA may be specified in ca_file, as a PEM filename. """ self.create_noise_file() - self.create_passwd_file(passwd) + self.create_passwd_file() self.create_certdbs() self.init_from_pkcs12( pkcs12_fname, @@ -624,7 +623,7 @@ def create_from_pkcs12(self, pkcs12_fname, pkcs12_passwd, passwd=None, trust_flags=trust_flags) def init_from_pkcs12(self, pkcs12_fname, pkcs12_passwd, - ca_file=None, trust_flags=None): + ca_file, trust_flags): self.import_pkcs12(pkcs12_fname, pkcs12_passwd) server_certs = self.find_server_certs() if len(server_certs) == 0: diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index 0db0368..0e4ae4b 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -769,7 +769,7 @@ def __enable_ssl(self): if self.ca_is_configured: trust_flags = IPA_CA_TRUST_FLAGS else: - trust_flags = None + trust_flags = EXTERNAL_CA_TRUST_FLAGS dsdb.create_from_pkcs12(self.pkcs12_info[0], self.pkcs12_info[1], ca_file=self.ca_file, trust_flags=trust_flags) diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py index a6aeb21..c76a1a4 100644 --- a/ipaserver/install/httpinstance.py +++ b/ipaserver/install/httpinstance.py @@ -32,7 +32,9 @@ from augeas import Augeas from ipalib.install import certmonger -from ipapython.certdb import IPA_CA_TRUST_FLAGS, TRUSTED_PEER_TRUST_FLAGS +from ipapython.certdb import (IPA_CA_TRUST_FLAGS, + EXTERNAL_CA_TRUST_FLAGS, + TRUSTED_PEER_TRUST_FLAGS) from ipaserver.install import service from ipaserver.install import certs from ipaserver.install import installutils @@ -384,7 +386,7 @@ def __setup_ssl(self): if self.ca_is_configured: trust_flags = IPA_CA_TRUST_FLAGS else: - trust_flags = None + trust_flags = EXTERNAL_CA_TRUST_FLAGS db.init_from_pkcs12(self.pkcs12_info[0], self.pkcs12_info[1], ca_file=self.ca_file, trust_flags=trust_flags) diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py index b6f0148..0445a1d 100644 --- a/ipaserver/install/installutils.py +++ b/ipaserver/install/installutils.py @@ -49,6 +49,7 @@ import ipaplatform from ipapython import ipautil, admintool, version from ipapython.admintool import ScriptError +from ipapython.certdb import EXTERNAL_CA_TRUST_FLAGS from ipapython.ipa_log_manager import root_logger from ipapython.ipaldap import DIRMAN_DN, LDAPClient from ipalib.util import validate_hostname @@ -1036,7 +1037,7 @@ def load_pkcs12(cert_files, key_password, key_nickname, ca_cert_files, if 'u' in trust_flags: key_nickname = nickname continue - nssdb.trust_root_cert(nickname) + nssdb.trust_root_cert(nickname, EXTERNAL_CA_TRUST_FLAGS) # Check we have the whole cert chain & the CA is in it trust_chain = list(reversed(nssdb.get_trust_chain(key_nickname))) @@ -1176,7 +1177,7 @@ def load_external_cert(files, ca_subject): cache[nickname] = (cert, subject, issuer) if subject == ca_subject: ca_nickname = nickname - nssdb.trust_root_cert(nickname) + nssdb.trust_root_cert(nickname, EXTERNAL_CA_TRUST_FLAGS) if ca_nickname is None: raise ScriptError( diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py index 5e78e6f..fb738cb 100644 --- a/ipaserver/install/server/replicainstall.py +++ b/ipaserver/install/server/replicainstall.py @@ -23,7 +23,7 @@ from ipalib.install import certstore, sysrestore from ipalib.install.kinit import kinit_keytab from ipapython import ipaldap, ipautil -from ipapython.certdb import IPA_CA_TRUST_FLAGS +from ipapython.certdb import IPA_CA_TRUST_FLAGS, EXTERNAL_CA_TRUST_FLAGS from ipapython.dn import DN from ipapython.ipa_log_manager import root_logger from ipapython.admintool import ScriptError @@ -740,7 +740,7 @@ def install_check(installer): if ca_enabled: trust_flags = IPA_CA_TRUST_FLAGS else: - trust_flags = None + trust_flags = EXTERNAL_CA_TRUST_FLAGS tmp_db.create_from_pkcs12(pkcs12_info[0], pkcs12_info[1], ca_file=cafile, trust_flags=trust_flags) From a272e1caded79a9822e96ef1d18daa492c4ee21f Mon Sep 17 00:00:00 2001 From: Jan Cholasta <jchol...@redhat.com> Date: Thu, 27 Apr 2017 09:37:38 +0200 Subject: [PATCH 03/14] certdb: use custom object for trust flags Replace trust flag strings with `TrustFlags` objects. The `TrustFlags` class encapsulates `certstore` key policy and has an additional flag indicating the presence of a private key. https://pagure.io/freeipa/issue/6831 --- install/restart_scripts/renew_ca_cert | 2 +- ipalib/install/certstore.py | 49 +------------ ipapython/certdb.py | 109 ++++++++++++++++++++++++++-- ipaserver/install/installutils.py | 2 +- ipaserver/install/ipa_cacert_manage.py | 6 +- ipaserver/install/ipa_server_certinstall.py | 4 +- ipaserver/install/plugins/upload_cacrt.py | 2 +- ipaserver/install/server/upgrade.py | 2 +- 8 files changed, 117 insertions(+), 59 deletions(-) diff --git a/install/restart_scripts/renew_ca_cert b/install/restart_scripts/renew_ca_cert index 7a54b4c..bb31def 100644 --- a/install/restart_scripts/renew_ca_cert +++ b/install/restart_scripts/renew_ca_cert @@ -125,7 +125,7 @@ def _main(): # Remove old external CA certificates for ca_nick, ca_flags in db.list_certs(): - if 'u' in ca_flags: + if ca_flags.has_key: continue # Delete *all* certificates that use the nickname while True: diff --git a/ipalib/install/certstore.py b/ipalib/install/certstore.py index 310e08e..bc2079f 100644 --- a/ipalib/install/certstore.py +++ b/ipalib/install/certstore.py @@ -25,7 +25,7 @@ from pyasn1.error import PyAsn1Error from ipapython.dn import DN -from ipapython.certdb import get_ca_nickname +from ipapython.certdb import get_ca_nickname, TrustFlags from ipalib import errors, x509 def _parse_cert(dercert): @@ -344,57 +344,14 @@ def trust_flags_to_key_policy(trust_flags): """ Convert certutil trust flags to certificate store key policy. """ - if 'p' in trust_flags: - if 'C' in trust_flags or 'P' in trust_flags or 'T' in trust_flags: - raise ValueError("cannot be both trusted and not trusted") - return False, None, None - elif 'C' in trust_flags or 'T' in trust_flags: - if 'P' in trust_flags: - raise ValueError("cannot be both CA and not CA") - ca = True - elif 'P' in trust_flags: - ca = False - else: - return None, None, set() - - trust_flags = trust_flags.split(',') - ext_key_usage = set() - for i, kp in enumerate((x509.EKU_SERVER_AUTH, - x509.EKU_EMAIL_PROTECTION, - x509.EKU_CODE_SIGNING)): - if 'C' in trust_flags[i] or 'P' in trust_flags[i]: - ext_key_usage.add(kp) - if 'T' in trust_flags[0]: - ext_key_usage.add(x509.EKU_CLIENT_AUTH) - - return True, ca, ext_key_usage + return trust_flags[1:] def key_policy_to_trust_flags(trusted, ca, ext_key_usage): """ Convert certificate store key policy to certutil trust flags. """ - if trusted is False: - return 'p,p,p' - elif trusted is None or ca is None: - return ',,' - elif ext_key_usage is None: - if ca: - return 'CT,C,C' - else: - return 'P,P,P' - - trust_flags = ['', '', ''] - for i, kp in enumerate((x509.EKU_SERVER_AUTH, - x509.EKU_EMAIL_PROTECTION, - x509.EKU_CODE_SIGNING)): - if kp in ext_key_usage: - trust_flags[i] += ('C' if ca else 'P') - if ca and x509.EKU_CLIENT_AUTH in ext_key_usage: - trust_flags[0] += 'T' - - trust_flags = ','.join(trust_flags) - return trust_flags + return TrustFlags(False, trusted, ca, ext_key_usage) def put_ca_cert_nss(ldap, base_dn, dercert, nickname, trust_flags, diff --git a/ipapython/certdb.py b/ipapython/certdb.py index d923dc3..ac0f956 100644 --- a/ipapython/certdb.py +++ b/ipapython/certdb.py @@ -17,6 +17,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # +import collections import os import io import pwd @@ -52,10 +53,26 @@ NSS_FILES = ("cert8.db", "key3.db", "secmod.db", "pwdfile.txt") -EMPTY_TRUST_FLAGS = ',,' -IPA_CA_TRUST_FLAGS = 'CT,C,C' -EXTERNAL_CA_TRUST_FLAGS = 'C,,' -TRUSTED_PEER_TRUST_FLAGS = 'P,,' +TrustFlags = collections.namedtuple('TrustFlags', 'has_key trusted ca usages') + +EMPTY_TRUST_FLAGS = TrustFlags(False, None, None, None) + +IPA_CA_TRUST_FLAGS = TrustFlags( + False, True, True, frozenset({ + x509.EKU_SERVER_AUTH, + x509.EKU_CLIENT_AUTH, + x509.EKU_CODE_SIGNING, + x509.EKU_EMAIL_PROTECTION, + }), +) + +EXTERNAL_CA_TRUST_FLAGS = TrustFlags( + False, True, True, frozenset({x509.EKU_SERVER_AUTH}), +) + +TRUSTED_PEER_TRUST_FLAGS = TrustFlags( + False, True, False, frozenset({x509.EKU_SERVER_AUTH}), +) def get_ca_nickname(realm, format=CA_NICKNAME_FMT): @@ -82,6 +99,82 @@ def find_cert_from_txt(cert, start=0): return (cert, e) +def parse_trust_flags(trust_flags): + """ + Convert certutil trust flags to TrustFlags object. + """ + has_key = 'u' in trust_flags + + if 'p' in trust_flags: + if 'C' in trust_flags or 'P' in trust_flags or 'T' in trust_flags: + raise ValueError("cannot be both trusted and not trusted") + return False, None, None + elif 'C' in trust_flags or 'T' in trust_flags: + if 'P' in trust_flags: + raise ValueError("cannot be both CA and not CA") + ca = True + elif 'P' in trust_flags: + ca = False + else: + return TrustFlags(has_key, None, None, frozenset()) + + trust_flags = trust_flags.split(',') + ext_key_usage = set() + for i, kp in enumerate((x509.EKU_SERVER_AUTH, + x509.EKU_EMAIL_PROTECTION, + x509.EKU_CODE_SIGNING)): + if 'C' in trust_flags[i] or 'P' in trust_flags[i]: + ext_key_usage.add(kp) + if 'T' in trust_flags[0]: + ext_key_usage.add(x509.EKU_CLIENT_AUTH) + + return TrustFlags(has_key, True, ca, frozenset(ext_key_usage)) + + +def unparse_trust_flags(trust_flags): + """ + Convert TrustFlags object to certutil trust flags. + """ + has_key, trusted, ca, ext_key_usage = trust_flags + + if trusted is False: + if has_key: + return 'pu,pu,pu' + else: + return 'p,p,p' + elif trusted is None or ca is None: + if has_key: + return 'u,u,u' + else: + return ',,' + elif ext_key_usage is None: + if ca: + if has_key: + return 'CTu,Cu,Cu' + else: + return 'CT,C,C' + else: + if has_key: + return 'Pu,Pu,Pu' + else: + return 'P,P,P' + + trust_flags = ['', '', ''] + for i, kp in enumerate((x509.EKU_SERVER_AUTH, + x509.EKU_EMAIL_PROTECTION, + x509.EKU_CODE_SIGNING)): + if kp in ext_key_usage: + trust_flags[i] += ('C' if ca else 'P') + if ca and x509.EKU_CLIENT_AUTH in ext_key_usage: + trust_flags[0] += 'T' + if has_key: + for i in range(3): + trust_flags[i] += 'u' + + trust_flags = ','.join(trust_flags) + return trust_flags + + class NSSDatabase(object): """A general-purpose wrapper around a NSS cert database @@ -200,7 +293,9 @@ def list_certs(self): for cert in certs: match = re.match(r'^(.+?)\s+(\w*,\w*,\w*)\s*$', cert) if match: - certlist.append(match.groups()) + nickname = match.group(1) + trust_flags = parse_trust_flags(match.group(2)) + certlist.append((nickname, trust_flags)) return tuple(certlist) @@ -213,7 +308,7 @@ def find_server_certs(self): """ server_certs = [] for name, flags in self.list_certs(): - if 'u' in flags: + if flags.has_key: server_certs.append((name, flags)) return server_certs @@ -472,6 +567,7 @@ def trust_root_cert(self, root_nickname, trust_flags): "No need to add trust for built-in root CAs, skipping %s" % root_nickname) else: + trust_flags = unparse_trust_flags(trust_flags) try: self.run_certutil(["-M", "-n", root_nickname, "-t", trust_flags]) @@ -533,6 +629,7 @@ def import_pem_cert(self, nickname, flags, location): location) def add_cert(self, cert, nick, flags, pem=False): + flags = unparse_trust_flags(flags) args = ["-A", "-n", nick, "-t", flags] if pem: args.append("-a") diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py index 0445a1d..5bce989 100644 --- a/ipaserver/install/installutils.py +++ b/ipaserver/install/installutils.py @@ -1034,7 +1034,7 @@ def load_pkcs12(cert_files, key_password, key_nickname, ca_cert_files, raise ScriptError(str(e)) for nickname, trust_flags in nssdb.list_certs(): - if 'u' in trust_flags: + if trust_flags.has_key: key_nickname = nickname continue nssdb.trust_root_cert(nickname, EXTERNAL_CA_TRUST_FLAGS) diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py index 88b40d4..d28a596 100644 --- a/ipaserver/install/ipa_cacert_manage.py +++ b/ipaserver/install/ipa_cacert_manage.py @@ -26,7 +26,9 @@ from ipalib.install import certmonger, certstore from ipapython import admintool, ipautil -from ipapython.certdb import EMPTY_TRUST_FLAGS, EXTERNAL_CA_TRUST_FLAGS +from ipapython.certdb import (EMPTY_TRUST_FLAGS, + EXTERNAL_CA_TRUST_FLAGS, + parse_trust_flags) from ipapython.dn import DN from ipaplatform.paths import paths from ipalib import api, errors, x509 @@ -366,6 +368,8 @@ def install(self): len(trust_flags.split(',')) != 3): raise admintool.ScriptError("Invalid trust flags") + trust_flags = parse_trust_flags(trust_flags) + try: certstore.put_ca_cert_nss( api.Backend.ldap2, api.env.basedn, cert, nickname, trust_flags) diff --git a/ipaserver/install/ipa_server_certinstall.py b/ipaserver/install/ipa_server_certinstall.py index ee93535..9f2cd95 100644 --- a/ipaserver/install/ipa_server_certinstall.py +++ b/ipaserver/install/ipa_server_certinstall.py @@ -170,13 +170,13 @@ def check_chain(self, pkcs12_filename, pkcs12_pin, nssdb): # this leaves only the server certs in the temp db tempnssdb.import_pkcs12(pkcs12_filename, pkcs12_pin) for nickname, flags in tempnssdb.list_certs(): - if 'u' not in flags: + if not flags.has_key: while tempnssdb.has_nickname(nickname): tempnssdb.delete_cert(nickname) # import all the CA certs from nssdb into the temp db for nickname, flags in nssdb.list_certs(): - if 'u' not in flags: + if not flags.has_key: cert = nssdb.get_cert_from_db(nickname) tempnssdb.add_cert(cert, nickname, flags) diff --git a/ipaserver/install/plugins/upload_cacrt.py b/ipaserver/install/plugins/upload_cacrt.py index 7d294ff..73cc91d 100644 --- a/ipaserver/install/plugins/upload_cacrt.py +++ b/ipaserver/install/plugins/upload_cacrt.py @@ -52,7 +52,7 @@ def execute(self, **options): ldap = self.api.Backend.ldap2 for nickname, trust_flags in db.list_certs(): - if 'u' in trust_flags: + if trust_flags.has_key: continue if nickname == ca_nickname and ca_enabled: trust_flags = certdb.IPA_CA_TRUST_FLAGS diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py index 73a4f11..c244958 100644 --- a/ipaserver/install/server/upgrade.py +++ b/ipaserver/install/server/upgrade.py @@ -1547,7 +1547,7 @@ def disable_httpd_system_trust(http): db = certs.CertDB(api.env.realm, nssdir=paths.HTTPD_ALIAS_DIR) for nickname, trust_flags in db.list_certs(): - if 'u' not in trust_flags: + if not trust_flags.has_key: cert = db.get_cert_from_db(nickname, pem=False) if cert: ca_certs.append((cert, nickname, trust_flags)) From 1456a55049d7609dfd6d39165ed0bd0c04cb9fb0 Mon Sep 17 00:00:00 2001 From: Jan Cholasta <jchol...@redhat.com> Date: Wed, 3 May 2017 06:38:20 +0000 Subject: [PATCH 04/14] install: trust IPA CA for PKINIT Trust IPA CA to issue PKINIT KDC and client authentication certificates in the IPA certificate store. https://pagure.io/freeipa/issue/6831 --- ipalib/x509.py | 2 ++ ipapython/certdb.py | 2 ++ ipaserver/install/dsinstance.py | 31 +++++++++++++++++++++++------- ipaserver/install/plugins/upload_cacrt.py | 6 +++++- ipaserver/install/server/install.py | 9 ++++++--- ipaserver/install/server/replicainstall.py | 1 + 6 files changed, 40 insertions(+), 11 deletions(-) diff --git a/ipalib/x509.py b/ipalib/x509.py index dbcbb59..4d866a6 100644 --- a/ipalib/x509.py +++ b/ipalib/x509.py @@ -66,6 +66,8 @@ EKU_CLIENT_AUTH = '1.3.6.1.5.5.7.3.2' EKU_CODE_SIGNING = '1.3.6.1.5.5.7.3.3' EKU_EMAIL_PROTECTION = '1.3.6.1.5.5.7.3.4' +EKU_PKINIT_CLIENT_AUTH = '1.3.6.1.5.2.3.4' +EKU_PKINIT_KDC = '1.3.6.1.5.2.3.5' EKU_ANY = '2.5.29.37.0' EKU_PLACEHOLDER = '1.3.6.1.4.1.3319.6.10.16' diff --git a/ipapython/certdb.py b/ipapython/certdb.py index ac0f956..7459238 100644 --- a/ipapython/certdb.py +++ b/ipapython/certdb.py @@ -63,6 +63,8 @@ x509.EKU_CLIENT_AUTH, x509.EKU_CODE_SIGNING, x509.EKU_EMAIL_PROTECTION, + x509.EKU_PKINIT_CLIENT_AUTH, + x509.EKU_PKINIT_KDC, }), ) diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index 0e4ae4b..39248ed 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -31,8 +31,11 @@ import ldap +from ipalib import x509 from ipalib.install import certmonger, certstore -from ipapython.certdb import IPA_CA_TRUST_FLAGS, EXTERNAL_CA_TRUST_FLAGS +from ipapython.certdb import (IPA_CA_TRUST_FLAGS, + EXTERNAL_CA_TRUST_FLAGS, + TrustFlags) from ipapython.ipa_log_manager import root_logger from ipapython import ipautil, ipaldap from ipapython import dogtag @@ -289,7 +292,8 @@ def __common_post_setup(self): def init_info(self, realm_name, fqdn, domain_name, dm_password, subject_base, ca_subject, - idstart, idmax, pkcs12_info, ca_file=None): + idstart, idmax, pkcs12_info, ca_file=None, + setup_pkinit=False): self.realm = realm_name.upper() self.serverid = installutils.realm_to_serverid(self.realm) self.suffix = ipautil.realm_to_suffix(self.realm) @@ -303,6 +307,7 @@ def init_info(self, realm_name, fqdn, domain_name, dm_password, self.pkcs12_info = pkcs12_info if pkcs12_info: self.ca_is_configured = False + self.setup_pkinit = setup_pkinit self.ca_file = ca_file self.__setup_sub_dict() @@ -311,11 +316,12 @@ def create_instance(self, realm_name, fqdn, domain_name, dm_password, pkcs12_info=None, idstart=1100, idmax=999999, subject_base=None, ca_subject=None, - hbac_allow=True, ca_file=None): + hbac_allow=True, ca_file=None, setup_pkinit=False): self.init_info( realm_name, fqdn, domain_name, dm_password, subject_base, ca_subject, - idstart, idmax, pkcs12_info, ca_file=ca_file) + idstart, idmax, pkcs12_info, ca_file=ca_file, + setup_pkinit=setup_pkinit) self.__common_setup() self.step("restarting directory server", self.__restart_instance) @@ -354,7 +360,8 @@ def create_replica(self, realm_name, master_fqdn, fqdn, domain_name, dm_password, subject_base, ca_subject, api, pkcs12_info=None, ca_file=None, - ca_is_configured=None, promote=False): + ca_is_configured=None, promote=False, + setup_pkinit=False): # idstart and idmax are configured so that the range is seen as # depleted by the DNA plugin and the replica will go and get a # new range from the master. @@ -372,7 +379,8 @@ def create_replica(self, realm_name, master_fqdn, fqdn, idstart=idstart, idmax=idmax, pkcs12_info=pkcs12_info, - ca_file=ca_file + ca_file=ca_file, + setup_pkinit=setup_pkinit, ) self.master_fqdn = master_fqdn if ca_is_configured is not None: @@ -882,8 +890,17 @@ def __upload_ca_cert(self): nickname = self.cacert_name cert = dsdb.get_cert_from_db(nickname, pem=False) + cacert_flags = trust_flags[nickname] + if self.setup_pkinit: + cacert_flags = TrustFlags( + cacert_flags.has_key, + cacert_flags.trusted, + cacert_flags.ca, + (cacert_flags.usages | + {x509.EKU_PKINIT_CLIENT_AUTH, x509.EKU_PKINIT_KDC}), + ) certstore.put_ca_cert_nss(conn, self.suffix, cert, nickname, - trust_flags[nickname], + cacert_flags, config_ipa=self.ca_is_configured, config_compat=self.master_fqdn is None) diff --git a/ipaserver/install/plugins/upload_cacrt.py b/ipaserver/install/plugins/upload_cacrt.py index 73cc91d..a1957ca 100644 --- a/ipaserver/install/plugins/upload_cacrt.py +++ b/ipaserver/install/plugins/upload_cacrt.py @@ -79,7 +79,11 @@ def execute(self, **options): try: ldap.add_entry(entry) except errors.DuplicateEntry: - pass + if nickname == ca_nickname and ca_enabled: + try: + ldap.update_entry(entry) + except errors.EmptyModlist: + pass if ca_cert: dn = DN(('cn', 'CACert'), ('cn', 'ipa'), ('cn','etc'), diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py index 0ce60e9..25c21db 100644 --- a/ipaserver/install/server/install.py +++ b/ipaserver/install/server/install.py @@ -737,7 +737,8 @@ def install(installer): idstart=options.idstart, idmax=options.idmax, subject_base=options.subject_base, ca_subject=options.ca_subject, - hbac_allow=not options.no_hbac_allow) + hbac_allow=not options.no_hbac_allow, + setup_pkinit=not options.no_pkinit) else: ds = dsinstance.DsInstance(fstore=fstore, domainlevel=options.domainlevel, @@ -748,7 +749,8 @@ def install(installer): idstart=options.idstart, idmax=options.idmax, subject_base=options.subject_base, ca_subject=options.ca_subject, - hbac_allow=not options.no_hbac_allow) + hbac_allow=not options.no_hbac_allow, + setup_pkinit=not options.no_pkinit) ntpinstance.ntp_ldap_enable(host_name, ds.suffix, realm_name) @@ -759,7 +761,8 @@ def install(installer): installer._ds = ds ds.init_info( realm_name, host_name, domain_name, dm_password, - options.subject_base, options.ca_subject, 1101, 1100, None) + options.subject_base, options.ca_subject, 1101, 1100, None, + setup_pkinit=not options.no_pkinit) krb = krbinstance.KrbInstance(fstore) if not options.external_cert_files: diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py index fb738cb..c19edce 100644 --- a/ipaserver/install/server/replicainstall.py +++ b/ipaserver/install/server/replicainstall.py @@ -107,6 +107,7 @@ def install_replica_ds(config, options, ca_is_configured, remote_api, ca_file=ca_file, promote=promote, # we need promote because of replication setup api=remote_api, + setup_pkinit=not options.no_pkinit, ) return ds From 9b6ddd3e6b94e69540580677c287bf0326141129 Mon Sep 17 00:00:00 2001 From: Jan Cholasta <jchol...@redhat.com> Date: Wed, 3 May 2017 06:48:57 +0000 Subject: [PATCH 05/14] client install: fix client PKINIT configuration Set `pkinit_anchors` in `krb5.conf` to a CA certificate bundle of CAs trusted to issue KDC certificates rather than `/etc/ipa/ca.crt`. Set `pkinit_pool` in `krb5.conf` to a CA certificate bundle of all CAs known to IPA. Make sure both bundles are exported in all installation code paths. https://pagure.io/freeipa/issue/6831 --- client/Makefile.am | 1 + freeipa.spec.in | 10 ++++++++++ install/share/krb5.conf.template | 3 ++- ipaclient/install/client.py | 15 ++++++++++++++- ipaclient/install/ipa_certupdate.py | 2 ++ ipaplatform/base/paths.py | 2 ++ ipaserver/install/cainstance.py | 11 +++++++---- ipaserver/install/ipa_backup.py | 2 ++ ipaserver/install/krbinstance.py | 4 +++- ipaserver/install/server/install.py | 10 ++++++++++ ipaserver/install/server/replicainstall.py | 4 ++++ ipaserver/install/server/upgrade.py | 4 +++- 12 files changed, 60 insertions(+), 8 deletions(-) diff --git a/client/Makefile.am b/client/Makefile.am index b6c9dea..e354cb4 100644 --- a/client/Makefile.am +++ b/client/Makefile.am @@ -101,4 +101,5 @@ EXTRA_DIST = \ install-data-hook: $(INSTALL) -d -m 755 $(DESTDIR)$(IPA_SYSCONF_DIR)/nssdb + $(INSTALL) -d -m 755 $(DESTDIR)$(localstatedir)/lib/ipa-client/pki $(INSTALL) -d -m 755 $(DESTDIR)$(localstatedir)/lib/ipa-client/sysrestore diff --git a/freeipa.spec.in b/freeipa.spec.in index 0f9952d..da3c24b 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -1101,6 +1101,15 @@ if [ $1 -gt 1 ] ; then fi fi + if [ $restore -ge 2 ]; then + if grep -E -q '\s*pkinit_anchors = FILE:/etc/ipa/ca.crt$' /etc/krb5.conf 2>/dev/null; then + sed -E 's|(\s*)pkinit_anchors = FILE:/etc/ipa/ca.crt$|\1pkinit_anchors = FILE:/var/lib/ipa-client/pki/kdc-ca-bundle.pem\n\1pkinit_pool = FILE:/var/lib/ipa-client/pki/ca-bundle.pem|' /etc/krb5.conf >/etc/krb5.conf.ipanew + mv -Z /etc/krb5.conf.ipanew /etc/krb5.conf + cp /etc/ipa/ca.crt /var/lib/ipa-client/pki/kdc-ca-bundle.pem + cp /etc/ipa/ca.crt /var/lib/ipa-client/pki/ca-bundle.pem + fi + fi + if [ -f '/etc/sysconfig/ntpd' -a $restore -ge 2 ]; then if grep -E -q 'OPTIONS=.*-u ntp:ntp' /etc/sysconfig/ntpd 2>/dev/null; then sed -r '/OPTIONS=/ { s/\s+-u ntp:ntp\s+/ /; s/\s*-u ntp:ntp\s*// }' /etc/sysconfig/ntpd >/etc/sysconfig/ntpd.ipanew @@ -1473,6 +1482,7 @@ fi %ghost %config(noreplace) %{_sysconfdir}/ipa/nssdb/pwdfile.txt %ghost %config(noreplace) %{_sysconfdir}/pki/ca-trust/source/ipa.p11-kit %dir %{_localstatedir}/lib/ipa-client +%dir %{_localstatedir}/lib/ipa-client/pki %dir %{_localstatedir}/lib/ipa-client/sysrestore %{_mandir}/man5/default.conf.5* diff --git a/install/share/krb5.conf.template b/install/share/krb5.conf.template index e8b2ad8..1f18ff9 100644 --- a/install/share/krb5.conf.template +++ b/install/share/krb5.conf.template @@ -21,7 +21,8 @@ $OTHER_LIBDEFAULTS master_kdc = $FQDN:88 admin_server = $FQDN:749 default_domain = $DOMAIN - pkinit_anchors = FILE:/etc/ipa/ca.crt + pkinit_anchors = FILE:$KDC_CA_BUNDLE_PEM + pkinit_pool = FILE:$CA_BUNDLE_PEM } [domain_realm] diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py index e78be90..6f10f52 100644 --- a/ipaclient/install/client.py +++ b/ipaclient/install/client.py @@ -710,7 +710,11 @@ def configure_krb5_conf( kropts.append(krbconf.setOption('default_domain', cli_domain)) kropts.append( - krbconf.setOption('pkinit_anchors', 'FILE:%s' % paths.IPA_CA_CRT)) + krbconf.setOption('pkinit_anchors', + 'FILE:%s' % paths.KDC_CA_BUNDLE_PEM)) + kropts.append( + krbconf.setOption('pkinit_pool', + 'FILE:%s' % paths.CA_BUNDLE_PEM)) ropts = [{ 'name': cli_realm, 'type': 'subsection', @@ -2770,6 +2774,13 @@ def _install(options): ca_certs_trust = [(c, n, certstore.key_policy_to_trust_flags(t, True, u)) for (c, n, t, u) in ca_certs] + x509.write_certificate_list( + [c for c, n, t, u in ca_certs if t is not False], + paths.KDC_CA_BUNDLE_PEM) + x509.write_certificate_list( + [c for c, n, t, u in ca_certs if t is not False], + paths.CA_BUNDLE_PEM) + # 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) @@ -3317,6 +3328,8 @@ def uninstall(options): # Remove the CA cert remove_file(paths.IPA_CA_CRT) + remove_file(paths.KDC_CA_BUNDLE_PEM) + remove_file(paths.CA_BUNDLE_PEM) root_logger.info("Client uninstall complete.") diff --git a/ipaclient/install/ipa_certupdate.py b/ipaclient/install/ipa_certupdate.py index 7dc88f0..7e8527e 100644 --- a/ipaclient/install/ipa_certupdate.py +++ b/ipaclient/install/ipa_certupdate.py @@ -113,6 +113,8 @@ def run(self): def update_client(self, certs): self.update_file(paths.IPA_CA_CRT, certs) + self.update_file(paths.KDC_CA_BUNDLE_PEM, certs) + self.update_file(paths.CA_BUNDLE_PEM, certs) ipa_db = certdb.NSSDatabase(api.env.nss_dir) diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py index 2d37c71..de3cdce 100644 --- a/ipaplatform/base/paths.py +++ b/ipaplatform/base/paths.py @@ -333,6 +333,8 @@ class BasePathNamespace(object): VAR_RUN_DIRSRV_DIR = "/var/run/dirsrv" IPA_CCACHES = "/var/run/ipa/ccaches" HTTP_CCACHE = "/var/lib/ipa/gssproxy/http.ccache" + CA_BUNDLE_PEM = "/var/lib/ipa-client/pki/ca-bundle.pem" + KDC_CA_BUNDLE_PEM = "/var/lib/ipa-client/pki/kdc-ca-bundle.pem" IPA_RENEWAL_LOCK = "/var/run/ipa/renewal.lock" SVC_LIST_FILE = "/var/run/ipa/services.list" KRB5CC_SAMBA = "/var/run/samba/krb5cc_samba" diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index a4aa4f2..b8c8cc4 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -794,10 +794,13 @@ def __export_ca_chain(self): certlist = x509.pkcs7_to_pems(data, x509.DER) # We have all the certificates in certlist, write them to a PEM file - with open(paths.IPA_CA_CRT, 'w') as ipaca_pem: - for cert in certlist: - ipaca_pem.write(cert) - ipaca_pem.write('\n') + for path in [paths.IPA_CA_CRT, + paths.KDC_CA_BUNDLE_PEM, + paths.CA_BUNDLE_PEM]: + with open(path, 'w') as ipaca_pem: + for cert in certlist: + ipaca_pem.write(cert) + ipaca_pem.write('\n') def __request_ra_certificate(self): # create a temp file storing the pwd diff --git a/ipaserver/install/ipa_backup.py b/ipaserver/install/ipa_backup.py index 40f08d7..f8cdd56 100644 --- a/ipaserver/install/ipa_backup.py +++ b/ipaserver/install/ipa_backup.py @@ -150,6 +150,8 @@ class Backup(admintool.AdminTool): paths.SSHD_CONFIG, paths.SSH_CONFIG, paths.KRB5_CONF, + paths.KDC_CA_BUNDLE_PEM, + paths.CA_BUNDLE_PEM, paths.IPA_CA_CRT, paths.IPA_DEFAULT_CONF, paths.DS_KEYTAB, diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py index 2f14ff5..e52577b 100644 --- a/ipaserver/install/krbinstance.py +++ b/ipaserver/install/krbinstance.py @@ -261,7 +261,9 @@ def __setup_sub_dict(self): KRB5KDC_KADM5_KEYTAB=paths.KRB5KDC_KADM5_KEYTAB, KDC_CERT=paths.KDC_CERT, KDC_KEY=paths.KDC_KEY, - CACERT_PEM=paths.CACERT_PEM) + CACERT_PEM=paths.CACERT_PEM, + KDC_CA_BUNDLE_PEM=paths.KDC_CA_BUNDLE_PEM, + CA_BUNDLE_PEM=paths.CA_BUNDLE_PEM) # IPA server/KDC is not a subdomain of default domain # Proper domain-realm mapping needs to be specified diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py index 25c21db..c1bdce6 100644 --- a/ipaserver/install/server/install.py +++ b/ipaserver/install/server/install.py @@ -796,6 +796,16 @@ def install(installer): x509.write_certificate(http_ca_cert, paths.IPA_CA_CRT) os.chmod(paths.IPA_CA_CRT, 0o444) + if not options.no_pkinit: + x509.write_certificate(http_ca_cert, paths.KDC_CA_BUNDLE_PEM) + else: + with open(paths.KDC_CA_BUNDLE_PEM, 'w'): + pass + os.chmod(paths.KDC_CA_BUNDLE_PEM, 0o444) + + x509.write_certificate(http_ca_cert, paths.CA_BUNDLE_PEM) + os.chmod(paths.CA_BUNDLE_PEM, 0o444) + # we now need to enable ssl on the ds ds.enable_ssl() diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py index c19edce..66d7ba4 100644 --- a/ipaserver/install/server/replicainstall.py +++ b/ipaserver/install/server/replicainstall.py @@ -1390,6 +1390,10 @@ def install(installer): # Update and istall updated CA file cafile = install_ca_cert(conn, api.env.basedn, api.env.realm, cafile) + install_ca_cert(conn, api.env.basedn, api.env.realm, cafile, + destfile=paths.KDC_CA_BUNDLE_PEM) + install_ca_cert(conn, api.env.basedn, api.env.realm, cafile, + destfile=paths.CA_BUNDLE_PEM) # Configure dirsrv ds = install_replica_ds(config, options, ca_enabled, diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py index c244958..648dc1f 100644 --- a/ipaserver/install/server/upgrade.py +++ b/ipaserver/install/server/upgrade.py @@ -1831,7 +1831,9 @@ def upgrade_configuration(): KRB5KDC_KADM5_KEYTAB=paths.KRB5KDC_KADM5_KEYTAB, KDC_CERT=paths.KDC_CERT, KDC_KEY=paths.KDC_KEY, - CACERT_PEM=paths.CACERT_PEM) + CACERT_PEM=paths.CACERT_PEM, + KDC_CA_BUNDLE_PEM=paths.KDC_CA_BUNDLE_PEM, + CA_BUNDLE_PEM=paths.CA_BUNDLE_PEM) krb.add_anonymous_principal() setup_pkinit(krb) From 25159f6c3f5ac5fcc9282fc98653974beab611be Mon Sep 17 00:00:00 2001 From: Jan Cholasta <jchol...@redhat.com> Date: Thu, 18 May 2017 07:57:40 +0000 Subject: [PATCH 06/14] install: introduce generic Kerberos Augeas lens Introduce new IPAKrb5 lens to handle krb5.conf and kdc.conf changes using Augeas. The stock Krb5 lens does not work on our krb5.conf and kdc.conf. https://pagure.io/freeipa/issue/6831 --- freeipa.spec.in | 1 + install/share/Makefile.am | 1 + install/share/ipakrb5.aug | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 install/share/ipakrb5.aug diff --git a/freeipa.spec.in b/freeipa.spec.in index da3c24b..beb11bc 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -1367,6 +1367,7 @@ fi %dir %{_usr}/share/ipa/schema.d %attr(0644,root,root) %{_usr}/share/ipa/schema.d/README %attr(0644,root,root) %{_usr}/share/ipa/gssapi.login +%{_usr}/share/ipa/ipakrb5.aug %files server-dns %defattr(-,root,root,-) diff --git a/install/share/Makefile.am b/install/share/Makefile.am index e7fac0c..7a36551 100644 --- a/install/share/Makefile.am +++ b/install/share/Makefile.am @@ -90,6 +90,7 @@ dist_app_DATA = \ ipa.conf.tmpfiles \ gssproxy.conf.template \ kdcproxy.wsgi \ + ipakrb5.aug \ $(NULL) kdcproxyconfdir = $(IPA_SYSCONF_DIR)/kdcproxy diff --git a/install/share/ipakrb5.aug b/install/share/ipakrb5.aug new file mode 100644 index 0000000..4a31a84 --- /dev/null +++ b/install/share/ipakrb5.aug @@ -0,0 +1,46 @@ +module IPAKrb5 = + autoload xfm + + let dels (s:string) = Util.del_str s + + let indent = Util.indent + let space = Sep.space + let opt_space = Sep.opt_space + let sep = Sep.space_equal + let eol = IniFile.eol + + let kw = Rx.word + let val = Rx.space_in + + let comment = IniFile.comment IniFile.comment_re "# " + let empty = IniFile.empty + + let entry_generic (v:lens) = [ indent . key kw . sep . v . eol ] + + (* + FIXME: combine entry and subrecord into a single recursive lens + + This does not work for some reason: + let rec entry = entry_generic ( store ( val - "{" ) ) + | entry_generic ( dels "{" . eol + . ( entry | comment | empty )* + . indent . dels "}" ) + *) + let entry = entry_generic ( store ( val - "{" ) ) + let subrecord = entry_generic ( dels "{" . eol + . ( entry | comment | empty )* + . indent . dels "}" ) + + let title = IniFile.indented_title kw + let record = IniFile.record title ( entry | subrecord | comment ) + + let directive = Build.key_value_line kw space ( store val ) + + let lns = IniFile.lns record ( directive | comment ) + + let filter = incl "/etc/krb5.conf" + . incl "/etc/krb5.conf.d/*" + . incl "/var/kerberos/krb5kdc/kdc.conf" + . Util.stdexcl + + let xfm = transform lns filter From 35a06017b871506aad0815fb44944e9eabd859f5 Mon Sep 17 00:00:00 2001 From: Jan Cholasta <jchol...@redhat.com> Date: Wed, 3 May 2017 06:09:03 +0000 Subject: [PATCH 07/14] server install: fix KDC PKINIT configuration Set `pkinit_pool` in `kdc.conf` to a CA certificate bundle of all CAs known to IPA. Make sure `cacert.pem` is exported in all installation code paths. Use the KDC certificate itself as a PKINIT anchor in `login_password`. https://pagure.io/freeipa/issue/6831 --- install/restart_scripts/Makefile.am | 1 + install/restart_scripts/renew_kdc_cert | 31 ++++++++++++++++++ install/share/kdc.conf.template | 2 ++ ipaclient/install/ipa_certupdate.py | 1 + ipalib/install/kinit.py | 7 +++-- ipaserver/install/krbinstance.py | 27 +++++++++------- ipaserver/install/server/upgrade.py | 57 ++++++++++++++++++++++++++-------- ipaserver/rpcserver.py | 5 ++- 8 files changed, 103 insertions(+), 28 deletions(-) create mode 100755 install/restart_scripts/renew_kdc_cert diff --git a/install/restart_scripts/Makefile.am b/install/restart_scripts/Makefile.am index 04881b4..240cebd 100644 --- a/install/restart_scripts/Makefile.am +++ b/install/restart_scripts/Makefile.am @@ -5,6 +5,7 @@ app_DATA = \ restart_dirsrv \ restart_httpd \ renew_ca_cert \ + renew_kdc_cert \ renew_ra_cert \ stop_pkicad \ renew_ra_cert_pre \ diff --git a/install/restart_scripts/renew_kdc_cert b/install/restart_scripts/renew_kdc_cert new file mode 100755 index 0000000..9247920 --- /dev/null +++ b/install/restart_scripts/renew_kdc_cert @@ -0,0 +1,31 @@ +#!/usr/bin/python2 -E +# +# Copyright (C) 2017 FreeIPA Contributors see COPYING for license +# + +import os +import syslog +import traceback + +from ipaplatform import services +from ipaplatform.paths import paths +from ipaserver.install import certs + + +def main(): + with certs.renewal_lock: + os.chmod(paths.KDC_CERT, 0o644) + + try: + if services.knownservices.krb5kdc.is_running(): + syslog.syslog(syslog.LOG_NOTICE, 'restarting krb5kdc') + services.knownservices.krb5kdc.restart() + except Exception as e: + syslog.syslog( + syslog.LOG_ERR, "cannot restart krb5kdc: {}".format(e)) + + +try: + main() +except Exception: + syslog.syslog(syslog.LOG_ERR, traceback.format_exc()) diff --git a/install/share/kdc.conf.template b/install/share/kdc.conf.template index ec53a1f..306351b 100644 --- a/install/share/kdc.conf.template +++ b/install/share/kdc.conf.template @@ -13,5 +13,7 @@ default_principal_flags = +preauth ; admin_keytab = $KRB5KDC_KADM5_KEYTAB pkinit_identity = FILE:$KDC_CERT,$KDC_KEY + pkinit_anchors = FILE:$KDC_CERT pkinit_anchors = FILE:$CACERT_PEM + pkinit_pool = FILE:$CA_BUNDLE_PEM } diff --git a/ipaclient/install/ipa_certupdate.py b/ipaclient/install/ipa_certupdate.py index 7e8527e..93da842 100644 --- a/ipaclient/install/ipa_certupdate.py +++ b/ipaclient/install/ipa_certupdate.py @@ -172,6 +172,7 @@ def update_server(self, certs): certmonger.modify(request_id, ca='dogtag-ipa-ca-renew-agent') self.update_file(paths.CA_CRT, certs) + self.update_file(paths.CACERT_PEM, certs) def update_file(self, filename, certs, mode=0o444): certs = (c[0] for c in certs if c[2] is not False) diff --git a/ipalib/install/kinit.py b/ipalib/install/kinit.py index fb6caee..73471f1 100644 --- a/ipalib/install/kinit.py +++ b/ipalib/install/kinit.py @@ -96,7 +96,7 @@ def kinit_password(principal, password, ccache_name, config=None, raise RuntimeError(result.error_output) -def kinit_armor(ccache_name, pkinit_anchor=None): +def kinit_armor(ccache_name, pkinit_anchors=None): """ perform anonymous pkinit to obtain anonymous ticket to be used as armor for FAST. @@ -113,8 +113,9 @@ def kinit_armor(ccache_name, pkinit_anchor=None): env = {'LC_ALL': 'C'} args = [paths.KINIT, '-n', '-c', ccache_name] - if pkinit_anchor is not None: - args.extend(['-X', 'X509_anchors=FILE:{}'.format(pkinit_anchor)]) + if pkinit_anchors is not None: + for pkinit_anchor in pkinit_anchors: + args.extend(['-X', 'X509_anchors=FILE:{}'.format(pkinit_anchor)]) # this workaround enables us to capture stderr and put it # into the raised exception in case of unsuccessful authentication diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py index e52577b..1692e0b 100644 --- a/ipaserver/install/krbinstance.py +++ b/ipaserver/install/krbinstance.py @@ -20,7 +20,6 @@ from __future__ import absolute_import from __future__ import print_function -import shutil import os import pwd import socket @@ -28,6 +27,8 @@ import dns.name +from ipalib import x509 +from ipalib.install import certstore from ipaserver.install import service from ipaserver.install import installutils from ipapython import ipaldap @@ -430,7 +431,8 @@ def _call_certmonger(self, certmonger_ca='IPA'): ca=certmonger_ca, dns=self.fqdn, storage='FILE', - profile=KDC_PROFILE) + profile=KDC_PROFILE, + post_command='renew_kdc_cert') except dbus.DBusException as e: # if the certificate is already tracked, ignore the error name = e.get_dbus_name() @@ -448,17 +450,23 @@ def pkinit_enable(self): service.set_service_entry_config( 'KDC', self.fqdn, [PKINIT_ENABLED], self.suffix) + def _install_pkinit_ca_bundle(self): + ca_certs = certstore.get_ca_certs(self.api.Backend.ldap2, + self.api.env.basedn, + self.api.env.realm, + False) + ca_certs = [c for c, _n, t, _u in ca_certs if t is not False] + x509.write_certificate_list(ca_certs, paths.CACERT_PEM) + def issue_selfsigned_pkinit_certs(self): self._call_certmonger(certmonger_ca="SelfSign") - # for self-signed certificate, the certificate is its own CA, copy it - # as CA cert - shutil.copyfile(paths.KDC_CERT, paths.CACERT_PEM) + with open(paths.CACERT_PEM, 'w'): + pass def issue_ipa_ca_signed_pkinit_certs(self): try: self._call_certmonger() - # copy IPA CA bundle to the KDC's CA cert bundle - shutil.copyfile(paths.IPA_CA_CRT, paths.CACERT_PEM) + self._install_pkinit_ca_bundle() self.pkinit_enable() except RuntimeError as e: root_logger.error("PKINIT certificate request failed: %s", e) @@ -473,10 +481,7 @@ def install_external_pkinit_certs(self): certs.install_key_from_p12(self.pkcs12_info[0], self.pkcs12_info[1], paths.KDC_KEY) - # copy IPA CA bundle to the KDC's CA cert bundle - # NOTE: this may not be the same set of CA certificates trusted by - # externally provided PKINIT cert. - shutil.copyfile(paths.IPA_CA_CRT, paths.CACERT_PEM) + self._install_pkinit_ca_bundle() self.pkinit_enable() def setup_pkinit(self): diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py index 648dc1f..db863531 100644 --- a/ipaserver/install/server/upgrade.py +++ b/ipaserver/install/server/upgrade.py @@ -11,6 +11,7 @@ import fileinput import sys +from augeas import Augeas import dns.exception import six @@ -1527,19 +1528,49 @@ def setup_pkinit(krb): else: krb.issue_selfsigned_pkinit_certs() - # reconfigure KDC just in case in order to handle potentially broken - # 4.5.0 -> 4.5.1 upgrade path - replacevars = dict() - replacevars['pkinit_identity'] = 'FILE:{},{}'.format( - paths.KDC_CERT,paths.KDC_KEY) - appendvars = {} - ipautil.backup_config_and_replace_variables( - krb.fstore, paths.KRB5KDC_KDC_CONF, replacevars=replacevars, - appendvars=appendvars) - tasks.restore_context(paths.KRB5KDC_KDC_CONF) - if krb.is_running(): - krb.stop() - krb.start() + aug = Augeas(flags=Augeas.NO_LOAD | Augeas.NO_MODL_AUTOLOAD, + loadpath=paths.USR_SHARE_IPA_DIR) + try: + aug.transform('IPAKrb5', paths.KRB5KDC_KDC_CONF) + aug.load() + + path = '/files{}/realms/{}'.format(paths.KRB5KDC_KDC_CONF, krb.realm) + modified = False + + value = 'FILE:{},{}'.format(paths.KDC_CERT, paths.KDC_KEY) + expr = '{}[count(pkinit_identity)=1][pkinit_identity="{}"]'.format( + path, value) + if not aug.match(expr): + aug.remove('{}/pkinit_identity'.format(path)) + aug.set('{}/pkinit_identity'.format(path), value) + modified = True + + for value in ['FILE:{}'.format(paths.KDC_CERT), + 'FILE:{}'.format(paths.CACERT_PEM)]: + expr = '{}/pkinit_anchors[.="{}"]'.format(path, value) + if not aug.match(expr): + aug.set('{}/pkinit_anchors[last()+1]'.format(path), value) + modified = True + + value = 'FILE:{}'.format(paths.CA_BUNDLE_PEM) + expr = '{}/pkinit_pool[.="{}"]'.format(path, value) + if not aug.match(expr): + aug.set('{}/pkinit_pool[last()+1]'.format(path), value) + modified = True + + if modified: + try: + aug.save() + except IOError: + for error_path in aug.match('/augeas//error'): + root_logger.error('augeas: %s', aug.get(error_path)) + raise + + if krb.is_running(): + krb.stop() + krb.start() + finally: + aug.close() def disable_httpd_system_trust(http): diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py index 996a3d2..4cde281 100644 --- a/ipaserver/rpcserver.py +++ b/ipaserver/rpcserver.py @@ -945,7 +945,10 @@ def kinit(self, principal, password, ccache_name): self.debug('Obtaining armor in ccache %s', armor_path) try: - kinit_armor(armor_path, pkinit_anchor=paths.CACERT_PEM) + kinit_armor( + armor_path, + pkinit_anchors=[paths.KDC_CERT, paths.KDC_CA_BUNDLE_PEM], + ) except RuntimeError as e: self.error("Failed to obtain armor cache") # We try to continue w/o armor, 2FA will be impacted From 6141ad3ad3140f9ce70010caa14575b5e5534848 Mon Sep 17 00:00:00 2001 From: David Kupka <dku...@redhat.com> Date: Tue, 11 Apr 2017 17:35:30 +0200 Subject: [PATCH 08/14] ipapython.ipautil.run: Add option to set umask before executing command https://pagure.io/freeipa/issue/6831 --- ipapython/ipautil.py | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py index cd66328..317fc22 100644 --- a/ipapython/ipautil.py +++ b/ipapython/ipautil.py @@ -309,7 +309,7 @@ class _RunResult(collections.namedtuple('_RunResult', def run(args, stdin=None, raiseonerr=True, nolog=(), env=None, capture_output=False, skip_output=False, cwd=None, runas=None, suplementary_groups=[], - capture_error=False, encoding=None, redirect_output=False): + capture_error=False, encoding=None, redirect_output=False, umask=None): """ Execute an external command. @@ -345,6 +345,7 @@ def run(args, stdin=None, raiseonerr=True, nolog=(), env=None, error_output, and (if it's not bytes) stdin. If None, the current encoding according to locale is used. :param redirect_output: Redirect (error) output to standard (error) output. + :param umask: Set file-creation mask before running the command. :return: An object with these attributes: @@ -416,25 +417,27 @@ def run(args, stdin=None, raiseonerr=True, nolog=(), env=None, root_logger.debug('Starting external process') root_logger.debug('args=%s' % arg_string) - preexec_fn = None - if runas is not None: - pent = pwd.getpwnam(runas) - - suplementary_gids = [ - grp.getgrnam(group).gr_gid for group in suplementary_groups - ] - - root_logger.debug('runas=%s (UID %d, GID %s)', runas, - pent.pw_uid, pent.pw_gid) - if suplementary_groups: - for group, gid in zip(suplementary_groups, suplementary_gids): - root_logger.debug('suplementary_group=%s (GID %d)', group, gid) - - preexec_fn = lambda: ( - os.setgroups(suplementary_gids), - os.setregid(pent.pw_gid, pent.pw_gid), - os.setreuid(pent.pw_uid, pent.pw_uid), - ) + def preexec_fn(): + if runas is not None: + pent = pwd.getpwnam(runas) + + suplementary_gids = [ + grp.getgrnam(group).gr_gid for group in suplementary_groups + ] + + root_logger.debug('runas=%s (UID %d, GID %s)', runas, + pent.pw_uid, pent.pw_gid) + if suplementary_groups: + for group, gid in zip(suplementary_groups, suplementary_gids): + root_logger.debug('suplementary_group=%s (GID %d)', + group, gid) + + os.setgroups(suplementary_gids) + os.setregid(pent.pw_gid, pent.pw_gid) + os.setreuid(pent.pw_uid, pent.pw_uid) + + if umask: + os.umask(umask) try: p = subprocess.Popen(args, stdin=p_in, stdout=p_out, stderr=p_err, From b46f6afafbc73cb7a7e9f4d55e479aac93af0b52 Mon Sep 17 00:00:00 2001 From: Jan Cholasta <jchol...@redhat.com> Date: Thu, 11 May 2017 07:00:42 +0000 Subject: [PATCH 09/14] certs: do not export keys world-readable in install_key_from_p12 Make sure the exported private key files are readable only by the owner. https://pagure.io/freeipa/issue/6831 --- ipaserver/install/certs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py index 17b9eba..06a7e21 100644 --- a/ipaserver/install/certs.py +++ b/ipaserver/install/certs.py @@ -73,7 +73,8 @@ def install_key_from_p12(p12_fname, p12_passwd, pem_fname): pwd = ipautil.write_tmp_file(p12_passwd) ipautil.run([paths.OPENSSL, "pkcs12", "-nodes", "-nocerts", "-in", p12_fname, "-out", pem_fname, - "-passin", "file:" + pwd.name]) + "-passin", "file:" + pwd.name], + umask=0o077) def export_pem_p12(pkcs12_fname, pkcs12_pwd_fname, nickname, pem_fname): From 5f5cb7ec2b44f78220ada2d6469dc94a19fe69e6 Mon Sep 17 00:00:00 2001 From: Jan Cholasta <jchol...@redhat.com> Date: Wed, 3 May 2017 06:12:36 +0000 Subject: [PATCH 10/14] certs: do not export CA certs in install_pem_from_p12 This fixes `kdc.crt` containing the full chain rather than just the KDC certificate in CA-less server install. https://pagure.io/freeipa/issue/6831 https://pagure.io/freeipa/issue/6869 --- ipaserver/install/certs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py index 06a7e21..02c479d 100644 --- a/ipaserver/install/certs.py +++ b/ipaserver/install/certs.py @@ -64,7 +64,7 @@ def get_cert_nickname(cert): def install_pem_from_p12(p12_fname, p12_passwd, pem_fname): pwd = ipautil.write_tmp_file(p12_passwd) - ipautil.run([paths.OPENSSL, "pkcs12", "-nokeys", + ipautil.run([paths.OPENSSL, "pkcs12", "-nokeys", "-clcerts", "-in", p12_fname, "-out", pem_fname, "-passin", "file:" + pwd.name]) From b6e39b517ebbba36048687393e781e26f730e4fa Mon Sep 17 00:00:00 2001 From: Jan Cholasta <jchol...@redhat.com> Date: Wed, 3 May 2017 06:14:27 +0000 Subject: [PATCH 11/14] 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 --- ipapython/certdb.py | 41 ++++++++++++++++++++++++++++++ ipaserver/install/installutils.py | 24 +++++++++++------ ipaserver/install/server/install.py | 11 ++++++-- ipaserver/install/server/replicainstall.py | 11 ++++++-- 4 files changed, 75 insertions(+), 12 deletions(-) diff --git a/ipapython/certdb.py b/ipapython/certdb.py index 7459238..cecbc4d 100644 --- a/ipapython/certdb.py +++ b/ipapython/certdb.py @@ -24,6 +24,7 @@ import grp import re import tempfile +from tempfile import NamedTemporaryFile import shutil import base64 @@ -32,6 +33,7 @@ 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 @@ -177,6 +179,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 @@ -678,3 +712,10 @@ def verify_ca_cert_validity(self, nickname): self.run_certutil(['-V', '-n', nickname, '-u', 'L']) except ipautil.CalledProcessError: raise ValueError('invalid for a CA') + + 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 5bce989..d2283af 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 c1bdce6..03380b8 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 66d7ba4..6f71f0b 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) From 493b69a34bc5d671a2d8ab91a326020acbaf82f4 Mon Sep 17 00:00:00 2001 From: Jan Cholasta <jchol...@redhat.com> Date: Thu, 11 May 2017 07:40:40 +0000 Subject: [PATCH 12/14] replica install: respect --pkinit-cert-file When --pkinit-cert-file is used, make sure the certificate and key is actually passed to `KrbInstance`. https://pagure.io/freeipa/issue/6831 --- ipaserver/install/server/replicainstall.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py index 6f71f0b..b30133f 100644 --- a/ipaserver/install/server/replicainstall.py +++ b/ipaserver/install/server/replicainstall.py @@ -113,12 +113,13 @@ def install_replica_ds(config, options, ca_is_configured, remote_api, return ds -def install_krb(config, setup_pkinit=False, promote=False): +def install_krb(config, setup_pkinit=False, pkcs12_info=None, promote=False): krb = krbinstance.KrbInstance() # pkinit files - pkcs12_info = make_pkcs12_info(config.dir, "pkinitcert.p12", - "pkinit_pin.txt") + if pkcs12_info is None: + pkcs12_info = make_pkcs12_info(config.dir, "pkinitcert.p12", + "pkinit_pin.txt") krb.create_replica(config.realm_name, config.master_host_name, config.host_name, @@ -1350,6 +1351,7 @@ def install(installer): cafile = installer._ca_file dirsrv_pkcs12_info = installer._dirsrv_pkcs12_info http_pkcs12_info = installer._http_pkcs12_info + pkinit_pkcs12_info = installer._pkinit_pkcs12_info remote_api = installer._remote_api conn = remote_api.Backend.ldap2 @@ -1430,6 +1432,7 @@ def install(installer): krb = install_krb( config, setup_pkinit=not options.no_pkinit, + pkcs12_info=pkinit_pkcs12_info, promote=promote) # we now need to enable ssl on the ds From 18930f5f33625f0f7d7db0a7134cf8d4368a3dbe Mon Sep 17 00:00:00 2001 From: Jan Cholasta <jchol...@redhat.com> Date: Wed, 3 May 2017 06:17:32 +0000 Subject: [PATCH 13/14] cacert manage: support PKINIT Allow installing 3rd party CA certificates trusted to issue PKINIT KDC and/or client certificates. https://pagure.io/freeipa/issue/6831 --- install/tools/man/ipa-cacert-manage.1 | 2 +- ipaserver/install/ipa_cacert_manage.py | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/install/tools/man/ipa-cacert-manage.1 b/install/tools/man/ipa-cacert-manage.1 index e36258d..0317281 100644 --- a/install/tools/man/ipa-cacert-manage.1 +++ b/install/tools/man/ipa-cacert-manage.1 @@ -90,7 +90,7 @@ File containing the IPA CA certificate and the external CA certificate chain. Th Nickname for the certificate. .TP \fB\-t\fR \fITRUST_FLAGS\fR, \fB\-\-trust\-flags\fR=\fITRUST_FLAGS\fR -Trust flags for the certificate in certutil format. Trust flags are of the form "X,Y,Z" where X is for SSL, Y is for S/MIME, and Z is for code signing. Use ",," for no explicit trust. +Trust flags for the certificate in certutil format. Trust flags are of the form "A,B,C" or "A,B,C,D" where A is for SSL, B is for S/MIME, C is for code signing, and D is for PKINIT. Use ",," for no explicit trust. .sp The supported trust flags are: .RS diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py index d28a596..e88e8b6 100644 --- a/ipaserver/install/ipa_cacert_manage.py +++ b/ipaserver/install/ipa_cacert_manage.py @@ -28,6 +28,7 @@ from ipapython import admintool, ipautil from ipapython.certdb import (EMPTY_TRUST_FLAGS, EXTERNAL_CA_TRUST_FLAGS, + TrustFlags, parse_trust_flags) from ipapython.dn import DN from ipaplatform.paths import paths @@ -363,12 +364,24 @@ def install(self): "http://www.freeipa.org/page/Troubleshooting for " "troubleshooting guide)" % e) - trust_flags = options.trust_flags - if ((set(trust_flags) - set(',CPTcgpuw')) or - len(trust_flags.split(',')) != 3): + trust_flags = options.trust_flags.split(',') + if (set(options.trust_flags) - set(',CPTcgpuw') or + len(trust_flags) not in [3, 4]): raise admintool.ScriptError("Invalid trust flags") - trust_flags = parse_trust_flags(trust_flags) + extra_flags = trust_flags[3:] + extra_usages = set() + if extra_flags: + if 'C' in extra_flags[0]: + extra_usages.add(x509.EKU_PKINIT_KDC) + if 'T' in extra_flags[0]: + extra_usages.add(x509.EKU_PKINIT_CLIENT_AUTH) + + trust_flags = parse_trust_flags(','.join(trust_flags[:3])) + trust_flags = TrustFlags(trust_flags.has_key, + trust_flags.trusted, + trust_flags.ca, + trust_flags.usages | extra_usages) try: certstore.put_ca_cert_nss( From eff9fd8a52c7b96720c11488ed0c7fded941aa2a Mon Sep 17 00:00:00 2001 From: Jan Cholasta <jchol...@redhat.com> Date: Wed, 3 May 2017 06:18:05 +0000 Subject: [PATCH 14/14] server certinstall: support PKINIT Allow replacing the KDC certificate. https://pagure.io/freeipa/issue/6831 --- install/tools/man/ipa-server-certinstall.1 | 5 ++- ipaserver/install/ipa_server_certinstall.py | 70 +++++++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/install/tools/man/ipa-server-certinstall.1 b/install/tools/man/ipa-server-certinstall.1 index d23bbd4..35cd8c6 100644 --- a/install/tools/man/ipa-server-certinstall.1 +++ b/install/tools/man/ipa-server-certinstall.1 @@ -22,7 +22,7 @@ ipa\-server\-certinstall \- Install new SSL server certificates .SH "SYNOPSIS" ipa\-server\-certinstall [\fIOPTION\fR]... FILE... .SH "DESCRIPTION" -Replace the current SSL Directory and/or Apache server certificate(s) with the certificate in the specified files. The files are accepted in PEM and DER certificate, PKCS#7 certificate chain, PKCS#8 and raw private key and PKCS#12 formats. +Replace the current Directory server SSL certificate, Apache server SSL certificate and/or Kerberos KDC certificate with the certificate in the specified files. The files are accepted in PEM and DER certificate, PKCS#7 certificate chain, PKCS#8 and raw private key and PKCS#12 formats. PKCS#12 is a file format used to safely transport SSL certificates and public/private keypairs. @@ -37,6 +37,9 @@ Install the certificate on the Directory Server \fB\-w\fR, \fB\-\-http\fR Install the certificate in the Apache Web Server .TP +\fB\-k\fR, \fB\-\-kdc\fR +Install the certificate in the Kerberos KDC +.TP \fB\-\-pin\fR=\fIPIN\fR The password to unlock the private key .TP diff --git a/ipaserver/install/ipa_server_certinstall.py b/ipaserver/install/ipa_server_certinstall.py index 9f2cd95..a14a84f 100644 --- a/ipaserver/install/ipa_server_certinstall.py +++ b/ipaserver/install/ipa_server_certinstall.py @@ -21,12 +21,17 @@ import os import os.path import pwd +import tempfile import optparse # pylint: disable=deprecated-module +from ipalib import x509 +from ipalib.install import certmonger from ipaplatform.constants import constants from ipaplatform.paths import paths from ipapython import admintool -from ipapython.certdb import get_ca_nickname, NSSDatabase +from ipapython.certdb import (get_ca_nickname, + NSSDatabase, + verify_kdc_cert_validity) from ipapython.dn import DN from ipalib import api, errors from ipaserver.install import certs, dsinstance, installutils @@ -35,7 +40,7 @@ class ServerCertInstall(admintool.AdminTool): command_name = 'ipa-server-certinstall' - usage = "%prog <-d|-w> [options] <file> ..." + usage = "%prog <-d|-w|-k> [options] <file> ..." description = "Install new SSL server certificates." @@ -52,6 +57,10 @@ def add_options(cls, parser): dest="http", action="store_true", default=False, help="install certificate for the http server") parser.add_option( + "-k", "--kdc", + dest="kdc", action="store_true", default=False, + help="install PKINIT certificate for the KDC") + parser.add_option( "--pin", dest="pin", metavar="PIN", sensitive=True, help="The password of the PKCS#12 file") @@ -73,8 +82,9 @@ def validate_options(self): installutils.check_server_configuration() - if not self.options.dirsrv and not self.options.http: - self.option_parser.error("you must specify dirsrv and/or http") + if not any((self.options.dirsrv, self.options.http, self.options.kdc)): + self.option_parser.error( + "you must specify dirsrv, http and/or kdc") if not self.args: self.option_parser.error("you must provide certificate filename") @@ -108,6 +118,9 @@ def run(self): if self.options.http: self.install_http_cert() + if self.options.kdc: + self.install_kdc_cert() + api.Backend.ldap2.disconnect() def install_dirsrv_cert(self): @@ -161,6 +174,55 @@ def install_http_cert(self): os.chown(os.path.join(dirname, 'key3.db'), 0, pent.pw_gid) os.chown(os.path.join(dirname, 'secmod.db'), 0, pent.pw_gid) + def install_kdc_cert(self): + ca_cert_file = paths.CA_BUNDLE_PEM + pkcs12_file, pin, ca_cert = installutils.load_pkcs12( + cert_files=self.args, + key_password=self.options.pin, + key_nickname=self.options.cert_name, + ca_cert_files=[ca_cert_file], + realm_name=api.env.realm) + + cdb = certs.CertDB(api.env.realm, nssdir=paths.IPA_NSSDB_DIR) + + # Check that the ca_cert is known and trusted + with tempfile.NamedTemporaryFile() as temp: + certs.install_pem_from_p12(pkcs12_file.name, pin, temp.name) + + kdc_cert = x509.load_certificate_from_file(temp.name) + ca_certs = x509.load_certificate_list_from_file(ca_cert_file) + + try: + verify_kdc_cert_validity(kdc_cert, ca_certs, api.env.realm) + except ValueError as e: + raise admintool.ScriptError( + "Peer's certificate issuer is not trusted (%s). " + "Please run ipa-cacert-manage install and ipa-certupdate " + "to install the CA certificate." % str(e)) + + try: + ca_enabled = api.Command.ca_is_enabled()['result'] + if ca_enabled: + certmonger.stop_tracking(certfile=paths.KDC_CERT) + + certs.install_pem_from_p12(pkcs12_file.name, pin, paths.KDC_CERT) + certs.install_key_from_p12(pkcs12_file.name, pin, paths.KDC_KEY) + + if ca_enabled: + # Start tracking only if the cert was issued by IPA CA + # Retrieve IPA CA + ipa_ca_cert = cdb.get_cert_from_db( + get_ca_nickname(api.env.realm), + pem=False) + # And compare with the CA which signed this certificate + if ca_cert == ipa_ca_cert: + certmonger.start_tracking( + (paths.KDC_CERT, paths.KDC_KEY), + storage='FILE', + profile='KDCs_PKINIT_Certs') + except RuntimeError as e: + raise admintool.ScriptError(str(e)) + def check_chain(self, pkcs12_filename, pkcs12_pin, nssdb): # create a temp nssdb with NSSDatabase() as tempnssdb:
_______________________________________________ FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org To unsubscribe send an email to freeipa-devel-le...@lists.fedorahosted.org