URL: https://github.com/freeipa/freeipa/pull/769
Author: Rezney
 Title: #769: test_caless: add pkinit option and test it
Action: opened

PR body:
"""
What was done?

~~~
1.) caless-create-pki

The script was kind of merged with 
https://github.com/freeipa/freeipa-tools/blob/master/makepki.sh. Standa took 
care of PKINIT certificates generation so that write_chain() function was 
introduced which handles cert chain in the pkcs12 files and also reverse chanin 
order for openssl command.

Then gen_pkinit_extensions() and gen_pkinit_cert() are handling the PKINIT 
certificate generation. See 
https://web.mit.edu/kerberos/krb5-1.13/doc/admin/pkinit.html for details.

2.) test_caless.py

As the tests are currently failing due to the pkinit option not provided 
"pkinit_pin, pkinit_pkcs12_exists and pkinit_pkcs12" parameters were added to 
both install_server() and prepare_replica methods and particular options are 
added to installator. Then copy_pkinit() is handling pkinit certs transfer.

TestPKINIT class contains test_server_replica_install_pkinit() test which 
checks both server and replica install with pkinit for a starter.

Eventually added "raiseonerr=False" to ipa_certs_cleanup() cause tests were 
failing there but that whole workaround for ticket 4639 will be removed in 
different commit.
~~~

What can be improved? (at least what I am aware of)

~~~
Currently pkinit certificates are not inside nss db so we copy it separately 
(we could also move it to certdir and copy as whole). Tried to put it there 
with pk12util but the certs were getting nicknames from openssl friendly names 
(I guess). Added -name parameter to "openssl pkcs12 -export" command and the 
nicknames were fine (e.g. "ca1/pkinit-server" after certuril -L) however after 
the "caless-create-pki" script was done all pkinit cert nicknames were just 
prefixed with "ca1/" (instead of ca1/ ca2/ etc.).
~~~

Issues found:

~~~
Replica install with pkinit is not failing anymore with "Certificate issuance 
failed (CA_UNREACHABLE)", however the ERROR message is still presented:


[ipa.ipatests.pytest_plugins.integration.host.Host.vm-021.cmd26]   [1/1]: 
installing X509 Certificate for PKINIT
[ipa.ipatests.pytest_plugins.integration.host.Host.vm-021.cmd26] ipa         : 
ERROR    PKINIT certificate request failed: Certificate issuance failed 
(CA_UNREACHABLE)
[ipa.ipatests.pytest_plugins.integration.host.Host.vm-021.cmd26] ipa         : 
ERROR    Failed to configure PKINIT
[ipa.ipatests.pytest_plugins.integration.host.Host.vm-021.cmd26] Done 
configuring Kerberos KDC (krb5kdc).
[ipa.ipatests.pytest_plugins.integration.host.Host.vm-021.cmd26] Applying LDAP 
updates
[ipa.ipatests.pytest_plugins.integration.host.Host.vm-021.cmd26] Upgrading 
IPA:. Estimated time: 1 minute 30 seconds
[ipa.ipatests.pytest_plugins.integration.host.Host.vm-021.cmd26]   [1/9]: 
stopping directory server
~~~
"""

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/769/head:pr769
git checkout pr769
From e8fbb3de2436936370b3be3df5c5dfbd59670333 Mon Sep 17 00:00:00 2001
From: Michal Reznik <mrez...@redhat.com>
Date: Tue, 9 May 2017 16:39:45 +0200
Subject: [PATCH] test_caless: add pkinit option and test it

change "caless-create-pki" so pkinit certificates can be
generated.

See https://web.mit.edu/kerberos/krb5-1.13/doc/admin/pkinit.html for details.

add pkinit option to the ipa installer and test both master and replica
install with pkinit.

Signed-off-by: Michal Reznik <mrez...@redhat.com>
---
 .../test_integration/scripts/caless-create-pki     | 112 ++++++++++++++++-----
 ipatests/test_integration/test_caless.py           |  77 +++++++++++---
 2 files changed, 150 insertions(+), 39 deletions(-)

