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 fb20f2009298f8e6e79b66b028c28b7e35e5ee03 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/13] 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 363ba37..edf699a 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
@@ -231,10 +232,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)
@@ -242,7 +243,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')
@@ -259,7 +261,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)
@@ -330,7 +336,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 7b0476d..0ab88d3 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -1387,7 +1387,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)
 
@@ -1405,7 +1405,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 fe39165937464804626ef38f8af2710f05a4ee1a 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/13] 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 9230e70..ba81599 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 ipalib.util import validate_hostname
 from ipalib import api, errors, x509
@@ -1020,7 +1021,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)))
@@ -1160,7 +1161,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 77452c0ee6a6ed720c52129e58e4ae611b92685f 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/13] 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 ba81599..f19bbdb 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -1018,7 +1018,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 edf699a..49cd05d 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
@@ -353,6 +355,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 0ab88d3..65b4ad0 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -1545,7 +1545,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 513154c035d3099903ac446111c458697af77796 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/13] 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 9190051c0fbbf675a4589083d22c65abb7dfc412 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/13] 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                | 16 +++++++++++++++-
 ipaclient/install/ipa_certupdate.py        | 14 ++++++++++++--
 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 | 16 ++++++++++++++--
 ipaserver/install/server/upgrade.py        |  4 +++-
 12 files changed, 81 insertions(+), 12 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 87ac7c3..b08b045 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
@@ -1461,6 +1470,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..49291b1 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,14 @@ 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 True and (u is None or x509.EKU_PKINIT_KDC in u)],
+        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 +3329,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 d6ffbde..4776576 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, x509.EKU_PKINIT_KDC)
+        self.update_file(paths.CA_BUNDLE_PEM, certs)
 
         ipa_db = certdb.NSSDatabase(api.env.nss_dir)
 
@@ -171,8 +173,16 @@ def update_server(self, certs):
 
         self.update_file(paths.CA_CRT, certs)
 
-    def update_file(self, filename, certs, mode=0o444):
-        certs = (c[0] for c in certs if c[2] is not False)
+    def update_file(self, filename, certs, usage=None, mode=0o444):
+        if usage is None:
+            def predicate(cert, nickname, trusted, usages):
+                return trusted is not False
+        else:
+            def predicate(cert, nickname, trusted, usages):
+                return (trusted is True and
+                        (usages is None or usage in usages))
+
+        certs = (c[0] for c in certs if predicate(*c))
         try:
             x509.write_certificate_list(certs, filename)
         except Exception as e:
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 d72feb8..0767646 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..b1a422f 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -130,7 +130,15 @@ def install_krb(config, setup_pkinit=False, promote=False):
     return krb
 
 
-def install_ca_cert(ldap, base_dn, realm, cafile, destfile=paths.IPA_CA_CRT):
+def install_ca_cert(ldap, base_dn, realm, cafile, usage=None,
+                    destfile=paths.IPA_CA_CRT):
+    if usage is None:
+        def predicate(cert, nickname, trusted, usages):
+            return trusted is not False
+    else:
+        def predicate(cert, nickname, trusted, usages):
+            return trusted is True and (usages is None or usage in usages)
+
     try:
         try:
             certs = certstore.get_ca_certs(ldap, base_dn, realm, False)
@@ -141,7 +149,7 @@ def install_ca_cert(ldap, base_dn, realm, cafile, destfile=paths.IPA_CA_CRT):
                 # cafile == IPA_CA_CRT
                 pass
         else:
-            certs = [c[0] for c in certs if c[2] is not False]
+            certs = [c[0] for c in certs if predicate(*c)]
             x509.write_certificate_list(certs, destfile)
     except Exception as e:
         raise ScriptError("error copying files: " + str(e))
