Commit e8eeb3ce authored by Vincent Pelletier's avatar Vincent Pelletier

test: Skip testHttpNetlocIPv6 if OpenSSL lacks IP constraints support

parent 40a89260
...@@ -20,7 +20,7 @@ Caucase - Certificate Authority for Users, Certificate Authority for SErvices ...@@ -20,7 +20,7 @@ Caucase - Certificate Authority for Users, Certificate Authority for SErvices
Test suite Test suite
""" """
# pylint: disable=too-many-lines # pylint: disable=too-many-lines, too-many-public-methods
from __future__ import absolute_import from __future__ import absolute_import
from Cookie import SimpleCookie from Cookie import SimpleCookie
import datetime import datetime
...@@ -47,8 +47,11 @@ from urllib import quote, urlencode ...@@ -47,8 +47,11 @@ from urllib import quote, urlencode
import urlparse import urlparse
from cryptography import x509 from cryptography import x509
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from caucase import cli from caucase import cli
from caucase.ca import Extension
from caucase.client import CaucaseError, CaucaseClient from caucase.client import CaucaseError, CaucaseClient
from caucase.exceptions import CertificateVerificationError
# Do not import caucase.http into this namespace: 2to3 will import standard # Do not import caucase.http into this namespace: 2to3 will import standard
# http module, which will then be masqued by caucase's http submodule. # http module, which will then be masqued by caucase's http submodule.
import caucase.http import caucase.http
...@@ -325,6 +328,160 @@ class CaucaseTest(unittest.TestCase): ...@@ -325,6 +328,160 @@ class CaucaseTest(unittest.TestCase):
self._stopServer() self._stopServer()
shutil.rmtree(self._data_dir) shutil.rmtree(self._data_dir)
@staticmethod
def _getCAKeyPair(extension_list=(), not_before=None, not_after=None):
"""
Build a reasonably-realistic CA, return key & self-signed cert.
"""
if not_before is None:
not_before = datetime.datetime.utcnow()
if not_after is None:
not_after = not_before + datetime.timedelta(10, 0)
private_key = utils.generatePrivateKey(2048)
subject = x509.Name([
x509.NameAttribute(
oid=x509.oid.NameOID.COMMON_NAME,
value=u'John Doe CA',
),
])
public_key = private_key.public_key()
subject_key_identifier = x509.SubjectKeyIdentifier.from_public_key(
public_key,
)
return private_key, x509.CertificateBuilder(
subject_name=subject,
issuer_name=subject,
not_valid_before=not_before,
not_valid_after=not_after,
serial_number=x509.random_serial_number(),
public_key=public_key,
extensions=[
Extension(
subject_key_identifier,
critical=False, # "MUST mark this extension as non-critical"
),
Extension(
x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
# Dummy extension, from_issuer_subject_key_identifier accesses
# .data directly
Extension(
subject_key_identifier,
critical=False,
),
),
critical=False, # "MUST mark this extension as non-critical"
),
Extension(
x509.BasicConstraints(
ca=True,
path_length=0,
),
critical=True, # "MUST mark the extension as critical"
),
Extension(
x509.KeyUsage(
# pylint: disable=bad-whitespace
digital_signature =False,
content_commitment=False,
key_encipherment =False,
data_encipherment =False,
key_agreement =False,
key_cert_sign =True,
crl_sign =True,
encipher_only =False,
decipher_only =False,
# pylint: enable=bad-whitespace
),
critical=True, # "SHOULD mark this extension critical"
),
] + list(extension_list),
).sign(
private_key=private_key,
algorithm=hashes.SHA256(),
backend=_cryptography_backend,
)
@staticmethod
def _getKeyPair(
ca_key,
ca_crt,
extension_list=(),
not_before=None,
not_after=None,
):
"""
Build a reasonably-realistic signed cert, return key & self-signed cert.
"""
if not_before is None:
not_before = datetime.datetime.utcnow()
if not_after is None:
not_after = not_before + datetime.timedelta(10, 0)
crt_key = utils.generatePrivateKey(2048)
return crt_key, x509.CertificateBuilder(
subject_name=x509.Name([
x509.NameAttribute(
oid=x509.oid.NameOID.ORGANIZATIONAL_UNIT_NAME,
value=u'Jane Doe',
),
]),
issuer_name=ca_crt.subject,
not_valid_before=not_before,
not_valid_after=not_after,
serial_number=x509.random_serial_number(),
public_key=crt_key.public_key(),
extensions=[
Extension(
x509.KeyUsage(
# pylint: disable=bad-whitespace
digital_signature =True,
content_commitment=False,
key_encipherment =True,
data_encipherment =False,
key_agreement =False,
key_cert_sign =False,
crl_sign =False,
encipher_only =False,
decipher_only =False,
# pylint: enable=bad-whitespace
),
critical=True,
),
] + list(extension_list),
).sign(
private_key=ca_key,
algorithm=hashes.SHA256(),
backend=_cryptography_backend,
)
def _skipIfOpenSSLDoesNotSupportIPContraints(self):
ca_key, ca_crt = self._getCAKeyPair(
extension_list=[
Extension(
x509.NameConstraints(
permitted_subtrees=[
x509.IPAddress(ipaddress.ip_network(u'127.0.0.1')),
],
excluded_subtrees=None,
),
critical=True,
),
],
)
_, crt = self._getKeyPair(
ca_key=ca_key,
ca_crt=ca_crt,
)
try:
# pylint: disable=protected-access
utils._verifyCertificateChain(
cert=crt,
trusted_cert_list=[ca_crt],
crl=None,
)
# pylint: enable=protected-access
except CertificateVerificationError:
raise unittest.SkipTest('OpenSSL versoin does not support IP constraints')
def _restoreServer( def _restoreServer(
self, self,
backup_path, backup_path,
...@@ -2567,16 +2724,15 @@ class CaucaseTest(unittest.TestCase): ...@@ -2567,16 +2724,15 @@ class CaucaseTest(unittest.TestCase):
after = ssl.get_server_certificate(address) after = ssl.get_server_certificate(address)
self.assertNotEqual(before, after) self.assertNotEqual(before, after)
def testHttpNetlocIPv6(self): def _testHttpCustomNetLoc(self, netloc):
""" """
Test that it is possible to use a literal IPv6 as netloc. Breaks on OpenSSL < 1.1.0 as it lacks support for validating
This used to fail because cryptography module would reject bare IPv6 certificates with IP constraints.
address in CRL distribution point extension.
""" """
self._skipIfOpenSSLDoesNotSupportIPContraints()
self._stopServer() self._stopServer()
os.unlink(self._server_key) os.unlink(self._server_key)
os.unlink(self._server_db) os.unlink(self._server_db)
netloc = '[::1]'
port = urlparse.urlparse(self._caucase_url).port port = urlparse.urlparse(self._caucase_url).port
if port: if port:
netloc += ':%s' % port netloc += ':%s' % port
...@@ -2601,6 +2757,20 @@ class CaucaseTest(unittest.TestCase): ...@@ -2601,6 +2757,20 @@ class CaucaseTest(unittest.TestCase):
uri, = distribution_point.full_name uri, = distribution_point.full_name
self.assertEqual(uri.value, self._caucase_url + u'/cas/crl') self.assertEqual(uri.value, self._caucase_url + u'/cas/crl')
def testHttpNetlocIPv4(self):
"""
Test that it is possible to use a literal IPv4 as netloc.
"""
self._testHttpCustomNetLoc(netloc='127.0.0.1')
def testHttpNetlocIPv6(self):
"""
Test that it is possible to use a literal IPv6 as netloc.
This used to fail because cryptography module would reject bare IPv6
address in CRL distribution point extension (unlike IPv4).
"""
self._testHttpCustomNetLoc(netloc='[::1]')
def testServerFilePermissions(self): def testServerFilePermissions(self):
""" """
Check that both the sqlite database and server keys are group- and Check that both the sqlite database and server keys are group- and
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment