server install: fix KDC certificate validation in CA-less

Verify that the provided certificate has the extended key usage and subject
alternative name required for KDC.

https://pagure.io/freeipa/issue/6831
https://pagure.io/freeipa/issue/6869

Reviewed-By: Stanislav Laznicka <slaznick@redhat.com>
Reviewed-By: Martin Babinsky <mbabinsk@redhat.com>
This commit is contained in:
Jan Cholasta 2017-05-03 06:14:27 +00:00 committed by Martin Basti
parent cc572378a6
commit 3b5dbf7cdb
4 changed files with 75 additions and 12 deletions

View File

@ -24,6 +24,7 @@ import pwd
import grp import grp
import re import re
import tempfile import tempfile
from tempfile import NamedTemporaryFile
import shutil import shutil
import base64 import base64
@ -32,6 +33,7 @@ import cryptography.x509
from ipapython.dn import DN from ipapython.dn import DN
from ipapython.ipa_log_manager import root_logger from ipapython.ipa_log_manager import root_logger
from ipapython.kerberos import Principal
from ipapython import ipautil from ipapython import ipautil
from ipalib import x509 # pylint: disable=ipa-forbidden-import from ipalib import x509 # pylint: disable=ipa-forbidden-import
@ -179,6 +181,38 @@ def unparse_trust_flags(trust_flags):
return trust_flags return trust_flags
def verify_kdc_cert_validity(kdc_cert, ca_certs, realm):
pem_kdc_cert = kdc_cert.public_bytes(serialization.Encoding.PEM)
pem_ca_certs = '\n'.join(
cert.public_bytes(serialization.Encoding.PEM) for cert in ca_certs)
with NamedTemporaryFile() as kdc_file, NamedTemporaryFile() as ca_file:
kdc_file.write(pem_kdc_cert)
kdc_file.flush()
ca_file.write(pem_ca_certs)
ca_file.flush()
try:
ipautil.run(
[OPENSSL, 'verify', '-CAfile', ca_file.name, kdc_file.name])
eku = kdc_cert.extensions.get_extension_for_class(
cryptography.x509.ExtendedKeyUsage)
list(eku.value).index(
cryptography.x509.ObjectIdentifier(x509.EKU_PKINIT_KDC))
except (ipautil.CalledProcessError,
cryptography.x509.ExtensionNotFound,
ValueError):
raise ValueError("invalid for a KDC")
principal = str(Principal(['krbtgt', realm], realm))
gns = x509.process_othernames(x509.get_san_general_names(kdc_cert))
for gn in gns:
if isinstance(gn, x509.KRB5PrincipalName) and gn.name == principal:
break
else:
raise ValueError("invalid for realm %s" % realm)
class NSSDatabase(object): class NSSDatabase(object):
"""A general-purpose wrapper around a NSS cert database """A general-purpose wrapper around a NSS cert database
@ -692,3 +726,10 @@ class NSSDatabase(object):
if msg == BAD_USAGE_ERR: if msg == BAD_USAGE_ERR:
msg = 'invalid for a CA.' msg = 'invalid for a CA.'
raise ValueError(msg) raise ValueError(msg)
def verify_kdc_cert_validity(self, nickname, realm):
nicknames = self.get_trust_chain(nickname)
certs = [self.get_cert(nickname) for nickname in nicknames]
certs = [x509.load_certificate(cert, x509.DER) for cert in certs]
verify_kdc_cert_validity(certs[-1], certs[:-1], realm)

View File

@ -1001,7 +1001,7 @@ def handle_error(error, log_file_name=None):
def load_pkcs12(cert_files, key_password, key_nickname, ca_cert_files, def load_pkcs12(cert_files, key_password, key_nickname, ca_cert_files,
host_name): host_name=None, realm_name=None):
""" """
Load and verify server certificate and private key from multiple files Load and verify server certificate and private key from multiple files
@ -1066,13 +1066,21 @@ def load_pkcs12(cert_files, key_password, key_nickname, ca_cert_files,
"CA certificate %s in %s is not valid: %s" % "CA certificate %s in %s is not valid: %s" %
(subject, ", ".join(cert_files), e)) (subject, ", ".join(cert_files), e))
# Check server validity if host_name is not None:
try: try:
nssdb.verify_server_cert_validity(key_nickname, host_name) nssdb.verify_server_cert_validity(key_nickname, host_name)
except ValueError as e: except ValueError as e:
raise ScriptError( raise ScriptError(
"The server certificate in %s is not valid: %s" % "The server certificate in %s is not valid: %s" %
(", ".join(cert_files), e)) (", ".join(cert_files), e))
if realm_name is not None:
try:
nssdb.verify_kdc_cert_validity(key_nickname, realm_name)
except ValueError as e:
raise ScriptError(
"The KDC certificate in %s is not valid: %s" %
(", ".join(cert_files), e))
out_file = tempfile.NamedTemporaryFile() out_file = tempfile.NamedTemporaryFile()
out_password = ipautil.ipa_generate_password() out_password = ipautil.ipa_generate_password()