@@ -1390,6 +1398,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,
+                        x509.EKU_PKINIT_KDC, 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 65b4ad0..dc12724 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -1829,7 +1829,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 1a70200e41dfe70a537eb7bfa8f4bd7b3dda0461 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 3 May 2017 06:09:03 +0000
Subject: [PATCH 06/13] server install: fix KDC PKINIT configuration

Make sure `cacert.pem` contains only certificates of CAs trusted to issue
PKINIT client certificates and is exported in all installation code paths.

Set `pkinit_pool` in `kdc.conf` to a CA certificate bundle of all CAs known
to IPA.

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       | 30 +++++++++++++++++++-----------
 ipaserver/install/server/upgrade.py    | 32 +++++++++++++++++++-------------
 ipaserver/rpcserver.py                 |  5 ++++-
 8 files changed, 81 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 4776576..f9163f5 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, profile='ipaCACertRenewal')
 
         self.update_file(paths.CA_CRT, certs)
+        self.update_file(paths.CACERT_PEM, certs, x509.EKU_PKINIT_CLIENT_AUTH)
 
     def update_file(self, filename, certs, usage=None, mode=0o444):
         if usage is None:
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..a2cd6a0 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,26 @@ 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 True and (u is None or x509.EKU_PKINIT_CLIENT_AUTH in u)
+        ]
+        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 +484,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 dc12724..074d38c 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -1525,19 +1525,25 @@ 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()
+    with open(paths.KRB5KDC_KDC_CONF, 'r') as f:
+        kdc_conf = f.read()
+
+    if 'pkinit_pool = FILE:{}\n'.format(paths.CA_BUNDLE_PEM) not in kdc_conf:
+        replacevars = dict()
+        replacevars['pkinit_identity'] = 'FILE:{},{}'.format(
+            paths.KDC_CERT, paths.KDC_KEY)
+        replacevars['pkinit_anchors'] = 'FILE:{}'.format(paths.KDC_CERT)
+        replacevars['pkinit_pool'] = 'FILE:{}'.format(paths.CA_BUNDLE_PEM)
+        appendvars = {}
+        appendvars['pkinit_anchors'] = 'FILE:{}'.format(paths.CACERT_PEM)
+        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()
 
 
 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 1a6df61899508008299065b589b797ea1cd9595a Mon Sep 17 00:00:00 2001
From: David Kupka <dku...@redhat.com>
Date: Tue, 11 Apr 2017 17:35:30 +0200
Subject: [PATCH 07/13] 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 94853ca8fe142af0135a40dd175fd150390080c8 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 11 May 2017 07:00:42 +0000
Subject: [PATCH 08/13] 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 0bbd71f97d0dab019b48233afc94e86725e4686e Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 3 May 2017 06:12:36 +0000
Subject: [PATCH 09/13] 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 f3ebfd4b072f568b9661bb854367e89a7d832631 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 3 May 2017 06:14:27 +0000
Subject: [PATCH 10/13] 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..20082f1 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 = ','.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 f19bbdb..bdae7c7 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -985,7 +985,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
 
@@ -1050,13 +1050,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 b1a422f..112f8bf 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -1077,12 +1077,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
@@ -1091,6 +1091,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 593fc6be61f053e071f61525921233705a70596e Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 11 May 2017 07:40:40 +0000
Subject: [PATCH 11/13] 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 112f8bf..20f0aae 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,
@@ -1358,6 +1359,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
@@ -1438,6 +1440,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 6829175d3a79fb002432469014a10125e50c4cf4 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 3 May 2017 06:17:32 +0000
Subject: [PATCH 12/13] 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 128edd8..ab071fd 100644
--- a/install/tools/man/ipa-cacert-manage.1
+++ b/install/tools/man/ipa-cacert-manage.1
@@ -87,7 +87,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 49cd05d..5193d0c 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
@@ -350,12 +351,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 f393afe7c44ef113015e0233bea92fe81aab9c66 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 3 May 2017 06:18:05 +0000
Subject: [PATCH 13/13] 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:
-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to