On Fri, Jul 15, 2016 at 03:04:48PM +1000, Fraser Tweedale wrote:
> The attached patch is a work in progress for
> https://fedorahosted.org/freeipa/ticket/2614 (BZ 828866).
> 
> I am sharing now to make the approach clear and solicit feedback.
> 
> It has been tested for server install, replica install (with and
> without CA) and CA-replica install (all hosts running master+patch).
> 
> Migration from earlier versions and server/replica/CA install on a
> CA-less deployment are not yet tested; these will be tested over
> coming days and patch will be tweaked as necessary.
> 
> Commit message has a fair bit to say so I won't repeat here but let
> me know your questions and comments.
> 
> Thanks,
> Fraser
>
It does help to attach the patch, of course ^_^
From 74102e13b041cd05396a579f12f26a9f80394ad1 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Mon, 11 Jul 2016 12:57:11 +1000
Subject: [PATCH] Allow full customisability of IPA CA subject DN

Currently only the "subject base" of the IPA CA subject DN can be
customised via the installer's --subject option.  The RDN
"CN=Certificate Authority" is appended to form the subject DN, and
this composition is widely assumed, hardcoded in many places.

Some administrators need more control over the CA subject DN,
especially to satisfy expectations of external CAs when the IPA CA
is to be externally signed.

This patch adds full customisability of the CA subject DN.  The
--subject argument can now be the full DN, and the subject base is
derived from it.  The full rules are as follows:

- If --subject is not given, default to
  "CN=Certificate Authority, O=$REALM" (existing behaviour)

- If --external-ca is used, subject is used as-is.

- If and only if --external-ca is not used, to meet Dogtag's
  expectations, the "most specific" CN AVA encountered shall be the
  most specific RDN (it is moved if necessary); if the subject DN
  does not contain a CN AVA, then "CN=Certificate Authority" is
  appended.

- The subject base is derived from the subject (after processing per
  preceding points) by extracting OU, O, L, ST, C and DC AVAs,
  preserving relative order.  If the resulting DN is empty, it
  defaults to "O=$REALM".

Fixes: https://fedorahosted.org/freeipa/ticket/2614
---
 install/share/certmap.conf.template        |  2 +-
 install/tools/ipa-ca-install               | 14 +++++-------
 install/tools/man/ipa-server-install.1     |  2 +-
 ipapython/ipautil.py                       | 20 +++++++++++++++++
 ipaserver/install/ca.py                    | 20 +++++++++--------
 ipaserver/install/cainstance.py            | 35 ++++++++++++++++++------------
 ipaserver/install/certs.py                 |  9 ++++----
 ipaserver/install/dsinstance.py            | 29 +++++++++++++++----------
 ipaserver/install/installutils.py          | 35 +++++++++++++++++++++++++++---
 ipaserver/install/ipa_cacert_manage.py     |  9 ++++++--
 ipaserver/install/krainstance.py           |  9 +++++---
 ipaserver/install/server/common.py         |  4 ++--
 ipaserver/install/server/install.py        | 17 ++++++++++-----
 ipaserver/install/server/replicainstall.py | 27 ++++++++++++++++-------
 14 files changed, 159 insertions(+), 73 deletions(-)

diff --git a/install/share/certmap.conf.template 
b/install/share/certmap.conf.template
index 
e76bf3c653a4f1d130ce8c264a28cac5dc63925c..d59b095faff804eae4cbd2ef984aa8ca3be52946
 100644
--- a/install/share/certmap.conf.template
+++ b/install/share/certmap.conf.template
@@ -41,6 +41,6 @@ certmap default         default
 #default:InitFn         <Init function's name>
 default:DNComps
 default:FilterComps     uid
-certmap ipaca           CN=Certificate Authority,$SUBJECT_BASE
+certmap ipaca           $ISSUER_DN
 ipaca:CmapLdapAttr      seeAlso
 ipaca:verifycert        on
diff --git a/install/tools/ipa-ca-install b/install/tools/ipa-ca-install
index 
ed685920cbadb9cd3fc80865afb1610ca42f8b13..8a8adb3984386bb88227d769a8c5132bb121b870
 100755
--- a/install/tools/ipa-ca-install
+++ b/install/tools/ipa-ca-install
@@ -32,7 +32,7 @@ from ipaserver.install import bindinstance, dsinstance, ca
 from ipaserver.install import cainstance, custodiainstance, service
 from ipapython import version
 from ipalib import api
-from ipalib.constants import DOMAIN_LEVEL_0
+from ipalib.constants import DOMAIN_LEVEL_0, IPA_CA_CN
 from ipapython.dn import DN
 from ipapython.config import IPAOptionParser
 from ipapython.ipa_log_manager import root_logger, standard_logging_setup
