simo5's pull request #62: "Configure Anonymous PKINIT on server install" was 
synchronize

See the full pull-request at https://github.com/freeipa/freeipa/pull/62
... or pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/62/head:pr62
git checkout pr62
From b8525fc326bfc6ef57bdfc308fe37bfbe175ca7c Mon Sep 17 00:00:00 2001
From: Simo Sorce <s...@redhat.com>
Date: Tue, 26 Jul 2016 11:19:01 -0400
Subject: [PATCH] Configure Anonymous PKINIT on server install

Allow anonymous pkinit to be used so that unenrolled hosts can perform FAST
authentication (necessary for 2FA for example) using an anonymous krbtgt
obtained via Pkinit.

Signed-off-by: Simo Sorce <s...@redhat.com>
---
 client/ipa-client-install                    |   2 +-
 install/share/kdc.conf.template              |   2 +-
 install/share/profiles/KDCs_PKINIT_Certs.cfg | 109 +++++++++++++++++++++++++++
 install/share/profiles/Makefile.am           |   1 +
 ipaplatform/base/paths.py                    |   3 +-
 ipapython/certmonger.py                      |  32 +++++---
 ipapython/dogtag.py                          |   4 +
 ipaserver/install/certs.py                   |  10 ++-
 ipaserver/install/krbinstance.py             |  49 ++++++++----
 ipaserver/install/server/common.py           |   5 +-
 ipaserver/install/server/install.py          |  26 ++++---
 ipaserver/install/server/replicainstall.py   |   4 +-
 ipaserver/plugins/cert.py                    |  65 +++++++++++++---
 ipaserver/plugins/dogtag.py                  |   1 +
 14 files changed, 260 insertions(+), 53 deletions(-)
 create mode 100644 install/share/profiles/KDCs_PKINIT_Certs.cfg

