mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Verify external CA's basic constraint pathlen
IPA no verifies that intermediate certs of external CAs have a basic constraint path len of at least 1 and increasing. Fixes: https://pagure.io/freeipa/issue/7877 Signed-off-by: Christian Heimes <cheimes@redhat.com> Reviewed-By: Fraser Tweedale <ftweedal@redhat.com> Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
This commit is contained in:
parent
3509545897
commit
3c354e74f3
@ -541,6 +541,9 @@ class NSSDatabase:
|
|||||||
def get_trust_chain(self, nickname):
|
def get_trust_chain(self, nickname):
|
||||||
"""Return names of certs in a given cert's trust chain
|
"""Return names of certs in a given cert's trust chain
|
||||||
|
|
||||||
|
The list starts with root ca, then first intermediate CA, second
|
||||||
|
intermediate, and so on.
|
||||||
|
|
||||||
:param nickname: Name of the cert
|
:param nickname: Name of the cert
|
||||||
:return: List of certificate names
|
:return: List of certificate names
|
||||||
"""
|
"""
|
||||||
@ -912,7 +915,7 @@ class NSSDatabase:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError('invalid for server %s' % hostname)
|
raise ValueError('invalid for server %s' % hostname)
|
||||||
|
|
||||||
def verify_ca_cert_validity(self, nickname):
|
def verify_ca_cert_validity(self, nickname, minpathlen=None):
|
||||||
cert = self.get_cert(nickname)
|
cert = self.get_cert(nickname)
|
||||||
|
|
||||||
if not cert.subject:
|
if not cert.subject:
|
||||||
@ -926,6 +929,15 @@ class NSSDatabase:
|
|||||||
|
|
||||||
if not bc.value.ca:
|
if not bc.value.ca:
|
||||||
raise ValueError("not a CA certificate")
|
raise ValueError("not a CA certificate")
|
||||||
|
if minpathlen is not None:
|
||||||
|
# path_length is None means no limitation
|
||||||
|
pl = bc.value.path_length
|
||||||
|
if pl is not None and pl < minpathlen:
|
||||||
|
raise ValueError(
|
||||||
|
"basic contraint pathlen {}, must be at least {}".format(
|
||||||
|
pl, minpathlen
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ski = cert.extensions.get_extension_for_class(
|
ski = cert.extensions.get_extension_for_class(
|
||||||
|
@ -893,9 +893,12 @@ def load_pkcs12(cert_files, key_password, key_nickname, ca_cert_files,
|
|||||||
"The full certificate chain is not present in %s" %
|
"The full certificate chain is not present in %s" %
|
||||||
(", ".join(cert_files)))
|
(", ".join(cert_files)))
|
||||||
|
|
||||||
for nickname in trust_chain[1:]:
|
# verify CA validity and pathlen. The trust_chain list is in reverse
|
||||||
|
# order. trust_chain[1] is the first intermediate CA cert and must
|
||||||
|
# have pathlen >= 0.
|
||||||
|
for minpathlen, nickname in enumerate(trust_chain[1:], start=0):
|
||||||
try:
|
try:
|
||||||
nssdb.verify_ca_cert_validity(nickname)
|
nssdb.verify_ca_cert_validity(nickname, minpathlen)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise ScriptError(
|
raise ScriptError(
|
||||||
"CA certificate %s in %s is not valid: %s" %
|
"CA certificate %s in %s is not valid: %s" %
|
||||||
@ -1038,9 +1041,12 @@ def load_external_cert(files, ca_subject):
|
|||||||
"missing certificate with subject '%s'" %
|
"missing certificate with subject '%s'" %
|
||||||
(", ".join(files), issuer))
|
(", ".join(files), issuer))
|
||||||
|
|
||||||
for nickname in trust_chain:
|
# verify CA validity and pathlen. The trust_chain list is in reverse
|
||||||
|
# order. The first entry is the signed IPA-CA and must have a
|
||||||
|
# pathlen of >= 0.
|
||||||
|
for minpathlen, nickname in enumerate(trust_chain, start=0):
|
||||||
try:
|
try:
|
||||||
nssdb.verify_ca_cert_validity(nickname)
|
nssdb.verify_ca_cert_validity(nickname, minpathlen)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
cert, subject, issuer = cache[nickname]
|
cert, subject, issuer = cache[nickname]
|
||||||
raise ScriptError(
|
raise ScriptError(
|
||||||
|
@ -38,7 +38,7 @@ class ExternalCA:
|
|||||||
self.now = datetime.datetime.utcnow()
|
self.now = datetime.datetime.utcnow()
|
||||||
self.delta = datetime.timedelta(days=days)
|
self.delta = datetime.timedelta(days=days)
|
||||||
|
|
||||||
def create_ca(self, cn=ISSUER_CN):
|
def create_ca(self, cn=ISSUER_CN, path_length=None):
|
||||||
"""Create root CA.
|
"""Create root CA.
|
||||||
|
|
||||||
:returns: bytes -- Root CA in PEM format.
|
:returns: bytes -- Root CA in PEM format.
|
||||||
@ -79,7 +79,7 @@ class ExternalCA:
|
|||||||
)
|
)
|
||||||
|
|
||||||
builder = builder.add_extension(
|
builder = builder.add_extension(
|
||||||
x509.BasicConstraints(ca=True, path_length=None),
|
x509.BasicConstraints(ca=True, path_length=path_length),
|
||||||
critical=True,
|
critical=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1152,7 +1152,7 @@ jobs:
|
|||||||
class: RunPytest
|
class: RunPytest
|
||||||
args:
|
args:
|
||||||
build_url: '{fedora-28/build_url}'
|
build_url: '{fedora-28/build_url}'
|
||||||
test_suite: test_integration/test_external_ca.py::TestExternalCAInvalidCert
|
test_suite: test_integration/test_external_ca.py::TestExternalCAInvalidCert test_integration/test_external_ca.py::TestExternalCAInvalidIntermediate
|
||||||
template: *ci-master-f28
|
template: *ci-master-f28
|
||||||
timeout: 3600
|
timeout: 3600
|
||||||
topology: *master_1repl
|
topology: *master_1repl
|
||||||
|
@ -1152,7 +1152,7 @@ jobs:
|
|||||||
class: RunPytest
|
class: RunPytest
|
||||||
args:
|
args:
|
||||||
build_url: '{fedora-29/build_url}'
|
build_url: '{fedora-29/build_url}'
|
||||||
test_suite: test_integration/test_external_ca.py::TestExternalCAInvalidCert
|
test_suite: test_integration/test_external_ca.py::TestExternalCAInvalidCert test_integration/test_external_ca.py::TestExternalCAInvalidIntermediate
|
||||||
template: *ci-master-f29
|
template: *ci-master-f29
|
||||||
timeout: 3600
|
timeout: 3600
|
||||||
topology: *master_1repl
|
topology: *master_1repl
|
||||||
|
@ -1152,7 +1152,7 @@ jobs:
|
|||||||
class: RunPytest
|
class: RunPytest
|
||||||
args:
|
args:
|
||||||
build_url: '{fedora-rawhide/build_url}'
|
build_url: '{fedora-rawhide/build_url}'
|
||||||
test_suite: test_integration/test_external_ca.py::TestExternalCAInvalidCert
|
test_suite: test_integration/test_external_ca.py::TestExternalCAInvalidCert test_integration/test_external_ca.py::TestExternalCAInvalidIntermediate
|
||||||
template: *ci-master-frawhide
|
template: *ci-master-frawhide
|
||||||
timeout: 3600
|
timeout: 3600
|
||||||
topology: *master_1repl
|
topology: *master_1repl
|
||||||
|
@ -1632,7 +1632,8 @@ def add_dns_zone(master, zone, skip_overlap_check=False,
|
|||||||
logger.debug('Zone %s already added.', zone)
|
logger.debug('Zone %s already added.', zone)
|
||||||
|
|
||||||
|
|
||||||
def sign_ca_and_transport(host, csr_name, root_ca_name, ipa_ca_name):
|
def sign_ca_and_transport(host, csr_name, root_ca_name, ipa_ca_name,
|
||||||
|
root_ca_path_length=None, ipa_ca_path_length=1):
|
||||||
"""
|
"""
|
||||||
Sign ipa csr and save signed CA together with root CA back to the host.
|
Sign ipa csr and save signed CA together with root CA back to the host.
|
||||||
Returns root CA and IPA CA paths on the host.
|
Returns root CA and IPA CA paths on the host.
|
||||||
@ -1645,9 +1646,9 @@ def sign_ca_and_transport(host, csr_name, root_ca_name, ipa_ca_name):
|
|||||||
|
|
||||||
external_ca = ExternalCA()
|
external_ca = ExternalCA()
|
||||||
# Create root CA
|
# Create root CA
|
||||||
root_ca = external_ca.create_ca()
|
root_ca = external_ca.create_ca(path_length=root_ca_path_length)
|
||||||
# Sign CSR
|
# Sign CSR
|
||||||
ipa_ca = external_ca.sign_csr(ipa_csr)
|
ipa_ca = external_ca.sign_csr(ipa_csr, path_length=ipa_ca_path_length)
|
||||||
|
|
||||||
root_ca_fname = os.path.join(test_dir, root_ca_name)
|
root_ca_fname = os.path.join(test_dir, root_ca_name)
|
||||||
ipa_ca_fname = os.path.join(test_dir, ipa_ca_name)
|
ipa_ca_fname = os.path.join(test_dir, ipa_ca_name)
|
||||||
@ -1656,7 +1657,7 @@ def sign_ca_and_transport(host, csr_name, root_ca_name, ipa_ca_name):
|
|||||||
host.put_file_contents(root_ca_fname, root_ca)
|
host.put_file_contents(root_ca_fname, root_ca)
|
||||||
host.put_file_contents(ipa_ca_fname, ipa_ca)
|
host.put_file_contents(ipa_ca_fname, ipa_ca)
|
||||||
|
|
||||||
return (root_ca_fname, ipa_ca_fname)
|
return root_ca_fname, ipa_ca_fname
|
||||||
|
|
||||||
|
|
||||||
def generate_ssh_keypair():
|
def generate_ssh_keypair():
|
||||||
|
@ -80,7 +80,8 @@ def install_server_external_ca_step1(host, extra_args=()):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def install_server_external_ca_step2(host, ipa_ca_cert, root_ca_cert):
|
def install_server_external_ca_step2(host, ipa_ca_cert, root_ca_cert,
|
||||||
|
raiseonerr=True):
|
||||||
"""Step 2 to install the ipa server with external ca"""
|
"""Step 2 to install the ipa server with external ca"""
|
||||||
args = ['ipa-server-install',
|
args = ['ipa-server-install',
|
||||||
'-a', host.config.admin_password,
|
'-a', host.config.admin_password,
|
||||||
@ -88,7 +89,7 @@ def install_server_external_ca_step2(host, ipa_ca_cert, root_ca_cert):
|
|||||||
'--external-cert-file', ipa_ca_cert,
|
'--external-cert-file', ipa_ca_cert,
|
||||||
'--external-cert-file', root_ca_cert]
|
'--external-cert-file', root_ca_cert]
|
||||||
|
|
||||||
cmd = host.run_command(args)
|
cmd = host.run_command(args, raiseonerr=raiseonerr)
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
@ -333,6 +334,22 @@ class TestExternalCAInvalidCert(IntegrationTest):
|
|||||||
assert result.returncode == 1
|
assert result.returncode == 1
|
||||||
|
|
||||||
|
|
||||||
|
class TestExternalCAInvalidIntermediate(IntegrationTest):
|
||||||
|
"""Test case for https://pagure.io/freeipa/issue/7877"""
|
||||||
|
|
||||||
|
def test_invalid_intermediate(self):
|
||||||
|
install_server_external_ca_step1(self.master)
|
||||||
|
root_ca_fname, ipa_ca_fname = tasks.sign_ca_and_transport(
|
||||||
|
self.master, paths.ROOT_IPA_CSR, ROOT_CA, IPA_CA,
|
||||||
|
root_ca_path_length=0
|
||||||
|
)
|
||||||
|
result = install_server_external_ca_step2(
|
||||||
|
self.master, ipa_ca_fname, root_ca_fname, raiseonerr=False
|
||||||
|
)
|
||||||
|
assert result.returncode > 0
|
||||||
|
assert "basic contraint pathlen" in result.stderr_text
|
||||||
|
|
||||||
|
|
||||||
class TestExternalCAInstall(IntegrationTest):
|
class TestExternalCAInstall(IntegrationTest):
|
||||||
"""install CA cert manually """
|
"""install CA cert manually """
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user