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:
Fraser Tweedale 2019-07-11 15:17:04 +10:00 committed by Alexander Bokovoy
parent 95c2b34c4b
commit 130e1dc343
8 changed files with 307 additions and 324 deletions

View File

@ -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")

View File

@ -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())
)

View File

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

View File

@ -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()

View File

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

View File

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

View File

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

View File

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