External CA installer options usability fixes

The --external_cert_file and --external_ca_file options of ipa-server-install
and ipa-ca-install have been replaced by --external-cert-file option which
accepts multiple files. The files are accepted in PEM and DER certificate and
PKCS#7 certificate chain formats.

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

Reviewed-By: Petr Viktorin <pviktori@redhat.com>
This commit is contained in:
Jan Cholasta
2014-09-24 16:31:39 +02:00
committed by Martin Kosek
parent 60ecba77cd
commit 3aa0731fc6
9 changed files with 136 additions and 141 deletions

View File

@@ -26,9 +26,10 @@ from ipapython import ipautil
from ipaserver.install import installutils
from ipaserver.install import certs
from ipaserver.install.installutils import (
ReplicaConfig, private_ccache, create_replica_config,
validate_external_cert)
from ipaserver.install.installutils import (HostnameLocalhost, ReplicaConfig,
expand_replica_info, read_replica_info, get_host_name, BadHostError,
private_ccache, read_replica_info_dogtag_port, load_external_cert,
create_replica_config, validate_external_cert)
from ipaserver.install import dsinstance, cainstance, bindinstance
from ipaserver.install.replication import replica_conn_check
from ipapython import version
@@ -65,10 +66,9 @@ def parse_options():
default=False, help="unattended installation never prompts the user")
parser.add_option("--external-ca", dest="external_ca", action="store_true",
default=False, help="Generate a CSR to be signed by an external CA")
parser.add_option("--external_cert_file", dest="external_cert_file",
help="PEM file containing a certificate signed by the external CA")
parser.add_option("--external_ca_file", dest="external_ca_file",
help="PEM file containing the external CA chain")
parser.add_option("--external-cert-file", dest="external_cert_files",
action="append", metavar="FILE",
help="File containing the IPA CA certificate and the external CA certificate chain")
options, args = parser.parse_args()
safe_options = parser.get_safe_opts(options)
@@ -83,12 +83,9 @@ def parse_options():
filename = None
if options.external_ca:
if options.external_cert_file:
parser.error("You cannot specify --external_cert_file "
if options.external_cert_files:
parser.error("You cannot specify --external-cert-file "
"together with --external-ca")
if options.external_ca_file:
parser.error("You cannot specify --external_ca_file together "
"with --external-ca")
return safe_options, options, filename
@@ -242,23 +239,19 @@ def install_master(safe_options, options):
if options.external_ca:
if cainstance.is_step_one_done():
print ("CA is already installed.\nRun the installer with "
"--external-cert-file and --external-ca-file.")
"--external-cert-file.")
sys.exit(1)
elif options.external_cert_file:
elif options.external_cert_files:
if not cainstance.is_step_one_done():
print ("CA is not installed yet. To install with an external CA "
"is a two-stage process.\nFirst run the installer with "
"--external-ca.")
sys.exit(1)
try:
validate_external_cert(options.external_cert_file,
options.external_ca_file, subject_base)
except ValueError, e:
print e
sys.exit(1)
external_cert_file, external_ca_file = load_external_cert(
options.external_cert_files, subject_base)
if options.external_cert_file:
if options.external_cert_files:
external = 2
elif options.external_ca:
external = 1
@@ -308,8 +301,8 @@ def install_master(safe_options, options):
else:
ca.configure_instance(host_name, domain_name, dm_password,
dm_password,
cert_file=options.external_cert_file,
cert_chain_file=options.external_ca_file,
cert_file=external_cert_file.name,
cert_chain_file=external_ca_file.name,
subject_base=subject_base)
ca.stop(ca.dogtag_constants.PKI_INSTANCE_NAME)

View File

