summaryrefslogtreecommitdiffstats
path: root/ipaserver
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 /ipaserver
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
Diffstat (limited to 'ipaserver')
-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
5 files changed, 161 insertions, 19 deletions
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)