@@ -160,9 +160,7 @@ def install_replica(safe_options, options, filename):
     conn.connect(bind_dn=DN(('cn', 'Directory Manager')),
                             bind_pw=dirman_password)
 
-    if config.subject_base is None:
-        attrs = conn.get_ipa_config()
-        config.subject_base = attrs.get('ipacertificatesubjectbase')[0]
+    subject = api.Command.ca_show(IPA_CA_CN)['result']['ipacasubjectdn'][0]
 
     if config.master_host_name is None:
         config.ca_host_name = \
@@ -175,7 +173,7 @@ def install_replica(safe_options, options, filename):
     options.domain_name = config.domain_name
     options.dm_password = config.dirman_password
     options.host_name = config.host_name
-    options.subject = config.subject_base
+    options.subject = subject
     if os.path.exists(cafile):
         options.ca_cert_file = cafile
     else:
@@ -193,7 +191,7 @@ def install_replica(safe_options, options, filename):
                                    host_name=config.host_name,
                                    dm_password=config.dirman_password)
         CA.configure_replica(config.ca_host_name,
-                             subject_base=config.subject_base,
+                             subject=subject,
                              ca_cert_bundle=ca_data)
         # Install CA DNS records
         if bindinstance.dns_container_exists(api.env.host, api.env.basedn,
@@ -220,13 +218,13 @@ def install_master(safe_options, options):
                               bind_pw=dm_password)
 
     config = api.Command['config_show']()['result']
-    subject_base = config['ipacertificatesubjectbase'][0]
+    subject = api.Command.ca_show(IPA_CA_CN)['result']['ipacasubjectdn']
 
     options.realm_name = api.env.realm
     options.domain_name = api.env.domain
     options.dm_password = dm_password
     options.host_name = api.env.host
-    options.subject = subject_base
+    options.subject = subject
 
     ca.install_check(True, None, options)
     ca.install(True, None, options)
diff --git a/install/tools/man/ipa-server-install.1 
b/install/tools/man/ipa-server-install.1
index 
55b49449e3c44aebfeefe5cb71d73e9abf07c5b2..775427de1a2a220fb01dc7b8892105dd40bf182d
 100644
--- a/install/tools/man/ipa-server-install.1
+++ b/install/tools/man/ipa-server-install.1
@@ -130,7 +130,7 @@ Name of the Kerberos KDC SSL certificate to install
 File containing the CA certificate of the CA which issued the Directory 
Server, Apache Server and Kerberos KDC certificates. The file is accepted in 
PEM and DER certificate and PKCS#7 certificate chain formats. This option may 
be used multiple times. Use this option if the CA certificate is not present in 
the certificate files.
 .TP
 \fB\-\-subject\fR=\fISUBJECT\fR
-The certificate subject base (default O=REALM.NAME)
+The CA certificate subject DN (default CN=Certificate Authority,O=REALM.NAME)
 .TP
 \fB\-\-ca\-signing\-algorithm\fR=\fIALGORITHM\fR
 Signing algorithm of the IPA CA certificate. Possible values are SHA1withRSA, 
SHA256withRSA, SHA512withRSA. Default value is SHA256withRSA. Use this option 
with --external-ca if the external CA does not support the default signing 
algorithm.
diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
index 
763a99c117e22a4ac49d8d34b38230f3da7c8435..b2c5a9adf57522bb28583c03eb60ce74c4d14868
 100644
--- a/ipapython/ipautil.py
+++ b/ipapython/ipautil.py
@@ -1469,3 +1469,23 @@ def is_fips_enabled():
         # Consider that the host is not fips-enabled if the file does not exist
         pass
     return False
+
+
+def extract_ca_subject_base(dn, realm_name):
+    """
+    Extract a base DN from the given CA subject DN.
+
+    Components extracted are OU, O, L, ST, C, DC.  Relative order
+    is retained.  If none of those attributes are present, return
+    O=<realm_name>.
+
+    """
+    base_attrs = ['ou', 'o', 'l', 'st', 'c', 'dc']
+    l = []
+    for rdn in DN(dn):
+        for ava in rdn:
+            if ava.attr.lower() in base_attrs:
+                l.append(ava)
+    if len(l) == 0:
+        l = [('O', realm_name)]
+    return DN(*l)
diff --git a/ipaserver/install/ca.py b/ipaserver/install/ca.py
index 
bce804ac1c4e3eaf8dd08bed894ad45ea2d73ae1..e2d12e829ff24d2d317ed4d136363df35c40d07c
 100644
--- a/ipaserver/install/ca.py
+++ b/ipaserver/install/ca.py
@@ -26,7 +26,8 @@ def install_check(standalone, replica_config, options):
 
     realm_name = options.realm_name
     host_name = options.host_name
