URL: https://github.com/freeipa/freeipa/pull/1575
Author: Rezney
 Title: #1575: [Backport][ipa-4-5] - External ca py crypto backport
Action: opened

PR body:
"""
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 <slazn...@redhat.com>
Reviewed-By: Christian Heimes <chei...@redhat.com>
"""

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/1575/head:pr1575
git checkout pr1575
From b6361b91de213fbc10e6030cbf4356dee77e845c Mon Sep 17 00:00:00 2001
From: Michal Reznik <mrez...@redhat.com>
Date: Fri, 8 Sep 2017 08:52:38 +0200
Subject: [PATCH] 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 <slazn...@redhat.com>
Reviewed-By: Christian Heimes <chei...@redhat.com>
---
 ipatests/test_integration/create_external_ca.py | 155 ++++++++++++++++++++++++
 ipatests/test_integration/test_external_ca.py   |  82 +++----------
 2 files changed, 174 insertions(+), 63 deletions(-)
 create mode 100644 ipatests/test_integration/create_external_ca.py

diff --git a/ipatests/test_integration/create_external_ca.py b/ipatests/test_integration/create_external_ca.py
new file mode 100644
index 0000000000..dc4ef048cc
--- /dev/null
+++ b/ipatests/test_integration/create_external_ca.py
@@ -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)
diff --git a/ipatests/test_integration/test_external_ca.py b/ipatests/test_integration/test_external_ca.py
index adb857b338..e3c44100e4 100644
--- a/ipatests/test_integration/test_external_ca.py
+++ b/ipatests/test_integration/test_external_ca.py
@@ -1,8 +1,6 @@
-# Authors:
-#   Ana Krivokapic <akriv...@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):
@@ -45,70 +40,31 @@ def test_external_ca(self):
             '--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
-        ])
-
-        # 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))
+        external_ca = ExternalCA()
+        # Create root CA
+        root_ca = external_ca.create_ca()
+        # Sign CSR
+        ipa_ca = external_ca.sign_csr(ipa_csr)
 
-        # 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))
+        root_ca_fname = os.path.join(test_dir, 'root_ca.crt')
+        ipa_ca_fname = os.path.join(test_dir, 'ipa_ca.crt')
 
-        # 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
_______________________________________________
FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org
To unsubscribe send an email to freeipa-devel-le...@lists.fedorahosted.org

Reply via email to