URL: https://github.com/freeipa/freeipa/pull/1126
Author: pvomacka
 Title: #1126: Backport PR 930 to ipa-4-6
Action: opened

PR body:
"""
This PR was opened automatically because PR #930 was pushed to master and 
backport to ipa-4-6 is required.
"""

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/1126/head:pr1126
git checkout pr1126
From 56080e3568174616dd932358b3df0cced85025fd Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Tue, 23 May 2017 19:32:24 +1000
Subject: [PATCH 01/10] cli: simplify parsing of arbitrary types

Add the 'constructor' type to IPAOption to allow parsing arbitrary
types.

When using this type, supply the 'constructor' attribute with the
constructor of the type.  The checker for the 'constructor' type
attempts to construct the data, returning if successful else raising
OptionValueError.

The 'knob' interface remains unchanged but now accepts arbitrary
constructors.

This feature subsumes the '_option_callback' mechanism, which has
been refactored away.

This feature also subsumes the "dn" type in IPAOption, but this
refactor is deferred.

Part of: https://pagure.io/freeipa/issue/6858
---
 ipapython/config.py      | 16 ++++++++++++++--
 ipapython/install/cli.py | 28 ++++------------------------
 2 files changed, 18 insertions(+), 26 deletions(-)

diff --git a/ipapython/config.py b/ipapython/config.py
index 6e53472e08..8393e0d5d5 100644
--- a/ipapython/config.py
+++ b/ipapython/config.py
@@ -79,16 +79,28 @@ def check_dn_option(option, opt, value):
     except Exception as e:
         raise OptionValueError("option %s: invalid DN: %s" % (opt, e))
 
+
+def check_constructor(option, opt, value):
+    con = option.constructor
+    assert con is not None, "Oops! Developer forgot to set 'constructor' kwarg"
+    try:
+        return con(value)
+    except Exception as e:
+        raise OptionValueError("option {} invalid: {}".format(opt, e))
+
+
 class IPAOption(Option):
     """
     optparse.Option subclass with support of options labeled as
     security-sensitive such as passwords.
     """
-    ATTRS = Option.ATTRS + ["sensitive"]
-    TYPES = Option.TYPES + ("ip", "dn")
+    ATTRS = Option.ATTRS + ["sensitive", "constructor"]
+    TYPES = Option.TYPES + ("ip", "dn", "constructor")
     TYPE_CHECKER = copy(Option.TYPE_CHECKER)
     TYPE_CHECKER["ip"] = check_ip_option
     TYPE_CHECKER["dn"] = check_dn_option
+    TYPE_CHECKER["constructor"] = check_constructor
+
 
 class IPAOptionParser(OptionParser):
     """
diff --git a/ipapython/install/cli.py b/ipapython/install/cli.py
index 9dff308d2f..1cac24d50e 100644
--- a/ipapython/install/cli.py
+++ b/ipapython/install/cli.py
@@ -8,7 +8,6 @@
 
 import collections
 import enum
-import functools
 import logging
 import optparse  # pylint: disable=deprecated-module
 import signal
@@ -105,17 +104,6 @@ def uninstall_tool(configurable_class, command_name, log_file_name,
     )
 
 
-def _option_callback(action, option, opt_str, value, parser, opt_type):
-    try:
-        value = opt_type(value)
-    except ValueError as e:
-        raise optparse.OptionValueError(
-            "option {0}: {1}".format(opt_str, e))
-
-    option.take_action(
-        action, option.dest, opt_str, value, parser.values, parser)
-
-
 class ConfigureTool(admintool.AdminTool):
     configurable_class = None
     debug_option = False
@@ -186,24 +174,16 @@ def add_options(cls, parser, positional=False):
                 kwargs['metavar'] = "{{{0}}}".format(
                                                 ",".join(kwargs['choices']))
             else:
-                kwargs['nargs'] = 1
-                kwargs['callback_args'] = (knob_scalar_type,)
+                kwargs['type'] = 'constructor'
+                kwargs['constructor'] = knob_scalar_type
             kwargs['dest'] = name
             if issubclass(knob_type, list):
-                if 'type' not in kwargs:
-                    kwargs['action'] = 'callback'
-                    kwargs['callback'] = (
-                        functools.partial(_option_callback, 'append'))
-                elif kwargs['type'] is None:
+                if kwargs['type'] is None:
                     kwargs['action'] = 'append_const'
                 else:
                     kwargs['action'] = 'append'
             else:
-                if 'type' not in kwargs:
-                    kwargs['action'] = 'callback'
-                    kwargs['callback'] = (
-                        functools.partial(_option_callback, 'store'))
-                elif kwargs['type'] is None:
+                if kwargs['type'] is None:
                     kwargs['action'] = 'store_const'
                 else:
                     kwargs['action'] = 'store'

From c90aa74e4cf830d6bfdd6c84574c3ed44d7196ee Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Thu, 18 May 2017 10:18:20 +1000
Subject: [PATCH 02/10] Remove duplicate references to external CA type

Part of: https://pagure.io/freeipa/issue/6858
---
 install/tools/ipa-ca-install           |  2 +-
 ipaserver/install/ca.py                |  7 +------
 ipaserver/install/cainstance.py        | 10 ++++++++--
 ipaserver/install/ipa_cacert_manage.py |  5 +++--
 4 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/install/tools/ipa-ca-install b/install/tools/ipa-ca-install
index fc485c5955..4579492891 100755
--- a/install/tools/ipa-ca-install
+++ b/install/tools/ipa-ca-install
@@ -64,7 +64,7 @@ def parse_options():
                       default=False, help="unattended installation never prompts the user")
     parser.add_option("--external-ca", dest="external_ca", action="store_true",
                       default=False, help="Generate a CSR to be signed by an external CA")
-    ext_cas = ("generic", "ms-cs")
+    ext_cas = tuple(x.value for x in cainstance.ExternalCAType)
     parser.add_option("--external-ca-type", dest="external_ca_type",
                       type="choice", choices=ext_cas,
                       metavar="{{{0}}}".format(",".join(ext_cas)),
diff --git a/ipaserver/install/ca.py b/ipaserver/install/ca.py
index 1f295f73a4..17b6befe27 100644
--- a/ipaserver/install/ca.py
+++ b/ipaserver/install/ca.py
@@ -363,11 +363,6 @@ def uninstall():
         ca_instance.uninstall()
 
 
-class ExternalCAType(enum.Enum):
-    GENERIC = 'generic'
-    MS_CS = 'ms-cs'
-
-
 class CASigningAlgorithm(enum.Enum):
     SHA1_WITH_RSA = 'SHA1withRSA'
     SHA_256_WITH_RSA = 'SHA256withRSA'
@@ -413,7 +408,7 @@ class CAInstallInterface(dogtag.DogtagInstallInterface,
     external_ca = master_install_only(external_ca)
 
     external_ca_type = knob(
-        ExternalCAType, None,
+        cainstance.ExternalCAType, None,
         description="Type of the external CA",
     )
     external_ca_type = master_install_only(external_ca_type)
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index 6b3ad3fb1b..b6fbd08ecc 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -25,6 +25,7 @@
 import logging
 
 import dbus
+import enum
 import ldap
 import os
 import pwd
@@ -95,6 +96,11 @@
 ]
 
 
+class ExternalCAType(enum.Enum):
+    GENERIC = 'generic'
+    MS_CS = 'ms-cs'
+
+
 def check_port():
     """
     Check that dogtag port (8443) is available.
