From cea42421bc17317f69143061173e8b9a5c0e153e Mon Sep 17 00:00:00 2001 From: Michal Reznik Date: May 19 2017 10:38:54 +0000 Subject: 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. https://pagure.io/freeipa/issue/6854 Signed-off-by: Michal Reznik Reviewed-By: Stanislav Laznicka --- diff --git a/ipatests/test_integration/scripts/caless-create-pki b/ipatests/test_integration/scripts/caless-create-pki index 8928e95..dbcdb3e 100644 --- a/ipatests/test_integration/scripts/caless-create-pki +++ b/ipatests/test_integration/scripts/caless-create-pki @@ -1,14 +1,32 @@ #!/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 +# +# Copyright (C) 2017 FreeIPA Contributors see COPYING for license +# + +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 +36,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 +63,14 @@ gen_cert() { csr="$(mktemp)" crt="$(mktemp)" - certutil -R -d "$dbdir" -s "$subject" -f "$pwfile" -z "$noise" -o "$csr" -4 -2 >/dev/null </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 +119,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 +170,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 +179,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..dde592f 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 @@ class CALessBase(IntegrationTest): 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,8 +156,9 @@ class CALessBase(IntegrationTest): 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, + http_pin=_DEFAULT, dirsrv_pin=_DEFAULT, pkinit_pin=None, + root_ca_file='root.pem', pkinit_pkcs12_exists=False, + pkinit_pkcs12='pkinit-server.p12', unattended=True, stdin_text=None): """Install a CA-less server @@ -163,16 +166,29 @@ class CALessBase(IntegrationTest): """ 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: dirsrv_pin = cls.cert_password + if pkinit_pin is _DEFAULT: + pkinit_pin = cls.cert_password tasks.prepare_host(host) files_to_copy = ['root.pem'] if http_pkcs12_exists: files_to_copy.append(http_pkcs12) if dirsrv_pkcs12_exists: files_to_copy.append(dirsrv_pkcs12) + + if pkinit_pkcs12_exists: + extra_args.extend(['--pkinit-cert-file', pkinit_pkcs12]) + else: + extra_args.append('--no-pkinit') for filename in set(files_to_copy): cls.copy_cert(host, filename) @@ -181,15 +197,12 @@ class CALessBase(IntegrationTest): 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: extra_args.extend(['--dirsrv-pin', dirsrv_pin]) + if pkinit_pin is not None: + extra_args.extend(['--pkinit-pin', dirsrv_pin]) return tasks.install_master(host, extra_args=extra_args, unattended=unattended, stdin_text=stdin_text, @@ -199,12 +212,19 @@ class CALessBase(IntegrationTest): 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_cert(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, + http_pin=_DEFAULT, dirsrv_pin=_DEFAULT, pkinit_pin=None, + root_ca_file='root.pem', pkinit_pkcs12_exists=False, + pkinit_pkcs12='pkinit-replica.p12', unattended=True, stdin_text=None, domain_level=None): """Prepare a CA-less replica @@ -221,6 +241,9 @@ class CALessBase(IntegrationTest): http_pin = self.cert_password if dirsrv_pin is _DEFAULT: dirsrv_pin = self.cert_password + if pkinit_pin is _DEFAULT: + pkinit_pin = self.cert_password + if domain_level is None: domain_level = tasks.domainlevel(master) files_to_copy = ['root.pem'] @@ -248,11 +271,18 @@ class CALessBase(IntegrationTest): extra_args.extend(['--http-cert-file', http_pkcs12]) if dirsrv_pkcs12_exists: extra_args.extend(['--dirsrv-cert-file', dirsrv_pkcs12]) + if pkinit_pkcs12_exists and domain_level != DOMAIN_LEVEL_0: + extra_args.extend(['--pkinit-cert-file', pkinit_pkcs12]) + 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: extra_args.extend(['--dirsrv-pin', dirsrv_pin]) + if pkinit_pin is not None: + extra_args.extend(['--pkinit-pin', dirsrv_pin]) + if domain_level == DOMAIN_LEVEL_0: result = tasks.replica_prepare(master, replica, extra_args=extra_args, @@ -1472,3 +1502,27 @@ class TestCertinstall(CALessBase): 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_cert(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_exists=True, + pkinit_pin=_DEFAULT) + 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_cert(self.replicas[0], 'ca1/pkinit-replica.p12') + result = self.prepare_replica(pkinit_pkcs12_exists=True, + pkinit_pin=_DEFAULT) + assert result.returncode == 0 + self.verify_installation()