summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Cholasta <jcholast@redhat.com>2014-09-24 16:31:39 +0200
committerMartin Kosek <mkosek@redhat.com>2014-09-30 08:50:47 +0200
commit3aa0731fc660ea3d111a44926ab5dea71dc510e7 (patch)
treec4ce2ba7de1163ce699d7fb65ce19bf30e86aa68
parent60ecba77cd98f37be0d2c0f69efd307a687e59dc (diff)
downloadfreeipa-3aa0731fc660ea3d111a44926ab5dea71dc510e7.tar.gz
freeipa-3aa0731fc660ea3d111a44926ab5dea71dc510e7.tar.xz
freeipa-3aa0731fc660ea3d111a44926ab5dea71dc510e7.zip
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>
-rwxr-xr-xinstall/tools/ipa-ca-install39
-rwxr-xr-xinstall/tools/ipa-server-install68
-rw-r--r--install/tools/man/ipa-ca-install.13
-rw-r--r--install/tools/man/ipa-cacert-manage.15
-rw-r--r--install/tools/man/ipa-server-install.111
-rw-r--r--ipaserver/install/cainstance.py4
-rw-r--r--ipaserver/install/installutils.py109
-rw-r--r--ipaserver/install/ipa_cacert_manage.py34
-rw-r--r--ipatests/test_integration/test_external_ca.py4
9 files changed, 136 insertions, 141 deletions
diff --git a/install/tools/ipa-ca-install b/install/tools/ipa-ca-install
index 475794bb6..e54af2f59 100755
--- a/install/tools/ipa-ca-install
+++ b/install/tools/ipa-ca-install
@@ -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)
diff --git a/install/tools/ipa-server-install b/install/tools/ipa-server-install
index e73a098df..6988b1068 100755
--- a/install/tools/ipa-server-install
+++ b/install/tools/ipa-server-install
@@ -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)
diff --git a/install/tools/man/ipa-ca-install.1 b/install/tools/man/ipa-ca-install.1
index 2e0b0795a..8f7201c20 100644
--- a/install/tools/man/ipa-ca-install.1
+++ b/install/tools/man/ipa-ca-install.1
@@ -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
diff --git a/install/tools/man/ipa-cacert-manage.1 b/install/tools/man/ipa-cacert-manage.1
index 3006be7fc..1f3778833 100644
--- a/install/tools/man/ipa-cacert-manage.1
+++ b/install/tools/man/ipa-cacert-manage.1
@@ -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.
diff --git a/install/tools/man/ipa-server-install.1 b/install/tools/man/ipa-server-install.1
index ecea26db1..92d9ec85a 100644
--- a/install/tools/man/ipa-server-install.1
+++ b/install/tools/man/ipa-server-install.1
@@ -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
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index c26046c47..26c603702 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -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
diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
index c8e1a8de9..395023f6c 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -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
-
- if certissuer not in certdict:
- raise ValueError(
- "The external certificate is not signed by the external CA "
- "(unknown issuer %s)." % certissuer)
+ :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)
- 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):
diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py
index c681261e8..6a7fd0517 100644
--- a/ipaserver/install/ipa_cacert_manage.py
+++ b/ipaserver/install/ipa_cacert_manage.py
@@ -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
diff --git a/ipatests/test_integration/test_external_ca.py b/ipatests/test_integration/test_external_ca.py
index 747990cff..fbffdf14b 100644
--- a/ipatests/test_integration/test_external_ca.py
+++ b/ipatests/test_integration/test_external_ca.py
@@ -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