@@ -353,7 +359,7 @@ def configure_instance(self, host_name, dm_password, admin_password,
         if ca_type is not None:
             self.ca_type = ca_type
         else:
-            self.ca_type = 'generic'
+            self.ca_type = ExternalCAType.GENERIC.value
         self.no_db_setup = promote
         self.use_ldaps = use_ldaps
 
@@ -565,7 +571,7 @@ def __spawn_instance(self):
             config.set("CA", "pki_external", "True")
             config.set("CA", "pki_external_csr_path", self.csr_file)
 
-            if self.ca_type == 'ms-cs':
+            if self.ca_type == ExternalCAType.MS_CS.value:
                 # Include MS template name extension in the CSR
                 config.set("CA", "pki_req_ext_add", "True")
                 config.set("CA", "pki_req_ext_oid", "1.3.6.1.4.1.311.20.2")
diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py
index 2d499c1f9b..b227d318bd 100644
--- a/ipaserver/install/ipa_cacert_manage.py
+++ b/ipaserver/install/ipa_cacert_manage.py
@@ -60,7 +60,7 @@ def add_options(cls, parser):
             "--self-signed", dest='self_signed',
             action='store_true',
             help="Sign the renewed certificate by itself")
-        ext_cas = ("generic", "ms-cs")
+        ext_cas = tuple(x.value for x in cainstance.ExternalCAType)
         renew_group.add_option(
             "--external-ca-type", dest="external_ca_type",
             type="choice", choices=ext_cas,
@@ -191,7 +191,8 @@ def renew_self_signed(self, ca):
     def renew_external_step_1(self, ca):
         print("Exporting CA certificate signing request, please wait")
 
-        if self.options.external_ca_type == 'ms-cs':
+        if self.options.external_ca_type \
+                == cainstance.ExternalCAType.MS_CS.value:
             profile = 'SubCA'
         else:
             profile = ''

From 08e51ee42f56ff9fc162e21e315dcbb8d5469126 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Wed, 24 May 2017 17:59:46 +1000
Subject: [PATCH 03/10] install: allow specifying external CA template

Allow the MS/AD-CS target certificate template to be specified by
name or OID, via the new option --external-ca-profile.

Part of: https://pagure.io/freeipa/issue/6858
---
 install/tools/man/ipa-server-install.1 |  21 +++-
 ipaserver/install/ca.py                |  28 +++++
 ipaserver/install/cainstance.py        | 181 ++++++++++++++++++++++++++++++++-
 ipaserver/install/server/__init__.py   |   5 +
 4 files changed, 231 insertions(+), 4 deletions(-)

diff --git a/install/tools/man/ipa-server-install.1 b/install/tools/man/ipa-server-install.1
index 3f46eba0f1..ca1857d1dc 100644
--- a/install/tools/man/ipa-server-install.1
+++ b/install/tools/man/ipa-server-install.1
@@ -87,7 +87,26 @@ The path to LDIF file that will be used to modify configuration of dse.ldif duri
 Generate a CSR for the IPA CA certificate to be signed by an external CA.
 .TP
 \fB\-\-external\-ca\-type\fR=\fITYPE\fR
-Type of the external CA. Possible values are "generic", "ms-cs". Default value is "generic". Use "ms-cs" to include template name required by Microsoft Certificate Services (MS CS) in the generated CSR.
+Type of the external CA. Possible values are "generic", "ms-cs". Default value is "generic". Use "ms-cs" to include the template name required by Microsoft Certificate Services (MS CS) in the generated CSR (see \fB\-\-external\-ca\-profile\fR for full details).
+
+.TP
+\fB\-\-external\-ca\-profile\fR=\fIPROFILE_SPEC\fR
+Specify the certificate profile or template to use at the external CA.
+
+When \fB\-\-external\-ca\-type\fR is "ms-cs" the following specifiers may be used:
+
+.RS
+.TP
+\fB<oid>:<majorVersion>[:<minorVersion>]\fR
+Specify a certificate template by OID and major version, optionally also specifying minor version.
+.TP
+\fB<name>\fR
+Specify a certificate template by name.  The name cannot contain any \fI:\fR characters and cannot be an OID (otherwise the OID-based template specifier syntax takes precedence).
+.TP
+\fBdefault\fR
+If no template is specified, the template name "SubCA" is used.
+.RE
+
 .TP
 \fB\-\-external\-cert\-file\fR=\fIFILE\fR
 File containing the IPA CA certificate and the external CA certificate chain. The file is accepted in PEM and DER certificate and PKCS#7 certificate chain formats. This option may be used multiple times.
diff --git a/ipaserver/install/ca.py b/ipaserver/install/ca.py
index 17b6befe27..5647651701 100644
--- a/ipaserver/install/ca.py
+++ b/ipaserver/install/ca.py
@@ -174,6 +174,22 @@ def install_check(standalone, replica_config, options):
                 "remove the file and run the installer again." %
                 paths.ROOT_IPA_CSR)
 
+        if not options.external_ca_type:
+            options.external_ca_type = \
+                cainstance.ExternalCAType.GENERIC.value
+
+        if options.external_ca_profile is not None:
+            # check that profile is valid for the external ca type
+            if options.external_ca_type \
+                    not in options.external_ca_profile.valid_for:
+                raise ScriptError(
+                    "External CA profile specification '{}' "
+                    "cannot be used with external CA type '{}'."
+                    .format(
+                        options.external_ca_profile.unparsed_input,
+                        options.external_ca_type)
+                    )
+
     if not options.external_cert_files:
         if not cainstance.check_port():
             print("IPA requires port 8443 for PKI but it is currently in use.")
@@ -217,11 +233,13 @@ def install_step_0(standalone, replica_config, options):
     host_name = options.host_name
     ca_subject = options._ca_subject
     subject_base = options._subject_base
+    external_ca_profile = None
 
     if replica_config is None:
         ca_signing_algorithm = options.ca_signing_algorithm
         if options.external_ca:
             ca_type = options.external_ca_type
