Make CA-less ipa-server-install option --root-ca-file optional.

The CA cert specified by --root-ca-file option must always be the CA cert of
the CA which issued the server certificates in the PKCS#12 files. As the cert
is not actually user selectable, use CA cert from the PKCS#12 files by default
if it is present.

Document --root-ca-file in ipa-server-install man page.

https://fedorahosted.org/freeipa/ticket/4457

Reviewed-By: Petr Viktorin <pviktori@redhat.com>
This commit is contained in:
Jan Cholasta 2014-08-05 09:06:39 +02:00 committed by Petr Viktorin
parent 418ce870bf
commit 6ad8c464a4
6 changed files with 59 additions and 44 deletions

View File

@ -69,7 +69,7 @@ from ipapython import sysrestore
from ipapython.ipautil import * from ipapython.ipautil import *
from ipapython import ipautil from ipapython import ipautil
from ipapython import dogtag from ipapython import dogtag
from ipalib import api, errors, util from ipalib import api, errors, util, x509
from ipapython.config import IPAOptionParser from ipapython.config import IPAOptionParser
from ipalib.util import validate_domain_name from ipalib.util import validate_domain_name
from ipalib.constants import CACERT from ipalib.constants import CACERT
@ -223,7 +223,7 @@ def parse_options():
cert_group.add_option("--pkinit_pin", dest="pkinit_pin", cert_group.add_option("--pkinit_pin", dest="pkinit_pin",
help="The password of the Kerberos KDC PKCS#12 file") help="The password of the Kerberos KDC PKCS#12 file")
cert_group.add_option("--root-ca-file", dest="root_ca_file", cert_group.add_option("--root-ca-file", dest="root_ca_file",
help="PEM file with root CA certificate(s) to trust") help="PEM file containing the CA certificate for the PKCS#12 files")
cert_group.add_option("--subject", action="callback", callback=subject_callback, cert_group.add_option("--subject", action="callback", callback=subject_callback,
type="string", type="string",
help="The certificate subject base (default O=<realm-name>)") help="The certificate subject base (default O=<realm-name>)")
@ -316,10 +316,6 @@ def parse_options():
if options.pkinit_pkcs12 and options.pkinit_pin is None: if options.pkinit_pkcs12 and options.pkinit_pin is None:
parser.error("You must specify --pkinit_pin with --pkinit_pkcs12") parser.error("You must specify --pkinit_pin with --pkinit_pkcs12")
if options.dirsrv_pkcs12 and not options.root_ca_file:
parser.error(
"--root-ca-file must be given with the PKCS#12 options.")
if (options.external_cert_file or options.external_ca_file) and options.dirsrv_pkcs12: if (options.external_cert_file or options.external_ca_file) and options.dirsrv_pkcs12:
parser.error( parser.error(
"PKCS#12 options cannot be used with the external CA options.") "PKCS#12 options cannot be used with the external CA options.")
@ -920,7 +916,7 @@ def main():
if options.http_pin is None: if options.http_pin is None:
sys.exit("%s unlock password required" % options.http_pkcs12) sys.exit("%s unlock password required" % options.http_pkcs12)
http_pkcs12_info = (options.http_pkcs12, options.http_pin) http_pkcs12_info = (options.http_pkcs12, options.http_pin)
http_cert_name = installutils.check_pkcs12( http_ca_cert = installutils.check_pkcs12(
http_pkcs12_info, ca_file, host_name) http_pkcs12_info, ca_file, host_name)
if options.dirsrv_pkcs12: if options.dirsrv_pkcs12:
@ -931,7 +927,7 @@ def main():
if options.dirsrv_pin is None: if options.dirsrv_pin is None:
sys.exit("%s unlock password required" % options.dirsrv_pkcs12) sys.exit("%s unlock password required" % options.dirsrv_pkcs12)
dirsrv_pkcs12_info = (options.dirsrv_pkcs12, options.dirsrv_pin) dirsrv_pkcs12_info = (options.dirsrv_pkcs12, options.dirsrv_pin)
dirsrv_cert_name = installutils.check_pkcs12( dirsrv_ca_cert = installutils.check_pkcs12(
dirsrv_pkcs12_info, ca_file, host_name) dirsrv_pkcs12_info, ca_file, host_name)
if options.pkinit_pkcs12: if options.pkinit_pkcs12:
@ -943,6 +939,11 @@ def main():
sys.exit("%s unlock password required" % options.pkinit_pkcs12) sys.exit("%s unlock password required" % options.pkinit_pkcs12)
pkinit_pkcs12_info = (options.pkinit_pkcs12, options.pkinit_pin) pkinit_pkcs12_info = (options.pkinit_pkcs12, options.pkinit_pin)
if (options.http_pkcs12 and options.dirsrv_pkcs12 and
http_ca_cert != dirsrv_ca_cert):
sys.exit("%s and %s are not signed by the same CA certificate" %
(options.http_pkcs12, options.dirsrv_pkcs12))
if not options.dm_password: if not options.dm_password:
dm_password = read_dm_password() dm_password = read_dm_password()
@ -1073,8 +1074,7 @@ def main():
ntp.create_instance() ntp.create_instance()
if options.dirsrv_pkcs12: if options.dirsrv_pkcs12:
ds = dsinstance.DsInstance(fstore=fstore, ds = dsinstance.DsInstance(fstore=fstore)
cert_nickname=dirsrv_cert_name)
ds.create_instance(realm_name, host_name, domain_name, ds.create_instance(realm_name, host_name, domain_name,
dm_password, dirsrv_pkcs12_info, dm_password, dirsrv_pkcs12_info,
idstart=options.idstart, idmax=options.idmax, idstart=options.idstart, idmax=options.idmax,
@ -1128,7 +1128,7 @@ def main():
ca.publish_ca_cert(CACERT) ca.publish_ca_cert(CACERT)
else: else:
# Put the CA cert where other instances expect it # Put the CA cert where other instances expect it
shutil.copy(options.root_ca_file, CACERT) x509.write_certificate(http_ca_cert, CACERT)
os.chmod(CACERT, 0444) os.chmod(CACERT, 0444)
# we now need to enable ssl on the ds # we now need to enable ssl on the ds

View File

@ -118,6 +118,9 @@ The password of the Apache Server PKCS#12 file
\fB\-\-pkinit_pin\fR=\fIPKINIT_PIN\fR \fB\-\-pkinit_pin\fR=\fIPKINIT_PIN\fR
The password of the Kerberos KDC PKCS#12 file The password of the Kerberos KDC PKCS#12 file
.TP .TP
\fB\-\-root\-ca\-file\fR=\fIFILE\fR
PEM file containing the CA certificate of the CA which issued the Directory Server, Apache Server and Kerberos KDC SSL certificates. Use this option if the CA certificate is not present in the PKCS#12 files.
.TP
\fB\-\-subject\fR=\fISUBJECT\fR \fB\-\-subject\fR=\fISUBJECT\fR
The certificate subject base (default O=REALM.NAME) The certificate subject base (default O=REALM.NAME)

View File

@ -223,14 +223,14 @@ info: IPA V2.0
class DsInstance(service.Service): class DsInstance(service.Service):
def __init__(self, realm_name=None, domain_name=None, dm_password=None, def __init__(self, realm_name=None, domain_name=None, dm_password=None,
fstore=None, cert_nickname='Server-Cert'): fstore=None):
service.Service.__init__(self, "dirsrv", service.Service.__init__(self, "dirsrv",
service_desc="directory server", service_desc="directory server",
dm_password=dm_password, dm_password=dm_password,
ldapi=False, ldapi=False,
autobind=service.DISABLED autobind=service.DISABLED
) )
self.nickname = cert_nickname self.nickname = 'Server-Cert'
self.dm_password = dm_password self.dm_password = dm_password
self.realm = realm_name self.realm = realm_name
self.sub_dict = None self.sub_dict = None
@ -641,24 +641,23 @@ class DsInstance(service.Service):
raise RuntimeError("Could not find a suitable server cert in import in %s" % self.pkcs12_info[0]) raise RuntimeError("Could not find a suitable server cert in import in %s" % self.pkcs12_info[0])
# We only handle one server cert # We only handle one server cert
nickname = server_certs[0][0] self.nickname = server_certs[0][0]
self.dercert = dsdb.get_cert_from_db(nickname, pem=False) self.dercert = dsdb.get_cert_from_db(self.nickname, pem=False)
else: else:
nickname = self.nickname
cadb = certs.CertDB(self.realm, host_name=self.fqdn, subject_base=self.subject_base) cadb = certs.CertDB(self.realm, host_name=self.fqdn, subject_base=self.subject_base)
# FIXME, need to set this nickname in the RA plugin # FIXME, need to set this nickname in the RA plugin
cadb.export_ca_cert('ipaCert', False) cadb.export_ca_cert('ipaCert', False)
dsdb.create_from_cacert(cadb.cacert_fname, passwd=None) dsdb.create_from_cacert(cadb.cacert_fname, passwd=None)
self.dercert = dsdb.create_server_cert( self.dercert = dsdb.create_server_cert(
nickname, self.fqdn, cadb) self.nickname, self.fqdn, cadb)
dsdb.create_pin_file() dsdb.create_pin_file()
self.cacert_name = dsdb.cacert_name self.cacert_name = dsdb.cacert_name
if self.ca_is_configured: if self.ca_is_configured:
dsdb.track_server_cert( dsdb.track_server_cert(
nickname, self.principal, dsdb.passwd_fname, self.nickname, self.principal, dsdb.passwd_fname,
'restart_dirsrv %s' % self.serverid) 'restart_dirsrv %s' % self.serverid)
conn = ipaldap.IPAdmin(self.fqdn) conn = ipaldap.IPAdmin(self.fqdn)
@ -679,7 +678,7 @@ class DsInstance(service.Service):
DN(('cn', 'RSA'), ('cn', 'encryption'), ('cn', 'config')), DN(('cn', 'RSA'), ('cn', 'encryption'), ('cn', 'config')),
objectclass=["top", "nsEncryptionModule"], objectclass=["top", "nsEncryptionModule"],
cn=["RSA"], cn=["RSA"],
nsSSLPersonalitySSL=[nickname], nsSSLPersonalitySSL=[self.nickname],
nsSSLToken=["internal (software)"], nsSSLToken=["internal (software)"],
nsSSLActivation=["on"], nsSSLActivation=["on"],
) )

