mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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:
committed by
Pavel Vomacka
parent
1699cff350
commit
b4365e3a7f
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user