Add support for configuring KDC certs for PKINIT

This patch adds support only for the selfsign case.
Replica support is also still missing at this stage.
This commit is contained in:
Simo Sorce
2010-10-29 16:23:21 -04:00
parent 74ba0cc7c1
commit 52a46d121b
7 changed files with 214 additions and 6 deletions

View File

@@ -24,6 +24,8 @@ app_DATA = \
bind.zone.db.template \ bind.zone.db.template \
certmap.conf.template \ certmap.conf.template \
kdc.conf.template \ kdc.conf.template \
kdc_extensions.template \
kdc_req.conf.template \
krb5.conf.template \ krb5.conf.template \
krb5.ini.template \ krb5.ini.template \
krb.con.template \ krb.con.template \

View File

@@ -12,4 +12,6 @@
dict_file = /usr/share/dict/words dict_file = /usr/share/dict/words
default_principal_flags = +preauth default_principal_flags = +preauth
; admin_keytab = /var/kerberos/krb5kdc/kadm5.keytab ; admin_keytab = /var/kerberos/krb5kdc/kadm5.keytab
pkinit_identity = FILE:/var/kerberos/krb5kdc/kdc.pem
pkinit_anchors = FILE:/var/kerberos/krb5kdc/cacert.pem
} }

View File

@@ -0,0 +1,32 @@
[ kdc_cert ]
basicConstraints=CA:FALSE
# Here are some examples of the usage of nsCertType. If it is omitted
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, keyAgreement
#Pkinit EKU
extendedKeyUsage = 1.3.6.1.5.2.3.5
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
# Copy subject details
issuerAltName=issuer:copy
# Add id-pkinit-san (pkinit subjectAlternativeName)
# Also add the KDC fqdn, for good measure.
subjectAltName=otherName:1.3.6.1.5.2.2;SEQUENCE:kdc_princ_name,DNS:${ENV::HOST_FQDN}
[kdc_princ_name]
realm = EXP:0, GeneralString:${ENV::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:${ENV::REALM}

View File

@@ -0,0 +1,14 @@
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
attributes = req_attributes
prompt = no
output_password = $PASSWORD
[ req_distinguished_name ]
$SUBJBASE
$CERTNAME
[ req_attributes ]
challengePassword = A challenge password

View File

@@ -106,14 +106,20 @@ def parse_options():
default=False, help="uninstall an existing installation") default=False, help="uninstall an existing installation")
parser.add_option("-N", "--no-ntp", dest="conf_ntp", action="store_false", parser.add_option("-N", "--no-ntp", dest="conf_ntp", action="store_false",
help="do not configure ntp", default=True) help="do not configure ntp", default=True)
parser.add_option("--no-pkinit", dest="setup_pkinit", action="store_false",
default=True, help="disables pkinit setup steps")
parser.add_option("--dirsrv_pkcs12", dest="dirsrv_pkcs12", parser.add_option("--dirsrv_pkcs12", dest="dirsrv_pkcs12",
help="PKCS#12 file containing the Directory Server SSL certificate") help="PKCS#12 file containing the Directory Server SSL certificate")
parser.add_option("--http_pkcs12", dest="http_pkcs12", parser.add_option("--http_pkcs12", dest="http_pkcs12",
help="PKCS#12 file containing the Apache Server SSL certificate") help="PKCS#12 file containing the Apache Server SSL certificate")
parser.add_option("--pkinit_pkcs12", dest="pkinit_pkcs12",
help="PKCS#12 file containing the Kerberos KDC SSL certificate")
parser.add_option("--dirsrv_pin", dest="dirsrv_pin", sensitive=True, parser.add_option("--dirsrv_pin", dest="dirsrv_pin", sensitive=True,
help="The password of the Directory Server PKCS#12 file") help="The password of the Directory Server PKCS#12 file")
parser.add_option("--http_pin", dest="http_pin", sensitive=True, parser.add_option("--http_pin", dest="http_pin", sensitive=True,
help="The password of the Apache Server PKCS#12 file") help="The password of the Apache Server PKCS#12 file")
parser.add_option("--pkinit_pin", dest="pkinit_pin",
help="The password of the Kerberos KDC PKCS#12 file")
parser.add_option("--no-host-dns", dest="no_host_dns", action="store_true", parser.add_option("--no-host-dns", dest="no_host_dns", action="store_true",
default=False, default=False,
help="Do not use DNS for hostname lookup during installation") help="Do not use DNS for hostname lookup during installation")
@@ -503,6 +509,8 @@ def main():
print " * Configure Apache (httpd)" print " * Configure Apache (httpd)"
if options.setup_dns: if options.setup_dns:
print " * Configure DNS (bind)" print " * Configure DNS (bind)"
if options.setup_pkinit:
print " * Configure the KDC to enable PKINIT"
if not options.conf_ntp: if not options.conf_ntp:
print "" print ""
print "Excluded by options:" print "Excluded by options:"
@@ -529,6 +537,12 @@ def main():
print "Aborting installation" print "Aborting installation"
return 1 return 1
# check the pkinit plugin is installed
if options.setup_pkinit:
if not krbinstance.check_pkinit_plugin():
print "Aborting installation"
return 1
# check the hostname is correctly configured, it must be as the kldap # check the hostname is correctly configured, it must be as the kldap
# utilities just use the hostname as returned by gethostbyname to set # utilities just use the hostname as returned by gethostbyname to set
# up some of the standard entries # up some of the standard entries
@@ -722,9 +736,29 @@ def main():
else: else:
ds.create_instance(ds_user, realm_name, host_name, domain_name, dm_password, self_signed_ca=options.selfsign, uidstart=options.uidstart, gidstart=options.gidstart, subject_base=options.subject, hbac_allow=not options.hbac_allow) ds.create_instance(ds_user, realm_name, host_name, domain_name, dm_password, self_signed_ca=options.selfsign, uidstart=options.uidstart, gidstart=options.gidstart, subject_base=options.subject, hbac_allow=not options.hbac_allow)
if options.pkinit_pin:
[pw_fd, pw_name] = tempfile.mkstemp()
os.write(pw_fd, options.dirsrv_pin)
os.close(pw_fd)
# Create a kerberos instance # Create a kerberos instance
krb = krbinstance.KrbInstance(fstore) krb = krbinstance.KrbInstance(fstore)
krb.create_instance(ds_user, realm_name, host_name, domain_name, dm_password, master_password) if options.pkinit_pkcs12:
pkcs12_info = (options.pkinit_pkcs12, pw_name)
krb.create_instance(ds_user, realm_name, host_name, domain_name,
dm_password, master_password,
setup_pkinit=options.setup_pkinit,
pkcs12_info=pkcs12_info,
subject_base=options.subject)
else:
krb.create_instance(ds_user, realm_name, host_name, domain_name,
dm_password, master_password,
setup_pkinit=options.setup_pkinit,
self_signed_ca=options.selfsign,
subject_base=options.subject)
if options.pkinit_pin:
os.remove(pw_name)
# The DS instance is created before the keytab, add the SSL cert we # The DS instance is created before the keytab, add the SSL cert we
# generated # generated