+            external_ca_profile = options.external_ca_profile
             csr_file = paths.ROOT_IPA_CSR
         else:
             ca_type = None
@@ -277,6 +295,7 @@ def install_step_0(standalone, replica_config, options):
                           ca_subject=ca_subject,
                           ca_signing_algorithm=ca_signing_algorithm,
                           ca_type=ca_type,
+                          external_ca_profile=external_ca_profile,
                           csr_file=csr_file,
                           cert_file=cert_file,
                           cert_chain_file=cert_chain_file,
@@ -413,6 +432,15 @@ class CAInstallInterface(dogtag.DogtagInstallInterface,
     )
     external_ca_type = master_install_only(external_ca_type)
 
+    external_ca_profile = knob(
+        type=cainstance.ExternalCAProfile,
+        default=None,
+        description=(
+            "Specify the certificate profile/template to use at the "
+            "external CA"),
+    )
+    external_ca_profile = master_install_only(external_ca_profile)
+
     external_cert_files = knob(
         # pylint: disable=invalid-sequence-index
         typing.List[str], None,
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index b6fbd08ecc..4a6909a254 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -22,6 +22,7 @@
 from __future__ import print_function
 
 import base64
+import binascii
 import logging
 
 import dbus
@@ -41,6 +42,9 @@
 # pylint: disable=import-error
 from six.moves.configparser import RawConfigParser
 # pylint: enable=import-error
+from pyasn1.codec.der import encoder
+from pyasn1.type import char, univ, namedtype
+import pyasn1.error
 
 from ipalib import api
 from ipalib import x509
@@ -324,7 +328,8 @@ def configure_instance(self, host_name, dm_password, admin_password,
                            master_replication_port=None,
                            subject_base=None, ca_subject=None,
                            ca_signing_algorithm=None,
-                           ca_type=None, ra_p12=None, ra_only=False,
+                           ca_type=None, external_ca_profile=None,
+                           ra_p12=None, ra_only=False,
                            promote=False, use_ldaps=False):
         """Create a CA instance.
 
@@ -360,6 +365,8 @@ def configure_instance(self, host_name, dm_password, admin_password,
             self.ca_type = ca_type
         else:
             self.ca_type = ExternalCAType.GENERIC.value
+        self.external_ca_profile = external_ca_profile
+
         self.no_db_setup = promote
         self.use_ldaps = use_ldaps
 
@@ -573,10 +580,16 @@ def __spawn_instance(self):
 
             if self.ca_type == ExternalCAType.MS_CS.value:
                 # Include MS template name extension in the CSR
+                template = self.external_ca_profile
+                if template is None:
+                    # default template name
+                    template = MSCSTemplateV1(u"SubCA")
+
+                ext_data = binascii.hexlify(template.get_ext_data())
                 config.set("CA", "pki_req_ext_add", "True")
-                config.set("CA", "pki_req_ext_oid", "1.3.6.1.4.1.311.20.2")
+                config.set("CA", "pki_req_ext_oid", template.ext_oid)
                 config.set("CA", "pki_req_ext_critical", "False")
-                config.set("CA", "pki_req_ext_data", "1E0A00530075006200430041")
+                config.set("CA", "pki_req_ext_data", ext_data.decode('ascii'))
 
         elif self.external == 2:
             cert_file = tempfile.NamedTemporaryFile()
@@ -1879,6 +1892,168 @@ def update_ipa_conf():
         parser.write(f)
 
 
+class ExternalCAProfile(object):
+    """
+    An external CA profile configuration.  Currently the only
+    subclasses are for Microsoft CAs, for providing data in the
+    "Certificate Template" extension.
+
+    Constructing this class will actually return an instance of a
+    subclass.
+
+    Subclasses MUST set ``valid_for``.
+
+    """
+    def __init__(self, s=None):
+        self.unparsed_input = s
+
+    # Which external CA types is the data valid for?
+    # A set of VALUES of the ExternalCAType enum.
+    valid_for = set()
+
+    def __new__(cls, s=None):
+        """Construct the ExternalCAProfile value.
+
+        Return an instance of a subclass determined by
+        the format of the argument.
+
+        """
+        # we are directly constructing a subclass; instantiate
+        # it and be done
+        if cls is not ExternalCAProfile:
+            return super(ExternalCAProfile, cls).__new__(cls)
+
+        # construction via the base class; therefore the string
+        # argument is required, and is used to determine which
+        # subclass to construct
+        if s is None:
+            raise ValueError('string argument is required')
+
+        parts = s.split(':')
+
+        try:
+            # Is the first part on OID?
+            _oid = univ.ObjectIdentifier(parts[0])
+
+            # It is; construct a V2 template
+            return MSCSTemplateV2.__new__(MSCSTemplateV2, s)
+
+        except pyasn1.error.PyAsn1Error:
+            # It is not an OID; treat as a template name
+            return MSCSTemplateV1.__new__(MSCSTemplateV1, s)
+
+    def __getstate__(self):
+        return self.unparsed_input
+
+    def __setstate__(self, state):
+        # explicitly call __init__ method to initialise object
+        self.__init__(state)
+
+
+class MSCSTemplate(ExternalCAProfile):
+    """
+    An Microsoft AD-CS Template specifier.
+
+    Subclasses MUST set ext_oid.
+
+    Subclass constructors MUST set asn1obj.
+
+    """
+    valid_for = set([ExternalCAType.MS_CS.value])
+
+    ext_oid = None  # extension OID, as a Python str
+    asn1obj = None  # unencoded extension data
+
+    def get_ext_data(self):
+        """Return DER-encoded extension data."""
+        return encoder.encode(self.asn1obj)
+
+
+class MSCSTemplateV1(MSCSTemplate):
+    """
+    A v1 template specifier, per
+    https://msdn.microsoft.com/en-us/library/cc250011.aspx.
+
+    ::
+
+        CertificateTemplateName ::= SEQUENCE {
+           Name            UTF8String
+        }
+
+    But note that a bare BMPString is used in practice.
+
+    """
+    ext_oid = "1.3.6.1.4.1.311.20.2"
+
+    def __init__(self, s):
+        super(MSCSTemplateV1, self).__init__(s)
+        parts = s.split(':')
+        if len(parts) > 1:
+            raise ValueError(
+                "Cannot specify certificate template version when using name.")
+        self.asn1obj = char.BMPString(six.text_type(parts[0]))
+
+
+class MSCSTemplateV2(MSCSTemplate):
+    """
+    A v2 template specifier, per
+    https://msdn.microsoft.com/en-us/library/windows/desktop/aa378274(v=vs.85).aspx
+
+    ::
+
+        CertificateTemplate ::= SEQUENCE {
+            templateID              EncodedObjectID,
+            templateMajorVersion    TemplateVersion,
+            templateMinorVersion    TemplateVersion OPTIONAL
+        }
+
+        TemplateVersion ::= INTEGER (0..4294967295)
+
+    """
+    ext_oid = "1.3.6.1.4.1.311.21.7"
+
+    @staticmethod
+    def check_version_in_range(desc, n):
+        if n < 0 or n >= 2**32:
+            raise ValueError(
+                "Template {} version must be in range 0..4294967295"
+                .format(desc))
+
+    def __init__(self, s):
+        super(MSCSTemplateV2, self).__init__(s)
+
+        parts = s.split(':')
+
+        obj = CertificateTemplateV2()
+        if len(parts) < 2 or len(parts) > 3:
+            raise ValueError(
+                "Incorrect template specification; required format is: "
+                "<oid>:<majorVersion>[:<minorVersion>]")
+        try:
+            obj['templateID'] = univ.ObjectIdentifier(parts[0])
+
+            major = int(parts[1])
+            self.check_version_in_range("major", major)
+            obj['templateMajorVersion'] = major
+
+            if len(parts) > 2:
+                minor = int(parts[2])
+                self.check_version_in_range("minor", minor)
+                obj['templateMinorVersion'] = int(parts[2])
+
+        except pyasn1.error.PyAsn1Error:
+            raise ValueError("Could not parse certificate template specifier.")
+        self.asn1obj = obj
+
+
+class CertificateTemplateV2(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('templateID', univ.ObjectIdentifier()),
+        namedtype.NamedType('templateMajorVersion', univ.Integer()),
+        namedtype.OptionalNamedType('templateMinorVersion', univ.Integer())
+    )
+
+
 if __name__ == "__main__":
     standard_logging_setup("install.log")
     ds = dsinstance.DsInstance()
diff --git a/ipaserver/install/server/__init__.py b/ipaserver/install/server/__init__.py
index 028a4aa60f..fe5555b037 100644
--- a/ipaserver/install/server/__init__.py
+++ b/ipaserver/install/server/__init__.py
@@ -437,6 +437,11 @@ def __init__(self, **kwargs):
                     "You cannot specify --external-ca-type without "
                     "--external-ca")
 
+            if self.external_ca_profile and not self.external_ca:
+                raise RuntimeError(
+                    "You cannot specify --external-ca-profile without "
+                    "--external-ca")
+
             if self.uninstalling:
                 if (self.realm_name or self.admin_password or
                         self.master_password):

From cc98c771f5491325eaba3267612cd7005963586d Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Tue, 25 Jul 2017 17:03:36 +1000
Subject: [PATCH 04/10] ipa-ca-install: add --external-ca-profile option

Fixes: https://pagure.io/freeipa/issue/6858
---
 install/tools/ipa-ca-install       | 10 ++++++++++
 install/tools/man/ipa-ca-install.1 | 21 ++++++++++++++++++++-
 2 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/install/tools/ipa-ca-install b/install/tools/ipa-ca-install
index 4579492891..3bdd7634dc 100755
--- a/install/tools/ipa-ca-install
+++ b/install/tools/ipa-ca-install
@@ -69,6 +69,11 @@ def parse_options():
                       type="choice", choices=ext_cas,
                       metavar="{{{0}}}".format(",".join(ext_cas)),
                       help="Type of the external CA. Default: generic")
+    parser.add_option("--external-ca-profile", dest="external_ca_profile",
+                      type='constructor', constructor=cainstance.ExternalCAProfile,
+                      default=None, metavar="PROFILE-SPEC",
+                      help="Specify the certificate profile/template to use "
+                           "at the external CA")
     parser.add_option("--external-cert-file", dest="external_cert_files",
                       action="append", metavar="FILE",
                       help="File containing the IPA CA certificate and the external CA certificate chain")
@@ -116,6 +121,11 @@ def parse_options():
             parser.error(
                 "You cannot specify --external-ca-type without --external-ca")
 
+        if options.external_ca_profile and not options.external_ca:
+            parser.error(
+                "You cannot specify --external-ca-profile "
+                "without --external-ca")
+
     return safe_options, options, filename
 
 
diff --git a/install/tools/man/ipa-ca-install.1 b/install/tools/man/ipa-ca-install.1
index 79703a47c0..99ff918789 100644
--- a/install/tools/man/ipa-ca-install.1
+++ b/install/tools/man/ipa-ca-install.1
@@ -48,7 +48,26 @@ Admin user Kerberos password used for connection check
 Generate a CSR for the IPA CA certificate to be signed by an external CA.
 .TP
 \fB\-\-external\-ca\-type\fR=\fITYPE\fR
-Type of the external CA. Possible values are "generic", "ms-cs". Default value is "generic". Use "ms-cs" to include template name required by Microsoft Certificate Services (MS CS) in the generated CSR.
+Type of the external CA. Possible values are "generic", "ms-cs". Default value is "generic". Use "ms-cs" to include the template name required by Microsoft Certificate Services (MS CS) in the generated CSR (see \fB\-\-external\-ca\-profile\fR for full details).
+
+.TP
+\fB\-\-external\-ca\-profile\fR=\fIPROFILE_SPEC\fR
+Specify the certificate profile or template to use at the external CA.
+
+When \fB\-\-external\-ca\-type\fR is "ms-cs" the following specifiers may be used:
+
+.RS
+.TP
+\fB<oid>:<majorVersion>[:<minorVersion>]\fR
+Specify a certificate template by OID and major version, optionally also specifying minor version.
+.TP
+\fB<name>\fR
+Specify a certificate template by name.  The name cannot contain any \fI:\fR characters and cannot be an OID (otherwise the OID-based template specifier syntax takes precedence).
+.TP
+\fBdefault\fR
+If no template is specified, the template name "SubCA" is used.
+.RE
+
 .TP
 \fB\-\-external\-cert\-file\fR=\fIFILE\fR
 File containing the IPA CA certificate and the external CA certificate chain. The file is accepted in PEM and DER certificate and PKCS#7 certificate chain formats. This option may be used multiple times.

From c5247edd308f17519116ce3be0e414edff789072 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Tue, 22 Aug 2017 15:39:21 +1000
Subject: [PATCH 05/10] certmonger: refactor 'resubmit_request' and 'modify'

certmonger.resubmit_request() and .modify() contain a redundant if
statement that means more lines of code must be changed when adding
or removing a function argument.  Perform a small refactor to
improve these functions.

Part of: https://pagure.io/freeipa/issue/6858
---
 ipalib/install/certmonger.py | 35 ++++++++++++++++++-----------------
 1 file changed, 18 insertions(+), 17 deletions(-)

diff --git a/ipalib/install/certmonger.py b/ipalib/install/certmonger.py
index 7ceeee3f16..2c37899af7 100644
--- a/ipalib/install/certmonger.py
+++ b/ipalib/install/certmonger.py
@@ -508,14 +508,14 @@ def stop_tracking(secdir=None, request_id=None, nickname=None, certfile=None):
 
 
 def modify(request_id, ca=None, profile=None):
-    if ca or profile:
+    update = {}
+    if ca is not None:
+        cm = _certmonger()
+        update['CA'] = cm.obj_if.find_ca_by_nickname(ca)
+    if profile is not None:
+        update['template-profile'] = profile
+    if len(update) > 0:
         request = _get_request({'nickname': request_id})
-        update = {}
-        if ca is not None:
-            cm = _certmonger()
-            update['CA'] = cm.obj_if.find_ca_by_nickname(ca)
-        if profile is not None:
-            update['template-profile'] = profile
         request.obj_if.modify(update)
 
 
@@ -528,16 +528,17 @@ def resubmit_request(request_id, ca=None, profile=None, is_ca=False):
     """
     request = _get_request({'nickname': request_id})
     if request:
-        if ca or profile or is_ca:
-            update = {}
-            if ca is not None:
-                cm = _certmonger()
-                update['CA'] = cm.obj_if.find_ca_by_nickname(ca)
-            if profile is not None:
-                update['template-profile'] = profile
-            if is_ca:
-                update['template-is-ca'] = True
-                update['template-ca-path-length'] = -1  # no path length
+        update = {}
+        if ca is not None:
+            cm = _certmonger()
+            update['CA'] = cm.obj_if.find_ca_by_nickname(ca)
+        if profile is not None:
+            update['template-profile'] = profile
+        if is_ca:
+            update['template-is-ca'] = True
+            update['template-ca-path-length'] = -1  # no path length
+
+        if len(update) > 0:
             request.obj_if.modify(update)
         request.obj_if.resubmit()
 

From bef18583e0f5bf0ca207d26c36b0b1158b26d01e Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Tue, 22 Aug 2017 15:39:53 +1000
Subject: [PATCH 06/10] certmonger: add support for MS V2 template

Update certmonger.resubmit_request() and .modify() to support
specifying the Microsoft V2 certificate template extension.

This feature was introduced in certmonger-0.79.5 so bump the minimum
version in the spec file.

Part of: https://pagure.io/freeipa/issue/6858
---
 freeipa.spec.in              |  6 ++----
 ipalib/install/certmonger.py | 21 ++++++++++++++++++---
 2 files changed, 20 insertions(+), 7 deletions(-)

diff --git a/freeipa.spec.in b/freeipa.spec.in
index 6d992ba151..8b7f179da4 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -320,8 +320,7 @@ Requires(preun): python systemd-units
 Requires(postun): python systemd-units
 Requires: policycoreutils >= 2.1.12-5
 Requires: tar
-# certmonger-0.79.4-2 fixes newlines in PEM files
-Requires(pre): certmonger >= 0.79.4-2
+Requires(pre): certmonger >= 0.79.5-1
 Requires(pre): 389-ds-base >= 1.3.5.14
 Requires: fontawesome-fonts
 Requires: open-sans-fonts
@@ -540,8 +539,7 @@ Requires: libcurl >= 7.21.7-2
 Requires: xmlrpc-c >= 1.27.4
 Requires: sssd >= 1.14.0
 Requires: python-sssdconfig
-# certmonger-0.79.4-2 fixes newlines in PEM files
-Requires: certmonger >= 0.79.4-2
+Requires: certmonger >= 0.79.5-1
 Requires: nss-tools
 Requires: bind-utils
 Requires: oddjob-mkhomedir
diff --git a/ipalib/install/certmonger.py b/ipalib/install/certmonger.py
index 2c37899af7..e52005c2a6 100644
--- a/ipalib/install/certmonger.py
+++ b/ipalib/install/certmonger.py
@@ -507,23 +507,36 @@ def stop_tracking(secdir=None, request_id=None, nickname=None, certfile=None):
         request.parent.obj_if.remove_request(request.path)
 
 
-def modify(request_id, ca=None, profile=None):
+def modify(request_id, ca=None, profile=None, template_v2=None):
     update = {}
     if ca is not None:
         cm = _certmonger()
         update['CA'] = cm.obj_if.find_ca_by_nickname(ca)
     if profile is not None:
         update['template-profile'] = profile
+    if template_v2 is not None:
+        update['template-ms-certificate-template'] = template_v2
+
     if len(update) > 0:
         request = _get_request({'nickname': request_id})
         request.obj_if.modify(update)
 
 
-def resubmit_request(request_id, ca=None, profile=None, is_ca=False):
+def resubmit_request(
+        request_id,
+        ca=None,
+        profile=None,
+        template_v2=None,
+        is_ca=False):
     """
     :param request_id: the certmonger numeric request ID
     :param ca: the nickname for the certmonger CA, e.g. IPA or SelfSign
-    :param profile: the dogtag template profile to use, e.g. SubCA
+    :param profile: the profile to use, e.g. SubCA.  For requests using the
+                    Dogtag CA, this is the profile to use.  This also causes
+                    the Microsoft certificate tempalte name extension to the
+                    CSR (for telling AD CS what template to use).
+    :param template_v2: Microsoft V2 template specifier extension value.
+                        Format: <oid>:<major-version>[:<minor-version>]
     :param is_ca: boolean that if True adds the CA basic constraint
     """
     request = _get_request({'nickname': request_id})
@@ -534,6 +547,8 @@ def resubmit_request(request_id, ca=None, profile=None, is_ca=False):
             update['CA'] = cm.obj_if.find_ca_by_nickname(ca)
         if profile is not None:
             update['template-profile'] = profile
+        if template_v2 is not None:
+            update['template-ms-certificate-template'] = template_v2
         if is_ca:
             update['template-is-ca'] = True
             update['template-ca-path-length'] = -1  # no path length

From 27da63d1196fc880d9798262514c388246511f71 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Tue, 22 Aug 2017 15:40:00 +1000
Subject: [PATCH 07/10] ipa-cacert-manage: support MS V2 template extension

Update ipa-cacert-manage to support the MS V2 certificate template
extension.

Part of: https://pagure.io/freeipa/issue/6858
---
 install/tools/man/ipa-cacert-manage.1  | 21 +++++++++++-
 ipaserver/install/ipa_cacert_manage.py | 63 +++++++++++++++++++++++++++-------
 2 files changed, 70 insertions(+), 14 deletions(-)

diff --git a/install/tools/man/ipa-cacert-manage.1 b/install/tools/man/ipa-cacert-manage.1
index 03172814ff..bacd56b5a8 100644
--- a/install/tools/man/ipa-cacert-manage.1
+++ b/install/tools/man/ipa-cacert-manage.1
@@ -79,7 +79,26 @@ Sign the renewed certificate by itself.
 Sign the renewed certificate by external CA.
 .TP
 \fB\-\-external\-ca\-type\fR=\fITYPE\fR
-Type of the external CA. Possible values are "generic", "ms-cs". Default value is "generic". Use "ms-cs" to include template name required by Microsoft Certificate Services (MS CS) in the generated CSR.
+Type of the external CA. Possible values are "generic", "ms-cs". Default value is "generic". Use "ms-cs" to include the template name required by Microsoft Certificate Services (MS CS) in the generated CSR (see \fB\-\-external\-ca\-profile\fR for full details).
+
+.TP
+\fB\-\-external\-ca\-profile\fR=\fIPROFILE_SPEC\fR
+Specify the certificate profile or template to use at the external CA.
+
+When \fB\-\-external\-ca\-type\fR is "ms-cs" the following specifiers may be used:
+
+.RS
+.TP
+\fB<oid>:<majorVersion>[:<minorVersion>]\fR
+Specify a certificate template by OID and major version, optionally also specifying minor version.
+.TP
+\fB<name>\fR
+Specify a certificate template by name.  The name cannot contain any \fI:\fR characters and cannot be an OID (otherwise the OID-based template specifier syntax takes precedence).
+.TP
+\fBdefault\fR
+If no template is specified, the template name "SubCA" is used.
+.RE
+
 .TP
 \fB\-\-external\-cert\-file\fR=\fIFILE\fR
 File containing the IPA CA certificate and the external CA certificate chain. The file is accepted in PEM and DER certificate and PKCS#7 certificate chain formats. This option may be used multiple times.
diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py
index b227d318bd..bff1678b05 100644
--- a/ipaserver/install/ipa_cacert_manage.py
+++ b/ipaserver/install/ipa_cacert_manage.py
@@ -60,6 +60,10 @@ def add_options(cls, parser):
             "--self-signed", dest='self_signed',
             action='store_true',
             help="Sign the renewed certificate by itself")