View File

@ -800,8 +800,6 @@ def check_pkcs12(pkcs12_info, ca_file, hostname):
This is used for files given to --*_pkcs12 to ipa-server-install and This is used for files given to --*_pkcs12 to ipa-server-install and
ipa-replica-prepare. ipa-replica-prepare.
Return a (server cert name, CA cert names) tuple
""" """
pkcs12_filename, pkcs12_passwd = pkcs12_info pkcs12_filename, pkcs12_passwd = pkcs12_info
root_logger.debug('Checking PKCS#12 certificate %s', pkcs12_filename) root_logger.debug('Checking PKCS#12 certificate %s', pkcs12_filename)
@ -812,13 +810,18 @@ def check_pkcs12(pkcs12_info, ca_file, hostname):
# Import the CA cert first so it has a known nickname # Import the CA cert first so it has a known nickname
# (if it's present in the PKCS#12 it won't be overwritten) # (if it's present in the PKCS#12 it won't be overwritten)
ca_cert_name = 'The Root CA' ca_cert_name = 'The Root CA'
try: if ca_file:
nssdb.import_pem_cert(ca_cert_name, "CT,C,C", ca_file) try:
except (ValueError, RuntimeError) as e: nssdb.import_pem_cert(ca_cert_name, "CT,C,C", ca_file)
raise ScriptError(str(e)) except (ValueError, RuntimeError) as e:
raise ScriptError(str(e))
# Import everything in the PKCS#12 # Import everything in the PKCS#12
nssdb.import_pkcs12(pkcs12_filename, db_pwd_file.name, pkcs12_passwd) try:
nssdb.import_pkcs12(
pkcs12_filename, db_pwd_file.name, pkcs12_passwd)
except RuntimeError as e:
raise ScriptError(str(e))
# Check we have exactly one server cert (one with a private key) # Check we have exactly one server cert (one with a private key)
server_certs = nssdb.find_server_certs() server_certs = nssdb.find_server_certs()
@ -833,21 +836,23 @@ def check_pkcs12(pkcs12_info, ca_file, hostname):
# Check we have the whole cert chain & the CA is in it # Check we have the whole cert chain & the CA is in it
trust_chain = nssdb.get_trust_chain(server_cert_name) trust_chain = nssdb.get_trust_chain(server_cert_name)
while trust_chain: if len(trust_chain) < 2:
if trust_chain[0] == ca_cert_name: if ca_file:
break raise ScriptError(
trust_chain = trust_chain[1:] '%s is not signed by %s, or the full certificate chain is '
else: 'not present in the PKCS#12 file' %
(pkcs12_filename, ca_file))
else:
raise ScriptError(
'The full certificate chain is not present in %s' %
pkcs12_filename)
if ca_file and trust_chain[-2] != ca_cert_name:
raise ScriptError( raise ScriptError(
'%s is not signed by %s, or the full certificate chain is not ' '%s is not signed by %s' % (pkcs12_filename, ca_file))
'present in the PKCS#12 file' % (pkcs12_filename, ca_file)) ca_cert_name = trust_chain[-2]
if len(trust_chain) != 2:
raise ScriptError(
'trust chain of the server certificate in %s contains %s '
'certificates, expected 2' %
(pkcs12_filename, len(trust_chain)))
# Check server validity # Check server validity
nssdb.trust_root_cert(ca_cert_name)
try: try:
nssdb.verify_server_cert_validity(server_cert_name, hostname) nssdb.verify_server_cert_validity(server_cert_name, hostname)
except ValueError as e: except ValueError as e:
@ -855,8 +860,7 @@ def check_pkcs12(pkcs12_info, ca_file, hostname):
'The server certificate in %s is not valid: %s' % 'The server certificate in %s is not valid: %s' %
(pkcs12_filename, e)) (pkcs12_filename, e))
return server_cert_name return nssdb.get_cert(ca_cert_name)
@contextmanager @contextmanager
def private_ccache(path=None): def private_ccache(path=None):