diff --git a/ipatests/test_integration/scripts/caless-create-pki b/ipatests/test_integration/scripts/caless-create-pki
index 8928e95..816e7dc 100644
--- a/ipatests/test_integration/scripts/caless-create-pki
+++ b/ipatests/test_integration/scripts/caless-create-pki
@@ -1,14 +1,29 @@
 #!/bin/bash -e
 
-profile_ca=(-t CT,C,C -v 120)
-profile_server=(-t ,, -v 12)
-
-crl_path=${crl_path-$(readlink -f $dbdir)}
-
-serial_number=0
+profile_ca_request_options=(-1 -2 -4)
+profile_ca_request_input="\$'0\n1\n5\n6\n9\ny\ny\n\ny\n1\n7\nfile://'\$(readlink -f \$dbdir)/\$ca.crl\$'\n-1\n-1\n-1\nn\nn\n'"
+profile_ca_create_options=(-v 120)
+profile_ca_add_options=(-t CT,C,C)
+profile_server_request_options=(-4)
+profile_server_request_input="\$'1\n7\nfile://'\$(readlink -f \$dbdir)/\$ca.crl\$'\n-1\n-1\n-1\nn\nn\n'"
+profile_server_create_options=(-v 12)
+profile_server_add_options=(-t ,,)
+
+write_chain() {
+    local nick="$1"
+
+    chain=`certutil -O -d $dbdir -n "$nick" |
+             sed -e '/^\s*$/d' -e "s/\s*\"\(.*\)\" \[.*/\1/g"`
+
+    while read -r name; do
+        # OpenSSL requires a reverse order to what we get from NSS
+        echo -e "`certutil -L -d "$dbdir" -n "$name" -a`\n`cat $dbdir/$nick.pem`
+        " > "$dbdir/$nick.pem"
+    done <<< "$chain"
+}
 
 gen_cert() {
-    local profile="$1" nick="$2" subject="$3" ca options pwfile noise csr crt
+    local profile="$1" nick="$2" subject="$3" ca request_options request_input create_options serial add_options pwfile noise csr crt
     shift 3
 
     echo "gen_cert(profile=$profile nick=$nick subject=$subject)"
@@ -18,13 +33,20 @@ gen_cert() {
         ca="$nick"
     fi
 
-    eval "options=(\"\${profile_$profile[@]}\")"
+    eval "request_options=(\"\${profile_${profile}_request_options[@]}\")"
+    eval "eval request_input=\"\${profile_${profile}_request_input}\""
+
+    eval "create_options=(\"\${profile_${profile}_create_options[@]}\")"
     if [ "$ca" = "$nick" ]; then
-        options=("${options[@]}" -x -m 1)
+        create_options=("${create_options[@]}" -x -m 1)
     else
-        options=("${options[@]}" -c "$ca")
+        eval "serial_${ca//\//_}=\$((\${serial_${ca//\//_}:-1}+1))"
+        eval "serial=\$serial_${ca//\//_}"
+        create_options=("${create_options[@]}" -c "$ca" -m "$serial")
     fi
 
+    eval "add_options=(\"\${profile_${profile}_add_options[@]}\")"
+
     pwfile="$(mktemp)"
     echo "$dbpassword" >"$pwfile"
 
@@ -38,22 +60,14 @@ gen_cert() {
 
     csr="$(mktemp)"
     crt="$(mktemp)"
-    certutil -R -d "$dbdir" -s "$subject" -f "$pwfile" -z "$noise" -o "$csr" -4 -2 >/dev/null <<EOF
-y
-0
-N
-1
-7
-file://$crl_path/$ca.crl
--1
--1
--1
-n
-n
-EOF
-    serial_number=$(($serial_number+1))
-    certutil -C -d "$dbdir" -f "$pwfile" -m "$serial_number" -i "$csr" -o "$crt" "${options[@]}" "$@"
-    certutil -A -d "$dbdir" -n "$nick" -f "$pwfile" -i "$crt" "${options[@]}"
+
+    certutil -R -d "$dbdir" -s "$subject" -f "$pwfile" -z "$noise" -o "$csr" "${request_options[@]}" >/dev/null <<<"$request_input"
+    certutil -C -d "$dbdir" -f "$pwfile" -i "$csr" -o "$crt" "${create_options[@]}" "$@"
+    certutil -A -d "$dbdir" -n "$nick" -f "$pwfile" -i "$crt" "${add_options[@]}"
+
+    mkdir -p "$(dirname $dbdir/$nick.pem)"
+    write_chain "$nick"
+    pk12util -o "$dbdir/$nick.p12" -n "$nick" -d "$dbdir" -k "$pwfile" -w "$pwfile"
 
     rm -f "$pwfile" "$noise" "$csr" "$crt"
 }
@@ -102,6 +116,49 @@ gen_server_certs() {
     revoke_cert "$nick-revoked"
 }
 
+gen_pkinit_extensions() {
+   echo "[kdc_cert]
+basicConstraints=CA:FALSE
+keyUsage=nonRepudiation,digitalSignature,keyEncipherment,keyAgreement
+extendedKeyUsage=TLS Web Server Authentication, 1.3.6.1.5.2.3.5
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+issuerAltName=issuer:copy
+subjectAltName=otherName:1.3.6.1.5.2.2;SEQUENCE:kdc_princ_name
+[kdc_princ_name]
+realm=EXP:0,GeneralString:${realm}
+principal_name=EXP:1,SEQUENCE:kdc_principal_seq
+[kdc_principal_seq]
+name_type=EXP:0,INTEGER:1
+name_string=EXP:1,SEQUENCE:kdc_principals
+[kdc_principals]
+princ1=GeneralString:krbtgt
+princ2=GeneralString:${realm}" > "$dbdir/ext.kdc"
+}
+
+gen_pkinit_cert() {
+    local nick="$1" subj="$2" outname="$3"
+    shift 3
+
+    openssl genrsa -out "$dbdir/$nick/kdc.key" 2048 > /dev/null
+    openssl req -new -out "$dbdir/$nick/kdc.req" -key "$dbdir/$nick/kdc.key" \
+    -subj "$subj"
+
+    openssl pkcs12 -in "$dbdir/$nick.p12" -passin "pass:$dbpassword" \
+    -nodes -nocerts -out "$dbdir/$nick.key" > /dev/null
+
+    openssl x509 -req -in "$dbdir/$nick/kdc.req" \
+    -CAkey "$dbdir/$nick.key" -CA "$dbdir/$nick.pem" \
+    -out "$dbdir/$nick/kdc.crt" -days 365 \
+    -extfile "$dbdir/ext.kdc" -extensions kdc_cert -CAcreateserial > /dev/null
+
+    rm "$dbdir/$nick/kdc.req"
+
+    openssl pkcs12 -export -in "$dbdir/$nick/kdc.crt" \
+    -inkey "$dbdir/$nick/kdc.key" -password "pass:$dbpassword" \
+    -out "$dbdir/$nick/$outname.p12" -chain -CAfile "$dbdir/$nick.pem"
+}
+
 gen_subtree() {
     local nick="$1" org="$2"
     shift 2
@@ -110,6 +167,8 @@ gen_subtree() {
 
     gen_cert ca "$nick" "CN=CA,O=$org" "$@"
     gen_cert server "$nick/wildcard" "CN=*.$domain,O=$org"
+    gen_pkinit_cert "$nick" "/O=$realm/CN=$server1" "pkinit-server"
+    gen_pkinit_cert "$nick" "/O=$realm/CN=$server2" "pkinit-replica"
     gen_server_certs "$nick/server" "$server1" "$org"
     gen_server_certs "$nick/replica" "$server2" "$org"
     gen_server_certs "$nick/client" "$client" "$org"
@@ -117,6 +176,7 @@ gen_subtree() {
 
 gen_cert server server-selfsign "CN=$server1,O=Self-signed"
 gen_cert server replica-selfsign "CN=$server2,O=Self-signed"
+gen_pkinit_extensions
 gen_cert server noca "CN=$server1,O=No-CA"
 gen_subtree ca1 'Example Organization'
 gen_subtree ca1/subca 'Subsidiary Example Organization'
diff --git a/ipatests/test_integration/test_caless.py b/ipatests/test_integration/test_caless.py
index d7692ec..0438472 100644
--- a/ipatests/test_integration/test_caless.py
+++ b/ipatests/test_integration/test_caless.py
@@ -61,11 +61,11 @@ def ipa_certs_cleanup(host):
                      raiseonerr=False)
     # A workaround for https://fedorahosted.org/freeipa/ticket/4639
     result = host.run_command(['certutil', '-L', '-d',
-                               paths.HTTPD_ALIAS_DIR])
+                               paths.HTTPD_ALIAS_DIR], raiseonerr=False)
     for rawcert in result.stdout_text.split('\n')[4: -1]:
         cert = rawcert.split('    ')[0]
         host.run_command(['certutil', '-D', '-d', paths.HTTPD_ALIAS_DIR,
-                          '-n', cert])
+                          '-n', cert], raiseonerr=False)
 
 
 def server_install_teardown(func):