diff --git a/client/ipa-client-install b/client/ipa-client-install
index 4a263b3..590f598 100755
--- a/client/ipa-client-install
+++ b/client/ipa-client-install
@@ -1175,7 +1175,7 @@ def configure_certmonger(fstore, subject_base, cli_realm, hostname, options,
     subject = str(DN(('CN', hostname), subject_base))
     passwd_fname = os.path.join(paths.IPA_NSSDB_DIR, 'pwdfile.txt')
     try:
-        certmonger.request_cert(nssdb=paths.IPA_NSSDB_DIR,
+        certmonger.request_cert(certpath=paths.IPA_NSSDB_DIR,
                                 nickname='Local IPA host',
                                 subject=subject, dns=[hostname],
                                 principal=principal,
diff --git a/install/share/kdc.conf.template b/install/share/kdc.conf.template
index 296b75b..ec53a1f 100644
--- a/install/share/kdc.conf.template
+++ b/install/share/kdc.conf.template
@@ -12,6 +12,6 @@
   dict_file = $DICT_WORDS
   default_principal_flags = +preauth
 ;  admin_keytab = $KRB5KDC_KADM5_KEYTAB
-  pkinit_identity = FILE:$KDC_PEM
+  pkinit_identity = FILE:$KDC_CERT,$KDC_KEY
   pkinit_anchors = FILE:$CACERT_PEM
  }
diff --git a/install/share/profiles/KDCs_PKINIT_Certs.cfg b/install/share/profiles/KDCs_PKINIT_Certs.cfg
new file mode 100644
index 0000000..c5e412b
--- /dev/null
+++ b/install/share/profiles/KDCs_PKINIT_Certs.cfg
@@ -0,0 +1,109 @@
+profileId=KDCs_PKINIT_Certs
+classId=caEnrollImpl
+desc=This certificate profile is for enrolling server certificates with IPA-RA agent authentication.
+visible=false
+enable=true
+enableBy=admin
+auth.instance_id=raCertAuth
+name=IPA-RA Agent-Authenticated Server Certificate Enrollment
+input.list=i1,i2
+input.i1.class_id=certReqInputImpl
+input.i2.class_id=submitterInfoInputImpl
+output.list=o1
+output.o1.class_id=certOutputImpl
+policyset.list=serverCertSet
+policyset.serverCertSet.list=1,2,3,4,5,6,7,8,9,10,11
+policyset.serverCertSet.1.constraint.class_id=subjectNameConstraintImpl
+policyset.serverCertSet.1.constraint.name=Subject Name Constraint
+policyset.serverCertSet.1.constraint.params.pattern=CN=[^,]+,.+
+policyset.serverCertSet.1.constraint.params.accept=true
+policyset.serverCertSet.1.default.class_id=subjectNameDefaultImpl
+policyset.serverCertSet.1.default.name=Subject Name Default
+policyset.serverCertSet.1.default.params.name=CN=$$request.req_subject_name.cn$$, $SUBJECT_DN_O
+policyset.serverCertSet.2.constraint.class_id=validityConstraintImpl
+policyset.serverCertSet.2.constraint.name=Validity Constraint
+policyset.serverCertSet.2.constraint.params.range=740
+policyset.serverCertSet.2.constraint.params.notBeforeCheck=false
+policyset.serverCertSet.2.constraint.params.notAfterCheck=false
+policyset.serverCertSet.2.default.class_id=validityDefaultImpl
+policyset.serverCertSet.2.default.name=Validity Default
+policyset.serverCertSet.2.default.params.range=731
+policyset.serverCertSet.2.default.params.startTime=0
+policyset.serverCertSet.3.constraint.class_id=keyConstraintImpl
+policyset.serverCertSet.3.constraint.name=Key Constraint
+policyset.serverCertSet.3.constraint.params.keyType=RSA
+policyset.serverCertSet.3.constraint.params.keyParameters=2048,3072,4096
+policyset.serverCertSet.3.default.class_id=userKeyDefaultImpl
+policyset.serverCertSet.3.default.name=Key Default
+policyset.serverCertSet.4.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.4.constraint.name=No Constraint
+policyset.serverCertSet.4.default.class_id=authorityKeyIdentifierExtDefaultImpl
+policyset.serverCertSet.4.default.name=Authority Key Identifier Default
+policyset.serverCertSet.5.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.5.constraint.name=No Constraint
+policyset.serverCertSet.5.default.class_id=authInfoAccessExtDefaultImpl
+policyset.serverCertSet.5.default.name=AIA Extension Default
+policyset.serverCertSet.5.default.params.authInfoAccessADEnable_0=true
+policyset.serverCertSet.5.default.params.authInfoAccessADLocationType_0=URIName
+policyset.serverCertSet.5.default.params.authInfoAccessADLocation_0=http://$IPA_CA_RECORD.$DOMAIN/ca/ocsp
+policyset.serverCertSet.5.default.params.authInfoAccessADMethod_0=1.3.6.1.5.5.7.48.1
+policyset.serverCertSet.5.default.params.authInfoAccessCritical=false
+policyset.serverCertSet.5.default.params.authInfoAccessNumADs=1
+policyset.serverCertSet.6.constraint.class_id=keyUsageExtConstraintImpl
+policyset.serverCertSet.6.constraint.name=Key Usage Extension Constraint
+policyset.serverCertSet.6.constraint.params.keyUsageCritical=true
+policyset.serverCertSet.6.constraint.params.keyUsageDigitalSignature=true
+policyset.serverCertSet.6.constraint.params.keyUsageNonRepudiation=true
+policyset.serverCertSet.6.constraint.params.keyUsageDataEncipherment=true
+policyset.serverCertSet.6.constraint.params.keyUsageKeyEncipherment=true
+policyset.serverCertSet.6.constraint.params.keyUsageKeyAgreement=false
+policyset.serverCertSet.6.constraint.params.keyUsageKeyCertSign=false
+policyset.serverCertSet.6.constraint.params.keyUsageCrlSign=false
+policyset.serverCertSet.6.constraint.params.keyUsageEncipherOnly=false
+policyset.serverCertSet.6.constraint.params.keyUsageDecipherOnly=false
+policyset.serverCertSet.6.default.class_id=keyUsageExtDefaultImpl
+policyset.serverCertSet.6.default.name=Key Usage Default
+policyset.serverCertSet.6.default.params.keyUsageCritical=true
+policyset.serverCertSet.6.default.params.keyUsageDigitalSignature=true
+policyset.serverCertSet.6.default.params.keyUsageNonRepudiation=true
+policyset.serverCertSet.6.default.params.keyUsageDataEncipherment=true
+policyset.serverCertSet.6.default.params.keyUsageKeyEncipherment=true
+policyset.serverCertSet.6.default.params.keyUsageKeyAgreement=false
+policyset.serverCertSet.6.default.params.keyUsageKeyCertSign=false
+policyset.serverCertSet.6.default.params.keyUsageCrlSign=false
+policyset.serverCertSet.6.default.params.keyUsageEncipherOnly=false
+policyset.serverCertSet.6.default.params.keyUsageDecipherOnly=false
+policyset.serverCertSet.7.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.7.constraint.name=No Constraint
+policyset.serverCertSet.7.default.class_id=extendedKeyUsageExtDefaultImpl
+policyset.serverCertSet.7.default.name=Extended Key Usage Extension Default
+policyset.serverCertSet.7.default.params.exKeyUsageCritical=false
+policyset.serverCertSet.7.default.params.exKeyUsageOIDs=1.3.6.1.5.5.7.3.1,1.3.6.1.5.2.3.5
+policyset.serverCertSet.8.constraint.class_id=signingAlgConstraintImpl
+policyset.serverCertSet.8.constraint.name=No Constraint
+policyset.serverCertSet.8.constraint.params.signingAlgsAllowed=SHA1withRSA,SHA256withRSA,SHA512withRSA,SHA1withDSA,SHA1withEC,SHA256withEC,SHA384withEC,SHA512withEC
+policyset.serverCertSet.8.default.class_id=signingAlgDefaultImpl
+policyset.serverCertSet.8.default.name=Signing Alg
+policyset.serverCertSet.8.default.params.signingAlg=-
+policyset.serverCertSet.9.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.9.constraint.name=No Constraint
+policyset.serverCertSet.9.default.class_id=crlDistributionPointsExtDefaultImpl
+policyset.serverCertSet.9.default.name=CRL Distribution Points Extension Default
+policyset.serverCertSet.9.default.params.crlDistPointsCritical=false
+policyset.serverCertSet.9.default.params.crlDistPointsNum=1
+policyset.serverCertSet.9.default.params.crlDistPointsEnable_0=true
+policyset.serverCertSet.9.default.params.crlDistPointsIssuerName_0=$CRL_ISSUER
+policyset.serverCertSet.9.default.params.crlDistPointsIssuerType_0=DirectoryName
+policyset.serverCertSet.9.default.params.crlDistPointsPointName_0=http://$IPA_CA_RECORD.$DOMAIN/ipa/crl/MasterCRL.bin
+policyset.serverCertSet.9.default.params.crlDistPointsPointType_0=URIName
+policyset.serverCertSet.9.default.params.crlDistPointsReasons_0=
+policyset.serverCertSet.10.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.10.constraint.name=No Constraint
+policyset.serverCertSet.10.default.class_id=subjectKeyIdentifierExtDefaultImpl
+policyset.serverCertSet.10.default.name=Subject Key Identifier Extension Default
+policyset.serverCertSet.10.default.params.critical=false
+policyset.serverCertSet.11.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.11.constraint.name=No Constraint
+policyset.serverCertSet.11.default.class_id=userExtensionDefaultImpl
+policyset.serverCertSet.11.default.name=User Supplied Extension Default
+policyset.serverCertSet.11.default.params.userExtOID=2.5.29.17
diff --git a/install/share/profiles/Makefile.am b/install/share/profiles/Makefile.am
index b5ccb6e..9978422 100644
--- a/install/share/profiles/Makefile.am
+++ b/install/share/profiles/Makefile.am
@@ -4,6 +4,7 @@ appdir = $(IPA_DATA_DIR)/profiles
 app_DATA =				\
 	caIPAserviceCert.cfg		\
 	IECUserRoles.cfg		\
+	KDCs_PKINIT_Certs.cfg		\
 	$(NULL)
 
 EXTRA_DIST =				\
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index f927a7a..eaca6c1 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -251,7 +251,8 @@ class BasePathNamespace(object):
     KRB5KDC_KADM5_ACL = "/var/kerberos/krb5kdc/kadm5.acl"
     KRB5KDC_KADM5_KEYTAB = "/var/kerberos/krb5kdc/kadm5.keytab"
     KRB5KDC_KDC_CONF = "/var/kerberos/krb5kdc/kdc.conf"
-    KDC_PEM = "/var/kerberos/krb5kdc/kdc.pem"
+    KDC_CERT = "/var/kerberos/krb5kdc/kdc.crt"
+    KDC_KEY = "/var/kerberos/krb5kdc/kdc.key"
     VAR_LIB = "/var/lib"
     AUTHCONFIG_LAST = "/var/lib/authconfig/last"
     VAR_LIB_CERTMONGER_DIR = "/var/lib/certmonger"
diff --git a/ipapython/certmonger.py b/ipapython/certmonger.py
index 1f22fee..ca86a17 100644
--- a/ipapython/certmonger.py
+++ b/ipapython/certmonger.py
@@ -298,8 +298,8 @@ def add_subject(request_id, subject):
 
 
 def request_cert(
-        nssdb, nickname, subject, principal, passwd_fname=None,
-        dns=None):
+        certpath, nickname, subject, principal, passwd_fname=None,
+        dns=None, storage='NSSDB', profile=None):
     """
     Execute certmonger to request a server certificate.
 
@@ -310,15 +310,22 @@ def request_cert(
     ca_path = cm.obj_if.find_ca_by_nickname('IPA')
     if not ca_path:
         raise RuntimeError('IPA CA not found')
-    request_parameters = dict(KEY_STORAGE='NSSDB', CERT_STORAGE='NSSDB',
-                              CERT_LOCATION=nssdb, CERT_NICKNAME=nickname,
-                              KEY_LOCATION=nssdb, KEY_NICKNAME=nickname,
+    if storage == 'FILE':
+        certfile, keyfile = certpath
+    else:
+        certfile = certpath
+        keyfile = certpath
+    request_parameters = dict(KEY_STORAGE=storage, CERT_STORAGE=storage,
+                              CERT_LOCATION=certfile, CERT_NICKNAME=nickname,
+                              KEY_LOCATION=keyfile, KEY_NICKNAME=nickname,
                               SUBJECT=subject, PRINCIPAL=[principal],
                               CA=ca_path)
     if dns is not None and len(dns) > 0:
         request_parameters['DNS'] = dns
     if passwd_fname:
         request_parameters['KEY_PIN_FILE'] = passwd_fname
+    if profile is not None and len(profile) > 0:
+        request_parameters['template-profile'] = profile
     result = cm.obj_if.add_request(request_parameters)
     try:
         if result[0]:
@@ -374,20 +381,27 @@ def start_tracking(nickname, secdir, password_file=None, command=None):
     return request.prop_if.Get(DBUS_CM_REQUEST_IF, 'nickname')
 
 
-def stop_tracking(secdir, request_id=None, nickname=None):
+def stop_tracking(secdir=None, request_id=None, nickname=None, certfile=None):
     """
     Stop tracking the current request using either the request_id or nickname.
 
     Returns True or False
     """
-    if request_id is None and nickname is None:
-        raise RuntimeError('Both request_id and nickname are missing.')
+    if request_id is None and nickname is None and certfile is None:
+        raise RuntimeError('One of request_id, nickname and certfile is'
+                           ' required.')
+    if secdir is not None and certfile is not None:
+        raise RuntimeError("Can't specify both secdir and certfile.")
 
-    criteria = {'cert-database': secdir}
+    criteria = dict()
+    if secdir:
+        criteria['cert-database'] = secdir
     if request_id:
         criteria['nickname'] = request_id
     if nickname:
         criteria['cert-nickname'] = nickname
+    if certfile:
+        criteria['cert-file'] = certfile
     try:
         request = _get_request(criteria)
     except RuntimeError as e:
diff --git a/ipapython/dogtag.py b/ipapython/dogtag.py
index 6f13880..07c0d5c 100644
--- a/ipapython/dogtag.py
+++ b/ipapython/dogtag.py
@@ -45,9 +45,13 @@
 INCLUDED_PROFILES = {
     Profile(u'caIPAserviceCert', u'Standard profile for network services', True),
     Profile(u'IECUserRoles', u'User profile that includes IECUserRoles extension from request', True),
+    Profile(u'KDCs_PKINIT_Certs',
+            u'Profile for PKINIT support by KDCs',
+            False),
     }
 
 DEFAULT_PROFILE = u'caIPAserviceCert'
+KDC_PROFILE = u'KDCs_PKINIT_Certs'
 
 
 def error_from_xml(doc, message_template):
diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py
index 693a00d..8d95ecc 100644
--- a/ipaserver/install/certs.py
+++ b/ipaserver/install/certs.py
@@ -638,7 +638,13 @@ def create_from_pkcs12(self, pkcs12_fname, pkcs12_passwd, passwd=None,
 
     def install_pem_from_p12(self, p12_fname, p12_passwd, pem_fname):
         pwd = ipautil.write_tmp_file(p12_passwd)
-        ipautil.run([paths.OPENSSL, "pkcs12", "-nodes",
+        ipautil.run([paths.OPENSSL, "pkcs12", "-nokeys",
+                     "-in", p12_fname, "-out", pem_fname,
+                     "-passin", "file:" + pwd.name])
+
+    def install_key_from_p12(self, 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])
 
@@ -653,7 +659,7 @@ def request_service_cert(self, nickname, principal, host, pwdconf=False):
         self.create_from_cacert(paths.IPA_CA_CRT)
         if pwdconf:
             self.create_password_conf()
-        reqid = certmonger.request_cert(nssdb=self.secdir,
+        reqid = certmonger.request_cert(certpath=self.secdir,
                                         nickname=nickname,
                                         principal=principal,
                                         subject=host,
diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py
index d3b5d68..5f5181c 100644
--- a/ipaserver/install/krbinstance.py
+++ b/ipaserver/install/krbinstance.py
@@ -31,6 +31,7 @@
 from ipapython import sysrestore
 from ipapython import ipautil
 from ipapython import kernel_keyring
+from ipapython import certmonger
 from ipalib.constants import CACERT
 from ipapython.ipa_log_manager import root_logger
 from ipapython.dn import DN
@@ -158,12 +159,13 @@ def create_instance(self, realm_name, host_name, domain_name, admin_password, ma
         self.step("creating a keytab for the directory", self.__create_ds_keytab)
         self.step("creating a keytab for the machine", self.__create_host_keytab)
         self.step("adding the password extension to the directory", self.__add_pwd_extop_module)
-        if setup_pkinit:
-            self.step("creating X509 Certificate for PKINIT", self.__setup_pkinit)
-            self.step("creating principal for anonymous PKINIT", self.__add_anonymous_pkinit_principal)
 
         self.__common_post_setup()
 
+        if setup_pkinit:
+            self.step("creating principal for anonymous PKINIT",
+                      self.__add_anonymous_pkinit_principal)
+
         self.start_creation(runtime=30)
 
         self.kpasswd = KpasswdInstance()
@@ -226,7 +228,8 @@ def __setup_sub_dict(self):
                              KRB5KDC_KADM5_ACL=paths.KRB5KDC_KADM5_ACL,
                              DICT_WORDS=paths.DICT_WORDS,
                              KRB5KDC_KADM5_KEYTAB=paths.KRB5KDC_KADM5_KEYTAB,
-                             KDC_PEM=paths.KDC_PEM,
+                             KDC_CERT=paths.KDC_CERT,
+                             KDC_KEY=paths.KDC_KEY,
                              CACERT_PEM=paths.CACERT_PEM)
 
         # IPA server/KDC is not a subdomain of default domain
@@ -350,19 +353,29 @@ def __create_host_keytab(self):
         self.move_service_to_host(host_principal)
 
     def __setup_pkinit(self):
+        shutil.copyfile(CACERT, paths.CACERT_PEM)
+
         ca_db = certs.CertDB(self.realm, host_name=self.fqdn,
                                 subject_base=self.subject_base)
 
         if self.pkcs12_info:
             ca_db.install_pem_from_p12(self.pkcs12_info[0],
                                        self.pkcs12_info[1],
-                                       paths.KDC_PEM)
+                                       paths.KDC_CERT)
+            ca_db.install_key_from_p12(self.pkcs12_info[0],
+                                       self.pkcs12_info[1],
+                                       paths.KDC_KEY)
         else:
-            raise RuntimeError("PKI not supported yet\n")
-
-        # Finally copy the cacert in the krb directory so we don't
-        # have any selinux issues with the file context
-        shutil.copyfile(CACERT, paths.CACERT_PEM)
+            subject = str(DN(('cn', self.fqdn), self.subject_base))
+            krbtgt = "krbtgt/" + self.realm + "@" + self.realm
+            reqid = certmonger.request_cert((paths.KDC_CERT, paths.KDC_KEY),
+                                            u'KDC-Cert', subject, krbtgt,
+                                            dns=self.fqdn, storage='FILE',
+                                            profile='KDCs_PKINIT_Certs')
+            try:
+                certmonger.wait_for_request(reqid)
+            except RuntimeError as e:
+                root_logger.error("Failed to wait for request: %s", e)
 
     def __add_anonymous_pkinit_principal(self):
         princ = "WELLKNOWN/ANONYMOUS"
@@ -370,10 +383,9 @@ def __add_anonymous_pkinit_principal(self):
 
         # Create the special anonymous principal
         installutils.kadmin_addprinc(princ_realm)
-        dn = DN(('krbprincipalname', princ_realm), self.get_realm_suffix())
-        entry = self.admin_conn.get_entry(dn)
-        entry['nsAccountlock'] = ['TRUE']
-        self.admin_conn.update_entry(entry)
+
+    def setup_pkinit(self):
+        self.__setup_pkinit()
 
     def __convert_to_gssapi_replication(self):
         repl = replication.ReplicationManager(self.realm,
@@ -383,6 +395,9 @@ def __convert_to_gssapi_replication(self):
                                            r_binddn=DN(('cn', 'Directory Manager')),
                                            r_bindpw=self.dm_password)
 
+    def stop_tracking_certs(self):
+        certmonger.stop_tracking(certfile=paths.KDC_CERT)
+
     def uninstall(self):
         if self.is_configured():
             self.print_msg("Unconfiguring %s" % self.service_name)
@@ -405,6 +420,12 @@ def uninstall(self):
         if enabled:
             self.enable()
 
+        # stop tracking and remove certificates
+        self.stop_tracking_certs()
+        installutils.remove_file(paths.CACERT_PEM)
+        installutils.remove_file(paths.KDC_CERT)
+        installutils.remove_file(paths.KDC_KEY)
+
         if running:
             self.restart()
 
diff --git a/ipaserver/install/server/common.py b/ipaserver/install/server/common.py
index e6093d1..93b4ca9 100644
--- a/ipaserver/install/server/common.py
+++ b/ipaserver/install/server/common.py
@@ -343,7 +343,7 @@ def domain_name(self, value):
 
     no_pkinit = Knob(
         bool, False,
-        description="disables pkinit setup steps",
+        description="do not configure KDC PKINIT",
     )
 
     no_ui_redirect = Knob(
@@ -468,9 +468,6 @@ def __init__(self, **kwargs):
                 "You cannot specify a --auto-reverse option together with "
                 "--no-reverse")
 
-        # Automatically disable pkinit w/ dogtag until that is supported
-        self.no_pkinit = True
-
         self.unattended = not self.interactive
 
     ca = core.Component(BaseServerCA)
diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py
index 6644a6b..0ffb6e0 100644
--- a/ipaserver/install/server/install.py
+++ b/ipaserver/install/server/install.py
@@ -518,6 +518,11 @@ def install_check(installer):
         dirsrv_pkcs12_info = (dirsrv_pkcs12_file.name, dirsrv_pin)
 
     if options.pkinit_cert_files:
+        if not options.no_pkinit:
+            sys.exit("Cannot create KDC PKINIT certificate and use provided "
+                     "external PKINIT certificate at the same time. Please "
+                     "choose one of them")
+
         if options.pkinit_pin is None:
             options.pkinit_pin = read_password(
                 "Enter Kerberos KDC private key unlock",
@@ -786,17 +791,11 @@ def install(installer):
     ds.enable_ssl()
 
     krb = krbinstance.KrbInstance(fstore)
-    if options.pkinit_cert_files:
-        krb.create_instance(realm_name, host_name, domain_name,
-                            dm_password, master_password,
-                            setup_pkinit=not options.no_pkinit,
-                            pkcs12_info=pkinit_pkcs12_info,
-                            subject_base=options.subject)
-    else:
-        krb.create_instance(realm_name, host_name, domain_name,
-                            dm_password, master_password,
-                            setup_pkinit=not options.no_pkinit,
-                            subject_base=options.subject)
+    krb.create_instance(realm_name, host_name, domain_name,
+                        dm_password, master_password,
+                        setup_pkinit=not options.no_pkinit,
+                        pkcs12_info=pkinit_pkcs12_info,
+                        subject_base=options.subject)
 
     if setup_ca:
         ca.install_step_1(False, None, options)
@@ -845,6 +844,11 @@ def install(installer):
     service.print_msg("Applying LDAP updates")
     ds.apply_updates()
 
+    # Now that all services are up, configure pkinit certs
+    if not options.no_pkinit:
+        service.print_msg("Creating X509 Certificate for PKINIT")
+        krb.setup_pkinit()
+
     # Restart ds and krb after configurations have been changed
     service.print_msg("Restarting the directory server")
     ds.restart()
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
index c73600c..abd37df 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -140,7 +140,9 @@ def install_krb(config, setup_pkinit=False, promote=False):
     krb.create_replica(config.realm_name,
                        config.master_host_name, config.host_name,
                        config.domain_name, config.dirman_password,
-                       setup_pkinit, pkcs12_info, promote=promote)
+                       setup_pkinit, pkcs12_info,
+                       subject_base=config.subject_base,
+                       promote=promote)
 
     return krb
 
diff --git a/ipaserver/plugins/cert.py b/ipaserver/plugins/cert.py
index 6195a6b..534088e 100644
--- a/ipaserver/plugins/cert.py
+++ b/ipaserver/plugins/cert.py
@@ -145,11 +145,12 @@
 
 """)
 
