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.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")
|
||||
|
171
ipalib/x509.py
171
ipalib/x509.py
@ -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())
|
||||
)
|
||||
|
@ -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 "
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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