+        renew_group.add_option(
+            "--external-ca", dest='self_signed',
+            action='store_false',
+            help="Sign the renewed certificate by external CA")
         ext_cas = tuple(x.value for x in cainstance.ExternalCAType)
         renew_group.add_option(
             "--external-ca-type", dest="external_ca_type",
@@ -67,9 +71,11 @@ def add_options(cls, parser):
             metavar="{{{0}}}".format(",".join(ext_cas)),
             help="Type of the external CA. Default: generic")
         renew_group.add_option(
-            "--external-ca", dest='self_signed',
-            action='store_false',
-            help="Sign the renewed certificate by external CA")
+            "--external-ca-profile", dest="external_ca_profile",
+            type='constructor', constructor=cainstance.ExternalCAProfile,
+            default=None, metavar="PROFILE-SPEC",
+            help="Specify the certificate profile/template to use "
+                 "at the external CA")
         renew_group.add_option(
             "--external-cert-file", dest="external_cert_files",
             action="append", metavar="FILE",
@@ -179,6 +185,12 @@ def renew(self):
     def renew_self_signed(self, ca):
         print("Renewing CA certificate, please wait")
 
+        msg = "You cannot specify {} when renewing a self-signed CA"
+        if self.options.external_ca_type:
+            raise admintool.ScriptError(msg.format("--external-ca-type"))
+        if self.options.external_ca_profile:
+            raise admintool.ScriptError(msg.format("--external-ca-profile"))
+
         try:
             ca.set_renewal_master()
         except errors.NotFound:
@@ -191,13 +203,30 @@ def renew_self_signed(self, ca):
     def renew_external_step_1(self, ca):
         print("Exporting CA certificate signing request, please wait")
 
-        if self.options.external_ca_type \
-                == cainstance.ExternalCAType.MS_CS.value:
-            profile = 'SubCA'
-        else:
-            profile = ''
+        options = self.options
+
+        if not options.external_ca_type:
+            options.external_ca_type = cainstance.ExternalCAType.GENERIC.value
+
+        if options.external_ca_type == cainstance.ExternalCAType.MS_CS.value \
+                and options.external_ca_profile is None:
+            options.external_ca_profile = cainstance.MSCSTemplateV1(u"SubCA")
 
-        self.resubmit_request('dogtag-ipa-ca-renew-agent-reuse', profile)
+        if options.external_ca_profile is not None:
+            # check that profile is valid for the external ca type
+            if options.external_ca_type \
+                    not in options.external_ca_profile.valid_for:
+                raise admintool.ScriptError(
+                    "External CA profile specification '{}' "
+                    "cannot be used with external CA type '{}'."
+                    .format(
+                        options.external_ca_profile.unparsed_input,
+                        options.external_ca_type)
+                    )
+
+        self.resubmit_request(
+            'dogtag-ipa-ca-renew-agent-reuse',
+            profile=options.external_ca_profile)
 
         print(("The next step is to get %s signed by your CA and re-run "
               "ipa-cacert-manage as:" % paths.IPA_CA_CSR))
@@ -299,12 +328,20 @@ def renew_external_step_2(self, ca, old_cert):
 
         print("CA certificate successfully renewed")
 
-    def resubmit_request(self, ca='dogtag-ipa-ca-renew-agent', profile=''):
+    def resubmit_request(self, ca='dogtag-ipa-ca-renew-agent', profile=None):
         timeout = api.env.startup_timeout + 60
 
+        cm_profile = None
+        if isinstance(profile, cainstance.MSCSTemplateV1):
+            cm_profile = profile.unparsed_input
+
+        cm_template = None
+        if isinstance(profile, cainstance.MSCSTemplateV2):
+            cm_template = profile.unparsed_input
+
         logger.debug("resubmitting certmonger request '%s'", self.request_id)
-        certmonger.resubmit_request(self.request_id, ca=ca, profile=profile,
-                                    is_ca=True)
+        certmonger.resubmit_request(self.request_id, ca=ca, profile=cm_profile,
+                                    template_v2=cm_template, is_ca=True)
         try:
             state = certmonger.wait_for_request(self.request_id, timeout)
         except RuntimeError:
@@ -320,7 +357,7 @@ def resubmit_request(self, ca='dogtag-ipa-ca-renew-agent', profile=''):
         logger.debug("modifying certmonger request '%s'", self.request_id)
         certmonger.modify(self.request_id,
                           ca='dogtag-ipa-ca-renew-agent',
-                          profile='')
+                          profile='', template_v2='')
 
     def install(self):
         print("Installing CA certificate, please wait")