-USER, HOST, SERVICE = range(3)
+USER, HOST, KRBTGT, SERVICE = range(4)
 
 PRINCIPAL_TYPE_STRING_MAP = {
     USER: _('user'),
     HOST: _('host'),
+    KRBTGT: _('krbtgt'),
     SERVICE: _('service'),
 }
 
@@ -239,6 +240,16 @@ def caacl_check(principal_type, principal, ca, profile_id):
         )
 
 
+def ca_kdc_check(ldap, basedn, hostname):
+    kdc_master_dn = DN(('cn', 'KDC'), ('cn', hostname),
+                       ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
+                       basedn)
+    try:
+        ldap.get_entry(kdc_master_dn, ['objectclass'])
+    except errors.NotFound:
+        raise errors.ACIError(info=_(
+                "Host '%(hostname)s' is not a KDC") % dict(hostname=hostname))
+
 def validate_certificate(value):
     return x509.validate_certificate(value, x509.DER)
 
@@ -548,6 +559,8 @@ def execute(self, csr, all=False, raw=False, **kw):
         ca_enabled_check()
 
         ldap = self.api.Backend.ldap2
+        basedn = self.api.env.basedn
+        realm = unicode(self.api.env.realm)
         add = kw.get('add')
         request_type = kw.get('request_type')
         profile_id = kw.get('profile_id', self.Backend.ra.DEFAULT_PROFILE)