@@ -38,7 +38,7 @@ import nss.error
import base64
import pwd
import textwrap
from optparse import OptionGroup, OptionValueError
from optparse import OptionGroup, OptionValueError, SUPPRESS_HELP
try:
from ipaserver.install import adtrustinstance
@@ -204,10 +204,15 @@ def parse_options():
cert_group = OptionGroup(parser, "certificate system options")
cert_group.add_option("", "--external-ca", dest="external_ca", action="store_true",
default=False, help="Generate a CSR for the IPA CA certificate to be signed by an external CA")
cert_group.add_option("", "--external_cert_file", dest="external_cert_file",
help="File containing the IPA CA certificate signed by the external CA in PEM format")
cert_group.add_option("", "--external_ca_file", dest="external_ca_file",
help="File containing the external CA certificate chain in PEM format")
cert_group.add_option("--external-cert-file", dest="external_cert_files",
action="append", metavar="FILE",
help="File containing the IPA CA certificate and the external CA certificate chain")
cert_group.add_option("--external_cert_file", dest="external_cert_files",
action="append",
help=SUPPRESS_HELP)
cert_group.add_option("--external_ca_file", dest="external_cert_files",
action="append",
help=SUPPRESS_HELP)
cert_group.add_option("--no-pkinit", dest="setup_pkinit", action="store_false",
default=True, help="disables pkinit setup steps")
cert_group.add_option("--dirsrv_pkcs12", dest="dirsrv_pkcs12",
@@ -321,25 +326,19 @@ def parse_options():
if options.pkinit_pkcs12 and options.pkinit_pin is None:
parser.error("You must specify --pkinit_pin with --pkinit_pkcs12")
if (options.external_cert_file or options.external_ca_file) and options.dirsrv_pkcs12:
parser.error(
"PKCS#12 options cannot be used with the external CA options.")
if options.external_cert_files and options.dirsrv_pkcs12:
parser.error("Service certificate file options cannot be used with "
"the external CA options.")
if options.external_ca:
if options.external_cert_file:
parser.error("You cannot specify --external_cert_file together with --external-ca")
if options.external_ca_file:
parser.error("You cannot specify --external_ca_file together with --external-ca")
if options.external_cert_files:
parser.error("You cannot specify --external-cert-file "
"together with --external-ca")
if options.dirsrv_pkcs12:
parser.error("You cannot specify PKCS#12 options together with --external-ca")
if ((options.external_cert_file and not options.external_ca_file) or
(not options.external_cert_file and options.external_ca_file)):
parser.error("if either external CA option is used, both are required.")
if (options.external_ca_file and not os.path.isabs(options.external_ca_file)):
parser.error("--external-ca-file must use an absolute path")
if (options.external_cert_file and not os.path.isabs(options.external_cert_file)):
if (options.external_cert_files and
any(not os.path.isabs(path) for path in options.external_cert_files)):
parser.error("--external-cert-file must use an absolute path")
if options.idmax == 0:
@@ -393,11 +392,10 @@ def read_cache(dm_password):
shutil.rmtree(top_dir)
# These are the only ones that may be overridden
for opt in ('external_ca_file', 'external_cert_file'):
try:
del optdict[opt]
except KeyError:
pass
try:
del optdict['external_cert_files']
except KeyError:
pass
return optdict
@@ -636,7 +634,7 @@ def main():
else:
standard_logging_setup(paths.IPASERVER_INSTALL_LOG, debug=options.debug)
print "\nThe log file for this installation can be found in /var/log/ipaserver-install.log"
if not options.external_ca and not options.external_cert_file and is_ipa_configured():
if not options.external_ca and not options.external_cert_files and is_ipa_configured():
installation_cleanup = False
sys.exit("IPA server is already configured on this system.\n" +
"If you want to reinstall the IPA server, please uninstall " +
@@ -729,14 +727,14 @@ def main():
if options.external_ca:
if cainstance.is_step_one_done():
print ("CA is already installed.\nRun the installer with "
"--external_cert_file and --external_ca_file.")
"--external-cert-file.")
sys.exit(1)
if ipautil.file_exists(paths.ROOT_IPA_CSR):
print ("CA CSR file %s already exists.\nIn order to continue "
"remove the file and run the installer again." %
paths.ROOT_IPA_CSR)
sys.exit(1)
elif options.external_cert_file:
elif options.external_cert_files:
if not cainstance.is_step_one_done():
# This can happen if someone passes external_ca_file without
# already having done the first stage of the CA install.
@@ -758,13 +756,9 @@ def main():
except Exception, e:
sys.exit("Cannot process the cache file: %s" % str(e))
if options.external_cert_file:
try:
validate_external_cert(options.external_cert_file,
options.external_ca_file, options.subject)
except ValueError, e:
print e
sys.exit(1)
if options.external_cert_files:
external_cert_file, external_ca_file = load_external_cert(
options.external_cert_files, options.subject)
# We only set up the CA if the PKCS#12 options are not given.
if options.dirsrv_pkcs12:
@@ -779,7 +773,7 @@ def main():
# Figure out what external CA step we're in. See cainstance.py for more
# info on the 3 states.
if options.external_cert_file:
if options.external_cert_files:
external = 2
elif options.external_ca:
external = 1
@@ -1119,8 +1113,8 @@ def main():
# stage 2 of external CA installation
ca.configure_instance(host_name, domain_name, dm_password,
dm_password,
cert_file=options.external_cert_file,
cert_chain_file=options.external_ca_file,
cert_file=external_cert_file.name,
cert_chain_file=external_ca_file.name,
subject_base=options.subject,
ca_signing_algorithm=options.ca_signing_algorithm)

View File

@@ -37,6 +37,9 @@ Directory Manager (existing master) password
\fB\-w\fR \fIADMIN_PASSWORD\fR, \fB\-\-admin\-password\fR=\fIADMIN_PASSWORD\fR
Admin user Kerberos password used for connection check
.TP
\fB\-\-external\-cert\-file\fR=\fIFILE\fR
File containing the IPA CA certificate and the external CA certificate chain. The file is accepted in PEM and DER certificate and PKCS#7 certificate chain formats. This option may be used multiple times.
.TP
\fB\-\-no\-host\-dns\fR
Do not use DNS for hostname lookup during installation
.TP

View File

@@ -56,10 +56,7 @@ Sign the renewed certificate by itself.
Sign the renewed certificate by external CA.
.TP
\fB\-\-external\-cert\-file\fR=\fIFILE\fR
PEM file containing a certificate signed by the external CA. Must be given with \-\-external\-ca\-file.
.TP
\fB\-\-external\-ca\-file\fR=\fIFILE\fR
PEM file containing the external CA chain.
File containing the IPA CA certificate and the external CA certificate chain. The file is accepted in PEM and DER certificate and PKCS#7 certificate chain formats. This option may be used multiple times.
.TP
\fB\-n\fR \fINICKNAME\fR, \fB\-\-nickname\fR=\fINICKNAME\fR
Nickname for the certificate.

View File

@@ -87,15 +87,8 @@ An unattended installation that will never prompt for user input
\fB\-\-external\-ca\fR
Generate a CSR for the IPA CA certificate to be signed by an external CA.
.TP
\fB\-\-external_cert_file\fR=\fIFILE\fR
File containing the IPA CA certificate signed by the external CA in PEM format. Must be given with \-\-external_ca_file.
.TP
\fB\-\-external_ca_file\fR=\fIFILE\fR
File containing the external CA certificate chain in PEM format. Must be given with \-\-external_cert_file.
If the CA certificate chain is in PKCS#7 format you can convert it to PEM using:
openssl pkcs7 -in PKCS7_FILE -print_certs -out PEM_FILE
\fB\-\-external\-cert\-file\fR=\fIFILE\fR
File containing the IPA CA certificate and the external CA certificate chain. The file is accepted in PEM and DER certificate and PKCS#7 certificate chain formats. This option may be used multiple times.
.TP
\fB\-\-no\-pkinit\fR
Disables pkinit setup steps

View File

@@ -585,7 +585,7 @@ class CAInstance(DogtagInstance):
if self.external == 1:
print "The next step is to get %s signed by your CA and re-run %s as:" % (self.csr_file, sys.argv[0])
print "%s --external_cert_file=/path/to/signed_certificate --external_ca_file=/path/to/external_ca_certificate" % sys.argv[0]
print "%s --external-cert-file=/path/to/signed_certificate --external-cert-file=/path/to/external_ca_certificate" % sys.argv[0]
sys.exit(0)
else:
shutil.move(paths.CA_BACKUP_KEYS_P12,
@@ -726,7 +726,7 @@ class CAInstance(DogtagInstance):
if self.external == 1:
print "The next step is to get %s signed by your CA and re-run %s as:" % (self.csr_file, sys.argv[0])
print "%s --external_cert_file=/path/to/signed_certificate --external_ca_file=/path/to/external_ca_certificate" % sys.argv[0]
print "%s --external-cert-file=/path/to/signed_certificate --external-cert-file=/path/to/external_ca_certificate" % sys.argv[0]
sys.exit(0)
# pkisilent makes a copy of the CA PKCS#12 file for us but gives

View File

@@ -942,52 +942,77 @@ def check_entropy():
except ValueError as e:
root_logger.debug("Invalid value in %s %s", paths.ENTROPY_AVAIL, e)
def validate_external_cert(cert_file, ca_file, subject_base):
extcert = None
try:
extcert = x509.load_certificate_from_file(cert_file)
certsubject = DN(str(extcert.subject))
certissuer = DN(str(extcert.issuer))
except IOError, e:
raise ValueError("Can't load the PEM certificate: %s." % e)
except (TypeError, NSPRError):
raise ValueError(
"'%s' is not a valid PEM-encoded certificate." % cert_file)
finally:
del extcert
def load_external_cert(files, subject_base):
"""
Load and verify external CA certificate chain from multiple files.
wantsubject = DN(('CN', 'Certificate Authority'), subject_base)
if certsubject != wantsubject:
raise ValueError(
"Subject of the external certificate is not correct (got %s, "
"expected %s)." % (certsubject, wantsubject))
The files are accepted in PEM and DER certificate and PKCS#7 certificate
chain formats.
extchain = None
try:
extchain = x509.load_certificate_list_from_file(ca_file)
certdict = dict((DN(str(cert.subject)), DN(str(cert.issuer)))
for cert in extchain)
except IOError, e:
raise ValueError("Can't load the external CA chain: %s." % e)
except (TypeError, NSPRError):
raise ValueError(
"'%s' is not a valid PEM-encoded certificate chain." % ca_file)
finally:
del extchain
:param files: Names of files to import
:param subject_base: Subject name base for IPA certificates
:returns: Temporary file with the IPA CA certificate and temporary file
with the external CA certificate chain
"""
with certs.NSSDatabase() as nssdb:
db_password = ipautil.ipa_generate_password()
db_pwdfile = ipautil.write_tmp_file(db_password)
nssdb.create_db(db_pwdfile.name)
if certissuer not in certdict:
raise ValueError(
"The external certificate is not signed by the external CA "
"(unknown issuer %s)." % certissuer)
while certsubject != certissuer:
certsubject = certissuer
try:
certissuer = certdict[certsubject]
except KeyError:
raise ValueError(
"The external CA chain is incomplete (%s is missing from the "
"chain)." % certsubject)
nssdb.import_files(files, db_pwdfile.name)
except RuntimeError as e:
raise ScriptError(str(e))
ca_subject = DN(('CN', 'Certificate Authority'), subject_base)
ca_nickname = None
cache = {}
for nickname, trust_flags in nssdb.list_certs():
cert = nssdb.get_cert(nickname, pem=True)
nss_cert = x509.load_certificate(cert)
subject = DN(str(nss_cert.subject))
issuer = DN(str(nss_cert.issuer))
del nss_cert
cache[nickname] = (cert, subject, issuer)
if subject == ca_subject:
ca_nickname = nickname
nssdb.trust_root_cert(nickname)
if ca_nickname is None:
raise ScriptError(
"IPA CA certificate not found in %s" % (", ".join(files)))
trust_chain = reversed(nssdb.get_trust_chain(ca_nickname))
ca_cert_chain = []
for nickname in trust_chain:
cert, subject, issuer = cache[nickname]
ca_cert_chain.append(cert)
if subject == issuer:
break
else:
raise ScriptError(
"CA certificate chain in %s is incomplete" %
(", ".join(files)))
for nickname in trust_chain:
try:
nssdb.verify_ca_cert_validity(nickname)
except ValueError, e:
raise ScriptError(
"CA certificate %s in %s is not valid: %s" %
(subject, ", ".join(files), e))
cert_file = tempfile.NamedTemporaryFile()
cert_file.write(ca_cert_chain[0] + '\n')
cert_file.flush()
ca_file = tempfile.NamedTemporaryFile()
ca_file.write('\n'.join(ca_cert_chain[1:]) + '\n')
ca_file.flush()
return cert_file, ca_file
def create_system_user(name, group, homedir, shell):

View File

@@ -60,11 +60,10 @@ class CACertManage(admintool.AdminTool):
action='store_false',
help="Sign the renewed certificate by external CA")
renew_group.add_option(
"--external-cert-file", dest='external_cert_file',
help="PEM file containing a certificate signed by the external CA")
renew_group.add_option(
"--external-ca-file", dest='external_ca_file',
help="PEM file containing the external CA chain")
"--external-cert-file", dest="external_cert_files",
action="append", metavar="FILE",
help="File containing the IPA CA certificate and the external CA "
"certificate chain")
parser.add_option_group(renew_group)
install_group = OptionGroup(parser, "Install options")
@@ -90,10 +89,7 @@ class CACertManage(admintool.AdminTool):
options = self.options
if command == 'renew':
if options.external_cert_file and not options.external_ca_file:
parser.error("--external-ca-file not specified")
elif not options.external_cert_file and options.external_ca_file:
parser.error("--external-cert-file not specified")
pass
elif command == 'install':
if len(self.args) < 2:
parser.error("certificate file name not provided")
@@ -107,7 +103,7 @@ class CACertManage(admintool.AdminTool):
api.bootstrap(in_server=True)
api.finalize()
if ((command == 'renew' and options.external_cert_file) or
if ((command == 'renew' and options.external_cert_files) or
command == 'install'):
self.conn = self.ldap_connect()
else:
@@ -166,7 +162,7 @@ class CACertManage(admintool.AdminTool):
cert = db.get_cert_from_db(self.cert_nickname, pem=False)
options = self.options
if options.external_cert_file:
if options.external_cert_files:
return self.renew_external_step_2(ca, cert)
if options.self_signed is not None:
@@ -200,31 +196,25 @@ class CACertManage(admintool.AdminTool):
"ipa-cacert-manage as:" % paths.IPA_CA_CSR)
print("ipa-cacert-manage renew "
"--external-cert-file=/path/to/signed_certificate "
"--external-ca-file=/path/to/external_ca_certificate")
"--external-cert-file=/path/to/external_ca_certificate")
def renew_external_step_2(self, ca, old_cert):
print "Importing the renewed CA certificate, please wait"
options = self.options
cert_filename = options.external_cert_file
ca_filename = options.external_ca_file
cert_file, ca_file = installutils.load_external_cert(
options.external_cert_files, x509.subject_base())
nss_cert = None
nss.nss_init(ca.dogtag_constants.ALIAS_DIR)
try:
try:
installutils.validate_external_cert(
cert_filename, ca_filename, x509.subject_base())
except ValueError, e:
raise admintool.ScriptError(e)
nss_cert = x509.load_certificate(old_cert, x509.DER)
subject = nss_cert.subject
#pylint: disable=E1101
pkinfo = nss_cert.subject_public_key_info.format()
#pylint: enable=E1101
nss_cert = x509.load_certificate_from_file(cert_filename)
nss_cert = x509.load_certificate_from_file(cert_file.name)
if not nss_cert.is_ca_cert():
raise admintool.ScriptError("Not a CA certificate")
if nss_cert.subject != subject:
@@ -249,7 +239,7 @@ class CACertManage(admintool.AdminTool):
raise admintool.ScriptError(
"Not compatible with the current CA certificate: %s", e)
ca_certs = x509.load_certificate_list_from_file(ca_filename)
ca_certs = x509.load_certificate_list_from_file(ca_file.name)
for ca_cert in ca_certs:
tmpdb.add_cert(ca_cert.der_data, str(ca_cert.subject), 'C,,')
del ca_certs

View File

@@ -97,8 +97,8 @@ class TestExternalCA(IntegrationTest):
'ipa-server-install',
'-a', self.master.config.admin_password,
'-p', self.master.config.dirman_password,
'--external_cert_file', external_cert_file,
'--external_ca_file', external_ca_file
'--external-cert-file', external_cert_file,
'--external-cert-file', external_ca_file
])
# Make sure IPA server is working properly