From ba2feafb5d7049d5c8823465eb2a0ed553ea94eb Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Mon, 25 Sep 2017 11:39:51 +1000
Subject: [PATCH 08/10] Add tests for external CA profile specifiers

Part of: https://pagure.io/freeipa/issue/6858
---
 .../test_ipaserver/test_install/test_cainstance.py | 125 +++++++++++++++++++++
 1 file changed, 125 insertions(+)
 create mode 100644 ipatests/test_ipaserver/test_install/test_cainstance.py

diff --git a/ipatests/test_ipaserver/test_install/test_cainstance.py b/ipatests/test_ipaserver/test_install/test_cainstance.py
new file mode 100644
index 0000000000..7af474df41
--- /dev/null
+++ b/ipatests/test_ipaserver/test_install/test_cainstance.py
@@ -0,0 +1,125 @@
+#
+# Copyright (C) 2017  FreeIPA Contributors see COPYING for license
+#
+
+from binascii import hexlify
+import pickle
+# pylint: disable=import-error
+from six.moves.configparser import RawConfigParser
+# pylint: enable=import-error
+from six import StringIO
+import pytest
+from ipaserver.install import cainstance
+
+pytestmark = pytest.mark.tier0
+
+
+class test_ExternalCAProfile(object):
+    def test_MSCSTemplateV1_good(self):
+        o = cainstance.MSCSTemplateV1("MySubCA")
+        assert hexlify(o.get_ext_data()) == b'1e0e004d007900530075006200430041'
+
+    def test_MSCSTemplateV1_bad(self):
+        with pytest.raises(ValueError):
+            cainstance.MSCSTemplateV1("MySubCA:1")
+
+    def test_MSCSTemplateV1_pickle_roundtrip(self):
+        o = cainstance.MSCSTemplateV1("MySubCA")
+        s = pickle.dumps(o)
+        assert o.get_ext_data() == pickle.loads(s).get_ext_data()
+
+    def test_MSCSTemplateV2_too_few_parts(self):
+        with pytest.raises(ValueError):
+            cainstance.MSCSTemplateV2("1.2.3.4")
+
+    def test_MSCSTemplateV2_too_many_parts(self):
+        with pytest.raises(ValueError):
+            cainstance.MSCSTemplateV2("1.2.3.4:100:200:300")
+
+    def test_MSCSTemplateV2_bad_oid(self):
+        with pytest.raises(ValueError):
+            cainstance.MSCSTemplateV2("not_an_oid:1")
+
+    def test_MSCSTemplateV2_non_numeric_major_version(self):
+        with pytest.raises(ValueError):
+            cainstance.MSCSTemplateV2("1.2.3.4:major:200")
+
+    def test_MSCSTemplateV2_non_numeric_minor_version(self):
+        with pytest.raises(ValueError):
+            cainstance.MSCSTemplateV2("1.2.3.4:100:minor")
+
+    def test_MSCSTemplateV2_major_version_lt_zero(self):
+        with pytest.raises(ValueError):
+            cainstance.MSCSTemplateV2("1.2.3.4:-1:200")
+
+    def test_MSCSTemplateV2_minor_version_lt_zero(self):
+        with pytest.raises(ValueError):
+            cainstance.MSCSTemplateV2("1.2.3.4:100:-1")
+
+    def test_MSCSTemplateV2_major_version_gt_max(self):
+        with pytest.raises(ValueError):
+            cainstance.MSCSTemplateV2("1.2.3.4:4294967296:200")
+
+    def test_MSCSTemplateV2_minor_version_gt_max(self):
+        with pytest.raises(ValueError):
+            cainstance.MSCSTemplateV2("1.2.3.4:100:4294967296")
+
+    def test_MSCSTemplateV2_good_major(self):
+        o = cainstance.MSCSTemplateV2("1.2.3.4:4294967295")
+        assert hexlify(o.get_ext_data()) == b'300c06032a0304020500ffffffff'
+
+    def test_MSCSTemplateV2_good_major_minor(self):
+        o = cainstance.MSCSTemplateV2("1.2.3.4:4294967295:0")
+        assert hexlify(o.get_ext_data()) \
+            == b'300f06032a0304020500ffffffff020100'
+
+    def test_MSCSTemplateV2_pickle_roundtrip(self):
+        o = cainstance.MSCSTemplateV2("1.2.3.4:4294967295:0")
+        s = pickle.dumps(o)
+        assert o.get_ext_data() == pickle.loads(s).get_ext_data()
+
+    def test_ExternalCAProfile_dispatch(self):
+        """
+        Test that constructing ExternalCAProfile actually returns an
+        instance of the appropriate subclass.
+        """
+        assert isinstance(
+            cainstance.ExternalCAProfile("MySubCA"),
+            cainstance.MSCSTemplateV1)
+        assert isinstance(
+            cainstance.ExternalCAProfile("1.2.3.4:100"),
+            cainstance.MSCSTemplateV2)
+
+    def test_write_pkispawn_config_file_MSCSTemplateV1(self):
+        template = cainstance.MSCSTemplateV1(u"SubCA")
+        expected = (
+            '[CA]\n'
+            'pki_req_ext_oid = 1.3.6.1.4.1.311.20.2\n'
+            'pki_req_ext_data = 1e0a00530075006200430041\n\n'
+        )
+        self._test_write_pkispawn_config_file(template, expected)
+
+    def test_write_pkispawn_config_file_MSCSTemplateV2(self):
+        template = cainstance.MSCSTemplateV2(u"1.2.3.4:4294967295")
+        expected = (
+            '[CA]\n'
+            'pki_req_ext_oid = 1.3.6.1.4.1.311.21.7\n'
+            'pki_req_ext_data = 300c06032a0304020500ffffffff\n\n'
+        )
+        self._test_write_pkispawn_config_file(template, expected)
+
+    def _test_write_pkispawn_config_file(self, template, expected):
+        """
+        Test that the values we read from an ExternalCAProfile
+        object can be used to produce a reasonable-looking pkispawn
+        configuration.
+        """
+        config = RawConfigParser()
+        config.optionxform = str
+        config.add_section("CA")
+        config.set("CA", "pki_req_ext_oid", template.ext_oid)
+        config.set("CA", "pki_req_ext_data",
+                   hexlify(template.get_ext_data()).decode('ascii'))
+        out = StringIO()
+        config.write(out)
+        assert out.getvalue() == expected