-    subject_base = options.subject
+    subject_dn = options.subject
+    subject_base = ipautil.extract_ca_subject_base(subject_dn, realm_name)
 
     if replica_config is not None:
         if standalone and api.env.ra_plugin == 'selfsign':
@@ -106,7 +107,7 @@ def install_check(standalone, replica_config, options):
                 if not cert:
                     continue
                 subject = DN(str(x509.get_subject(cert)))
-                if subject in (DN('CN=Certificate Authority', subject_base),
+                if subject in (DN(subject_dn),
                                DN('CN=IPA RA', subject_base),
                                DN('CN=Object Signing Cert', subject_base)):
                     print(("Certificate with subject %s is present in %s, "
@@ -124,7 +125,6 @@ def install_step_0(standalone, replica_config, options):
     domain_name = options.domain_name
     dm_password = options.dm_password
     host_name = options.host_name
-    subject_base = options.subject
 
     if replica_config is not None:
         # Configure the CA if necessary
@@ -136,8 +136,10 @@ def install_step_0(standalone, replica_config, options):
         if standalone:
             api.Backend.ldap2.disconnect()
 
-        cainstance.install_replica_ca(replica_config, postinstall,
-                ra_p12=getattr(options, 'ra_p12', None))
+        cainstance.install_replica_ca(
+                replica_config, postinstall,
+                ra_p12=getattr(options, 'ra_p12', None),
+                subject=options.subject)
 
         if standalone and not api.Backend.ldap2.isconnected():
             api.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')),
@@ -157,19 +159,19 @@ def install_step_0(standalone, replica_config, options):
         ca.create_ra_agent_db = False
     if external == 0:
         ca.configure_instance(host_name, dm_password,
-                              dm_password, subject_base=subject_base,
+                              dm_password, subject=options.subject,
                               
ca_signing_algorithm=options.ca_signing_algorithm)
     elif external == 1:
         ca.configure_instance(host_name, dm_password,
                               dm_password, csr_file=paths.ROOT_IPA_CSR,
-                              subject_base=subject_base,
+                              subject=options.subject,
                               
ca_signing_algorithm=options.ca_signing_algorithm,
                               ca_type=options.external_ca_type)
     else:
         ca.configure_instance(host_name, dm_password, dm_password,
                               cert_file=external_cert_file.name,
                               cert_chain_file=external_ca_file.name,
-                              subject_base=subject_base,
+                              subject=options.subject,
                               
ca_signing_algorithm=options.ca_signing_algorithm)
 
 
@@ -178,7 +180,7 @@ def install_step_1(standalone, replica_config, options):
     domain_name = options.domain_name
     dm_password = options.dm_password
     host_name = options.host_name
-    subject_base = options.subject
+    subject_base = ipautil.extract_ca_subject_base(options.subject, realm_name)
 
     basedn = ipautil.realm_to_suffix(realm_name)
 
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index 
070498fe8a394802ea55f848a268e2b6563ec472..afeb8007f47571f1de38f190e64cb6cae52b3d98
 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -344,7 +344,7 @@ class CAInstance(DogtagInstance):
                            pkcs12_info=None, master_host=None, csr_file=None,
                            cert_file=None, cert_chain_file=None,
                            master_replication_port=None,
-                           subject_base=None, ca_signing_algorithm=None,
+                           subject=None, ca_signing_algorithm=None,
                            ca_type=None, ra_p12=None):
         """Create a CA instance.
 
@@ -364,10 +364,14 @@ class CAInstance(DogtagInstance):
             self.clone = True
         self.master_host = master_host
         self.master_replication_port = master_replication_port
-        if subject_base is None:
-            self.subject_base = DN(('O', self.realm))
+
+        if subject is None:
+            self.subject = installutils.default_ca_subject_dn(self.realm)
         else:
-            self.subject_base = subject_base
+            self.subject = subject
+        self.subject_base = ipautil.extract_ca_subject_base(
+                self.subject, self.realm)
+
         if ca_signing_algorithm is None:
             self.ca_signing_algorithm = 'SHA256withRSA'
         else:
@@ -501,7 +505,7 @@ class CAInstance(DogtagInstance):
         config.set("CA", "pki_audit_signing_subject_dn",
             str(DN(('cn', 'CA Audit'), self.subject_base)))
         config.set("CA", "pki_ca_signing_subject_dn",
-            str(DN(('cn', 'Certificate Authority'), self.subject_base)))
+            str(self.subject))
 
         # Certificate nicknames
         config.set("CA", "pki_subsystem_nickname", "subsystemCert cert-pki-ca")
@@ -775,7 +779,7 @@ class CAInstance(DogtagInstance):
             userCertificate=[cert_data],
             description=['2;%s;%s;%s' % (
                 cert.serial_number,
-                DN(('CN', 'Certificate Authority'), self.subject_base),
+                DN(self.subject),
                 DN(('CN', 'IPA RA'), self.subject_base))])
         conn.add_entry(entry)
 
@@ -854,7 +858,7 @@ class CAInstance(DogtagInstance):
         st = 1
         en = 0
         subid = 0
-        ca_dn = DN(('CN','Certificate Authority'), self.subject_base)
+        ca_dn = DN(self.subject)
         while st > 0:
             st = certlist.find('-----BEGIN', en)
             en = certlist.find('-----END', en+1)
@@ -1305,7 +1309,7 @@ class CAInstance(DogtagInstance):
         basedn = ipautil.realm_to_suffix(self.realm)
         self.ldap_enable('CA', self.fqdn, None, basedn)
 
-    def configure_replica(self, master_host, subject_base=None,
+    def configure_replica(self, master_host, subject=None,
                           ca_cert_bundle=None, ca_signing_algorithm=None,
                           ca_type=None):
         """Creates a replica CA, creating a local DS backend and using
@@ -1314,10 +1318,14 @@ class CAInstance(DogtagInstance):
         """
         self.master_host = master_host
         self.master_replication_port = 389
-        if subject_base is None:
-            self.subject_base = DN(('O', self.realm))
+
+        if subject is None:
+            self.subject = installutils.default_ca_subject_dn(self.realm)
         else:
-            self.subject_base = subject_base
+            self.subject = subject
+        self.subject_base = ipautil.extract_ca_subject_base(
+                self.subject, self.realm)
+
         if ca_signing_algorithm is None:
             self.ca_signing_algorithm = 'SHA256withRSA'
         else:
@@ -1489,7 +1497,7 @@ def replica_ca_install_check(config):
         exit('IPA schema missing on master CA directory server')
 
 
-def install_replica_ca(config, postinstall=False, ra_p12=None):
+def install_replica_ca(config, postinstall=False, ra_p12=None, subject=None):
     """
     Install a CA on a replica.
 
@@ -1509,7 +1517,6 @@ def install_replica_ca(config, postinstall=False, 
ra_p12=None):
 
     ca = CAInstance(config.realm_name, certs.NSS_DIR)
     ca.dm_password = config.dirman_password
-    ca.subject_base = config.subject_base
 
     if not config.setup_ca:
         # We aren't configuring the CA in this step but we still need
@@ -1528,7 +1535,7 @@ def install_replica_ca(config, postinstall=False, 
ra_p12=None):
                           pkcs12_info=(cafile,), ra_p12=ra_p12,
                           master_host=config.master_host_name,
                           master_replication_port=config.ca_ds_port,
-                          subject_base=config.subject_base)
+                          subject=subject)
 
     # Restart httpd since we changed it's config and added ipa-pki-proxy.conf
     # Without the restart, CA service status check would fail due to missing
diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py
index 
b3d273ff107f0493516845745c4f14242fc518ca..06248e2387c11ff69e26a61e4a0a584711afa7c9
 100644
--- a/ipaserver/install/certs.py
+++ b/ipaserver/install/certs.py
@@ -93,15 +93,14 @@ class CertDB(object):
         self.certreq_fname = None
         self.certder_fname = None
         self.host_name = host_name
-        self.subject_base = subject_base
+        self.subject = subject_base or DN(('O', 'IPA'))
+        self.subject_base = ipautil.extract_ca_subject_base(
+                self.subject, realm)
         try:
             self.cwd = os.getcwd()
         except OSError as e:
             raise RuntimeError("Unable to determine the current directory: %s" 
% str(e))
 
-        if not subject_base:
-            self.subject_base = DN(('O', 'IPA'))
-
         self.cacert_name = get_ca_nickname(self.realm)
         self.valid_months = "120"
         self.keysize = "1024"
@@ -253,7 +252,7 @@ class CertDB(object):
         certs = fd.read()
         fd.close()
 
-        ca_dn = DN(('CN','Certificate Authority'), self.subject_base)
+        ca_dn = self.subject
         st = 0
         while True:
             try:
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index 
c93b3b4ff58c4102a9de448247966ad3dd8e4e7c..12cc838a1e42f89d1f6c1107ffc82ef02bd48fe7
 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -227,6 +227,7 @@ class DsInstance(service.Service):
         self.dercert = None
         self.idstart = None
         self.idmax = None
+        self.subject = None
         self.subject_base = None
         self.open_ports = []
         self.run_init_memberof = True