View File

@ -520,12 +520,12 @@ def install_check(installer):
if options.pkinit_pin is None: if options.pkinit_pin is None:
raise ScriptError( raise ScriptError(
"Kerberos KDC private key unlock password required") "Kerberos KDC private key unlock password required")
pkinit_pkcs12_file, pkinit_pin, _pkinit_ca_cert = load_pkcs12( pkinit_pkcs12_file, pkinit_pin, pkinit_ca_cert = load_pkcs12(
cert_files=options.pkinit_cert_files, cert_files=options.pkinit_cert_files,
key_password=options.pkinit_pin, key_password=options.pkinit_pin,
key_nickname=options.pkinit_cert_name, key_nickname=options.pkinit_cert_name,
ca_cert_files=options.ca_cert_files, ca_cert_files=options.ca_cert_files,
host_name=host_name) realm_name=realm_name)
pkinit_pkcs12_info = (pkinit_pkcs12_file.name, pkinit_pin) pkinit_pkcs12_info = (pkinit_pkcs12_file.name, pkinit_pin)
if (options.http_cert_files and options.dirsrv_cert_files and if (options.http_cert_files and options.dirsrv_cert_files and
@ -534,6 +534,13 @@ def install_check(installer):
"Apache Server SSL certificate and Directory Server SSL " "Apache Server SSL certificate and Directory Server SSL "
"certificate are not signed by the same CA certificate") "certificate are not signed by the same CA certificate")
if (options.http_cert_files and
options.pkinit_cert_files and
http_ca_cert != pkinit_ca_cert):
raise ScriptError(
"Apache Server SSL certificate and PKINIT KDC "
"certificate are not signed by the same CA certificate")
if not options.dm_password: if not options.dm_password:
dm_password = read_dm_password() dm_password = read_dm_password()

View File

@ -1069,12 +1069,12 @@ def promote_check(installer):
if options.pkinit_pin is None: if options.pkinit_pin is None:
raise ScriptError( raise ScriptError(
"Kerberos KDC private key unlock password required") "Kerberos KDC private key unlock password required")
pkinit_pkcs12_file, pkinit_pin, _pkinit_ca_cert = load_pkcs12( pkinit_pkcs12_file, pkinit_pin, pkinit_ca_cert = load_pkcs12(
cert_files=options.pkinit_cert_files, cert_files=options.pkinit_cert_files,
key_password=options.pkinit_pin, key_password=options.pkinit_pin,
key_nickname=options.pkinit_cert_name, key_nickname=options.pkinit_cert_name,
ca_cert_files=options.ca_cert_files, ca_cert_files=options.ca_cert_files,
host_name=config.host_name) realm_name=config.realm_name)
pkinit_pkcs12_info = (pkinit_pkcs12_file.name, pkinit_pin) pkinit_pkcs12_info = (pkinit_pkcs12_file.name, pkinit_pin)
if (options.http_cert_files and options.dirsrv_cert_files and if (options.http_cert_files and options.dirsrv_cert_files and
@ -1083,6 +1083,13 @@ def promote_check(installer):
"Server SSL certificate are not signed by the same" "Server SSL certificate are not signed by the same"
" CA certificate") " CA certificate")
if (options.http_cert_files and
options.pkinit_cert_files and
http_ca_cert != pkinit_ca_cert):
raise RuntimeError("Apache Server SSL certificate and PKINIT KDC "
"certificate are not signed by the same CA "
"certificate")
installutils.verify_fqdn(config.host_name, options.no_host_dns) installutils.verify_fqdn(config.host_name, options.no_host_dns)
installutils.verify_fqdn(config.master_host_name, options.no_host_dns) installutils.verify_fqdn(config.master_host_name, options.no_host_dns)