test_external_ca: switch to python-cryptography

Switch external CA generation from certutil to python-cryptography
as this way of handling the certificates should be more readable,
maintainable and extendable (e.g. extensions handling).

Also as external CA is now a separate module we can import it and
use elsewhere.

https://pagure.io/freeipa/issue/7154

Reviewed-By: Stanislav Laznicka <slaznick@redhat.com>
Reviewed-By: Christian Heimes <cheimes@redhat.com>
This commit is contained in:
Michal Reznik 2017-09-08 08:52:38 +02:00 committed by Stanislav Laznicka
parent ee87b66bd3
commit 7902fc9a06
2 changed files with 174 additions and 63 deletions

View File

@ -0,0 +1,155 @@
#
# Copyright (C) 2017 FreeIPA Contributors see COPYING for license
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import datetime
import six
class ExternalCA(object):
"""
Provide external CA for testing
"""
def create_ca(self, cn='example.test'):
"""Create root CA.
:returns: bytes -- Root CA in PEM format.
"""
self.ca_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend(),
)
self.ca_public_key = self.ca_key.public_key()
subject = self.issuer = x509.Name([
x509.NameAttribute(NameOID.COMMON_NAME, six.text_type(cn)),
])
builder = x509.CertificateBuilder()
builder = builder.subject_name(subject)
builder = builder.issuer_name(self.issuer)
builder = builder.public_key(self.ca_public_key)
builder = builder.serial_number(x509.random_serial_number())
builder = builder.not_valid_before(datetime.datetime.utcnow())
builder = builder.not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=365)
)
builder = builder.add_extension(
x509.KeyUsage(
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,
),
critical=True,
)
builder = builder.add_extension(
x509.BasicConstraints(ca=True, path_length=None),
critical=True,
)
builder = builder.add_extension(
x509.SubjectKeyIdentifier.from_public_key(self.ca_public_key),
critical=False,
)
builder = builder.add_extension(
x509.AuthorityKeyIdentifier.from_issuer_public_key(
self.ca_public_key
),
critical=False,
)
cert = builder.sign(self.ca_key, hashes.SHA256(), default_backend())
return cert.public_bytes(serialization.Encoding.PEM)
def sign_csr(self, ipa_csr):
"""Sign certificate CSR.
:param ipa_csr: CSR in PEM format.
:type ipa_csr: bytes.
:returns: bytes -- Signed CA in PEM format.
"""
csr_tbs = x509.load_pem_x509_csr(ipa_csr, default_backend())
csr_public_key = csr_tbs.public_key()
csr_subject = csr_tbs.subject
builder = x509.CertificateBuilder()
builder = builder.public_key(csr_public_key)
builder = builder.subject_name(csr_subject)
builder = builder.serial_number(x509.random_serial_number())
builder = builder.issuer_name(self.issuer)
builder = builder.not_valid_before(datetime.datetime.utcnow())
builder = builder.not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=365))
builder = builder.add_extension(
x509.KeyUsage(
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,
),
critical=True,
)
builder = builder.add_extension(
x509.SubjectKeyIdentifier.from_public_key(csr_public_key),
critical=False,
)
builder = builder.add_extension(
x509.AuthorityKeyIdentifier.from_issuer_public_key(
self.ca_public_key
),
critical=False,
)
builder = builder.add_extension(
x509.BasicConstraints(ca=True, path_length=1),
critical=True,
)
cert = builder.sign(
private_key=self.ca_key,
algorithm=hashes.SHA256(),
backend=default_backend(),
)
return cert.public_bytes(serialization.Encoding.PEM)

View File

@ -1,8 +1,6 @@
# Authors:
# Ana Krivokapic <akrivoka@redhat.com>
#
# Copyright (C) 2013 Red Hat
# see file 'COPYING' for use and warranty information
# Copyright (C) 2017 FreeIPA Contributors see COPYING for license
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -16,15 +14,12 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import base64
from ipatests.pytest_plugins.integration import tasks
from ipatests.test_integration.base import IntegrationTest
EXTERNAL_CA_KEY_ID = base64.b64encode(os.urandom(64))
IPA_CA_KEY_ID = base64.b64encode(os.urandom(64))
from ipatests.test_integration.create_external_ca import ExternalCA
class TestExternalCA(IntegrationTest):
@ -44,70 +39,31 @@ class TestExternalCA(IntegrationTest):
'--external-ca'
])
nss_db = os.path.join(self.master.config.test_dir, 'testdb')
external_cert_file = os.path.join(nss_db, 'ipa.crt')
external_ca_file = os.path.join(nss_db, 'ca.crt')
noisefile = os.path.join(self.master.config.test_dir, 'noise.txt')
pwdfile = os.path.join(self.master.config.test_dir, 'pwdfile.txt')
test_dir = self.master.config.test_dir
# Create noise and password files for NSS database
self.master.run_command('date | sha256sum > %s' % noisefile)
self.master.run_command('echo %s > %s' %
(self.master.config.admin_password, pwdfile))
# Get IPA CSR as bytes
ipa_csr = self.master.get_file_contents('/root/ipa.csr')
# Create NSS database
self.master.run_command(['mkdir', nss_db])
self.master.run_command([
'certutil', '-N',
'-d', nss_db,
'-f', pwdfile
])
external_ca = ExternalCA()
# Create root CA
root_ca = external_ca.create_ca()
# Sign CSR
ipa_ca = external_ca.sign_csr(ipa_csr)
# Create external CA
self.master.run_command([
'certutil', '-S',
'-d', nss_db,
'-f', pwdfile,
'-n', 'external',
'-s', 'CN=External CA, O=%s' % self.master.domain.name,
'-x',
'-t', 'CTu,CTu,CTu',
'-g', '2048',
'-m', '0',
'-v', '60',
'-z', noisefile,
'-2', '-1', '-5', '--extSKID'
], stdin_text='5\n9\nn\ny\n10\ny\n{}\nn\n5\n6\n7\n9\nn\n'
''.format(EXTERNAL_CA_KEY_ID))
root_ca_fname = os.path.join(test_dir, 'root_ca.crt')
ipa_ca_fname = os.path.join(test_dir, 'ipa_ca.crt')
# Sign IPA cert request using the external CA
self.master.run_command([
'certutil', '-C',
'-d', nss_db,
'-f', pwdfile,
'-c', 'external',
'-m', '1',
'-v', '60',
'-2', '-1', '-3', '--extSKID',
'-i', '/root/ipa.csr',
'-o', external_cert_file,
'-a'
], stdin_text='0\n1\n5\n9\ny\ny\n\ny\ny\n{}\n-1\n\nn\n{}\nn\n'
''.format(EXTERNAL_CA_KEY_ID, IPA_CA_KEY_ID))
# Export external CA file
self.master.run_command(
'certutil -L -d %s -n "external" -a > %s' %
(nss_db, external_ca_file)
)
# Transport certificates (string > file) to master
self.master.put_file_contents(root_ca_fname, root_ca)
self.master.put_file_contents(ipa_ca_fname, ipa_ca)
# Step 2 of ipa-server-install
self.master.run_command([
'ipa-server-install',
'-a', self.master.config.admin_password,
'-p', self.master.config.dirman_password,
'--external-cert-file', external_cert_file,
'--external-cert-file', external_ca_file
'--external-cert-file', ipa_ca_fname,
'--external-cert-file', root_ca_fname
])
# Make sure IPA server is working properly