@@ -578,6 +591,12 @@ def execute(self, csr, all=False, raw=False, **kw):
             principal_type = USER
         elif principal.is_host:
             principal_type = HOST
+        elif principal.service_name == 'krbtgt':
+            principal_type = KRBTGT
+            if profile_id != self.Backend.ra.KDC_PROFILE:
+                raise errors.ACIError(
+                    info=_("krbtgt certs can use only the %s profile") % (
+                           self.Backend.ra.KDC_PROFILE))
         else:
             principal_type = SERVICE
 
@@ -604,7 +623,10 @@ def execute(self, csr, all=False, raw=False, **kw):
             bypass_caacl = False
 
         if not bypass_caacl:
-            caacl_check(principal_type, principal, ca, profile_id)
+            if principal_type == KRBTGT:
+                ca_kdc_check(ldap, basedn, bind_principal.hostname)
+            else:
+                caacl_check(principal_type, principal, ca, profile_id)
 
         try:
             subject = pkcs10.get_subject(csr)
@@ -627,6 +649,11 @@ def execute(self, csr, all=False, raw=False, **kw):
         try:
             if principal_type == SERVICE:
                 principal_obj = api.Command['service_show'](principal_string, all=True)
+            elif principal_type == KRBTGT:
+                # Allow only our own realm krbtgt for now, no trusted realm's.
+                if principal != kerberos.Principal((u'krbtgt', realm),
+                                                   realm=realm):
+                    raise errors.NotFound("Not our realm's krbtgt")
             elif principal_type == HOST:
                 principal_obj = api.Command['host_show'](
                     principal.hostname, all=True)