View File

@@ -180,6 +180,7 @@ class CertDB(object):
self.certreq_fname = None self.certreq_fname = None
self.certder_fname = None self.certder_fname = None
self.host_name = host_name self.host_name = host_name
self.subject_base = subject_base
try: try:
self.cwd = os.getcwd() self.cwd = os.getcwd()
except OSError, e: except OSError, e:
@@ -187,10 +188,9 @@ class CertDB(object):
self.self_signed_ca = ipa_self_signed() self.self_signed_ca = ipa_self_signed()
if subject_base: if not subject_base:
self.subject_format = "CN=%%s,%s" % subject_base self.subject_base = "O=IPA"
else: self.subject_format = "CN=%%s,%s" % self.subject_base
self.subject_format = "CN=%s,O=IPA"
self.cacert_name = get_ca_nickname(self.realm) self.cacert_name = get_ca_nickname(self.realm)
self.valid_months = "120" self.valid_months = "120"
@@ -937,6 +937,86 @@ class CertDB(object):
except: except:
pass pass
def create_kdc_cert(self, nickname, hostname, destdir):
"""Create a new certificate with the spcial othername encoding needed
by a KDC certificate.
nickname: the CN name set in the certificate
destdir: the location where cert and key are to be installed
destdir will contain kdc.pem if the operation is successful
"""
reqcfg = "kdc_req.conf"
extcfg = ipautil.SHARE_DIR + "kdc_extensions.template"
key_fname = destdir + "/kdckey.pem"
cert_fname = destdir + "/kdccert.pem"
key_cert_fname = destdir + "/kdc.pem"
# Setup the temp dir
self.setup_cert_request()
# Copy the CA password file because openssl apparently can't use
# the same file twice within the same command and throws an error
ca_pwd_file = self.reqdir + "pwdfile.txt"
shutil.copyfile(self.passwd_fname, ca_pwd_file)
# Extract the cacert.pem file used by openssl to sign the certs
ipautil.run(["/usr/bin/openssl", "pkcs12",
"-in", self.pk12_fname,
"-passin", "file:" + self.passwd_fname,
"-passout", "file:" + ca_pwd_file,
"-out", "cacert.pem"])
# Create the kdc key
ipautil.run(["/usr/bin/openssl", "genrsa",
"-out", key_fname, "2048"])
# Prepare a simple cert request
req_dict = dict(PASSWORD=self.gen_password(),
SUBJBASE=self.subject_base,
CERTNAME="CN="+nickname)
req_template = ipautil.SHARE_DIR + reqcfg + ".template"
conf = ipautil.template_file(req_template, req_dict)
fd = open(reqcfg, "w+")
fd.write(conf)
fd.close()
base = self.subject_base.replace(",", "/")
esc_subject = "CN=%s/%s" % (nickname, base)
ipautil.run(["/usr/bin/openssl", "req", "-new",
"-config", reqcfg,
"-subj", esc_subject,
"-key", key_fname,
"-out", "kdc.req"])
# Finally, sign the cert using the extensions file to set the
# special name
ipautil.run(["/usr/bin/openssl", "x509", "-req",
"-CA", "cacert.pem",
"-extfile", extcfg,
"-extensions", "kdc_cert",
"-passin", "file:" + ca_pwd_file,
"-set_serial", next_serial(),
"-in", "kdc.req",
"-out", cert_fname],
env = { 'REALM':self.realm, 'HOST_FQDN':hostname })
# Merge key and cert in a single file
fd = open(key_fname, "r")
key = fd.read()
fd.close()
fd = open(cert_fname, "r")
cert = fd.read()
fd.close()
fd = open(key_cert_fname, "w")
fd.write(key)
fd.write(cert)
fd.close()
os.unlink(key_fname)
os.unlink(cert_fname)
def backup_files(self): def backup_files(self):
self.fstore.backup_file(self.noise_fname) self.fstore.backup_file(self.noise_fname)
self.fstore.backup_file(self.passwd_fname) self.fstore.backup_file(self.passwd_fname)