View File

@ -139,7 +139,7 @@ class ReplicaPrepare(admintool.AdminTool):
"could not find directory instance: %s" % config_dir) "could not find directory instance: %s" % config_dir)
def check_pkcs12(self, pkcs12_file, pkcs12_pin): def check_pkcs12(self, pkcs12_file, pkcs12_pin):
installutils.check_pkcs12( return installutils.check_pkcs12(
pkcs12_info=(pkcs12_file, pkcs12_pin), pkcs12_info=(pkcs12_file, pkcs12_pin),
ca_file=CACERT, ca_file=CACERT,
hostname=self.replica_fqdn) hostname=self.replica_fqdn)
@ -221,7 +221,8 @@ class ReplicaPrepare(admintool.AdminTool):
if options.http_pin is None: if options.http_pin is None:
raise admintool.ScriptError( raise admintool.ScriptError(
"%s unlock password required" % options.http_pkcs12) "%s unlock password required" % options.http_pkcs12)
self.check_pkcs12(options.http_pkcs12, options.http_pin) http_ca_cert = self.check_pkcs12(
options.http_pkcs12, options.http_pin)
if options.dirsrv_pkcs12: if options.dirsrv_pkcs12:
if options.dirsrv_pin is None: if options.dirsrv_pin is None:
@ -231,7 +232,8 @@ class ReplicaPrepare(admintool.AdminTool):
if options.dirsrv_pin is None: if options.dirsrv_pin is None:
raise admintool.ScriptError( raise admintool.ScriptError(
"%s unlock password required" % options.dirsrv_pkcs12) "%s unlock password required" % options.dirsrv_pkcs12)
self.check_pkcs12(options.dirsrv_pkcs12, options.dirsrv_pin) dirsrv_ca_cert = self.check_pkcs12(
options.dirsrv_pkcs12, options.dirsrv_pin)
if options.pkinit_pkcs12: if options.pkinit_pkcs12:
if options.pkinit_pin is None: if options.pkinit_pin is None:
@ -242,6 +244,12 @@ class ReplicaPrepare(admintool.AdminTool):
raise admintool.ScriptError( raise admintool.ScriptError(
"%s unlock password required" % options.pkinit_pkcs12) "%s unlock password required" % options.pkinit_pkcs12)
if (options.http_pkcs12 and options.dirsrv_pkcs12 and
http_ca_cert != dirsrv_ca_cert):
raise admintool.ScriptError(
"%s and %s are not signed by the same CA certificate" %
(options.http_pkcs12, options.dirsrv_pkcs12))
if (not ipautil.file_exists( if (not ipautil.file_exists(
dogtag.configured_constants().CS_CFG_PATH) and dogtag.configured_constants().CS_CFG_PATH) and
options.dirsrv_pin is None): options.dirsrv_pin is None):