From af89a79a8368dcadb7738f23f0a625262adbeebf Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Mon, 25 Sep 2017 17:11:46 +1000
Subject: [PATCH 09/10] ipa-cacert-manage: handle alternative tracking request
 CA name

For an externally-signed CA, if an earlier run of ipa-cacert-manage
was interrupted, the CA name in the IPA CA tracking request may have
been left as "dogtag-ipa-ca-renew-agent-reuse" (it gets reverted to
"dogtag-ipa-ca-renew-agent" at the end of the CSR generation
procedure).  `ipa-cacert-manage renew` currently only looks for a
tracking request with the "dogtag-ipa-ca-renew-agent" CA, so in this
scenario the program fails with message "CA certificate is not
tracked by certmonger".

To handle this scenario, if the IPA CA tracking request is not
found, try once again but with the "dogtag-ipa-ca-renew-agent-renew"
CA name.

Part of: https://pagure.io/freeipa/issue/6858
---
 ipaserver/install/ipa_cacert_manage.py | 26 ++++++++++++++++++--------
 1 file changed, 18 insertions(+), 8 deletions(-)

diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py
index bff1678b05..f764638c71 100644
--- a/ipaserver/install/ipa_cacert_manage.py
+++ b/ipaserver/install/ipa_cacert_manage.py
@@ -148,20 +148,30 @@ def ldap_connect(self):
 
         api.Backend.ldap2.connect(bind_pw=password)
 