@@ -244,6 +245,7 @@ class DsInstance(service.Service):
             self.fstore = sysrestore.FileStore(paths.SYSRESTORE)
 
 
+    subject = ipautil.dn_attribute_property('_subject')
     subject_base = ipautil.dn_attribute_property('_subject_base')
 
     def __common_setup(self, enable_ssl=False):
@@ -292,7 +294,7 @@ class DsInstance(service.Service):
         self.step("configuring directory to start on boot", self.__enable)
 
     def init_info(self, realm_name, fqdn, domain_name, dm_password,
-                  subject_base, idstart, idmax, pkcs12_info, ca_file=None):
+                  subject, idstart, idmax, pkcs12_info, ca_file=None):
         self.realm = realm_name.upper()
         self.serverid = installutils.realm_to_serverid(self.realm)
         self.suffix = ipautil.realm_to_suffix(self.realm)
@@ -300,7 +302,9 @@ class DsInstance(service.Service):
         self.dm_password = dm_password
         self.domain = domain_name
         self.principal = "ldap/%s@%s" % (self.fqdn, self.realm)
-        self.subject_base = subject_base
+        self.subject = subject
+        self.subject_base = ipautil.extract_ca_subject_base(
+                self.subject, self.realm)
         self.idstart = idstart
         self.idmax = idmax
         self.pkcs12_info = pkcs12_info
@@ -312,11 +316,11 @@ class DsInstance(service.Service):
 
     def create_instance(self, realm_name, fqdn, domain_name,
                         dm_password, pkcs12_info=None,
-                        idstart=1100, idmax=999999, subject_base=None,
+                        idstart=1100, idmax=999999, subject=None,
                         hbac_allow=True, ca_file=None):
         self.init_info(
             realm_name, fqdn, domain_name, dm_password,
-            subject_base, idstart, idmax, pkcs12_info, ca_file=ca_file)
+            subject, idstart, idmax, pkcs12_info, ca_file=ca_file)
 
         self.__common_setup()
         self.step("restarting directory server", self.__restart_instance)
@@ -350,7 +354,7 @@ class DsInstance(service.Service):
         self.start_creation(runtime=10)
 
     def create_replica(self, realm_name, master_fqdn, fqdn,
-                       domain_name, dm_password, subject_base, api,
+                       domain_name, dm_password, subject, api,
                        pkcs12_info=None, ca_file=None,
                        ca_is_configured=None, promote=False):
         # idstart and idmax are configured so that the range is seen as
@@ -365,7 +369,7 @@ class DsInstance(service.Service):
             fqdn=fqdn,
             domain_name=domain_name,
             dm_password=dm_password,
-            subject_base=subject_base,
+            subject=subject,
             idstart=idstart,
             idmax=idmax,
             pkcs12_info=pkcs12_info,
@@ -865,7 +869,7 @@ class DsInstance(service.Service):
         shutil.copyfile(ipautil.SHARE_DIR + "certmap.conf.template",
                         config_dirname(self.serverid) + "certmap.conf")
         installutils.update_file(config_dirname(self.serverid) + 
"certmap.conf",
-                                 '$SUBJECT_BASE', str(self.subject_base))
+                                 '$ISSUER_DN', str(self.subject))
         sysupgrade.set_upgrade_state(
             'certmap.conf',
             'subject_base',
@@ -1190,9 +1194,12 @@ class DsInstance(service.Service):
             )
             try:
                 with open(os.path.join(certmap_dir, 'certmap.conf')) as f:
+                    prefix = 'certmap ipaca'
                     for line in f:
-                        if line.startswith('certmap ipaca'):
-                            subject_base = line.strip().split(',')[-1]
+                        if line.startswith(prefix):
+                            subject_dn = line[len(prefix):].strip()
+                            subject_base = ipautil.extract_ca_subject_base(
+                                    subject_dn, api.env.realm)
                             root_logger.debug(
                                 'Found certificate subject base in 
certmap.conf: '
                                 '%s', subject_base)
@@ -1237,9 +1244,9 @@ class DsInstance(service.Service):
         os.chown(paths.DS_KEYTAB, pent.pw_uid, pent.pw_gid)
 
     def __get_ds_cert(self):
-        subject = DN(('O', self.realm))
         nssdb_dir = config_dirname(self.serverid)
-        db = certs.CertDB(self.realm, nssdir=nssdb_dir, subject_base=subject)
+        db = certs.CertDB(
+            self.realm, nssdir=nssdb_dir, subject_base=self.subject_base)
         db.request_service_cert(self.nickname, self.principal, self.fqdn)
         db.create_pin_file()
 