@@ -646,8 +673,9 @@ def execute(self, csr, all=False, raw=False, **kw):
             else:
                 raise errors.NotFound(
                     reason=_("The principal for this request doesn't exist."))
-        principal_obj = principal_obj['result']
-        dn = principal_obj['dn']
+        if principal_obj:
+            principal_obj = principal_obj['result']
+            dn = principal_obj['dn']
 
         # Ensure that the DN in the CSR matches the principal
         cn = subject.common_name  #pylint: disable=E1101
@@ -661,6 +689,13 @@ def execute(self, csr, all=False, raw=False, **kw):
                     info=_("hostname in subject of request '%(cn)s' "
                         "does not match principal hostname '%(hostname)s'")
                         % dict(cn=cn, hostname=principal.hostname))
+        elif principal_type == KRBTGT and not bypass_caacl:
+            if cn.lower() != bind_principal.hostname.lower():
+                raise errors.ACIError(
+                    info=_("hostname in subject of request '%(cn)s' "
+                           "does not match principal hostname "
+                           "'%(hostname)s'") % dict(
+                                cn=cn, hostname=bind_principal.hostname))
         elif principal_type == USER:
             # check user name
             if cn != principal.username:
@@ -679,10 +714,12 @@ def execute(self, csr, all=False, raw=False, **kw):
                         "any of user's email addresses")
                 )
 