View File

@ -154,7 +154,7 @@ class ServerCertInstall(admintool.AdminTool):
os.chown(os.path.join(dirname, 'secmod.db'), 0, pent.pw_gid) os.chown(os.path.join(dirname, 'secmod.db'), 0, pent.pw_gid)
def import_cert(self, dirname, pkcs12_passwd, old_cert, principal, command): def import_cert(self, dirname, pkcs12_passwd, old_cert, principal, command):
server_cert = installutils.check_pkcs12( installutils.check_pkcs12(
pkcs12_info=(self.pkcs12_fname, pkcs12_passwd), pkcs12_info=(self.pkcs12_fname, pkcs12_passwd),
ca_file=CACERT, ca_file=CACERT,
hostname=api.env.host) hostname=api.env.host)
@ -166,6 +166,7 @@ class ServerCertInstall(admintool.AdminTool):
cdb.delete_cert(old_cert) cdb.delete_cert(old_cert)
cdb.import_pkcs12(self.pkcs12_fname, pkcs12_passwd) cdb.import_pkcs12(self.pkcs12_fname, pkcs12_passwd)
server_cert = cdb.find_server_certs()[0][0]
if api.env.enable_ra: if api.env.enable_ra:
cdb.track_server_cert(server_cert, principal, cdb.passwd_fname, cdb.track_server_cert(server_cert, principal, cdb.passwd_fname,