summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPetr Viktorin <pviktori@redhat.com>2013-03-14 13:58:27 +0100
committerMartin Kosek <mkosek@redhat.com>2013-04-02 15:28:50 +0200
commit03a2c66eda695ad2d4bfe675fa2902035e6b37f0 (patch)
tree6f497733efb8da696a82730f455ad4b6310bb612
parenta03aba5704036e375fab36ed2b7cbbc31adf5411 (diff)
downloadfreeipa-03a2c66eda695ad2d4bfe675fa2902035e6b37f0.tar.gz
freeipa-03a2c66eda695ad2d4bfe675fa2902035e6b37f0.tar.xz
freeipa-03a2c66eda695ad2d4bfe675fa2902035e6b37f0.zip
Support installing with custom SSL certs, without a CA
Design: http://freeipa.org/page/V3/CA-less_install https://fedorahosted.org/freeipa/ticket/3363
-rwxr-xr-xinstall/tools/ipa-replica-install7
-rwxr-xr-xinstall/tools/ipa-server-install61
-rw-r--r--ipaserver/install/certs.py60
-rw-r--r--ipaserver/install/dsinstance.py28
-rw-r--r--ipaserver/install/httpinstance.py10
-rw-r--r--ipaserver/install/installutils.py54
-rw-r--r--ipaserver/install/ipa_replica_prepare.py28
7 files changed, 217 insertions, 31 deletions
diff --git a/install/tools/ipa-replica-install b/install/tools/ipa-replica-install
index 94d60bec6..a0f20e44b 100755
--- a/install/tools/ipa-replica-install
+++ b/install/tools/ipa-replica-install
@@ -536,6 +536,9 @@ def main():
fd.write("ra_plugin=dogtag\n")
fd.write("dogtag_version=%s\n" %
dogtag.install_constants.DOGTAG_VERSION)
+ else:
+ fd.write("enable_ra=False\n")
+ fd.write("ra_plugin=none\n")
fd.write("mode=production\n")
fd.close()
finally:
@@ -560,9 +563,7 @@ def main():
sstore.backup_state("install", "group_exists", group_exists)
#Automatically disable pkinit w/ dogtag until that is supported
- #[certs.ipa_self_signed() must be called only after api.finalize()]
- if not ipautil.file_exists(config.dir + "/pkinitcert.p12") and not certs.ipa_self_signed():
- options.setup_pkinit = False
+ options.setup_pkinit = False
# Install CA cert so that we can do SSL connections with ldap
install_ca_cert(config)
diff --git a/install/tools/ipa-server-install b/install/tools/ipa-server-install
index add037920..5aa5cd73f 100755
--- a/install/tools/ipa-server-install
+++ b/install/tools/ipa-server-install
@@ -38,6 +38,7 @@ import pickle
import random
import tempfile
import nss.error
+import base64
from optparse import OptionGroup, OptionValueError, SUPPRESS_HELP
from ipaserver.install import dsinstance
@@ -60,7 +61,7 @@ from ipapython import sysrestore
from ipapython.ipautil import *
from ipapython import ipautil
from ipapython import dogtag
-from ipalib import api, errors, util
+from ipalib import api, errors, util, x509
from ipapython.config import IPAOptionParser
from ipalib.x509 import load_certificate_from_file, load_certificate_chain_from_file
from ipalib.util import validate_domain_name
@@ -185,6 +186,8 @@ def parse_options():
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 with root CA certificate(s) to trust")
cert_group.add_option("--subject", action="callback", callback=subject_callback,
type="string",
help="The certificate subject base (default O=<realm-name>)")
@@ -280,7 +283,14 @@ def parse_options():
if cnt > 0 and cnt < 4:
parser.error("All PKCS#12 options are required if any are used.")
- if (options.external_cert_file or options.external_ca_file) and cnt:
+ if options.dirsrv_pkcs12 and not options.root_ca_file:
+ parser.error(
+ "--root-ca-file must be given with the PKCS#12 options.")
+ if options.dirsrv_pkcs12 and not options.root_ca_file:
+ parser.error(
+ "The PKCS#12 options must be given with --root-ca-file.")
+
+ 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.")
@@ -289,6 +299,8 @@ def parse_options():
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.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)):
@@ -561,6 +573,7 @@ def set_subject_in_config(realm_name, dm_password, suffix, subject_base):
conn.update_entry(dn, mod)
conn.disconnect()
+
def main():
global ds
global uninstalling
@@ -821,6 +834,13 @@ def main():
else:
domain_name = options.domain_name
+ if options.http_pkcs12:
+ # Check the given PKCS#12 files
+ ca_file = options.root_ca_file
+ check_pkcs12 = installutils.check_pkcs12
+ http_cert_name = check_pkcs12(http_pkcs12_info, ca_file, host_name)
+ dirsrv_cert_name = check_pkcs12(dirsrv_pkcs12_info, ca_file, host_name)
+
domain_name = domain_name.lower()
ip = get_server_ip_address(host_name, fstore, options.unattended, options)
@@ -921,6 +941,7 @@ def main():
dogtag.install_constants.DOGTAG_VERSION)
else:
fd.write("enable_ra=False\n")
+ fd.write("ra_plugin=none\n")
fd.write("mode=production\n")
fd.close()
@@ -955,8 +976,6 @@ def main():
root_logger.critical("failed to add DS group: %s" % e)
# Create a directory server instance
- ds = dsinstance.DsInstance(fstore=fstore)
-
if external != 2:
# Configure ntpd
if options.conf_ntp:
@@ -966,17 +985,22 @@ def main():
ntp.create_instance()
if options.dirsrv_pkcs12:
+ ds = dsinstance.DsInstance(fstore=fstore,
+ cert_nickname=dirsrv_cert_name)
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)
else:
+ ds = dsinstance.DsInstance(fstore=fstore)
ds.create_instance(realm_name, host_name, domain_name,
dm_password,
idstart=options.idstart, idmax=options.idmax,
subject_base=options.subject,
hbac_allow=not options.hbac_allow)
else:
+ ds = dsinstance.DsInstance(fstore=fstore)
ds.init_info(
realm_name, host_name, domain_name, dm_password,
False, options.subject, 1101, 1100, None)
@@ -1031,8 +1055,8 @@ def main():
ds.enable_ssl()
ds.restart()
- # We need to ldap_enable the CA now that DS is up and running
if setup_ca:
+ # We need to ldap_enable the CA now that DS is up and running
ca.ldap_enable('CA', host_name, dm_password,
ipautil.realm_to_suffix(realm_name))
if not dogtag.install_constants.SHARED_DB:
@@ -1047,8 +1071,29 @@ def main():
ca.enable_client_auth_to_db()
ca.restart()
- # Upload the CA cert to the directory
- ds.upload_ca_cert()
+ # Upload the CA cert to the directory
+ ds.upload_ca_cert()
+ else:
+ with open(options.root_ca_file) as f:
+ pem_cert = f.read()
+
+ # Trust the CA cert
+ root_logger.info(
+ 'Trusting certificate authority from %s' % options.root_ca_file)
+
+ certs.NSSDatabase('/etc/pki/nssdb').import_pem_cert(
+ 'External CA cert', 'CT,,', options.root_ca_file)
+
+ # Put a CA cert where other instances expect it
+ with open('/etc/ipa/ca.crt', 'wb') as f:
+ f.write(pem_cert)
+
+ # Install the CA cert for the HTTP server
+ with open('/usr/share/ipa/html/ca.crt', 'wb') as f:
+ f.write(pem_cert)
+
+ # Upload the CA cert to the directory
+ ds.upload_ca_dercert(base64.b64decode(x509.strip_header(pem_cert)))
krb = krbinstance.KrbInstance(fstore)
if options.pkinit_pkcs12:
@@ -1178,8 +1223,6 @@ def main():
else:
print "In order for Firefox autoconfiguration to work you will need to"
print "use a SSL signing certificate. See the IPA documentation for more details."
- print "You also need to install a PEM copy of the CA certificate into"
- print "/usr/share/ipa/html/ca.crt"
if ipautil.file_exists(ANSWER_CACHE):
os.remove(ANSWER_CACHE)
diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py
index 6d688b351..81f403df2 100644
--- a/ipaserver/install/certs.py
+++ b/ipaserver/install/certs.py
@@ -29,6 +29,8 @@ import base64
from hashlib import sha1
from ConfigParser import RawConfigParser, MissingSectionHeaderError
+from nss import nss
+
from ipapython import dogtag
from ipapython import sysrestore
from ipapython import ipautil
@@ -293,9 +295,11 @@ class NSSDatabase(object):
ipautil.run(args)
except ipautil.CalledProcessError, e:
if e.returncode == 17:
- raise RuntimeError("incorrect password for pkcs#12 file")
+ raise RuntimeError("incorrect password for pkcs#12 file %s" %
+ pkcs12_filename)
else:
- raise RuntimeError("unknown error import pkcs#12 file")
+ raise RuntimeError("unknown error import pkcs#12 file %s" %
+ pkcs12_filename)
def find_root_cert_from_pkcs12(self, pkcs12_fname, passwd_fname=None):
"""Given a PKCS#12 file, try to find any certificates that do
@@ -355,6 +359,53 @@ class NSSDatabase(object):
fd.write(cert)
os.chmod(location, 0444)
+ def import_pem_cert(self, nickname, flags, location):
+ """Import a cert form the given PEM file.
+
+ The file must contain exactly one certificate.
+ """
+ with open(location) as fd:
+ certs = fd.read()
+
+ cert, st = find_cert_from_txt(certs)
+ self.add_single_pem_cert(nickname, flags, cert)
+
+ try:
+ find_cert_from_txt(certs, st)
+ except RuntimeError:
+ pass
+ else:
+ raise ValueError('%s contains more than one certificate')
+
+ def add_single_pem_cert(self, nick, flags, cert):
+ """Import a cert in PEM format"""
+ self.run_certutil(["-A", "-n", nick,
+ "-t", flags,
+ "-a"],
+ stdin=cert)
+
+ def verify_server_cert_validity(self, nickname, hostname):
+ """Verify a certificate is valid for a SSL server with given hostname
+
+ Raises a ValueError if the certificate is invalid.
+ """
+ certdb = cert = None
+ nss.nss_init(self.secdir)
+ try:
+ certdb = nss.get_default_certdb()
+ cert = nss.find_cert_from_nickname(nickname)
+ intended_usage = nss.certificateUsageSSLServer
+ approved_usage = cert.verify_now(certdb, True, intended_usage)
+ if not approved_usage & intended_usage:
+ raise ValueError('invalid for a SSL server')
+ if not cert.verify_hostname(hostname):
+ raise ValueError('invalid for server %s' % hostname)
+ finally:
+ del certdb, cert
+ nss.nss_shutdown()
+
+ return None
+
class CertDB(object):
"""An IPA-server-specific wrapper around NSS
@@ -610,10 +661,7 @@ class CertDB(object):
nick = get_ca_nickname(self.realm)
else:
nick = str(subject_dn)
- self.run_certutil(["-A", "-n", nick,
- "-t", "CT,,C",
- "-a"],
- stdin=cert)
+ self.nssdb.add_single_pem_cert(nick, "CT,,C", cert)
except RuntimeError:
break
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index 5f3041c22..38dc94e42 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -36,7 +36,7 @@ import certs
import ldap
from ipaserver.install import ldapupdate
from ipaserver.install import replication
-from ipalib import errors
+from ipalib import errors, api
from ipapython.dn import DN
SERVER_ROOT_64 = "/usr/lib64/dirsrv"
@@ -541,7 +541,10 @@ class DsInstance(service.Service):
# We only handle one server cert
nickname = server_certs[0][0]
self.dercert = dsdb.get_cert_from_db(nickname, pem=False)
- dsdb.track_server_cert(nickname, self.principal, dsdb.passwd_fname, 'restart_dirsrv %s' % self.serverid )
+ if api.env.enable_ra:
+ dsdb.track_server_cert(
+ nickname, self.principal, dsdb.passwd_fname,
+ 'restart_dirsrv %s' % self.serverid)
else:
nickname = self.nickname
cadb = certs.CertDB(self.realm_name, host_name=self.fqdn, subject_base=self.subject_base)
@@ -592,15 +595,30 @@ class DsInstance(service.Service):
# check for open secure port 636 from now on
self.open_ports.append(636)
- def upload_ca_cert(self):
+ def export_ca_cert(self, nickname, location):
+ dirname = config_dirname(self.serverid)
+ dsdb = certs.NSSDatabase(nssdir=dirname)
+ dsdb.export_pem_cert(nickname, location)
+
+ def upload_ca_cert(self, cacert_name=None):
"""
- Upload the CA certificate in DER form in the LDAP directory.
+ Upload the CA certificate from the NSS database to the LDAP directory.
"""
dirname = config_dirname(self.serverid)
certdb = certs.CertDB(self.realm_name, nssdir=dirname, subject_base=self.subject_base)
- dercert = certdb.get_cert_from_db(certdb.cacert_name, pem=False)
+ if cacert_name is None:
+ cacert_name = certdb.cacert_name
+ dercert = certdb.get_cert_from_db(cacert_name, pem=False)
+ self.upload_ca_dercert(dercert)
+
+ def upload_ca_dercert(self, dercert):
+ """Upload the CA DER certificate to the LDAP directory
+ """
+ # Note: Don't try to optimize if base64 data is already available.
+ # We want to re-encode using Python's b64encode to ensure the
+ # data is normalized (no extra newlines in the ldif)
self.sub_dict['CADERCERT'] = base64.b64encode(dercert)
self._ldap_mod('upload-cacert.ldif', self.sub_dict)
diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py
index 59782cb6f..458112fa0 100644
--- a/ipaserver/install/httpinstance.py
+++ b/ipaserver/install/httpinstance.py
@@ -61,7 +61,10 @@ class HTTPInstance(service.Service):
subject_base = ipautil.dn_attribute_property('_subject_base')
- def create_instance(self, realm, fqdn, domain_name, dm_password=None, autoconfig=True, pkcs12_info=None, self_signed_ca=False, subject_base=None, auto_redirect=True):
+ def create_instance(self, realm, fqdn, domain_name, dm_password=None,
+ autoconfig=True, pkcs12_info=None,
+ self_signed_ca=False, subject_base=None,
+ auto_redirect=True):
self.fqdn = fqdn
self.realm = realm
self.domain = domain_name
@@ -247,10 +250,13 @@ class HTTPInstance(service.Service):
raise RuntimeError("Could not find a suitable server cert in import in %s" % self.pkcs12_info[0])
db.create_password_conf()
+
# We only handle one server cert
nickname = server_certs[0][0]
self.dercert = db.get_cert_from_db(nickname, pem=False)
- db.track_server_cert(nickname, self.principal, db.passwd_fname, 'restart_httpd')
+
+ if api.env.enable_ra:
+ db.track_server_cert(nickname, self.principal, db.passwd_fname, 'restart_httpd')
self.__set_mod_nss_nickname(nickname)
else:
diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
index a9728582c..600acfeec 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -40,6 +40,7 @@ from ipalib.util import validate_hostname
from ipapython import config
from ipalib import errors
from ipapython.dn import DN
+from ipaserver.install import certs
# Used to determine install status
IPA_MODULES = [
@@ -699,3 +700,56 @@ def handle_error(error, log_file_name=None):
message = "Unexpected error"
message += '\n%s: %s' % (type(error).__name__, error)
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.
+
+ Return a (server cert name, CA cert names) tuple
+ """
+ pkcs12_filename, pin_filename = 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'
+ nssdb.import_pem_cert(ca_cert_name, "CT,C,C", ca_file)
+
+ # Import everything in the PKCS#12
+ nssdb.import_pkcs12(pkcs12_filename, db_pwd_file.name, pin_filename)
+
+ # 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
+
+ # Check we have the whole cert chain & the CA is in it
+ for cert_name in nssdb.get_trust_chain(server_cert_name):
+ if cert_name == ca_cert_name:
+ break
+ else:
+ 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))
+
+ # Check server validity
+ try:
+ nssdb.verify_server_cert_validity(server_cert_name, hostname)
+ except ValueError as e:
+ raise ScriptError(
+ 'The server certificate in %s is not valid: %s' %
+ (pkcs12_filename, e))
+
+ return server_cert_name
diff --git a/ipaserver/install/ipa_replica_prepare.py b/ipaserver/install/ipa_replica_prepare.py
index 8afa4e8e1..e7a922666 100644
--- a/ipaserver/install/ipa_replica_prepare.py
+++ b/ipaserver/install/ipa_replica_prepare.py
@@ -99,6 +99,9 @@ class ReplicaPrepare(admintool.AdminTool):
self.option_parser.error("You cannot specify a --reverse-zone "
"option together with --no-reverse")
+ #Automatically disable pkinit w/ dogtag until that is supported
+ options.setup_pkinit = False
+
# If any of the PKCS#12 options are selected, all are required.
pkcs12_opts = [options.dirsrv_pkcs12, options.dirsrv_pin,
options.http_pkcs12, options.http_pin]
@@ -127,11 +130,6 @@ class ReplicaPrepare(admintool.AdminTool):
if api.env.host == self.replica_fqdn:
raise admintool.ScriptError("You can't create a replica on itself")
- #Automatically disable pkinit w/ dogtag until that is supported
- #[certs.ipa_self_signed() must be called only after api.finalize()]
- if not options.pkinit_pkcs12 and not certs.ipa_self_signed():
- options.setup_pkinit = False
-
# FIXME: certs.ipa_self_signed_master return value can be
# True, False, None, with different meanings.
# So, we need to explicitly compare to False
@@ -139,12 +137,30 @@ class ReplicaPrepare(admintool.AdminTool):
raise admintool.ScriptError("A selfsign CA backend can only "
"prepare on the original master")
+ if not api.env.enable_ra and not options.http_pkcs12:
+ raise admintool.ScriptError(
+ "Cannot issue certificates: a CA is not installed. Use the "
+ "--http_pkcs12, --dirsrv_pkcs12 options to provide custom "
+ "certificates.")
+
+ if options.http_pkcs12:
+ # Check the given PKCS#12 files
+ self.check_pkcs12(options.http_pkcs12, options.http_pin)
+ self.check_pkcs12(options.dirsrv_pkcs12, options.dirsrv_pin)
+
config_dir = dsinstance.config_dirname(
dsinstance.realm_to_serverid(api.env.realm))
if not ipautil.dir_exists(config_dir):
raise admintool.ScriptError(
"could not find directory instance: %s" % config_dir)
+ def check_pkcs12(self, pkcs12_file, pkcs12_pin):
+ pin_file = ipautil.write_tmp_file(pkcs12_pin)
+ installutils.check_pkcs12(
+ pkcs12_info=(pkcs12_file, pin_file.name),
+ ca_file='/etc/ipa/ca.crt',
+ hostname=self.replica_fqdn)
+
def ask_for_options(self):
options = self.options
super(ReplicaPrepare, self).ask_for_options()
@@ -275,7 +291,7 @@ class ReplicaPrepare(admintool.AdminTool):
"Creating SSL certificate for the Directory Server")
self.export_certdb("dscert", passwd_fname)
- if not certs.ipa_self_signed():
+ if not options.dirsrv_pkcs12 and not certs.ipa_self_signed():
self.log.info(
"Creating SSL certificate for the dogtag Directory Server")
self.export_certdb("dogtagcert", passwd_fname)