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

Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
This commit is contained in:
Fraser Tweedale
2017-05-24 17:59:46 +10:00
committed by Pavel Vomacka
parent 1699cff350
commit b4365e3a7f
4 changed files with 231 additions and 4 deletions

View File

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

View File

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

View File

@@ -22,6 +22,7 @@
from __future__ import print_function
import base64
import binascii
import logging
import dbus
@@ -41,6 +42,9 @@ import six
# 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 @@ class CAInstance(DogtagInstance):
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 @@ class CAInstance(DogtagInstance):
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 @@ class CAInstance(DogtagInstance):
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()

View File

@@ -437,6 +437,11 @@ class ServerInstallInterface(ServerCertificateInstallInterface,
"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):