summaryrefslogtreecommitdiffstats
path: root/ipaserver/install/certs.py
diff options
context:
space:
mode:
authorRob Crittenden <rcritten@redhat.com>2009-04-13 13:39:15 -0400
committerRob Crittenden <rcritten@redhat.com>2009-04-20 14:01:00 -0400
commit9182c10b03a7841c9318ad64ae6c5deda77d93d1 (patch)
tree75f7d73a2d1bab18686ddc9e31fe876401c633fe /ipaserver/install/certs.py
parentfdf03cb07b6d75eb3cdffbe4cf21cb510134c26d (diff)
downloadfreeipa-9182c10b03a7841c9318ad64ae6c5deda77d93d1.tar.gz
freeipa-9182c10b03a7841c9318ad64ae6c5deda77d93d1.tar.xz
freeipa-9182c10b03a7841c9318ad64ae6c5deda77d93d1.zip
Issue DS and Apache server certs during CA installation.
Notes: - will create a CA instance (pki-ca) if it doesn't exist - maintains support for a self-signed CA - A signing cert is still not created so Firefox autoconfig still won't work
Diffstat (limited to 'ipaserver/install/certs.py')
-rw-r--r--ipaserver/install/certs.py254
1 files changed, 212 insertions, 42 deletions
diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py
index e9061fe11..45958f2ae 100644
--- a/ipaserver/install/certs.py
+++ b/ipaserver/install/certs.py
@@ -22,15 +22,46 @@ import sha
import errno
import tempfile
import shutil
+import logging
+import urllib
+import xml.dom.minidom
+from ipapython import nsslib
from ipapython import sysrestore
from ipapython import ipautil
+from nss.error import NSPRError
+import nss.nss as nss
+
CA_SERIALNO="/var/lib/ipa/ca_serialno"
+def client_auth_data_callback(ca_names, chosen_nickname, password, certdb):
+ cert = None
+ if chosen_nickname:
+ try:
+ cert = nss.find_cert_from_nickname(chosen_nickname, password)
+ priv_key = nss.find_key_by_any_cert(cert, password)
+ return cert, priv_key
+ except NSPRError, e:
+ logging.debug("client auth callback failed %s" % str(e))
+ return False
+ else:
+ nicknames = nss.get_cert_nicknames(certdb, nss.SEC_CERT_NICKNAMES_USER)
+ for nickname in nicknames:
+ try:
+ cert = nss.find_cert_from_nickname(nickname, password)
+ if cert.check_valid_times():
+ if cert.has_signer_in_ca_names(ca_names):
+ priv_key = nss.find_key_by_any_cert(cert, password)
+ return cert, priv_key
+ except NSPRError, e:
+ logging.debug("client auth callback failed %s" % str(e))
+ return False
+ return False
+
class CertDB(object):
- def __init__(self, dir, fstore=None):
- self.secdir = dir
+ def __init__(self, nssdir, fstore=None, host_name=None):
+ self.secdir = nssdir
self.noise_fname = self.secdir + "/noise.txt"
self.passwd_fname = self.secdir + "/pwdfile.txt"
@@ -40,9 +71,16 @@ class CertDB(object):
self.cacert_fname = self.secdir + "/cacert.asc"
self.pk12_fname = self.secdir + "/cacert.p12"
self.pin_fname = self.secdir + "/pin.txt"
+ self.pwd_conf = "/etc/httpd/conf/password.conf"
self.reqdir = tempfile.mkdtemp('', 'ipa-', '/var/lib/ipa')
self.certreq_fname = self.reqdir + "/tmpcertreq"
self.certder_fname = self.reqdir + "/tmpcert.der"
+ self.host_name = host_name
+
+ if ipautil.file_exists(CA_SERIALNO):
+ self.self_signed_ca = True
+ else:
+ self.self_signed_ca = False
# Making this a starting value that will generate
# unique values for the current DB is the
@@ -72,6 +110,22 @@ class CertDB(object):
def __del__(self):
shutil.rmtree(self.reqdir, ignore_errors=True)
+ def find_cert_from_txt(self, cert):
+ """
+ Given a cert blob (str) which may or may not contian leading and
+ trailing text, pull out just the certificate part. This will return
+ the FIRST cert in a stream of data.
+ """
+ s = cert.find('-----BEGIN CERTIFICATE-----')
+ e = cert.find('-----END CERTIFICATE-----')
+ if e > 0: e = e + 25
+
+ if s < 0 or e < 0:
+ raise RuntimeError("Unable to find certificate")
+
+ cert = cert[s:e]
+ return cert
+
def set_serial_from_pkcs12(self):
"""A CA cert was loaded from a PKCS#12 file. Set up our serial file"""
@@ -94,6 +148,7 @@ class CertDB(object):
f.close()
except IOError, e:
if e.errno == errno.ENOENT:
+ self.self_signed_ca = True
self.cur_serial = 1000
f=open(CA_SERIALNO,"w")
f.write(str(self.cur_serial))
@@ -131,7 +186,8 @@ class CertDB(object):
ipautil.run(new_args, stdin)
def create_noise_file(self):
- ipautil.backup_file(self.noise_fname)
+ if ipautil.file_exists(self.noise_fname):
+ os.remove(self.noise_fname)
f = open(self.noise_fname, "w")
f.write(self.gen_password())
self.set_perms(self.noise_fname)
@@ -193,6 +249,14 @@ class CertDB(object):
"-a",
"-i", cacert_fname])
+ def get_cert_from_db(self, nickname):
+ try:
+ args = ["-L", "-n", nickname, "-a"]
+ (cert, err) = self.run_certutil(args)
+ return cert
+ except ipautil.CalledProcessError:
+ return ''
+
def find_cacert_serial(self):
(out,err) = self.run_certutil(["-L", "-n", self.cacert_name])
data = out.split('\n')
@@ -203,11 +267,20 @@ class CertDB(object):
raise RuntimeError("Unable to find serial number")
- def create_server_cert(self, nickname, name, other_certdb=None):
+ def create_server_cert(self, nickname, subject, other_certdb=None):
+ """
+ other_certdb can mean one of two things, depending on the context.
+
+ If we are using a self-signed CA then other_certdb contains the
+ CA that will be signing our CSR.
+
+ If we are using a dogtag CA then it contains the RA agent key
+ that will issue our cert.
+ """
cdb = other_certdb
if not cdb:
cdb = self
- self.request_cert(name)
+ (out, err) = self.request_cert(subject)
cdb.issue_server_cert(self.certreq_fname, self.certder_fname)
self.add_cert(self.certder_fname, nickname)
os.unlink(self.certreq_fname)
@@ -223,41 +296,103 @@ class CertDB(object):
os.unlink(self.certreq_fname)
os.unlink(self.certder_fname)
- def request_cert(self, name):
- self.run_certutil(["-R", "-s", name,
- "-o", self.certreq_fname,
- "-g", self.keysize,
- "-z", self.noise_fname,
- "-f", self.passwd_fname])
+ def request_cert(self, subject, certtype="rsa", keysize="2048"):
+ self.create_noise_file()
+ args = ["-R", "-s", subject,
+ "-o", self.certreq_fname,
+ "-k", certtype,
+ "-g", keysize,
+ "-z", self.noise_fname,
+ "-f", self.passwd_fname]
+ if not self.self_signed_ca:
+ args.append("-a")
+ (stdout, stderr) = self.run_certutil(args)
+ os.remove(self.noise_fname)
+
+ return (stdout, stderr)
def issue_server_cert(self, certreq_fname, cert_fname):
- p = subprocess.Popen(["/usr/bin/certutil",
- "-d", self.secdir,
- "-C", "-c", self.cacert_name,
- "-i", certreq_fname,
- "-o", cert_fname,
- "-m", self.next_serial(),
- "-v", self.valid_months,
- "-f", self.passwd_fname,
- "-1", "-5"],
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE)
+ if self.self_signed_ca:
+ p = subprocess.Popen(["/usr/bin/certutil",
+ "-d", self.secdir,
+ "-C", "-c", self.cacert_name,
+ "-i", certreq_fname,
+ "-o", cert_fname,
+ "-m", self.next_serial(),
+ "-v", self.valid_months,
+ "-f", self.passwd_fname,
+ "-1", "-5"],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+
+ # Bah - this sucks, but I guess it isn't possible to fully
+ # control this with command line arguments.
+ #
+ # What this is requesting is:
+ # -1 (Create key usage extension)
+ # 2 - Key encipherment
+ # 9 - done
+ # n - not critical
+ #
+ # -5 (Create netscape cert type extension)
+ # 1 - SSL Server
+ # 9 - done
+ # n - not critical
+ p.stdin.write("2\n9\nn\n1\n9\nn\n")
+ p.wait()
+ else:
+ if self.host_name is None:
+ raise RuntimeError("CA Host is not set.")
- # Bah - this sucks, but I guess it isn't possible to fully
- # control this with command line arguments.
- #
- # What this is requesting is:
- # -1 (Create key usage extension)
- # 2 - Key encipherment
- # 9 - done
- # n - not critical
- #
- # -5 (Create netscape cert type extension)
- # 1 - SSL Server
- # 9 - done
- # n - not critical
- p.stdin.write("2\n9\nn\n1\n9\nn\n")
- p.wait()
+ f = open(certreq_fname, "r")
+ csr = f.readlines()
+ f.close()
+ csr = "".join(csr)
+
+ # We just want the CSR bits, make sure there is nothing else
+ s = csr.find("-----BEGIN NEW CERTIFICATE REQUEST-----")
+ e = csr.find("-----END NEW CERTIFICATE REQUEST-----")
+ if e > 0:
+ e = e + 37
+ if s >= 0:
+ csr = csr[s:]
+
+ params = urllib.urlencode({'profileId': 'caRAserverCert',
+ 'cert_request_type': 'pkcs10',
+ 'requestor_name': 'IPA Installer',
+ 'cert_request': csr,
+ 'xmlOutput': 'true'})
+ headers = {"Content-type": "application/x-www-form-urlencoded",
+ "Accept": "text/plain"}
+
+ # Send the CSR request to the CA
+ f = open(self.passwd_fname)
+ password = f.readline()
+ f.close()
+ conn = nsslib.NSSConnection(self.host_name, 9444, dbdir=self.secdir)
+ conn.sslsock.set_client_auth_data_callback(client_auth_data_callback, "ipaCert", password, nss.get_default_certdb())
+ conn.set_debuglevel(0)
+
+ conn.request("POST", "/ca/ee/ca/profileSubmit", params, headers)
+ res = conn.getresponse()
+ data = res.read()
+ conn.close()
+ if res.status != 200:
+ raise RuntimeError("Unable to submit cert request")
+
+ # The result is an XML blob. Pull the certificate out of that
+ doc = xml.dom.minidom.parseString(data)
+ item_node = doc.getElementsByTagName("b64")
+ cert = item_node[0].childNodes[0].data
+ doc.unlink()
+
+ # Write the certificate to a file. It will be imported in a later
+ # step.
+ f = open(cert_fname, "w")
+ f.write(cert)
+ f.close()
+
+ return
def issue_signing_cert(self, certreq_fname, cert_fname):
p = subprocess.Popen(["/usr/bin/certutil",
@@ -290,12 +425,18 @@ class CertDB(object):
p.wait()
def add_cert(self, cert_fname, nickname):
- self.run_certutil(["-A", "-n", nickname,
- "-t", "u,u,u",
- "-i", cert_fname,
- "-f", cert_fname])
+ args = ["-A", "-n", nickname,
+ "-t", "u,u,u",
+ "-i", cert_fname,
+ "-f", cert_fname]
+ if not self.self_signed_ca:
+ args.append("-a")
+ self.run_certutil(args)
def create_pin_file(self):
+ """
+ This is the format of Directory Server pin files.
+ """
ipautil.backup_file(self.pin_fname)
f = open(self.pin_fname, "w")
f.write("Internal (Software) Token:")
@@ -304,6 +445,17 @@ class CertDB(object):
f.close()
self.set_perms(self.pin_fname)
+ def create_password_conf(self):
+ """
+ This is the format of mod_nss pin files.
+ """
+ ipautil.backup_file(self.pwd_conf)
+ f = open(self.pwd_conf, "w")
+ f.write("internal:")
+ pwd = open(self.passwd_fname)
+ f.write(pwd.read())
+ f.close()
+
def find_root_cert(self, nickname):
p = subprocess.Popen(["/usr/bin/certutil", "-d", self.secdir,
"-O", "-n", nickname], stdout=subprocess.PIPE)
@@ -375,7 +527,24 @@ class CertDB(object):
self.create_pin_file()
def create_from_cacert(self, cacert_fname, passwd=""):
- self.create_noise_file()
+ if ipautil.file_exists(self.certdb_fname):
+ # We already have a cert db, see if it is for the same CA.
+ # If it is we leave things as they are.
+ f = open(cacert_fname, "r")
+ newca = f.readlines()
+ f.close()
+ newca = "".join(newca)
+ newca = self.find_cert_from_txt(newca)
+
+ cacert = self.get_cert_from_db(self.cacert_name)
+ if cacert != '':
+ cacert = self.find_cert_from_txt(cacert)
+
+ if newca == cacert:
+ return
+
+ # The CA certificates are different or something went wrong. Start with
+ # a new certificate database.
self.create_passwd_file(passwd)
self.create_certdbs()
self.load_cacert(cacert_fname)
@@ -403,6 +572,7 @@ class CertDB(object):
self.trust_root_cert(nickname)
self.create_pin_file()
self.export_ca_cert(self.cacert_name, False)
+ self.self_signed_ca=False
# This file implies that we have our own self-signed CA. Ensure
# that it no longer exists (from previous installs, for example).