From 88083887c994ab505d6e07151e5dd26b56bb7732 Mon Sep 17 00:00:00 2001 From: Jan Cholasta Date: Wed, 24 Sep 2014 16:41:47 +0200 Subject: [PATCH] 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 --- install/tools/ipa-server-install | 157 +++++++++++++------- install/tools/man/ipa-replica-prepare.1 | 24 +-- install/tools/man/ipa-server-certinstall.1 | 6 +- install/tools/man/ipa-server-install.1 | 28 ++-- ipaserver/install/installutils.py | 126 +++++++++------- ipaserver/install/ipa_replica_prepare.py | 150 +++++++++++-------- ipaserver/install/ipa_server_certinstall.py | 26 ++-- ipatests/test_integration/test_caless.py | 33 ++-- 8 files changed, 322 insertions(+), 228 deletions(-) diff --git a/install/tools/ipa-server-install b/install/tools/ipa-server-install index 6988b1068..636ba7496 100755 --- a/install/tools/ipa-server-install +++ b/install/tools/ipa-server-install @@ -215,20 +215,45 @@ def parse_options(): 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", - help="PKCS#12 file containing the Directory Server SSL certificate") - cert_group.add_option("--http_pkcs12", dest="http_pkcs12", - help="PKCS#12 file containing the Apache Server SSL certificate") - cert_group.add_option("--pkinit_pkcs12", dest="pkinit_pkcs12", - help="PKCS#12 file containing the Kerberos KDC SSL certificate") + cert_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") + cert_group.add_option("--dirsrv_pkcs12", dest="dirsrv_cert_files", + action="append", + help=SUPPRESS_HELP) + cert_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") + cert_group.add_option("--http_pkcs12", dest="http_cert_files", + action="append", + help=SUPPRESS_HELP) + cert_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") + cert_group.add_option("--pkinit_pkcs12", dest="pkinit_cert_files", + action="append", + help=SUPPRESS_HELP) + cert_group.add_option("--dirsrv-pin", dest="dirsrv_pin", sensitive=True, + metavar="PIN", + help="The password to unlock the Directory Server private key") cert_group.add_option("--dirsrv_pin", dest="dirsrv_pin", sensitive=True, - help="The password of the Directory Server PKCS#12 file") + help=SUPPRESS_HELP) + cert_group.add_option("--http-pin", dest="http_pin", sensitive=True, + metavar="PIN", + help="The password to unlock the Apache Server private key") cert_group.add_option("--http_pin", dest="http_pin", sensitive=True, - help="The password of the Apache Server PKCS#12 file") - cert_group.add_option("--pkinit_pin", dest="pkinit_pin", - help="The password of the Kerberos KDC PKCS#12 file") - cert_group.add_option("--root-ca-file", dest="root_ca_file", - help="PEM file containing the CA certificate for the PKCS#12 files") + help=SUPPRESS_HELP) + cert_group.add_option("--pkinit-pin", dest="pkinit_pin", sensitive=True, + metavar="PIN", + help="The password to unlock the Kerberos KDC private key") + cert_group.add_option("--pkinit_pin", dest="pkinit_pin", sensitive=True, + help=SUPPRESS_HELP) + cert_group.add_option("--ca-cert-file", dest="ca_cert_files", + action="append", metavar="FILE", + help="File containing CA certificates for the service certificate files") + cert_group.add_option("--root-ca-file", dest="ca_cert_files", + action="append", + help=SUPPRESS_HELP) cert_group.add_option("--subject", action="callback", callback=subject_callback, type="string", help="The certificate subject base (default O=)") @@ -311,22 +336,25 @@ def parse_options(): if not options.forwarders and not options.no_forwarders: parser.error("You must specify at least one --forwarder option or --no-forwarders option") - # 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): - parser.error("--dirsrv_pkcs12 and --http_pkcs12 are required if any " - "PKCS#12 options are used.") + # If any of the key file options are selected, all are required. + 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): + parser.error("--dirsrv-cert-file and --http-cert-file are required if " + "any key file options are used.") if options.unattended: - if options.dirsrv_pkcs12 and options.dirsrv_pin is None: - parser.error("You must specify --dirsrv_pin with --dirsrv_pkcs12") - if options.http_pkcs12 and options.http_pin is None: - parser.error("You must specify --http_pin with --http_pkcs12") - if options.pkinit_pkcs12 and options.pkinit_pin is None: - parser.error("You must specify --pkinit_pin with --pkinit_pkcs12") + if options.dirsrv_cert_files and options.dirsrv_pin is None: + parser.error( + "You must specify --dirsrv-pin with --dirsrv-cert-file") + if options.http_cert_files and options.http_pin is None: + parser.error( + "You must specify --http-pin with --http-cert-file") + if options.pkinit_cert_files and options.pkinit_pin is None: + parser.error( + "You must specify --pkinit-pin with --pkinit-cert-file") - if options.external_cert_files and options.dirsrv_pkcs12: + if options.external_cert_files and options.dirsrv_cert_files: parser.error("Service certificate file options cannot be used with " "the external CA options.") @@ -334,8 +362,9 @@ def parse_options(): 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.dirsrv_cert_files: + parser.error("You cannot specify service certificate file options " + "together with --external-ca") if (options.external_cert_files and any(not os.path.isabs(path) for path in options.external_cert_files)): @@ -761,7 +790,7 @@ def main(): options.external_cert_files, options.subject) # We only set up the CA if the PKCS#12 options are not given. - if options.dirsrv_pkcs12: + if options.dirsrv_cert_files: setup_ca = False setup_kra = False else: @@ -903,43 +932,58 @@ def main(): if not options.subject: options.subject = DN(('O', realm_name)) - ca_file = options.root_ca_file - - if options.http_pkcs12: + 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: - sys.exit("%s unlock password required" % options.http_pkcs12) - http_pkcs12_info = (options.http_pkcs12, options.http_pin) - http_ca_cert = installutils.check_pkcs12( - http_pkcs12_info, ca_file, host_name) + sys.exit( + "Apache Server private key unlock password required") + http_pkcs12_file, http_pin, http_ca_cert = load_pkcs12( + cert_files=options.http_cert_files, + key_password=options.http_pin, + key_nickname=None, + ca_cert_files=options.ca_cert_files, + host_name=host_name) + http_pkcs12_info = (http_pkcs12_file.name, 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, + options.dirsrv_pin = read_password( + "Enter Directory Server private key unlock", confirm=False, validate=False) if options.dirsrv_pin is None: - sys.exit("%s unlock password required" % options.dirsrv_pkcs12) - dirsrv_pkcs12_info = (options.dirsrv_pkcs12, options.dirsrv_pin) - dirsrv_ca_cert = installutils.check_pkcs12( - dirsrv_pkcs12_info, ca_file, host_name) + sys.exit( + "Directory Server private key unlock password required") + dirsrv_pkcs12_file, dirsrv_pin, dirsrv_ca_cert = load_pkcs12( + cert_files=options.dirsrv_cert_files, + key_password=options.dirsrv_pin, + key_nickname=None, + ca_cert_files=options.ca_cert_files, + host_name=host_name) + dirsrv_pkcs12_info = (dirsrv_pkcs12_file.name, 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, + options.pkinit_pin = read_password( + "Enter Kerberos KDC private key unlock", confirm=False, validate=False) if options.pkinit_pin is None: - sys.exit("%s unlock password required" % options.pkinit_pkcs12) - pkinit_pkcs12_info = (options.pkinit_pkcs12, options.pkinit_pin) + sys.exit( + "Kerberos KDC private key unlock password required") + pkinit_pkcs12_file, pkinit_pin, pkinit_ca_cert = load_pkcs12( + cert_files=options.pkinit_cert_files, + key_password=options.pkinit_pin, + key_nickname=None, + ca_cert_files=options.ca_cert_files, + host_name=host_name) + pkinit_pkcs12_info = (pkinit_pkcs12_file.name, 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): - sys.exit("%s and %s are not signed by the same CA certificate" % - (options.http_pkcs12, options.dirsrv_pkcs12)) + sys.exit("Apache Server SSL certificate and Directory Server SSL " + "certificate are not signed by the same CA certificate") if not options.dm_password: dm_password = read_dm_password() @@ -1064,14 +1108,13 @@ def main(): if not ntp.is_configured(): ntp.create_instance() - if options.dirsrv_pkcs12: + if options.dirsrv_cert_files: ds = dsinstance.DsInstance(fstore=fstore) ds.create_instance(realm_name, host_name, domain_name, dm_password, dirsrv_pkcs12_info, idstart=options.idstart, idmax=options.idmax, subject_base=options.subject, - hbac_allow=not options.hbac_allow, - ca_file=ca_file) + hbac_allow=not options.hbac_allow) else: ds = dsinstance.DsInstance(fstore=fstore) ds.create_instance(realm_name, host_name, domain_name, @@ -1137,7 +1180,7 @@ def main(): ca.enable_client_auth_to_db(ca.dogtag_constants.CS_CFG_PATH) krb = krbinstance.KrbInstance(fstore) - if options.pkinit_pkcs12: + if options.pkinit_cert_files: krb.create_instance(realm_name, host_name, domain_name, dm_password, master_password, setup_pkinit=options.setup_pkinit, @@ -1163,11 +1206,11 @@ def main(): # Create a HTTP instance http = httpinstance.HTTPInstance(fstore) - if options.http_pkcs12: + if options.http_cert_files: http.create_instance( realm_name, host_name, domain_name, dm_password, pkcs12_info=http_pkcs12_info, subject_base=options.subject, - auto_redirect=options.ui_redirect, ca_file=ca_file) + auto_redirect=options.ui_redirect) else: http.create_instance( realm_name, host_name, domain_name, dm_password, diff --git a/install/tools/man/ipa-replica-prepare.1 b/install/tools/man/ipa-replica-prepare.1 index 8e1e60a25..fc8bf8332 100644 --- a/install/tools/man/ipa-replica-prepare.1 +++ b/install/tools/man/ipa-replica-prepare.1 @@ -35,23 +35,23 @@ Once the file has been created it will be named replica\-hostname. This file can A replica should only be installed on the same or higher version of IPA on the remote system. .SH "OPTIONS" .TP -\fB\-\-dirsrv_pkcs12\fR=\fIFILE\fR -PKCS#12 file containing the Directory Server SSL Certificate and Private Key +\fB\-\-dirsrv\-cert\-file\fR=\fIFILE\fR +File containing the Directory Server SSL certificate and private key. The files are accepted in PEM and DER certificate, PKCS#7 certificate chain, PKCS#8 and raw private key and PKCS#12 formats. This option may be used multiple times. .TP -\fB\-\-http_pkcs12\fR=\fIFILE\fR -PKCS#12 file containing the Apache Server SSL Certificate and Private Key +\fB\-\-http\-cert\-file\fR=\fIFILE\fR +File containing the Apache Server SSL certificate and private key. The files are accepted in PEM and DER certificate, PKCS#7 certificate chain, PKCS#8 and raw private key and PKCS#12 formats. This option may be used multiple times. .TP -\fB\-\-pkinit_pkcs12\fR=\fIFILE\fR -PKCS#12 file containing the Kerberos KDC Certificate and Private Key +\fB\-\-pkinit\-cert\-file\fR=\fIFILE\fR +File containing the Kerberos KDC SSL certificate and private key. The files are accepted in PEM and DER certificate, PKCS#7 certificate chain, PKCS#8 and raw private key and PKCS#12 formats. This option may be used multiple times. .TP -\fB\-\-dirsrv_pin\fR=\fIDIRSRV_PIN\fR -The password of the Directory Server PKCS#12 file +\fB\-\-dirsrv\-pin\fR=\fIPIN\fR +The password to unlock the Directory Server private key .TP -\fB\-\-http_pin\fR=\fIHTTP_PIN\fR -The password of the Apache Server PKCS#12 file +\fB\-\-http\-pin\fR=\fIPIN\fR +The password to unlock the Apache Server private key .TP -\fB\-\-pkinit_pin\fR=\fIPKINIT_PIN\fR -The password of the Kerberos KDC PKCS#12 file +\fB\-\-pkinit\-pin\fR=\fIPIN\fR +The password to unlock the Kerberos KDC private key .TP \fB\-p\fR \fIDM_PASSWORD\fR, \fB\-\-password\fR=\fIDM_PASSWORD\fR Directory Manager (existing master) password diff --git a/install/tools/man/ipa-server-certinstall.1 b/install/tools/man/ipa-server-certinstall.1 index f428402da..a5cb4bb7f 100644 --- a/install/tools/man/ipa-server-certinstall.1 +++ b/install/tools/man/ipa-server-certinstall.1 @@ -20,9 +20,9 @@ .SH "NAME" ipa\-server\-certinstall \- Install new SSL server certificates .SH "SYNOPSIS" -ipa\-server\-certinstall [\fIOPTION\fR]... PKCS12_FILE +ipa\-server\-certinstall [\fIOPTION\fR]... FILE... .SH "DESCRIPTION" -Replace the current SSL Directory and/or Apache server certificate(s) with the certificate in the PKCS#12 file. +Replace the current SSL Directory and/or Apache server certificate(s) with the certificate in the specified files. The files are accepted in PEM and DER certificate, PKCS#7 certificate chain, PKCS#8 and raw private key and PKCS#12 formats. PKCS#12 is a file format used to safely transport SSL certificates and public/private keypairs. @@ -38,7 +38,7 @@ Install the certificate on the Directory Server Install the certificate in the Apache Web Server .TP \fB\-\-pin\fR=\fIPIN\fR -The password of the PKCS#12 file +The password to unlock the private key .TP \fB\-\-dirman\-password\fR=\fIDIRMAN_PASSWORD\fR Directory Manager password diff --git a/install/tools/man/ipa-server-install.1 b/install/tools/man/ipa-server-install.1 index 92d9ec85a..0bd59687d 100644 --- a/install/tools/man/ipa-server-install.1 +++ b/install/tools/man/ipa-server-install.1 @@ -93,26 +93,26 @@ File containing the IPA CA certificate and the external CA certificate chain. Th \fB\-\-no\-pkinit\fR Disables pkinit setup steps .TP -\fB\-\-dirsrv_pkcs12\fR=\fIFILE\fR -PKCS#12 file containing the Directory Server SSL Certificate +\fB\-\-dirsrv\-cert\-file\fR=\fIFILE\fR +File containing the Directory Server SSL certificate and private key. The files are accepted in PEM and DER certificate, PKCS#7 certificate chain, PKCS#8 and raw private key and PKCS#12 formats. This option may be used multiple times. .TP -\fB\-\-http_pkcs12\fR=\fIFILE\fR -PKCS#12 file containing the Apache Server SSL Certificate +\fB\-\-http\-cert\-file\fR=\fIFILE\fR +File containing the Apache Server SSL certificate and private key. The files are accepted in PEM and DER certificate, PKCS#7 certificate chain, PKCS#8 and raw private key and PKCS#12 formats. This option may be used multiple times. .TP -\fB\-\-pkinit_pkcs12\fR=\fIFILE\fR -PKCS#12 file containing the Kerberos KDC SSL certificate +\fB\-\-pkinit\-cert\-file\fR=\fIFILE\fR +File containing the Kerberos KDC SSL certificate and private key. The files are accepted in PEM and DER certificate, PKCS#7 certificate chain, PKCS#8 and raw private key and PKCS#12 formats. This option may be used multiple times. .TP -\fB\-\-dirsrv_pin\fR=\fIDIRSRV_PIN\fR -The password of the Directory Server PKCS#12 file +\fB\-\-dirsrv\-pin\fR=\fIPIN\fR +The password to unlock the Directory Server private key .TP -\fB\-\-http_pin\fR=\fIHTTP_PIN\fR -The password of the Apache Server PKCS#12 file +\fB\-\-http\-pin\fR=\fIPIN\fR +The password to unlock the Apache Server private key .TP -\fB\-\-pkinit_pin\fR=\fIPKINIT_PIN\fR -The password of the Kerberos KDC PKCS#12 file +\fB\-\-pkinit\-pin\fR=\fIPIN\fR +The password to unlock the Kerberos KDC private key .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. +\fB\-\-ca\-cert\-file\fR=\fIFILE\fR +File containing the CA certificate of the CA which issued the Directory Server, Apache Server and Kerberos KDC certificates. The file is accepted in PEM and DER certificate and PKCS#7 certificate chain formats. This option may be used multiple times. Use this option if the CA certificate is not present in the certificate files. .TP \fB\-\-subject\fR=\fISUBJECT\fR The certificate subject base (default O=REALM.NAME) 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) + db_password = ipautil.ipa_generate_password() + db_pwdfile = ipautil.write_tmp_file(db_password) + nssdb.create_db(db_pwdfile.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)) - - # 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)) - return nssdb.get_cert(ca_cert_name) + 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 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] " + usage = "%prog <-d|-w> [options] ..." 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: diff --git a/ipatests/test_integration/test_caless.py b/ipatests/test_integration/test_caless.py index 28bfae5a2..1bd8202c8 100644 --- a/ipatests/test_integration/test_caless.py +++ b/ipatests/test_integration/test_caless.py @@ -154,9 +154,9 @@ class CALessBase(IntegrationTest): args = [ 'ipa-server-install', - '--http_pkcs12', http_pkcs12, - '--dirsrv_pkcs12', dirsrv_pkcs12, - '--root-ca-file', root_ca_file, + '--http-cert-file', http_pkcs12, + '--dirsrv-cert-file', dirsrv_pkcs12, + '--ca-cert-file', root_ca_file, '--ip-address', host.ip, '-r', host.domain.name, '-p', host.config.dirman_password, @@ -166,9 +166,9 @@ class CALessBase(IntegrationTest): ] if http_pin is not None: - args.extend(['--http_pin', http_pin]) + args.extend(['--http-pin', http_pin]) if dirsrv_pin is not None: - args.extend(['--dirsrv_pin', dirsrv_pin]) + args.extend(['--dirsrv-pin', dirsrv_pin]) if unattended: args.extend(['-U']) @@ -230,13 +230,13 @@ class CALessBase(IntegrationTest): ] if http_pkcs12: - args.extend(['--http_pkcs12', http_pkcs12]) + args.extend(['--http-cert-file', http_pkcs12]) if dirsrv_pkcs12: - args.extend(['--dirsrv_pkcs12', dirsrv_pkcs12]) + args.extend(['--dirsrv-cert-file', dirsrv_pkcs12]) if http_pin is not None: - args.extend(['--http_pin', http_pin]) + args.extend(['--http-pin', http_pin]) if dirsrv_pin is not None: - args.extend(['--dirsrv_pin', dirsrv_pin]) + args.extend(['--dirsrv-pin', dirsrv_pin]) args.extend([replica.hostname]) @@ -428,8 +428,8 @@ class TestServerInstall(CALessBase): result = self.install_server(http_pin=None) assert_error(result, - 'ipa-server-install: error: You must specify --http_pin ' - 'with --http_pkcs12') + 'ipa-server-install: error: You must specify --http-pin ' + 'with --http-cert-file') def test_missing_ds_password(self): "IPA server install with missing DS PKCS#12 password (unattended)" @@ -441,7 +441,7 @@ class TestServerInstall(CALessBase): result = self.install_server(dirsrv_pin=None) assert_error(result, 'ipa-server-install: error: You must specify ' - '--dirsrv_pin with --dirsrv_pkcs12') + '--dirsrv-pin with --dirsrv-cert-file') def test_incorect_http_pin(self): "IPA server install with incorrect HTTP PKCS#12 password" @@ -784,8 +784,9 @@ class TestReplicaInstall(CALessBase): raiseonerr=False) assert result.returncode > 0 assert ('Cannot issue certificates: a CA is not installed. Use the ' - '--http_pkcs12, --dirsrv_pkcs12 options to provide custom ' - 'certificates.' in result.stderr_text), result.stderr_text + '--http-cert-file, --dirsrv-cert-file options to provide ' + 'custom certificates.' in result.stderr_text), \ + result.stderr_text def test_nonexistent_http_pkcs12_file(self): "IPA replica install with non-existent HTTP PKCS#12 file" @@ -1479,7 +1480,7 @@ class TestCertinstall(CALessBase): args = ['ipa-server-certinstall', '-w', 'server.p12', - '--http_pin', self.cert_password] + '--http-pin', self.cert_password] result = self.certinstall('w', 'ca1/server', args=args) assert result.returncode == 0 @@ -1490,7 +1491,7 @@ class TestCertinstall(CALessBase): args = ['ipa-server-certinstall', '-d', 'server.p12', - '--dirsrv_pin', self.cert_password] + '--dirsrv-pin', self.cert_password] stdin_text = self.master.config.dirman_password + '\n' result = self.certinstall('d', 'ca1/server',