@@ -123,10 +123,12 @@ def install(cls, mh):
             client_hostname = 'unused-client.test'
         cls.env = {
             'domain': cls.master.domain.name,
+            'realm': cls.master.domain.name.upper(),
             'server1': cls.master.hostname,
             'server2': replica_hostname,
             'client': client_hostname,
             'dbdir': 'nssdb',
+            'dbpassword': cls.cert_password,
             'crl_path': cls.crl_path,
             'dirman_password': cls.master.config.dirman_password,
         }
@@ -154,15 +156,21 @@ def uninstall(cls, mh):
     def install_server(cls, host=None,
                        http_pkcs12='server.p12', dirsrv_pkcs12='server.p12',
                        http_pkcs12_exists=True, dirsrv_pkcs12_exists=True,
-                       http_pin=_DEFAULT, dirsrv_pin=_DEFAULT,
-                       root_ca_file='root.pem', unattended=True,
-                       stdin_text=None):
+                       http_pin=_DEFAULT, dirsrv_pin=_DEFAULT, pkinit_pin=None,
+                       root_ca_file='root.pem', pkinit_pkcs12_exists=False,
+                       pkinit_pkcs12=None, unattended=True, stdin_text=None):
         """Install a CA-less server
 
         Return value is the remote ipa-server-install command
         """
         if host is None:
             host = cls.master
