mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
move MSCSTemplate classes to ipalib
As we expand the integration tests for external CA functionality, it is helpful (and avoids duplication) to use the MSCSTemplate* classes. These currently live in ipaserver.install.cainstance, but ipatests is no longer permitted to import from ipaserver (see commit 81714976e5e13131654c78eb734746a20237c933). So move these classes to ipalib. Part of: https://pagure.io/freeipa/issue/7548 Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
This commit is contained in:
parent
95c2b34c4b
commit
130e1dc343
@ -37,7 +37,7 @@ from ipaserver.install import cainstance, service
|
|||||||
from ipaserver.install import custodiainstance
|
from ipaserver.install import custodiainstance
|
||||||
from ipaserver.masters import find_providing_server
|
from ipaserver.masters import find_providing_server
|
||||||
from ipapython import version
|
from ipapython import version
|
||||||
from ipalib import api
|
from ipalib import api, x509
|
||||||
from ipalib.constants import DOMAIN_LEVEL_1
|
from ipalib.constants import DOMAIN_LEVEL_1
|
||||||
from ipapython.config import IPAOptionParser
|
from ipapython.config import IPAOptionParser
|
||||||
from ipapython.ipa_log_manager import standard_logging_setup
|
from ipapython.ipa_log_manager import standard_logging_setup
|
||||||
@ -68,13 +68,13 @@ def parse_options():
|
|||||||
default=False, help="unattended installation never prompts the user")
|
default=False, help="unattended installation never prompts the user")
|
||||||
parser.add_option("--external-ca", dest="external_ca", action="store_true",
|
parser.add_option("--external-ca", dest="external_ca", action="store_true",
|
||||||
default=False, help="Generate a CSR to be signed by an external CA")
|
default=False, help="Generate a CSR to be signed by an external CA")
|
||||||
ext_cas = tuple(x.value for x in cainstance.ExternalCAType)
|
ext_cas = tuple(x.value for x in x509.ExternalCAType)
|
||||||
parser.add_option("--external-ca-type", dest="external_ca_type",
|
parser.add_option("--external-ca-type", dest="external_ca_type",
|
||||||
type="choice", choices=ext_cas,
|
type="choice", choices=ext_cas,
|
||||||
metavar="{{{0}}}".format(",".join(ext_cas)),
|
metavar="{{{0}}}".format(",".join(ext_cas)),
|
||||||
help="Type of the external CA. Default: generic")
|
help="Type of the external CA. Default: generic")
|
||||||
parser.add_option("--external-ca-profile", dest="external_ca_profile",
|
parser.add_option("--external-ca-profile", dest="external_ca_profile",
|
||||||
type='constructor', constructor=cainstance.ExternalCAProfile,
|
type='constructor', constructor=x509.ExternalCAProfile,
|
||||||
default=None, metavar="PROFILE-SPEC",
|
default=None, metavar="PROFILE-SPEC",
|
||||||
help="Specify the certificate profile/template to use "
|
help="Specify the certificate profile/template to use "
|
||||||
"at the external CA")
|
"at the external CA")
|
||||||
|
171
ipalib/x509.py
171
ipalib/x509.py
@ -34,6 +34,7 @@ from __future__ import print_function
|
|||||||
import os
|
import os
|
||||||
import binascii
|
import binascii
|
||||||
import datetime
|
import datetime
|
||||||
|
import enum
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import ssl
|
import ssl
|
||||||
import base64
|
import base64
|
||||||
@ -47,6 +48,7 @@ from cryptography.hazmat.primitives.serialization import (
|
|||||||
Encoding, PublicFormat, PrivateFormat, load_pem_private_key
|
Encoding, PublicFormat, PrivateFormat, load_pem_private_key
|
||||||
)
|
)
|
||||||
import pyasn1
|
import pyasn1
|
||||||
|
import pyasn1.error
|
||||||
from pyasn1.type import univ, char, namedtype, tag
|
from pyasn1.type import univ, char, namedtype, tag
|
||||||
from pyasn1.codec.der import decoder, encoder
|
from pyasn1.codec.der import decoder, encoder
|
||||||
from pyasn1_modules import rfc2315, rfc2459
|
from pyasn1_modules import rfc2315, rfc2459
|
||||||
@ -745,3 +747,172 @@ def format_datetime(t):
|
|||||||
if t.tzinfo is None:
|
if t.tzinfo is None:
|
||||||
t = t.replace(tzinfo=UTC())
|
t = t.replace(tzinfo=UTC())
|
||||||
return unicode(t.strftime("%a %b %d %H:%M:%S %Y %Z"))
|
return unicode(t.strftime("%a %b %d %H:%M:%S %Y %Z"))
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalCAType(enum.Enum):
|
||||||
|
GENERIC = 'generic'
|
||||||
|
MS_CS = 'ms-cs'
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalCAProfile:
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
# pylint: disable=too-many-function-args
|
||||||
|
return MSCSTemplateV2.__new__(MSCSTemplateV2, s)
|
||||||
|
|
||||||
|
except pyasn1.error.PyAsn1Error:
|
||||||
|
# It is not an OID; treat as a template name
|
||||||
|
# pylint: disable=too-many-function-args
|
||||||
|
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(str(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())
|
||||||
|
)
|
||||||
|
@ -28,7 +28,7 @@ from ipaplatform import services
|
|||||||
from ipaplatform.paths import paths
|
from ipaplatform.paths import paths
|
||||||
from ipaserver.install import installutils, certs
|
from ipaserver.install import installutils, certs
|
||||||
from ipaserver.install.replication import replica_conn_check
|
from ipaserver.install.replication import replica_conn_check
|
||||||
from ipalib import api, errors
|
from ipalib import api, errors, x509
|
||||||
from ipapython.dn import DN
|
from ipapython.dn import DN
|
||||||
|
|
||||||
from . import conncheck, dogtag, cainstance
|
from . import conncheck, dogtag, cainstance
|
||||||
@ -216,8 +216,7 @@ def install_check(standalone, replica_config, options):
|
|||||||
paths.ROOT_IPA_CSR)
|
paths.ROOT_IPA_CSR)
|
||||||
|
|
||||||
if not options.external_ca_type:
|
if not options.external_ca_type:
|
||||||
options.external_ca_type = \
|
options.external_ca_type = x509.ExternalCAType.GENERIC.value
|
||||||
cainstance.ExternalCAType.GENERIC.value
|
|
||||||
|
|
||||||
if options.external_ca_profile is not None:
|
if options.external_ca_profile is not None:
|
||||||
# check that profile is valid for the external ca type
|
# check that profile is valid for the external ca type
|
||||||
@ -478,13 +477,11 @@ class CAInstallInterface(dogtag.DogtagInstallInterface,
|
|||||||
external_ca = master_install_only(external_ca)
|
external_ca = master_install_only(external_ca)
|
||||||
|
|
||||||
external_ca_type = knob(
|
external_ca_type = knob(
|
||||||
cainstance.ExternalCAType, None,
|
x509.ExternalCAType, None, description="Type of the external CA")
|
||||||
description="Type of the external CA",
|
|
||||||
)
|
|
||||||
external_ca_type = master_install_only(external_ca_type)
|
external_ca_type = master_install_only(external_ca_type)
|
||||||
|
|
||||||
external_ca_profile = knob(
|
external_ca_profile = knob(
|
||||||
type=cainstance.ExternalCAProfile,
|
type=x509.ExternalCAProfile,
|
||||||
default=None,
|
default=None,
|
||||||
description=(
|
description=(
|
||||||
"Specify the certificate profile/template to use at the "
|
"Specify the certificate profile/template to use at the "
|
||||||
|
@ -26,7 +26,6 @@ import binascii
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import dbus
|
import dbus
|
||||||
import enum
|
|
||||||
import ldap
|
import ldap
|
||||||
import os
|
import os
|
||||||
import pwd
|
import pwd
|
||||||
@ -39,10 +38,6 @@ import time
|
|||||||
import tempfile
|
import tempfile
|
||||||
from configparser import RawConfigParser
|
from configparser import RawConfigParser
|
||||||
|
|
||||||
from pyasn1.codec.der import encoder
|
|
||||||
from pyasn1.type import char, univ, namedtype
|
|
||||||
import pyasn1.error
|
|
||||||
|
|
||||||
from ipalib import api
|
from ipalib import api
|
||||||
from ipalib import x509
|
from ipalib import x509
|
||||||
from ipalib import errors
|
from ipalib import errors
|
||||||
@ -80,11 +75,6 @@ ADMIN_GROUPS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ExternalCAType(enum.Enum):
|
|
||||||
GENERIC = 'generic'
|
|
||||||
MS_CS = 'ms-cs'
|
|
||||||
|
|
||||||
|
|
||||||
def check_ports():
|
def check_ports():
|
||||||
"""Check that dogtag ports (8080, 8443) are available.
|
"""Check that dogtag ports (8080, 8443) are available.
|
||||||
|
|
||||||
@ -367,7 +357,7 @@ class CAInstance(DogtagInstance):
|
|||||||
if ca_type is not None:
|
if ca_type is not None:
|
||||||
self.ca_type = ca_type
|
self.ca_type = ca_type
|
||||||
else:
|
else:
|
||||||
self.ca_type = ExternalCAType.GENERIC.value
|
self.ca_type = x509.ExternalCAType.GENERIC.value
|
||||||
self.external_ca_profile = external_ca_profile
|
self.external_ca_profile = external_ca_profile
|
||||||
|
|
||||||
self.no_db_setup = promote
|
self.no_db_setup = promote
|
||||||
@ -537,12 +527,12 @@ class CAInstance(DogtagInstance):
|
|||||||
pki_ca_signing_csr_path=self.csr_file,
|
pki_ca_signing_csr_path=self.csr_file,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.ca_type == ExternalCAType.MS_CS.value:
|
if self.ca_type == x509.ExternalCAType.MS_CS.value:
|
||||||
# Include MS template name extension in the CSR
|
# Include MS template name extension in the CSR
|
||||||
template = self.external_ca_profile
|
template = self.external_ca_profile
|
||||||
if template is None:
|
if template is None:
|
||||||
# default template name
|
# default template name
|
||||||
template = MSCSTemplateV1(u"SubCA")
|
template = x509.MSCSTemplateV1(u"SubCA")
|
||||||
|
|
||||||
ext_data = binascii.hexlify(template.get_ext_data())
|
ext_data = binascii.hexlify(template.get_ext_data())
|
||||||
cfg.update(
|
cfg.update(
|
||||||
@ -2081,170 +2071,6 @@ def update_ipa_conf():
|
|||||||
parser.write(f)
|
parser.write(f)
|
||||||
|
|
||||||
|
|
||||||
class ExternalCAProfile:
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
# pylint: disable=too-many-function-args
|
|
||||||
return MSCSTemplateV2.__new__(MSCSTemplateV2, s)
|
|
||||||
|
|
||||||
except pyasn1.error.PyAsn1Error:
|
|
||||||
# It is not an OID; treat as a template name
|
|
||||||
# pylint: disable=too-many-function-args
|
|
||||||
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(str(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__":
|
if __name__ == "__main__":
|
||||||
standard_logging_setup("install.log")
|
standard_logging_setup("install.log")
|
||||||
ds = dsinstance.DsInstance()
|
ds = dsinstance.DsInstance()
|
||||||
|
@ -65,7 +65,7 @@ class CACertManage(admintool.AdminTool):
|
|||||||
"--external-ca", dest='self_signed',
|
"--external-ca", dest='self_signed',
|
||||||
action='store_false',
|
action='store_false',
|
||||||
help="Sign the renewed certificate by external CA")
|
help="Sign the renewed certificate by external CA")
|
||||||
ext_cas = tuple(x.value for x in cainstance.ExternalCAType)
|
ext_cas = tuple(x.value for x in x509.ExternalCAType)
|
||||||
renew_group.add_option(
|
renew_group.add_option(
|
||||||
"--external-ca-type", dest="external_ca_type",
|
"--external-ca-type", dest="external_ca_type",
|
||||||
type="choice", choices=ext_cas,
|
type="choice", choices=ext_cas,
|
||||||
@ -73,7 +73,7 @@ class CACertManage(admintool.AdminTool):
|
|||||||
help="Type of the external CA. Default: generic")
|
help="Type of the external CA. Default: generic")
|
||||||
renew_group.add_option(
|
renew_group.add_option(
|
||||||
"--external-ca-profile", dest="external_ca_profile",
|
"--external-ca-profile", dest="external_ca_profile",
|
||||||
type='constructor', constructor=cainstance.ExternalCAProfile,
|
type='constructor', constructor=x509.ExternalCAProfile,
|
||||||
default=None, metavar="PROFILE-SPEC",
|
default=None, metavar="PROFILE-SPEC",
|
||||||
help="Specify the certificate profile/template to use "
|
help="Specify the certificate profile/template to use "
|
||||||
"at the external CA")
|
"at the external CA")
|
||||||
@ -224,11 +224,11 @@ class CACertManage(admintool.AdminTool):
|
|||||||
options = self.options
|
options = self.options
|
||||||
|
|
||||||
if not options.external_ca_type:
|
if not options.external_ca_type:
|
||||||
options.external_ca_type = cainstance.ExternalCAType.GENERIC.value
|
options.external_ca_type = x509.ExternalCAType.GENERIC.value
|
||||||
|
|
||||||
if options.external_ca_type == cainstance.ExternalCAType.MS_CS.value \
|
if options.external_ca_type == x509.ExternalCAType.MS_CS.value \
|
||||||
and options.external_ca_profile is None:
|
and options.external_ca_profile is None:
|
||||||
options.external_ca_profile = cainstance.MSCSTemplateV1(u"SubCA")
|
options.external_ca_profile = x509.MSCSTemplateV1(u"SubCA")
|
||||||
|
|
||||||
if options.external_ca_profile is not None:
|
if options.external_ca_profile is not None:
|
||||||
# check that profile is valid for the external ca type
|
# check that profile is valid for the external ca type
|
||||||
@ -352,11 +352,11 @@ class CACertManage(admintool.AdminTool):
|
|||||||
timeout = api.env.startup_timeout + 60
|
timeout = api.env.startup_timeout + 60
|
||||||
|
|
||||||
cm_profile = None
|
cm_profile = None
|
||||||
if isinstance(profile, cainstance.MSCSTemplateV1):
|
if isinstance(profile, x509.MSCSTemplateV1):
|
||||||
cm_profile = profile.unparsed_input
|
cm_profile = profile.unparsed_input
|
||||||
|
|
||||||
cm_template = None
|
cm_template = None
|
||||||
if isinstance(profile, cainstance.MSCSTemplateV2):
|
if isinstance(profile, x509.MSCSTemplateV2):
|
||||||
cm_template = profile.unparsed_input
|
cm_template = profile.unparsed_input
|
||||||
|
|
||||||
logger.debug("resubmitting certmonger request '%s'", self.request_id)
|
logger.debug("resubmitting certmonger request '%s'", self.request_id)
|
||||||
|
@ -108,14 +108,14 @@ def check_ipaca_issuerDN(host, expected_dn):
|
|||||||
assert "Issuer DN: {}".format(expected_dn) in result.stdout_text
|
assert "Issuer DN: {}".format(expected_dn) in result.stdout_text
|
||||||
|
|
||||||
|
|
||||||
def check_mscs_extension(ipa_csr, oid, value):
|
def check_mscs_extension(ipa_csr, template):
|
||||||
csr = x509.load_pem_x509_csr(ipa_csr, default_backend())
|
csr = x509.load_pem_x509_csr(ipa_csr, default_backend())
|
||||||
extensions = [
|
extensions = [
|
||||||
ext for ext in csr.extensions
|
ext for ext in csr.extensions
|
||||||
if ext.oid.dotted_string == oid
|
if ext.oid.dotted_string == template.ext_oid
|
||||||
]
|
]
|
||||||
assert extensions
|
assert extensions
|
||||||
assert extensions[0].value.value == value
|
assert extensions[0].value.value == template.get_ext_data()
|
||||||
|
|
||||||
|
|
||||||
class TestExternalCA(IntegrationTest):
|
class TestExternalCA(IntegrationTest):
|
||||||
@ -134,10 +134,7 @@ class TestExternalCA(IntegrationTest):
|
|||||||
|
|
||||||
# check CSR for extension
|
# check CSR for extension
|
||||||
ipa_csr = self.master.get_file_contents(paths.ROOT_IPA_CSR)
|
ipa_csr = self.master.get_file_contents(paths.ROOT_IPA_CSR)
|
||||||
# Values for MSCSTemplateV1('SubCA')
|
check_mscs_extension(ipa_csr, ipa_x509.MSCSTemplateV1(u'SubCA'))
|
||||||
oid = "1.3.6.1.4.1.311.20.2"
|
|
||||||
value = b'\x1e\n\x00S\x00u\x00b\x00C\x00A'
|
|
||||||
check_mscs_extension(ipa_csr, oid, value)
|
|
||||||
|
|
||||||
# Sign CA, transport it to the host and get ipa a root ca paths.
|
# Sign CA, transport it to the host and get ipa a root ca paths.
|
||||||
root_ca_fname, ipa_ca_fname = tasks.sign_ca_and_transport(
|
root_ca_fname, ipa_ca_fname = tasks.sign_ca_and_transport(
|
||||||
|
@ -22,7 +22,11 @@ Test the `ipalib.x509` module.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
from binascii import hexlify
|
||||||
|
from configparser import RawConfigParser
|
||||||
import datetime
|
import datetime
|
||||||
|
from io import StringIO
|
||||||
|
import pickle
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -268,3 +272,114 @@ class test_x509:
|
|||||||
b'0 \x06\x03U\x1d%\x01\x01\xff\x04\x160\x14\x06\x08+\x06\x01'
|
b'0 \x06\x03U\x1d%\x01\x01\xff\x04\x160\x14\x06\x08+\x06\x01'
|
||||||
b'\x05\x05\x07\x03\x01\x06\x08+\x06\x01\x05\x05\x07\x03\x02'
|
b'\x05\x05\x07\x03\x01\x06\x08+\x06\x01\x05\x05\x07\x03\x02'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class test_ExternalCAProfile:
|
||||||
|
def test_MSCSTemplateV1_good(self):
|
||||||
|
o = x509.MSCSTemplateV1("MySubCA")
|
||||||
|
assert hexlify(o.get_ext_data()) == b'1e0e004d007900530075006200430041'
|
||||||
|
|
||||||
|
def test_MSCSTemplateV1_bad(self):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
x509.MSCSTemplateV1("MySubCA:1")
|
||||||
|
|
||||||
|
def test_MSCSTemplateV1_pickle_roundtrip(self):
|
||||||
|
o = x509.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):
|
||||||
|
x509.MSCSTemplateV2("1.2.3.4")
|
||||||
|
|
||||||
|
def test_MSCSTemplateV2_too_many_parts(self):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
x509.MSCSTemplateV2("1.2.3.4:100:200:300")
|
||||||
|
|
||||||
|
def test_MSCSTemplateV2_bad_oid(self):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
x509.MSCSTemplateV2("not_an_oid:1")
|
||||||
|
|
||||||
|
def test_MSCSTemplateV2_non_numeric_major_version(self):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
x509.MSCSTemplateV2("1.2.3.4:major:200")
|
||||||
|
|
||||||
|
def test_MSCSTemplateV2_non_numeric_minor_version(self):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
x509.MSCSTemplateV2("1.2.3.4:100:minor")
|
||||||
|
|
||||||
|
def test_MSCSTemplateV2_major_version_lt_zero(self):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
x509.MSCSTemplateV2("1.2.3.4:-1:200")
|
||||||
|
|
||||||
|
def test_MSCSTemplateV2_minor_version_lt_zero(self):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
x509.MSCSTemplateV2("1.2.3.4:100:-1")
|
||||||
|
|
||||||
|
def test_MSCSTemplateV2_major_version_gt_max(self):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
x509.MSCSTemplateV2("1.2.3.4:4294967296:200")
|
||||||
|
|
||||||
|
def test_MSCSTemplateV2_minor_version_gt_max(self):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
x509.MSCSTemplateV2("1.2.3.4:100:4294967296")
|
||||||
|
|
||||||
|
def test_MSCSTemplateV2_good_major(self):
|
||||||
|
o = x509.MSCSTemplateV2("1.2.3.4:4294967295")
|
||||||
|
assert hexlify(o.get_ext_data()) == b'300c06032a0304020500ffffffff'
|
||||||
|
|
||||||
|
def test_MSCSTemplateV2_good_major_minor(self):
|
||||||
|
o = x509.MSCSTemplateV2("1.2.3.4:4294967295:0")
|
||||||
|
assert hexlify(o.get_ext_data()) \
|
||||||
|
== b'300f06032a0304020500ffffffff020100'
|
||||||
|
|
||||||
|
def test_MSCSTemplateV2_pickle_roundtrip(self):
|
||||||
|
o = x509.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(
|
||||||
|
x509.ExternalCAProfile("MySubCA"),
|
||||||
|
x509.MSCSTemplateV1)
|
||||||
|
assert isinstance(
|
||||||
|
x509.ExternalCAProfile("1.2.3.4:100"),
|
||||||
|
x509.MSCSTemplateV2)
|
||||||
|
|
||||||
|
def test_write_pkispawn_config_file_MSCSTemplateV1(self):
|
||||||
|
template = x509.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 = x509.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
|
||||||
|
@ -1,123 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (C) 2017 FreeIPA Contributors see COPYING for license
|
|
||||||
#
|
|
||||||
|
|
||||||
from binascii import hexlify
|
|
||||||
from io import StringIO
|
|
||||||
import pickle
|
|
||||||
from configparser import RawConfigParser
|
|
||||||
import pytest
|
|
||||||
from ipaserver.install import cainstance
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.tier0
|
|
||||||
|
|
||||||
|
|
||||||
class test_ExternalCAProfile:
|
|
||||||
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
|
|
Loading…
Reference in New Issue
Block a user