diff options
author | Jan Cholasta <jcholast@redhat.com> | 2014-09-24 16:41:47 +0200 |
---|---|---|
committer | Martin Kosek <mkosek@redhat.com> | 2014-09-30 08:50:47 +0200 |
commit | 88083887c994ab505d6e07151e5dd26b56bb7732 (patch) | |
tree | fde6a1a529a9c5969082acf081854672154fa22a /ipaserver/install | |
parent | 3aa0731fc660ea3d111a44926ab5dea71dc510e7 (diff) | |
download | freeipa-88083887c994ab505d6e07151e5dd26b56bb7732.tar.gz freeipa-88083887c994ab505d6e07151e5dd26b56bb7732.tar.xz freeipa-88083887c994ab505d6e07151e5dd26b56bb7732.zip |
CA-less installer options usability fixes
The --*_pkcs12 options of ipa-server-install and ipa-replica-prepare have
been replaced by --*-cert-file options which accept multiple files.
ipa-server-certinstall now accepts multiple files as well. The files are
accepted in PEM and DER certificate, PKCS#7 certificate chain, PKCS#8 and
raw private key and PKCS#12 formats.
The --root-ca-file option of ipa-server-install has been replaced by
--ca-cert-file option which accepts multiple files. The files are
accepted in PEM and DER certificate and PKCS#7 certificate chain formats.
The --*_pin options of ipa-server-install and ipa-replica-prepare have been
renamed to --*-pin.
https://fedorahosted.org/freeipa/ticket/4489
Reviewed-By: Petr Viktorin <pviktori@redhat.com>
Diffstat (limited to 'ipaserver/install')
-rw-r--r-- | ipaserver/install/installutils.py | 126 | ||||
-rw-r--r-- | ipaserver/install/ipa_replica_prepare.py | 150 | ||||
-rw-r--r-- | ipaserver/install/ipa_server_certinstall.py | 26 |
3 files changed, 176 insertions, 126 deletions
diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py index 395023f6c..757bc5b1b 100644 --- a/ipaserver/install/installutils.py +++ b/ipaserver/install/installutils.py @@ -801,72 +801,98 @@ def handle_error(error, log_file_name=None): return message, 1 -def check_pkcs12(pkcs12_info, ca_file, hostname): - """Check the given PKCS#12 with server cert and return the cert nickname - - This is used for files given to --*_pkcs12 to ipa-server-install and - ipa-replica-prepare. +def load_pkcs12(cert_files, key_password, key_nickname, ca_cert_files, + host_name): + """ + Load and verify server certificate and private key from multiple files + + The files are accepted in PEM and DER certificate, PKCS#7 certificate + chain, PKCS#8 and raw private key and PKCS#12 formats. + + :param cert_files: Names of server certificate and private key files to + import + :param key_password: Password to decrypt private keys + :param key_nickname: Nickname of the private key to import from PKCS#12 + files + :param ca_cert_files: Names of CA certificate files to import + :param host_name: Host name of the server + :returns: Temporary PKCS#12 file with the server certificate, private key + and CA certificate chain, password to unlock the PKCS#12 file and + the CA certificate of the CA that issued the server certificate """ - pkcs12_filename, pkcs12_passwd = pkcs12_info - root_logger.debug('Checking PKCS#12 certificate %s', pkcs12_filename) - db_pwd_file = ipautil.write_tmp_file(ipautil.ipa_generate_password()) with certs.NSSDatabase() as nssdb: - nssdb.create_db(db_pwd_file.name) - - # Import the CA cert first so it has a known nickname - # (if it's present in the PKCS#12 it won't be overwritten) - ca_cert_name = 'The Root CA' - if ca_file: - try: - nssdb.import_pem_cert(ca_cert_name, "CT,C,C", ca_file) - except (ValueError, RuntimeError) as e: - raise ScriptError(str(e)) + db_password = ipautil.ipa_generate_password() + db_pwdfile = ipautil.write_tmp_file(db_password) + nssdb.create_db(db_pwdfile.name) - # Import everything in the PKCS#12 try: - nssdb.import_pkcs12( - pkcs12_filename, db_pwd_file.name, pkcs12_passwd) + nssdb.import_files(cert_files, db_pwdfile.name, + True, key_password, key_nickname) except RuntimeError as e: raise ScriptError(str(e)) - # Check we have exactly one server cert (one with a private key) - server_certs = nssdb.find_server_certs() - if not server_certs: - raise ScriptError( - 'no server certificate found in %s' % pkcs12_filename) - if len(server_certs) > 1: - raise ScriptError( - '%s server certificates found in %s, expecting only one' % - (len(server_certs), pkcs12_filename)) - [(server_cert_name, _server_cert_trust)] = server_certs + if ca_cert_files: + try: + nssdb.import_files(ca_cert_files, db_pwdfile.name) + except RuntimeError as e: + raise ScriptError(str(e)) + + for nickname, trust_flags in nssdb.list_certs(): + if 'u' in trust_flags: + key_nickname = nickname + continue + nssdb.trust_root_cert(nickname) # Check we have the whole cert chain & the CA is in it - trust_chain = nssdb.get_trust_chain(server_cert_name) - if len(trust_chain) < 2: - if ca_file: - raise ScriptError( - '%s is not signed by %s, or the full certificate chain is ' - '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: + trust_chain = list(reversed(nssdb.get_trust_chain(key_nickname))) + ca_cert = None + for nickname in trust_chain[1:]: + cert = nssdb.get_cert(nickname) + if ca_cert is None: + ca_cert = cert + + nss_cert = x509.load_certificate(cert, x509.DER) + subject = DN(str(nss_cert.subject)) + issuer = DN(str(nss_cert.issuer)) + del nss_cert + + if subject == issuer: + break + else: raise ScriptError( - '%s is not signed by %s' % (pkcs12_filename, ca_file)) - ca_cert_name = trust_chain[-2] + "The full certificate chain is not present in %s" % + (", ".join(cert_files))) + + for nickname in trust_chain[1:]: + try: + nssdb.verify_ca_cert_validity(nickname) + except ValueError, e: + raise ScriptError( + "CA certificate %s in %s is not valid: %s" % + (subject, ", ".join(cert_files), e)) # Check server validity - nssdb.trust_root_cert(ca_cert_name) try: - nssdb.verify_server_cert_validity(server_cert_name, hostname) + nssdb.verify_server_cert_validity(key_nickname, host_name) except ValueError as e: raise ScriptError( - 'The server certificate in %s is not valid: %s' % - (pkcs12_filename, e)) + "The server certificate in %s is not valid: %s" % + (", ".join(cert_files), e)) + + out_file = tempfile.NamedTemporaryFile() + out_password = ipautil.ipa_generate_password() + out_pwdfile = ipautil.write_tmp_file(out_password) + args = [ + paths.PK12UTIL, + '-o', out_file.name, + '-n', key_nickname, + '-d', nssdb.secdir, + '-k', db_pwdfile.name, + '-w', out_pwdfile.name, + ] + ipautil.run(args) - return nssdb.get_cert(ca_cert_name) + return out_file, out_password, ca_cert @contextmanager def private_ccache(path=None): diff --git a/ipaserver/install/ipa_replica_prepare.py b/ipaserver/install/ipa_replica_prepare.py index e27eb6dd4..7504172c5 100644 --- a/ipaserver/install/ipa_replica_prepare.py +++ b/ipaserver/install/ipa_replica_prepare.py @@ -22,7 +22,7 @@ import os import shutil import tempfile import time -from optparse import OptionGroup +from optparse import OptionGroup, SUPPRESS_HELP from ConfigParser import SafeConfigParser import dns.resolver @@ -75,21 +75,39 @@ class ReplicaPrepare(admintool.AdminTool): group = OptionGroup(parser, "SSL certificate options", "Only used if the server was installed using custom SSL certificates") - group.add_option("--dirsrv_pkcs12", dest="dirsrv_pkcs12", - metavar="FILE", - help="install certificate for the directory server") - group.add_option("--http_pkcs12", dest="http_pkcs12", - metavar="FILE", - help="install certificate for the http server") - group.add_option("--pkinit_pkcs12", dest="pkinit_pkcs12", - metavar="FILE", - help="install certificate for the KDC") - group.add_option("--dirsrv_pin", dest="dirsrv_pin", metavar="PIN", - help="PIN for the Directory Server PKCS#12 file") - group.add_option("--http_pin", dest="http_pin", metavar="PIN", - help="PIN for the Apache Server PKCS#12 file") - group.add_option("--pkinit_pin", dest="pkinit_pin", metavar="PIN", - help="PIN for the KDC pkinit PKCS#12 file") + group.add_option("--dirsrv-cert-file", dest="dirsrv_cert_files", + action="append", metavar="FILE", + help="File containing the Directory Server SSL certificate and private key") + group.add_option("--dirsrv_pkcs12", dest="dirsrv_cert_files", + action="append", + help=SUPPRESS_HELP) + group.add_option("--http-cert-file", dest="http_cert_files", + action="append", metavar="FILE", + help="File containing the Apache Server SSL certificate and private key") + group.add_option("--http_pkcs12", dest="http_cert_files", + action="append", + help=SUPPRESS_HELP) + group.add_option("--pkinit-cert-file", dest="pkinit_cert_files", + action="append", metavar="FILE", + help="File containing the Kerberos KDC SSL certificate and private key") + group.add_option("--pkinit_pkcs12", dest="pkinit_cert_files", + action="append", + help=SUPPRESS_HELP) + group.add_option("--dirsrv-pin", dest="dirsrv_pin", sensitive=True, + metavar="PIN", + help="The password to unlock the Directory Server private key") + group.add_option("--dirsrv_pin", dest="dirsrv_pin", sensitive=True, + help=SUPPRESS_HELP) + group.add_option("--http-pin", dest="http_pin", sensitive=True, + metavar="PIN", + help="The password to unlock the Apache Server private key") + group.add_option("--http_pin", dest="http_pin", sensitive=True, + help=SUPPRESS_HELP) + group.add_option("--pkinit-pin", dest="pkinit_pin", sensitive=True, + metavar="PIN", + help="The password to unlock the Kerberos KDC private key") + group.add_option("--pkinit_pin", dest="pkinit_pin", sensitive=True, + help=SUPPRESS_HELP) parser.add_option_group(group) def validate_options(self): @@ -112,11 +130,11 @@ class ReplicaPrepare(admintool.AdminTool): options.setup_pkinit = False # If any of the PKCS#12 options are selected, all are required. - pkcs12_req = (options.dirsrv_pkcs12, options.http_pkcs12) - pkcs12_opt = (options.pkinit_pkcs12,) - if any(pkcs12_req + pkcs12_opt) and not all(pkcs12_req): + cert_file_req = (options.dirsrv_cert_files, options.http_cert_files) + cert_file_opt = (options.pkinit_cert_files,) + if any(cert_file_req + cert_file_opt) and not all(cert_file_req): self.option_parser.error( - "--dirsrv_pkcs12 and --http_pkcs12 are required if any " + "--dirsrv-cert-file and --http-cert-file are required if any " "PKCS#12 options are used.") if len(self.args) < 1: @@ -134,11 +152,11 @@ class ReplicaPrepare(admintool.AdminTool): if api.env.host == self.replica_fqdn: raise admintool.ScriptError("You can't create a replica on itself") - if not api.env.enable_ra and not options.http_pkcs12: + if not api.env.enable_ra and not options.http_cert_files: raise admintool.ScriptError( "Cannot issue certificates: a CA is not installed. Use the " - "--http_pkcs12, --dirsrv_pkcs12 options to provide custom " - "certificates.") + "--http-cert-file, --dirsrv-cert-file options to provide " + "custom certificates.") config_dir = dsinstance.config_dirname( dsinstance.realm_to_serverid(api.env.realm)) @@ -146,11 +164,13 @@ class ReplicaPrepare(admintool.AdminTool): raise admintool.ScriptError( "could not find directory instance: %s" % config_dir) - def check_pkcs12(self, pkcs12_file, pkcs12_pin): - return installutils.check_pkcs12( - pkcs12_info=(pkcs12_file, pkcs12_pin), - ca_file=CACERT, - hostname=self.replica_fqdn) + def load_pkcs12(self, cert_files, key_password, key_nickname): + return installutils.load_pkcs12( + cert_files=cert_files, + key_password=key_password, + key_nickname=key_nickname, + ca_cert_files=[CACERT], + host_name=self.replica_fqdn) def ask_for_options(self): options = self.options @@ -231,42 +251,52 @@ class ReplicaPrepare(admintool.AdminTool): if disconnect: api.Backend.ldap2.disconnect() - if options.http_pkcs12: + self.http_pin = self.dirsrv_pin = self.pkinit_pin = None + + if options.http_cert_files: if options.http_pin is None: options.http_pin = installutils.read_password( - "Enter %s unlock" % options.http_pkcs12, + "Enter Apache Server private key unlock", confirm=False, validate=False) if options.http_pin is None: raise admintool.ScriptError( - "%s unlock password required" % options.http_pkcs12) - http_ca_cert = self.check_pkcs12( - options.http_pkcs12, options.http_pin) + "Apache Server private key unlock password required") + http_pkcs12_file, http_pin, http_ca_cert = self.load_pkcs12( + options.http_cert_files, options.http_pin, None) + self.http_pkcs12_file = http_pkcs12_file + self.http_pin = http_pin - if options.dirsrv_pkcs12: + if options.dirsrv_cert_files: if options.dirsrv_pin is None: options.dirsrv_pin = installutils.read_password( - "Enter %s unlock" % options.dirsrv_pkcs12, + "Enter Directory Server private key unlock", confirm=False, validate=False) if options.dirsrv_pin is None: raise admintool.ScriptError( - "%s unlock password required" % options.dirsrv_pkcs12) - dirsrv_ca_cert = self.check_pkcs12( - options.dirsrv_pkcs12, options.dirsrv_pin) + "Directory Server private key unlock password required") + dirsrv_pkcs12_file, dirsrv_pin, dirsrv_ca_cert = self.load_pkcs12( + options.dirsrv_cert_files, options.dirsrv_pin, None) + self.dirsrv_pkcs12_file = dirsrv_pkcs12_file + self.dirsrv_pin = dirsrv_pin - if options.pkinit_pkcs12: + if options.pkinit_cert_files: if options.pkinit_pin is None: options.pkinit_pin = installutils.read_password( - "Enter %s unlock" % options.pkinit_pkcs12, + "Enter Kerberos KDC private key unlock", confirm=False, validate=False) if options.pkinit_pin is None: raise admintool.ScriptError( - "%s unlock password required" % options.pkinit_pkcs12) + "Kerberos KDC private key unlock password required") + pkinit_pkcs12_file, pkinit_pin, pkinit_ca_cert = self.load_pkcs12( + options.pkinit_cert_files, options.pkinit_pin, None) + self.pkinit_pkcs12_file = pkinit_pkcs12_file + self.pkinit_pin = pkinit_pin - if (options.http_pkcs12 and options.dirsrv_pkcs12 and + if (options.http_cert_files and options.dirsrv_cert_files 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)) + "Apache Server SSL certificate and Directory Server SSL " + "certificate are not signed by the same CA certificate") if (not ipautil.file_exists( dogtag.configured_constants().CS_CFG_PATH) and @@ -316,13 +346,11 @@ class ReplicaPrepare(admintool.AdminTool): passwd_fname = os.path.join(self.dir, "dirsrv_pin.txt") with open(passwd_fname, "w") as fd: - fd.write("%s\n" % (options.dirsrv_pin or '')) + fd.write("%s\n" % (self.dirsrv_pin or '')) - if options.dirsrv_pkcs12: - self.log.info( - "Copying SSL certificate for the Directory Server from %s", - options.dirsrv_pkcs12) - self.copy_info_file(options.dirsrv_pkcs12, "dscert.p12") + if options.dirsrv_cert_files: + self.log.info("Copying SSL certificate for the Directory Server") + self.copy_info_file(self.dirsrv_pkcs12_file.name, "dscert.p12") else: if ipautil.file_exists(options.ca_file): # Since it is possible that the Directory Manager password @@ -339,7 +367,7 @@ class ReplicaPrepare(admintool.AdminTool): "Creating SSL certificate for the Directory Server") self.export_certdb("dscert", passwd_fname) - if not options.dirsrv_pkcs12: + if not options.dirsrv_cert_files: self.log.info( "Creating SSL certificate for the dogtag Directory Server") self.export_certdb("dogtagcert", passwd_fname) @@ -354,13 +382,11 @@ class ReplicaPrepare(admintool.AdminTool): passwd_fname = os.path.join(self.dir, "http_pin.txt") with open(passwd_fname, "w") as fd: - fd.write("%s\n" % (options.http_pin or '')) + fd.write("%s\n" % (self.http_pin or '')) - if options.http_pkcs12: - self.log.info( - "Copying SSL certificate for the Web Server from %s", - options.http_pkcs12) - self.copy_info_file(options.http_pkcs12, "httpcert.p12") + if options.http_cert_files: + self.log.info("Copying SSL certificate for the Web Server") + self.copy_info_file(self.http_pkcs12_file.name, "httpcert.p12") else: self.log.info("Creating SSL certificate for the Web Server") self.export_certdb("httpcert", passwd_fname) @@ -373,13 +399,11 @@ class ReplicaPrepare(admintool.AdminTool): passwd_fname = os.path.join(self.dir, "pkinit_pin.txt") with open(passwd_fname, "w") as fd: - fd.write("%s\n" % (options.pkinit_pin or '')) + fd.write("%s\n" % (self.pkinit_pin or '')) - if options.pkinit_pkcs12: - self.log.info( - "Copying SSL certificate for the KDC from %s", - options.pkinit_pkcs12) - self.copy_info_file(options.pkinit_pkcs12, "pkinitcert.p12") + if options.pkinit_cert_files: + self.log.info("Copying SSL certificate for the KDC") + self.copy_info_file(self.pkinit_pkcs12_file.name, "pkinitcert.p12") else: self.log.info("Creating SSL certificate for the KDC") self.export_certdb("pkinitcert", passwd_fname, is_kdc=True) diff --git a/ipaserver/install/ipa_server_certinstall.py b/ipaserver/install/ipa_server_certinstall.py index 6300a14ae..1744a6eb8 100644 --- a/ipaserver/install/ipa_server_certinstall.py +++ b/ipaserver/install/ipa_server_certinstall.py @@ -36,7 +36,7 @@ from ipaserver.plugins.ldap2 import ldap2 class ServerCertInstall(admintool.AdminTool): command_name = 'ipa-server-certinstall' - usage = "%prog <-d|-w> [options] <PKCS#12 file>" + usage = "%prog <-d|-w> [options] <file> ..." description = "Install new SSL server certificates." @@ -54,7 +54,7 @@ class ServerCertInstall(admintool.AdminTool): help="install certificate for the http server") parser.add_option( "--pin", - dest="pin", + dest="pin", metavar="PIN", sensitive=True, help="The password of the PKCS#12 file") parser.add_option( "--dirsrv_pin", "--http_pin", @@ -73,8 +73,8 @@ class ServerCertInstall(admintool.AdminTool): if not self.options.dirsrv and not self.options.http: self.option_parser.error("you must specify dirsrv and/or http") - if len(self.args) != 1: - self.option_parser.error("you must provide a pkcs12 filename") + if not self.args: + self.option_parser.error("you must provide certificate filename") def ask_for_options(self): super(ServerCertInstall, self).ask_for_options() @@ -88,17 +88,15 @@ class ServerCertInstall(admintool.AdminTool): if self.options.pin is None: self.options.pin = installutils.read_password( - "Enter %s unlock" % self.args[0], confirm=False, validate=False) + "Enter private key unlock", confirm=False, validate=False) if self.options.pin is None: raise admintool.ScriptError( - "%s unlock password required" % self.args[0]) + "Private key unlock password required") def run(self): api.bootstrap(in_server=True) api.finalize() - self.pkcs12_fname = self.args[0] - if self.options.dirsrv: self.install_dirsrv_cert() @@ -154,10 +152,12 @@ class ServerCertInstall(admintool.AdminTool): os.chown(os.path.join(dirname, 'secmod.db'), 0, pent.pw_gid) def import_cert(self, dirname, pkcs12_passwd, old_cert, principal, command): - installutils.check_pkcs12( - pkcs12_info=(self.pkcs12_fname, pkcs12_passwd), - ca_file=CACERT, - hostname=api.env.host) + pkcs12_file, pin, ca_cert = installutils.load_pkcs12( + cert_files=self.args, + key_password=pkcs12_passwd, + key_nickname=None, + ca_cert_files=[CACERT], + host_name=api.env.host) cdb = certs.CertDB(api.env.realm, nssdir=dirname) try: @@ -165,7 +165,7 @@ class ServerCertInstall(admintool.AdminTool): cdb.untrack_server_cert(old_cert) cdb.delete_cert(old_cert) - cdb.import_pkcs12(self.pkcs12_fname, pkcs12_passwd) + cdb.import_pkcs12(pkcs12_file.name, pin) server_cert = cdb.find_server_certs()[0][0] if api.env.enable_ra: |