+
+        extra_args = ['--http-cert-file', http_pkcs12,
+                      '--dirsrv-cert-file', dirsrv_pkcs12,
+                      '--ca-cert-file', root_ca_file,
+                      '--ip-address', host.ip]
+
         if http_pin is _DEFAULT:
             http_pin = cls.cert_password
         if dirsrv_pin is _DEFAULT:
@@ -173,6 +181,14 @@ def install_server(cls, host=None,
             files_to_copy.append(http_pkcs12)
         if dirsrv_pkcs12_exists:
             files_to_copy.append(dirsrv_pkcs12)
+
+        if pkinit_pkcs12_exists and not pkinit_pin:
+            pkinit_pin = cls.cert_password
+        if pkinit_pkcs12:
+            extra_args.extend(['--pkinit-cert-file', pkinit_pkcs12,
+                               '--pkinit-pin', pkinit_pin ])
+        else:
+            extra_args.append('--no-pkinit')
         for filename in set(files_to_copy):
             cls.copy_cert(host, filename)
 
@@ -181,11 +197,6 @@ def install_server(cls, host=None,
         host.run_command(args + ["ca1"], raiseonerr=False)
         host.run_command(args + ["ca1/server"], raiseonerr=False)
 
-        extra_args = ['--http-cert-file', http_pkcs12,
-                      '--dirsrv-cert-file', dirsrv_pkcs12,
-                      '--ca-cert-file', root_ca_file,
-                      '--ip-address', host.ip]
-
         if http_pin is not None:
             extra_args.extend(['--http-pin', http_pin])
         if dirsrv_pin is not None:
@@ -199,13 +210,20 @@ def install_server(cls, host=None,
     def copy_cert(cls, host, filename):
         host.transport.put_file(os.path.join(cls.cert_dir, filename),
                                 os.path.join(host.config.test_dir, filename))
+    @classmethod
+    def copy_pkinit(cls, host, pkinit_nick):
+        filename = pkinit_nick.split('/')[-1]
+        host.transport.put_file(os.path.join(cls.cert_dir, 'nssdb', pkinit_nick),
+                                os.path.join(host.config.test_dir, filename))
+
 
     def prepare_replica(self, _replica_number=0, replica=None, master=None,
                         http_pkcs12='replica.p12', dirsrv_pkcs12='replica.p12',
                         http_pkcs12_exists=True, dirsrv_pkcs12_exists=True,
-                        http_pin=_DEFAULT, dirsrv_pin=_DEFAULT,
-                        root_ca_file='root.pem', unattended=True,
-                        stdin_text=None, domain_level=None):
+                        http_pin=_DEFAULT, dirsrv_pin=_DEFAULT, pkinit_pin=None,
+                        root_ca_file='root.pem', pkinit_pkcs12_exists=False,
+                        pkinit_pkcs12=None, unattended=True, stdin_text=None,
+                        domain_level=None):
         """Prepare a CA-less replica
 
         Puts the bundle file into test_dir on the replica if successful,
@@ -249,6 +267,15 @@ def prepare_replica(self, _replica_number=0, replica=None, master=None,
         if dirsrv_pkcs12_exists:
             extra_args.extend(['--dirsrv-cert-file', dirsrv_pkcs12])
 
+        if pkinit_pkcs12_exists and not pkinit_pin:
+            pkinit_pin = self.cert_password
+        if pkinit_pkcs12 and domain_level != DOMAIN_LEVEL_0:
+            extra_args.extend(['--pkinit-cert-file', pkinit_pkcs12,
+                               '--pkinit-pin', pkinit_pin ])
+        else:
+            extra_args.append('--no-pkinit')
+
+
         if http_pin is not None:
             extra_args.extend(['--http-pin', http_pin])
         if dirsrv_pin is not None:
@@ -1472,3 +1499,27 @@ def test_ds_old_options(self):
         result = self.certinstall('d', 'ca1/server',
                                   args=args, stdin_text=stdin_text)
         assert_error(result, "no such option: --dirsrv-pin")
+
+
+class TestPKINIT(CALessBase):
+    num_replicas = 1
+
+    @classmethod
+    def install(cls, mh):
+        super(TestPKINIT, cls).install(mh)
+        cls.export_pkcs12('ca1/server')
+        cls.copy_pkinit(cls.master, 'ca1/pkinit-server.p12')
+        with open(cls.pem_filename, 'w') as f:
+            f.write(cls.get_pem('ca1'))
+        result = cls.install_server(pkinit_pkcs12='pkinit-server.p12',
+                                    pkinit_pkcs12_exists=True)
+        assert result.returncode == 0
+
+    @replica_install_teardown
+    def test_server_replica_install_pkinit(self):
+        self.export_pkcs12('ca1/replica', filename='replica.p12')
+        self.copy_pkinit(self.replicas[0], 'ca1/pkinit-replica.p12')
+        result = self.prepare_replica(pkinit_pkcs12='pkinit-replica.p12',
+                                      pkinit_pkcs12_exists=True)
+        assert result.returncode == 0
+        self.verify_installation()
-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to