diff --git a/ipaserver/install/installutils.py 
b/ipaserver/install/installutils.py
index 
25f48aed1eeaa03353465bc40abf3484ec19bf3b..dd625868c0078359db81e4eaee80dd619778fbde
 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -996,7 +996,7 @@ def check_entropy():
     except ValueError as e:
         root_logger.debug("Invalid value in %s %s", paths.ENTROPY_AVAIL, e)
 
-def load_external_cert(files, subject_base):
+def load_external_cert(files, subject):
     """
     Load and verify external CA certificate chain from multiple files.
 
@@ -1004,7 +1004,7 @@ def load_external_cert(files, subject_base):
     chain formats.
 
     :param files: Names of files to import
-    :param subject_base: Subject name base for IPA certificates
+    :param subject: IPA CA subject DN
     :returns: Temporary file with the IPA CA certificate and temporary file
         with the external CA certificate chain
     """
@@ -1018,7 +1018,7 @@ def load_external_cert(files, subject_base):
         except RuntimeError as e:
             raise ScriptError(str(e))
 
-        ca_subject = DN(('CN', 'Certificate Authority'), subject_base)
+        ca_subject = DN(subject)
         ca_nickname = None
         cache = {}
         for nickname, trust_flags in nssdb.list_certs():
@@ -1377,3 +1377,32 @@ def remove_ccache(ccache_path=None, run_as=None):
     except ipautil.CalledProcessError as e:
         root_logger.warning(
             "Failed to clear Kerberos credentials cache: {}".format(e))
+
+
+def default_ca_subject_dn(realm_name):
+    return DN(('CN', 'Certificate Authority'), ('O', realm_name))
+
+
+def normalize_dogtag_ca_subject_dn(dn):
+    """
+    Prepare a CA subject DN to be compliant with Dogtag.
+
+    Move the most specific CN AVA encountered to be the most
+    specific RDN, if it is not already so.
+
+    If no CN AVA is encountered, add 'CN=Certificate Authority' as
+    the most specific RDN.
+
+    """
+    cn_ava = None
+    l = []
+    for rdn in DN(dn):
+        for ava in rdn:
+            if cn_ava is None and ava.attr.lower() == 'cn':
+                cn_ava = ava
+            else:
+                l.append(ava)
+    if cn_ava is None:
+        cn_ava = ('cn', 'Certificate Authority')
+    l.insert(0, cn_ava)
+    return DN(*l)
diff --git a/ipaserver/install/ipa_cacert_manage.py 
b/ipaserver/install/ipa_cacert_manage.py
index 
de13ad39397ae5e9b924b0621521e5fc6016c8e6..f5e8a5f63396ad8ca887c1a41944c89c777e4311
 100644
--- a/ipaserver/install/ipa_cacert_manage.py
+++ b/ipaserver/install/ipa_cacert_manage.py
@@ -24,6 +24,7 @@ from optparse import OptionGroup
 from nss import nss
 from nss.error import NSPRError
 import gssapi
+import six
 
 from ipapython import admintool, certmonger, ipautil
 from ipapython.dn import DN
@@ -31,6 +32,9 @@ from ipaplatform.paths import paths
 from ipalib import api, errors, x509, certstore
 from ipaserver.install import certs, cainstance, installutils
 
+if six.PY3:
+    unicode = str
+
 
 class CACertManage(admintool.AdminTool):
     command_name = 'ipa-cacert-manage'
@@ -198,8 +202,6 @@ class CACertManage(admintool.AdminTool):
 
         options = self.options
         conn = api.Backend.ldap2
-        cert_file, ca_file = installutils.load_external_cert(
-            options.external_cert_files, x509.subject_base())
 
         nss_cert = None
         nss.nss_init(paths.PKI_TOMCAT_ALIAS_DIR)
@@ -211,6 +213,9 @@ class CACertManage(admintool.AdminTool):
             pkinfo = nss_cert.subject_public_key_info.format()
             #pylint: enable=E1101
 
+            cert_file, ca_file = installutils.load_external_cert(
+                options.external_cert_files, unicode(subject))
+
             nss_cert = x509.load_certificate_from_file(cert_file.name)
             cert = nss_cert.der_data
             if nss_cert.subject != subject:
diff --git a/ipaserver/install/krainstance.py b/ipaserver/install/krainstance.py
index 
dc44726887916c7216564679c6ea8e9902177b64..491c4e57f71a9aa89bb4736258b144681a93f061
 100644
--- a/ipaserver/install/krainstance.py
+++ b/ipaserver/install/krainstance.py
@@ -93,9 +93,12 @@ class KRAInstance(DogtagInstance):
             self.clone = True
         self.master_host = master_host
         if subject_base is None:
-            self.subject_base = DN(('O', self.realm))
+            self.subject = installutils.default_ca_subject_dn(realm_name)
         else:
-            self.subject_base = subject_base
+            self.subject = subject_base
+        self.subject_base = ipautil.extract_ca_subject_base(
+                self.subject, self.realm)
+
         self.realm = realm_name
         self.suffix = ipautil.realm_to_suffix(realm_name)
 
@@ -296,7 +299,7 @@ class KRAInstance(DogtagInstance):
             userCertificate=[cert_data],
             description=['2;%s;%s;%s' % (
                 cert.serial_number,
-                DN(('CN', 'Certificate Authority'), self.subject_base),
+                DN(self.subject),
                 DN(('CN', 'IPA RA'), self.subject_base))])
         conn.add_entry(entry)
 
diff --git a/ipaserver/install/server/common.py 
b/ipaserver/install/server/common.py
index 
45fb2dc17976a08acab16783584524721411fb4e..e2780acc96db8f19598468e2b694e419eb80fd8b
 100644
--- a/ipaserver/install/server/common.py
+++ b/ipaserver/install/server/common.py
@@ -19,7 +19,7 @@ from ipapython.dnsutil import check_zone_overlap
 if six.PY3:
     unicode = str
 
-VALID_SUBJECT_ATTRS = ['st', 'o', 'ou', 'dnqualifier', 'c',
+VALID_SUBJECT_ATTRS = ['cn', 'st', 'o', 'ou', 'dnqualifier', 'c',
                        'serialnumber', 'l', 'title', 'sn', 'givenname',
                        'initials', 'generationqualifier', 'dc', 'mail',
                        'uid', 'postaladdress', 'postalcode', 'postofficebox',
@@ -130,7 +130,7 @@ class BaseServerCA(common.Installable, core.Group, 
core.Composite):
 
     subject = Knob(
         str, None,
-        description="The certificate subject base (default O=<realm-name>)",
+        description="The CA certificate subject DN (default CN=Certificate 
Authority,O=<realm-name>)",
     )
 
     @subject.validator
diff --git a/ipaserver/install/server/install.py 
b/ipaserver/install/server/install.py
index 
c0c676b870b481696ae75742c7bf88074b0ecf9c..c3c4fce8f40ff52a6fb4f23a59feaa2bdeaa286b
 100644
--- a/ipaserver/install/server/install.py
+++ b/ipaserver/install/server/install.py
@@ -238,7 +238,7 @@ def check_dirsrv(unattended):
         sys.exit(1)
 
 
-def set_subject_in_config(realm_name, dm_password, suffix, subject_base):
+def set_subject_base_in_config(realm_name, dm_password, suffix, subject_base):
         ldapuri = 'ldapi://%%2fvar%%2frun%%2fslapd-%s.socket' % (
             installutils.realm_to_serverid(realm_name)
         )
@@ -479,7 +479,10 @@ def install_check(installer):
         realm_name = options.realm_name.upper()
 
     if not options.subject:
-        options.subject = DN(('O', realm_name))
+        options.subject = installutils.default_ca_subject_dn(realm_name)
+    if not options.external_ca:
+        options.subject = installutils.normalize_dogtag_ca_subject_dn(
+            options.subject)
 
     if options.http_cert_files:
         if options.http_pin is None:
@@ -733,7 +736,7 @@ def install(installer):
             ds.create_instance(realm_name, host_name, domain_name,
                                dm_password, dirsrv_pkcs12_info,
                                idstart=options.idstart, idmax=options.idmax,
-                               subject_base=options.subject,
+                               subject=options.subject,
                                hbac_allow=not options.no_hbac_allow)
         else:
             ds = dsinstance.DsInstance(fstore=fstore,
@@ -743,7 +746,7 @@ def install(installer):
             ds.create_instance(realm_name, host_name, domain_name,
                                dm_password,
                                idstart=options.idstart, idmax=options.idmax,
-                               subject_base=options.subject,
+                               subject=options.subject,
                                hbac_allow=not options.no_hbac_allow)
 
         ntpinstance.ntp_ldap_enable(host_name, ds.suffix, realm_name)
@@ -784,6 +787,7 @@ def install(installer):
     ds.enable_ssl()
 
     krb = krbinstance.KrbInstance(fstore)
+    subject_base = ipautil.extract_ca_subject_base(options.subject, realm_name)
     if options.pkinit_cert_files:
         krb.create_instance(realm_name, host_name, domain_name,
                             dm_password, master_password,
@@ -835,8 +839,9 @@ def install(installer):
     os.chmod(CACERT, 0o644)
     ca_db.publish_ca_cert(CACERT)
 
-    set_subject_in_config(realm_name, dm_password,
-                          ipautil.realm_to_suffix(realm_name), options.subject)
+    set_subject_base_in_config(
+        realm_name, dm_password,
+        ipautil.realm_to_suffix(realm_name), subject_base)
 
     # Apply any LDAP updates. Needs to be done after the configuration file
     # is created
diff --git a/ipaserver/install/server/replicainstall.py 
b/ipaserver/install/server/replicainstall.py
index 
9d05a0be5a2679d825b4ee6bc2ea55ed358e8ff9..84bfb9966f11e628a76de74a266245b79c5afe44
 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -19,6 +19,7 @@ import tempfile
 import six
 
 from ipapython import ipaldap, ipautil, sysrestore
+from ipapython.certdb import get_ca_nickname
 from ipapython.dn import DN
 from ipapython.install.common import step
 from ipapython.install.core import Knob
@@ -71,7 +72,7 @@ def make_pkcs12_info(directory, cert_name, password_name):
         return None
 
 
-def install_http_certs(config, fstore, remote_api):
+def install_http_certs(config, fstore, remote_api, subject_base):
 
     # Obtain keytab for the HTTP service
     fstore.backup_file(paths.IPA_KEYTAB)
@@ -89,8 +90,7 @@ def install_http_certs(config, fstore, remote_api):
 
     # Obtain certificate for the HTTP service
     nssdir = certs.NSS_DIR
-    subject = DN(('O', config.realm_name))
-    db = certs.CertDB(config.realm_name, nssdir=nssdir, subject_base=subject)
+    db = certs.CertDB(config.realm_name, nssdir=nssdir, 
subject_base=subject_base)
     db.request_service_cert('Server-Cert', principal, config.host_name, True)
     # FIXME: need Signing-Cert too ?
 
@@ -119,7 +119,7 @@ def install_replica_ds(config, options, ca_is_configured, 
remote_api,
         fqdn=config.host_name,
         domain_name=config.domain_name,
         dm_password=config.dirman_password,
-        subject_base=config.subject_base,
+        subject=options.subject,
         pkcs12_info=pkcs12_info,
         ca_is_configured=ca_is_configured,
         ca_file=ca_file,
@@ -625,6 +625,12 @@ def install_check(installer):
         finally:
             shutil.rmtree(tmp_db_dir)
 
+    # TODO look up CA subject name (needed for DS certmap.conf)
+    #ipa_ca_nickname = get_ca_nickname(config.realm_name)
+    #db = certs.CertDB(config.realm_name, nssdir=paths.IPA_NSSDB_DIR)
+    #cert = db.get_cert_from_db(ipa_ca_nickname)
+    #options.subject = unicode(cert.subject)
+
     ldapuri = 'ldaps://%s' % ipautil.format_netloc(config.master_host_name)
     remote_api = create_api(mode=None)
     remote_api.bootstrap(in_server=True, context='installer',
@@ -717,7 +723,6 @@ def install_check(installer):
         if options.setup_ca:
             options.realm_name = config.realm_name
             options.host_name = config.host_name
-            options.subject = config.subject_base
             ca.install_check(False, config, options)
 
         if config.setup_kra:
@@ -1256,6 +1261,12 @@ def promote_check(installer):
         if subject_base is not None:
             config.subject_base = DN(subject_base)
 
+        # look up CA subject name (needed for DS certmap.conf)
+        ipa_ca_nickname = get_ca_nickname(config.realm_name)
+        db = certs.CertDB(config.realm_name, nssdir=paths.IPA_NSSDB_DIR)
+        cert = db.get_cert_from_db(ipa_ca_nickname)
+        options.subject = unicode(x509.load_certificate(cert).subject)
+
         # Find if any server has a CA
         ca_host = service.find_providing_server('CA', conn, api.env.server)
         if ca_host is not None:
@@ -1289,7 +1300,7 @@ def promote_check(installer):
 
             options.realm_name = config.realm_name
             options.host_name = config.host_name
-            options.subject = config.subject_base
+
             ca.install_check(False, None, options)
 
         if config.setup_kra:
@@ -1419,7 +1430,7 @@ def promote(installer):
 
         # Must install http certs before changing ipa configuration file
         # or certmonger will fail to contact the peer master
-        install_http_certs(config, fstore, remote_api)
+        install_http_certs(config, fstore, remote_api, config.subject_base)
 
         ntpinstance.ntp_ldap_enable(config.host_name, ds.suffix,
                                     remote_api.env.realm)
@@ -1507,7 +1518,7 @@ def promote(installer):
                                    host_name=config.host_name,
                                    dm_password=config.dirman_password)
         ca.configure_replica(config.ca_host_name,
-                             subject_base=config.subject_base,
+                             subject=options.subject,
                              ca_cert_bundle=ca_data)
 
     if options.setup_kra:
-- 
2.5.5

-- 
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