View File

@@ -44,8 +44,21 @@ import pyasn1.codec.ber.encoder
import pyasn1.codec.ber.decoder import pyasn1.codec.ber.decoder
import struct import struct
import certs
import httpinstance
KRBMKEY_DENY_ACI = '(targetattr = "krbMKey")(version 3.0; acl "No external access"; deny (read,write,search,compare) userdn != "ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";)' KRBMKEY_DENY_ACI = '(targetattr = "krbMKey")(version 3.0; acl "No external access"; deny (read,write,search,compare) userdn != "ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";)'
def check_pkinit_plugin():
LIB32 = '/usr/lib/krb5/plugins/preauth/pkinit.so'
LIB64 = '/usr/lib64/krb5/plugins/preauth/pkinit.so'
if not os.path.exists(LIB32) and not os.path.exists(LIB64):
print "The pkinit plugin is missing"
print "Please install the 'krb5-pkinit-openssl' package and start the installation again"
return False
return True
def update_key_val_in_file(filename, key, val): def update_key_val_in_file(filename, key, val):
if os.path.exists(filename): if os.path.exists(filename):
pattern = "^[\s#]*%s\s*=\s*%s\s*" % (re.escape(key), re.escape(val)) pattern = "^[\s#]*%s\s*=\s*%s\s*" % (re.escape(key), re.escape(val))
@@ -83,6 +96,8 @@ class KrbInstance(service.Service):
self.suffix = None self.suffix = None
self.kdc_password = None self.kdc_password = None
self.sub_dict = None self.sub_dict = None
self.pkcs12_info = None
self.self_signed_ca = None
if fstore: if fstore:
self.fstore = fstore self.fstore = fstore
@@ -158,8 +173,11 @@ class KrbInstance(service.Service):
self.step("starting the KDC", self.__start_instance) self.step("starting the KDC", self.__start_instance)
self.step("configuring KDC to start on boot", self.__enable) self.step("configuring KDC to start on boot", self.__enable)
def create_instance(self, ds_user, realm_name, host_name, domain_name, admin_password, master_password): def create_instance(self, ds_user, realm_name, host_name, domain_name, admin_password, master_password, setup_pkinit=False, pkcs12_info=None, self_signed_ca=False, subject_base=None):
self.master_password = master_password self.master_password = master_password
self.pkcs12_info = pkcs12_info
self.self_signed_ca = self_signed_ca
self.subject_base = subject_base
self.__common_setup(ds_user, realm_name, host_name, domain_name, admin_password) self.__common_setup(ds_user, realm_name, host_name, domain_name, admin_password)
@@ -175,6 +193,8 @@ class KrbInstance(service.Service):
self.step("exporting the kadmin keytab", self.__export_kadmin_changepw_keytab) self.step("exporting the kadmin keytab", self.__export_kadmin_changepw_keytab)
self.step("adding the password extension to the directory", self.__add_pwd_extop_module) self.step("adding the password extension to the directory", self.__add_pwd_extop_module)
self.step("adding the kerberos master key to the directory", self.__add_master_key) self.step("adding the kerberos master key to the directory", self.__add_master_key)
if setup_pkinit:
self.step("creating X509 Certificate for PKINIT", self.__setup_pkinit)
self.__common_post_setup() self.__common_post_setup()
@@ -477,6 +497,30 @@ class KrbInstance(service.Service):
self.fstore.backup_file("/etc/sysconfig/ipa_kpasswd") self.fstore.backup_file("/etc/sysconfig/ipa_kpasswd")
update_key_val_in_file("/etc/sysconfig/ipa_kpasswd", "export KRB5_KTNAME", "/var/kerberos/krb5kdc/kpasswd.keytab") update_key_val_in_file("/etc/sysconfig/ipa_kpasswd", "export KRB5_KTNAME", "/var/kerberos/krb5kdc/kpasswd.keytab")
def __setup_pkinit(self):
if self.self_signed_ca:
ca_db = certs.CertDB(httpinstance.NSS_DIR, self.realm,
subject_base=self.subject_base)
else:
ca_db = certs.CertDB(httpinstance.NSS_DIR, self.realm,
host_name=self.fqdn,
subject_base=self.subject_base)
if self.pkcs12_info:
raise RuntimeError("Using PKCS12 Certs not supported yet\n")
else:
if self.self_signed_ca:
ca_db.create_kdc_cert("KDC-Cert", self.fqdn,
"/var/kerberos/krb5kdc")
else:
raise RuntimeError("Using PKCS12 Certs not supported yet\n")
# Finally copy the cacert in the krb directory so we don't
# have any selinux issues with the file context
shutil.copyfile("/usr/share/ipa/html/ca.crt",
"/var/kerberos/krb5kdc/cacert.pem")
def uninstall(self): def uninstall(self):
if self.is_configured(): if self.is_configured():
self.print_msg("Unconfiguring %s" % self.service_name) self.print_msg("Unconfiguring %s" % self.service_name)