+    def _get_ca_request_id(self, ca_name):
+        """Lookup tracking request for IPA CA, using given ca-name."""
+        criteria = {
+            'cert-database': paths.PKI_TOMCAT_ALIAS_DIR,
+            'cert-nickname': self.cert_nickname,
+            'ca-name': ca_name,
+        }
+        return certmonger.get_request_id(criteria)
+
     def renew(self):
         ca = cainstance.CAInstance(api.env.realm)
         if not ca.is_configured():
             raise admintool.ScriptError("CA is not configured on this system")
 
-        criteria = {
-            'cert-database': paths.PKI_TOMCAT_ALIAS_DIR,
-            'cert-nickname': self.cert_nickname,
-            'ca-name': 'dogtag-ipa-ca-renew-agent',
-        }
-        self.request_id = certmonger.get_request_id(criteria)
+        self.request_id = self._get_ca_request_id('dogtag-ipa-ca-renew-agent')
         if self.request_id is None:
-            raise admintool.ScriptError(
-                "CA certificate is not tracked by certmonger")
+            # if external CA renewal was interrupted, the request may have
+            # been left with the "dogtag-ipa-ca-renew-agent-reuse" CA;
+            # look for it too
+            self.request_id = \
+                self._get_ca_request_id('dogtag-ipa-ca-renew-agent-reuse')
+            if self.request_id is None:
+                raise admintool.ScriptError(
+                    "CA certificate is not tracked by certmonger")
         logger.debug(
             "Found certmonger request id %r", self.request_id)
 

From 9f0d9ba71f3f5efc97bba2768b0a3f19c62ab71b Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Mon, 25 Sep 2017 17:30:06 +1000
Subject: [PATCH 10/10] ipa-cacert-manage: avoid some duplicate string
 definitions

Part of: https://pagure.io/freeipa/issue/6858
---
 ipalib/constants.py                    |  1 +
 ipaserver/install/ipa_cacert_manage.py | 14 +++++++-------
 2 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/ipalib/constants.py b/ipalib/constants.py
index bc511d9379..dce0b152ad 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -286,6 +286,7 @@
 IPA_CA_RECORD = "ipa-ca"
 IPA_CA_NICKNAME = 'caSigningCert cert-pki-ca'
 RENEWAL_CA_NAME = 'dogtag-ipa-ca-renew-agent'
+RENEWAL_REUSE_CA_NAME = 'dogtag-ipa-ca-renew-agent-reuse'
 
 # regexp definitions
 PATTERN_GROUPUSER_NAME = '^[a-zA-Z0-9_.][a-zA-Z0-9_.-]*[a-zA-Z0-9_.$-]?$'
diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py
index f764638c71..0ac0c5c0a1 100644
--- a/ipaserver/install/ipa_cacert_manage.py
+++ b/ipaserver/install/ipa_cacert_manage.py
@@ -24,6 +24,7 @@
 from optparse import OptionGroup  # pylint: disable=deprecated-module
 import gssapi
 
+from ipalib.constants import RENEWAL_CA_NAME, RENEWAL_REUSE_CA_NAME
 from ipalib.install import certmonger, certstore
 from ipapython import admintool, ipautil
 from ipapython.certdb import (EMPTY_TRUST_FLAGS,
@@ -162,13 +163,12 @@ def renew(self):
         if not ca.is_configured():
             raise admintool.ScriptError("CA is not configured on this system")
 
-        self.request_id = self._get_ca_request_id('dogtag-ipa-ca-renew-agent')
+        self.request_id = self._get_ca_request_id(RENEWAL_CA_NAME)
         if self.request_id is None:
             # if external CA renewal was interrupted, the request may have
             # been left with the "dogtag-ipa-ca-renew-agent-reuse" CA;
             # look for it too
-            self.request_id = \
-                self._get_ca_request_id('dogtag-ipa-ca-renew-agent-reuse')
+            self.request_id = self._get_ca_request_id(RENEWAL_REUSE_CA_NAME)
             if self.request_id is None:
                 raise admintool.ScriptError(
                     "CA certificate is not tracked by certmonger")
@@ -235,7 +235,7 @@ def renew_external_step_1(self, ca):
                     )
 
         self.resubmit_request(
-            'dogtag-ipa-ca-renew-agent-reuse',
+            RENEWAL_REUSE_CA_NAME,
             profile=options.external_ca_profile)
 
         print(("The next step is to get %s signed by your CA and re-run "
@@ -334,11 +334,11 @@ def renew_external_step_2(self, ca, old_cert):
         except errors.NotFound:
             raise admintool.ScriptError("CA renewal master not found")
 
-        self.resubmit_request('dogtag-ipa-ca-renew-agent-reuse')
+        self.resubmit_request(RENEWAL_REUSE_CA_NAME)
 
         print("CA certificate successfully renewed")
 
-    def resubmit_request(self, ca='dogtag-ipa-ca-renew-agent', profile=None):
+    def resubmit_request(self, ca=RENEWAL_CA_NAME, profile=None):
         timeout = api.env.startup_timeout + 60
 
         cm_profile = None
@@ -366,7 +366,7 @@ def resubmit_request(self, ca='dogtag-ipa-ca-renew-agent', profile=None):
 
         logger.debug("modifying certmonger request '%s'", self.request_id)
         certmonger.modify(self.request_id,
-                          ca='dogtag-ipa-ca-renew-agent',
+                          ca=RENEWAL_CA_NAME,
                           profile='', template_v2='')
 
     def install(self):
_______________________________________________
FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org
To unsubscribe send an email to freeipa-devel-le...@lists.fedorahosted.org

Reply via email to