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.masters import find_providing_server
from ipapython import version
from ipalib import api
from ipalib import api, x509
from ipalib.constants import DOMAIN_LEVEL_1
from ipapython.config import IPAOptionParser
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")
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 = 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",
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,
type='constructor', constructor=x509.ExternalCAProfile,
default=None, metavar="PROFILE-SPEC",
help="Specify the certificate profile/template to use "
"at the external CA")

View File

@ -34,6 +34,7 @@ from __future__ import print_function
import os
import binascii
import datetime
import enum
import ipaddress
import ssl
import base64
@ -47,6 +48,7 @@ from cryptography.hazmat.primitives.serialization import (
Encoding, PublicFormat, PrivateFormat, load_pem_private_key
)
import pyasn1
import pyasn1.error
from pyasn1.type import univ, char, namedtype, tag
from pyasn1.codec.der import decoder, encoder
from pyasn1_modules import rfc2315, rfc2459
@ -745,3 +747,172 @@ def format_datetime(t):
if t.tzinfo is None:
t = t.replace(tzinfo=UTC())
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 ipaserver.install import installutils, certs
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 . import conncheck, dogtag, cainstance
@ -216,8 +216,7 @@ def install_check(standalone, replica_config, options):
paths.ROOT_IPA_CSR)
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_profile is not None:
# 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_type = knob(
cainstance.ExternalCAType, None,
description="Type of the external CA",
)
x509.ExternalCAType, None, description="Type of the external CA")
external_ca_type = master_install_only(external_ca_type)
external_ca_profile = knob(
type=cainstance.ExternalCAProfile,
type=x509.ExternalCAProfile,
default=None,
description=(
"Specify the certificate profile/template to use at the "

View File

@ -26,7 +26,6 @@ import binascii
import logging
import dbus
import enum
import ldap
import os
import pwd
@ -39,10 +38,6 @@ import time
import tempfile
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 x509
from ipalib import errors
@ -80,11 +75,6 @@ ADMIN_GROUPS = [
]
class ExternalCAType(enum.Enum):
GENERIC = 'generic'
MS_CS = 'ms-cs'
def check_ports():
"""Check that dogtag ports (8080, 8443) are available.
@ -367,7 +357,7 @@ class CAInstance(DogtagInstance):
if ca_type is not None:
self.ca_type = ca_type
else:
self.ca_type = ExternalCAType.GENERIC.value
self.ca_type = x509.ExternalCAType.GENERIC.value
self.external_ca_profile = external_ca_profile
self.no_db_setup = promote
@ -537,12 +527,12 @@ class CAInstance(DogtagInstance):
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
template = self.external_ca_profile
if template is None:
# default template name
template = MSCSTemplateV1(u"SubCA")
template = x509.MSCSTemplateV1(u"SubCA")
ext_data = binascii.hexlify(template.get_ext_data())
cfg.update(
@ -2081,170 +2071,6 @@ def update_ipa_conf():
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__":
standard_logging_setup("install.log")
ds = dsinstance.DsInstance()

View File

@ -65,7 +65,7 @@ class CACertManage(admintool.AdminTool):
"--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)
ext_cas = tuple(x.value for x in x509.ExternalCAType)
renew_group.add_option(
"--external-ca-type", dest="external_ca_type",
type="choice", choices=ext_cas,
@ -73,7 +73,7 @@ class CACertManage(admintool.AdminTool):
help="Type of the external CA. Default: generic")
renew_group.add_option(
"--external-ca-profile", dest="external_ca_profile",
type='constructor', constructor=cainstance.ExternalCAProfile,
type='constructor', constructor=x509.ExternalCAProfile,
default=None, metavar="PROFILE-SPEC",
help="Specify the certificate profile/template to use "
"at the external CA")
@ -224,11 +224,11 @@ class CACertManage(admintool.AdminTool):
options = self.options
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:
options.external_ca_profile = cainstance.MSCSTemplateV1(u"SubCA")
options.external_ca_profile = x509.MSCSTemplateV1(u"SubCA")
if options.external_ca_profile is not None:
# check that profile is valid for the external ca type
@ -352,11 +352,11 @@ class CACertManage(admintool.AdminTool):
timeout = api.env.startup_timeout + 60
cm_profile = None
if isinstance(profile, cainstance.MSCSTemplateV1):
if isinstance(profile, x509.MSCSTemplateV1):
cm_profile = profile.unparsed_input
cm_template = None
if isinstance(profile, cainstance.MSCSTemplateV2):
if isinstance(profile, x509.MSCSTemplateV2):
cm_template = profile.unparsed_input
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
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())
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[0].value.value == value
assert extensions[0].value.value == template.get_ext_data()
class TestExternalCA(IntegrationTest):
@ -134,10 +134,7 @@ class TestExternalCA(IntegrationTest):
# check CSR for extension
ipa_csr = self.master.get_file_contents(paths.ROOT_IPA_CSR)
# Values for MSCSTemplateV1('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)
check_mscs_extension(ipa_csr, ipa_x509.MSCSTemplateV1(u'SubCA'))
# 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(

View File

@ -22,7 +22,11 @@ Test the `ipalib.x509` module.
"""
import base64
from binascii import hexlify
from configparser import RawConfigParser
import datetime
from io import StringIO
import pickle
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'\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