-        # We got this far so the principal entry exists, can we write it?
-        if not ldap.can_write(dn, "usercertificate"):
-            raise errors.ACIError(info=_("Insufficient 'write' privilege "
-                "to the 'userCertificate' attribute of entry '%s'.") % dn)
+        if principal_type != KRBTGT:
+            # We got this far so the principal entry exists, can we write it?
+            if not ldap.can_write(dn, "usercertificate"):
+                raise errors.ACIError(
+                    info=_("Insufficient 'write' privilege to the "
+                           "'userCertificate' attribute of entry '%s'.") % dn)
 
         # Validate the subject alt name, if any
         for name_type, desc, name, der_name in subjectaltname:
@@ -695,6 +732,9 @@ def execute(self, csr, all=False, raw=False, **kw):
                         alt_principal = kerberos.Principal(
                             (u'host', name), principal.realm)
                         alt_principal_obj = api.Command['host_show'](name, all=True)
+                    elif principal_type == KRBTGT:
+                        alt_principal = kerberos.Principal(
+                            (u'host', name), principal.realm)
                     elif principal_type == SERVICE:
                         alt_principal = kerberos.Principal(
                             (principal.service_name, name), principal.realm)
@@ -720,7 +760,11 @@ def execute(self, csr, all=False, raw=False, **kw):
                             "Insufficient privilege to create a certificate "
                             "with subject alt name '%s'.") % name)
                 if alt_principal is not None and not bypass_caacl:
-                    caacl_check(principal_type, alt_principal, ca, profile_id)
+                    if principal_type == KRBTGT:
+                        ca_kdc_check(ldap, basedn, alt_principal.hostname)
+                    else:
+                        caacl_check(principal_type, alt_principal, ca,
+                                    profile_id)
             elif name_type in [
                 (nss.certOtherName, x509.SAN_UPN),
                 (nss.certOtherName, x509.SAN_KRB5PRINCIPALNAME),
@@ -769,6 +813,9 @@ def execute(self, csr, all=False, raw=False, **kw):
                 api.Command['host_mod'](principal.hostname, **kwargs)
             elif principal_type == USER:
                 api.Command['user_mod'](principal.username, **kwargs)
+            elif principal_type == KRBTGT:
+                root_logger.error("Profiles used to store cert should't be "
+                                  "used for krbtgt certificates")
 
         return dict(
             result=result,
diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py
index aef1e88..e132a5b 100644
--- a/ipaserver/plugins/dogtag.py
+++ b/ipaserver/plugins/dogtag.py
@@ -1292,6 +1292,7 @@ class ra(rabase.rabase):
     Request Authority backend plugin.
     """
     DEFAULT_PROFILE = dogtag.DEFAULT_PROFILE
+    KDC_PROFILE = dogtag.KDC_PROFILE
 
     def __init__(self, api):
         if api.env.in_tree:
-- 
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