diff options
Diffstat (limited to 'ipaserver')
-rw-r--r-- | ipaserver/install/cainstance.py | 211 | ||||
-rw-r--r-- | ipaserver/install/certs.py | 294 | ||||
-rw-r--r-- | ipaserver/install/dsinstance.py | 14 | ||||
-rw-r--r-- | ipaserver/install/httpinstance.py | 36 | ||||
-rw-r--r-- | ipaserver/plugins/dogtag.py (renamed from ipaserver/plugins/ra.py) | 50 | ||||
-rw-r--r-- | ipaserver/plugins/rabase.py | 113 | ||||
-rw-r--r-- | ipaserver/plugins/selfsign.py | 126 |
7 files changed, 652 insertions, 192 deletions
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index 054ceaf2d..d07e58295 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -36,6 +36,7 @@ import urllib import xml.dom.minidom import stat from ipapython import dogtag +import subprocess from nss.error import NSPRError import nss.nss as nss @@ -74,6 +75,23 @@ RootDN= cn=Directory Manager RootDNPwd= $PASSWORD """ +def check_inst(): + """ + Validate that the appropriate dogtag/RHCS packages have been installed. + """ + + # Check for a couple of binaries we need + if not os.path.exists('/usr/bin/pkicreate'): + return False + if not os.path.exists('/usr/bin/pkisilent'): + return False + + # This is the template tomcat file for a CA + if not os.path.exists('/usr/share/pki/ca/conf/server.xml'): + return False + + return True + def get_preop_pin(instance_root, instance_name): preop_pin = None @@ -355,6 +373,11 @@ class CAInstance(service.Service): NSS database in mod_python. In nsslib.py we do an nssinit but this will return success if the database is already initialized. It doesn't care if the database is different or not. + + external is a state machine: + 0 = not an externally signed CA + 1 = generating CSR to be signed + 2 = have signed cert, continue installation """ def __init__(self): @@ -365,7 +388,11 @@ class CAInstance(service.Service): self.host_name = None self.pkcs12_info = None self.clone = False - self.external = False + # for external CAs + self.external = 0 + self.csr_file = None + self.cert_file = None + self.cert_chain_file = None # The same database is used for mod_nss because the NSS context # will already have been initialized by Apache by the time @@ -384,7 +411,20 @@ class CAInstance(service.Service): def __del__(self): shutil.rmtree(self.ca_agent_db, ignore_errors=True) - def configure_instance(self, pki_user, host_name, dm_password, admin_password, ds_port=DEFAULT_DSPORT, pkcs12_info=None, master_host=None): + def configure_instance(self, pki_user, host_name, dm_password, + admin_password, ds_port=DEFAULT_DSPORT, + pkcs12_info=None, master_host=None, csr_file=None, + cert_file=None, cert_chain_file=None): + """Create a CA instance. This may involve creating the pki-ca instance + dogtag instance. + + To create a clone, pass in pkcs12_info. + + Creating a CA with an external signer is a 2-step process. In + step 1 we generate a CSR. In step 2 we are given the cert and + chain and actually proceed to create the CA. For step 1 set + csr_file. For step 2 set cert_file and cert_chain_file. + """ self.pki_user = pki_user self.host_name = host_name self.dm_password = dm_password @@ -395,23 +435,36 @@ class CAInstance(service.Service): self.clone = True self.master_host = master_host + # Determine if we are installing as an externally-signed CA and + # what stage we're in. + if csr_file is not None: + self.csr_file=csr_file + self.external=1 + elif cert_file is not None: + self.cert_file=cert_file + self.cert_chain_file=cert_chain_file + self.external=2 + if not ipautil.dir_exists("/var/lib/pki-ca"): self.step("creating pki-ca instance", self.create_instance) self.step("creating certificate server user", self.__create_ca_user) self.step("configuring certificate server instance", self.__configure_instance) - if not self.clone: - self.step("creating CA agent PKCS#12 file in /root", self.__create_ca_agent_pkcs12) - self.step("creating RA agent certificate database", self.__create_ra_agent_db) - self.step("importing CA chain to RA certificate database", self.__import_ca_chain) - if not self.clone: - self.step("requesting RA certificate from CA", self.__request_ra_certificate) - self.step("issuing RA agent certificate", self.__issue_ra_cert) - self.step("adding RA agent as a trusted user", self.__configure_ra) - self.step("fixing RA database permissions", self.fix_ra_perms) - self.step("setting up signing cert profile", self.__setup_sign_profile) - self.step("set up CRL publishing", self.__enable_crl_publish) - self.step("configuring certificate server to start on boot", self.__enable) - self.step("restarting certificate server", self.__restart_instance) + # Step 1 of external is getting a CSR so we don't need to do these + # steps until we get a cert back from the external CA. + if self.external != 1: + if not self.clone: + self.step("creating CA agent PKCS#12 file in /root", self.__create_ca_agent_pkcs12) + self.step("creating RA agent certificate database", self.__create_ra_agent_db) + self.step("importing CA chain to RA certificate database", self.__import_ca_chain) + if not self.clone: + self.step("requesting RA certificate from CA", self.__request_ra_certificate) + self.step("issuing RA agent certificate", self.__issue_ra_cert) + self.step("adding RA agent as a trusted user", self.__configure_ra) + self.step("fixing RA database permissions", self.fix_ra_perms) + self.step("setting up signing cert profile", self.__setup_sign_profile) + self.step("set up CRL publishing", self.__enable_crl_publish) + self.step("configuring certificate server to start on boot", self.__enable) + self.step("restarting certificate server", self.__restart_instance) self.start_creation("Configuring certificate server:") @@ -434,19 +487,6 @@ class CAInstance(service.Service): ] ipautil.run(args) - # Turn off Nonces - if installutils.update_file('/var/lib/pki-ca/conf/CS.cfg', 'ca.enableNonces=true', 'ca.enableNonces=false') != 0: - raise RuntimeError("Disabling nonces failed") - pent = pwd.getpwnam(self.pki_user) - os.chown('/var/lib/pki-ca/conf/CS.cfg', pent.pw_uid, pent.pw_gid ) - - logging.debug("restarting ca instance") - try: - self.restart() - logging.debug("done restarting ca instance") - except ipautil.CalledProcessError, e: - print "failed to restart ca instance", e - def __enable(self): self.backup_state("enabled", self.is_enabled()) self.chkconfig_on() @@ -470,7 +510,18 @@ class CAInstance(service.Service): self.backup_state("user_exists", user_exists) def __configure_instance(self): -#--skipcreate -u pkiuser -g pkiuser -p password -a password -d --hostname `hostname` -n IPA + # Turn off Nonces + if installutils.update_file('/var/lib/pki-ca/conf/CS.cfg', 'ca.enableNonces=true', 'ca.enableNonces=false') != 0: + raise RuntimeError("Disabling nonces failed") + pent = pwd.getpwnam(self.pki_user) + os.chown('/var/lib/pki-ca/conf/CS.cfg', pent.pw_uid, pent.pw_gid ) + + logging.debug("restarting ca instance") + try: + self.restart() + logging.debug("done restarting ca instance") + except ipautil.CalledProcessError, e: + print "failed to restart ca instance", e preop_pin = get_preop_pin(self.server_root, self.service_name) @@ -506,17 +557,18 @@ class CAInstance(service.Service): "-ca_server_cert_subject_name", "CN=" + self.host_name + ",O=" + self.domain_name, "-ca_audit_signing_cert_subject_name", "\"CN=CA Audit Signing Certificate,O=" + self.domain_name + "\"", "-ca_sign_cert_subject_name", "\"CN=Certificate Authority,O=" + self.domain_name + "\"" ] - if (self.external): + if self.external == 1: args.append("-external") args.append("true") -# args.append("-ext_csr_file") -# args.append(ext_csr_file) -# if (options.cacertfile): -# args.append("-ext_ca_cert_file") -# args.append(options.cacertfile) -# if (options.cacertchainfile): -# args.append("-ext_ca_cert_chain_file") -# args.append(options.cacertchainfile) + args.append("-ext_csr_file") + args.append(self.csr_file) + elif self.external == 2: + args.append("-external") + args.append("true") + args.append("-ext_ca_cert_file") + args.append(self.cert_file) + args.append("-ext_ca_cert_chain_file") + args.append(self.cert_chain_file) else: args.append("-external") args.append("false") @@ -551,6 +603,10 @@ class CAInstance(service.Service): logging.debug(args) ipautil.run(args) + if self.external == 1: + print "The next step is to get %s signed by your CA and re-run ipa-server-install" % self.csr_file + sys.exit(0) + # pkisilent doesn't return 1 on error so look at the output of # /sbin/service pki-ca status. It will tell us if the instance # still needs to be configured. @@ -593,11 +649,28 @@ class CAInstance(service.Service): os.write(admin_fd, self.admin_password) os.close(admin_fd) + # Look thru the cert chain to get all the certs we need to add + # trust for + p = subprocess.Popen(["/usr/bin/certutil", "-d", self.ca_agent_db, + "-O", "-n", "ipa-ca-agent"], stdout=subprocess.PIPE) + + chain = p.stdout.read() + chain = chain.split("\n") + + root_nickname=[] + for i in xrange(len(chain)): + m = re.match('\ *"(.*)" \[.*', chain[i]) + if m: + nick = m.groups(0)[0] + if nick != "ipa-ca-agent" and nick[:7] != "Builtin": + root_nickname.append(m.groups()[0]) + try: - self.__run_certutil( - ['-M', '-t', 'CT,C,C', '-n', - 'Certificate Authority - %s' % self.domain_name - ], database=self.ca_agent_db, pwd_file=self.admin_password) + for nick in root_nickname: + self.__run_certutil( + ['-M', '-t', 'CT,C,C', '-n', + nick], + database=self.ca_agent_db, pwd_file=self.admin_password) finally: os.remove(admin_name) @@ -695,7 +768,7 @@ class CAInstance(service.Service): ('usertype', "agentType"), ('userstate', "1"), ('userCertificate', decoded), - ('description', '2;7;CN=Certificate Authority,O=%s;CN=RA Subsystem Certificate,OU=pki-ipa,O=%s' % (self.domain_name, self.domain_name)),] + ('description', '2;%s;CN=Certificate Authority,O=%s;CN=RA Subsystem Certificate,OU=pki-ipa,O=%s' % (str(self.requestId), self.domain_name, self.domain_name)),] ld.add_s(entry_dn, entry) @@ -759,16 +832,48 @@ class CAInstance(service.Service): def __import_ca_chain(self): chain = self.__get_ca_chain() - (chain_fd, chain_name) = tempfile.mkstemp() - os.write(chain_fd, chain) - os.close(chain_fd) - try: - self.__run_certutil( - ['-A', '-t', 'CT,C,C', '-n', self.canickname, '-a', - '-i', chain_name] - ) - finally: - os.remove(chain_name) + + # If this chain contains multiple certs then certutil will only import + # the first one. So we have to pull them all out and import them + # separately. Unfortunately no NSS tool can do this so we have to + # use openssl. + + # Convert to DER because the chain comes back as one long string which + # makes openssl throw up. + data = base64.b64decode(chain) + + (certs, stderr) = ipautil.run(["/usr/bin/openssl", + "pkcs7", + "-inform", + "DER", + "-print_certs", + ], stdin=data) + + # Ok, now we have all the certificates in certs, walk thru it + # and pull out each certificate and add it to our database + + st = 1 + en = 0 + subid = 0 + while st > 0: + st = certs.find('-----BEGIN', en) + en = certs.find('-----END', en+1) + if st > 0: + try: + (chain_fd, chain_name) = tempfile.mkstemp() + os.write(chain_fd, certs[st:en+25]) + os.close(chain_fd) + if subid == 0: + nick = self.canickname + else: + nick = "%s sub %d" % (self.canickname, subid) + self.__run_certutil( + ['-A', '-t', 'CT,C,C', '-n', nick, '-a', + '-i', chain_name] + ) + finally: + os.remove(chain_name) + subid = subid + 1 def __request_ra_certificate(self): # Create a noise file for generating our private key diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py index df59acc3f..ca5351201 100644 --- a/ipaserver/install/certs.py +++ b/ipaserver/install/certs.py @@ -25,14 +25,18 @@ import logging import urllib import xml.dom.minidom import pwd +import fcntl from ipapython import nsslib from ipapython import sysrestore from ipapython import ipautil +from ConfigParser import RawConfigParser from nss.error import NSPRError import nss.nss as nss +from ipalib import api + # The sha module is deprecated in Python 2.6, replaced by hashlib. Try # that first and fall back to sha.sha if it isn't available. try: @@ -46,12 +50,9 @@ def ipa_self_signed(): """ Determine if the current IPA CA is self-signed or using another CA - Note that this doesn't distinguish between dogtag and being provided - PKCS#12 files from another CA. - - A server is self-signed if /var/lib/ipa/ca_serialno exists + We do this based on the CA plugin that is currently in use. """ - if ipautil.file_exists(CA_SERIALNO): + if api.env.ra_plugin == 'selfsign': return True else: return False @@ -80,6 +81,96 @@ def client_auth_data_callback(ca_names, chosen_nickname, password, certdb): return False return False +def find_cert_from_txt(cert, start=0): + """ + 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. + + Returns a tuple (certificate, last position in cert) + """ + s = cert.find('-----BEGIN CERTIFICATE-----', start) + e = cert.find('-----END CERTIFICATE-----', s) + if e > 0: e = e + 25 + + if s < 0 or e < 0: + raise RuntimeError("Unable to find certificate") + + cert = cert[s:e] + return (cert, e) + +def next_serial(serial_file=CA_SERIALNO): + """ + Get the next serial number if we're using an NSS-based self-signed CA. + + The file is an ini-like file with following properties: + lastvalue = the last serial number handed out + nextreplica = the serial number the next replica should start with + replicainterval = the number to add to nextreplica the next time a + replica is created + + File locking is attempted so we have unique serial numbers. + """ + fp = None + parser = RawConfigParser() + if ipautil.file_exists(serial_file): + try: + fp = open(serial_file, "r+") + fcntl.flock(fp.fileno(), fcntl.LOCK_EX) + parser.readfp(fp) + serial = parser.getint('selfsign', 'lastvalue') + cur_serial = serial + 1 + except IOError, e: + raise RuntimeError("Unable to determine serial number: %s" % str(e)) + else: + fp = open(serial_file, "w") + fcntl.flock(fp.fileno(), fcntl.LOCK_EX) + parser.add_section('selfsign') + parser.set('selfsign', 'nextreplica', 500000) + parser.set('selfsign', 'replicainterval', 500000) + cur_serial = 1000 + + try: + fp.seek(0) + parser.set('selfsign', 'lastvalue', cur_serial) + parser.write(fp) + fp.flush() + fcntl.flock(fp.fileno(), fcntl.LOCK_UN) + fp.close() + except IOError, e: + raise RuntimeError("Unable to increment serial number: %s" % str(e)) + + return str(cur_serial) + +def next_replica(serial_file=CA_SERIALNO): + """ + Return the starting serial number for a new self-signed replica + """ + fp = None + parser = RawConfigParser() + if ipautil.file_exists(serial_file): + try: + fp = open(serial_file, "r+") + fcntl.flock(fp.fileno(), fcntl.LOCK_EX) + parser.readfp(fp) + serial = parser.getint('selfsign', 'nextreplica') + nextreplica = serial + parser.getint('selfsign', 'replicainterval') + except IOError, e: + raise RuntimeError("Unable to determine serial number: %s" % str(e)) + else: + raise RuntimeError("%s does not exist, cannot create replica" % serial_file) + try: + fp.seek(0) + parser.set('selfsign', 'nextreplica', nextreplica) + parser.write(fp) + fp.flush() + fcntl.flock(fp.fileno(), fcntl.LOCK_UN) + fp.close() + except IOError, e: + raise RuntimeError("Unable to increment serial number: %s" % str(e)) + + return str(serial) + class CertDB(object): def __init__(self, nssdir, fstore=None, host_name=None): self.secdir = nssdir @@ -93,9 +184,9 @@ class CertDB(object): 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.reqdir = None + self.certreq_fname = None + self.certder_fname = None self.host_name = host_name self.self_signed_ca = ipa_self_signed() @@ -105,13 +196,6 @@ class CertDB(object): else: self.subject_format = "CN=%s,OU=pki-ipa,O=IPA" - # Making this a starting value that will generate - # unique values for the current DB is the - # responsibility of the caller for now. In the - # future we might automatically determine this - # for a given db. - self.cur_serial = -1 - self.cacert_name = "CA certificate" self.valid_months = "120" self.keysize = "1024" @@ -131,63 +215,40 @@ class CertDB(object): self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') def __del__(self): - shutil.rmtree(self.reqdir, ignore_errors=True) + if self.reqdir is not None: + 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. + def setup_cert_request(self): """ - s = cert.find('-----BEGIN CERTIFICATE-----') - e = cert.find('-----END CERTIFICATE-----') - if e > 0: e = e + 25 + Create a temporary directory to store certificate requests and + certificates. This should be called before requesting certificates. - if s < 0 or e < 0: - raise RuntimeError("Unable to find certificate") + This is set outside of __init__ to avoid creating a temporary + directory every time we open a cert DB. + """ + if self.reqdir is not None: + return - cert = cert[s:e] - return cert + self.reqdir = tempfile.mkdtemp('', 'ipa-', '/var/lib/ipa') + self.certreq_fname = self.reqdir + "/tmpcertreq" + self.certder_fname = self.reqdir + "/tmpcert.der" def set_serial_from_pkcs12(self): """A CA cert was loaded from a PKCS#12 file. Set up our serial file""" - self.cur_serial = self.find_cacert_serial() + cur_serial = self.find_cacert_serial() try: - f=open(CA_SERIALNO,"w") - f.write(str(self.cur_serial)) - f.close() - except IOError, e: - raise RuntimeError("Unable to increment serial number: %s" % str(e)) - - def next_serial(self): - try: - f=open(CA_SERIALNO,"r") - r = f.readline() - try: - self.cur_serial = int(r) + 1 - except ValueError: - raise RuntimeError("The value in %s is not an integer" % CA_SERIALNO) - 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)) - f.close() - else: - raise RuntimeError("Unable to determine serial number: %s" % str(e)) - - try: - f=open(CA_SERIALNO,"w") - f.write(str(self.cur_serial)) - f.close() + fp = open(CA_SERIALNO, "w") + parser = RawConfigParser() + parser.add_section('selfsign') + parser.set('selfsign', 'lastvalue', cur_serial) + parser.set('selfsign', 'nextreplica', 500000) + parser.set('selfsign', 'replicainterval', 500000) + parser.write(fp) + fp.close() except IOError, e: raise RuntimeError("Unable to increment serial number: %s" % str(e)) - return str(self.cur_serial) - def set_perms(self, fname, write=False, uid=None): if uid: pent = pwd.getpwnam(uid) @@ -280,27 +341,35 @@ class CertDB(object): return False def create_ca_cert(self): - # Generate the encryption key - self.run_certutil(["-G", "-z", self.noise_fname, "-f", self.passwd_fname]) - # Generate the self-signed cert p = subprocess.Popen(["/usr/bin/certutil", "-d", self.secdir, "-S", "-n", self.cacert_name, "-s", "cn=IPA Test Certificate Authority", "-x", "-t", "CT,,C", + "-1", "-2", - "-m", self.next_serial(), + "-5", + "-m", next_serial(), "-v", self.valid_months, "-z", self.noise_fname, "-f", self.passwd_fname], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + # Create key usage extension + # 0 - Digital Signature + # 1 - Non-repudiation + # 5 - Cert signing key + # Is this a critical extension [y/N]? y + p.stdin.write("0\n1\n5\n9\ny\n") + # Create basic constraint extension # Is this a CA certificate [y/N]? y # Enter the path length constraint, enter to skip [<0 for unlimited pat # Is this a critical extension [y/N]? y - p.stdin.write("y\n\n7\n") + # 5 6 7 9 n -> SSL, S/MIME, Object signing CA + p.stdin.write("y\n\ny\n") + p.stdin.write("5\n6\n7\n9\nn\n") p.wait() def export_ca_cert(self, nickname, create_pkcs12=False): @@ -310,9 +379,12 @@ class CertDB(object): do that step.""" # export the CA cert for use with other apps ipautil.backup_file(self.cacert_fname) - self.run_certutil(["-L", "-n", nickname, - "-a", - "-o", self.cacert_fname]) + root_nicknames = self.find_root_cert(nickname) + fd = open(self.cacert_fname, "w") + for root in root_nicknames: + (cert, stderr) = self.run_certutil(["-L", "-n", root, "-a"]) + fd.write(cert) + fd.close() os.chmod(self.cacert_fname, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) if create_pkcs12: ipautil.backup_file(self.pk12_fname) @@ -324,10 +396,30 @@ class CertDB(object): self.set_perms(self.pk12_fname) def load_cacert(self, cacert_fname): - self.run_certutil(["-A", "-n", self.cacert_name, - "-t", "CT,,C", - "-a", - "-i", cacert_fname]) + """ + Load all the certificates from a given file. It is assumed that + this file creates CA certificates. + """ + fd = open(cacert_fname) + certs = fd.read() + fd.close() + + st = 0 + subid=0 + while True: + try: + (cert, st) = find_cert_from_txt(certs, st) + if subid == 0: + nick = self.cacert_name + else: + nick = "%s sub %d" % (self.cacert_name, subid) + subid = subid + 1 + self.run_certutil(["-A", "-n", nick, + "-t", "CT,,C", + "-a"], + stdin=cert) + except RuntimeError: + break def get_cert_from_db(self, nickname): try: @@ -384,6 +476,7 @@ class CertDB(object): def request_cert(self, subject, certtype="rsa", keysize="2048"): self.create_noise_file() + self.setup_cert_request() args = ["-R", "-s", subject, "-o", self.certreq_fname, "-k", certtype, @@ -398,13 +491,14 @@ class CertDB(object): return (stdout, stderr) def issue_server_cert(self, certreq_fname, cert_fname): + self.setup_cert_request() 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(), + "-m", next_serial(), "-v", self.valid_months, "-f", self.passwd_fname, "-1", "-5"], @@ -469,9 +563,14 @@ class CertDB(object): # 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() - conn.close() + try: + try: + cert = item_node[0].childNodes[0].data + except IndexError: + raise RuntimeError("Certificate issuance failed") + finally: + doc.unlink() + conn.close() # Write the certificate to a file. It will be imported in a later # step. @@ -482,13 +581,14 @@ class CertDB(object): return def issue_signing_cert(self, certreq_fname, cert_fname): + self.setup_cert_request() 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(), + "-m", next_serial(), "-v", self.valid_months, "-f", self.passwd_fname, "-1", "-5"], @@ -565,10 +665,13 @@ class CertDB(object): return def add_cert(self, cert_fname, nickname): + """ + Load a certificate from a PEM file and add minimal trust. + """ args = ["-A", "-n", nickname, "-t", "u,u,u", "-i", cert_fname, - "-f", cert_fname] + "-f", self.passwd_fname] if not self.self_signed_ca: args.append("-a") self.run_certutil(args) @@ -600,21 +703,38 @@ class CertDB(object): self.set_perms(self.pwd_conf, uid="apache") def find_root_cert(self, nickname): + """ + Given a nickname, return a list of the certificates that make up + the trust chain. + """ + root_nicknames = [] p = subprocess.Popen(["/usr/bin/certutil", "-d", self.secdir, "-O", "-n", nickname], stdout=subprocess.PIPE) chain = p.stdout.read() chain = chain.split("\n") - root_nickname = re.match('\ *"(.*)" \[.*', chain[0]).groups()[0] + for c in chain: + m = re.match('\s*"(.*)" \[.*', c) + if m: + root_nicknames.append(m.groups()[0]) + + if len(root_nicknames) > 1: + # If you pass in the name of a CA to get the chain it may only + # return 1 (self-signed). Return that. + try: + root_nicknames.remove(nickname) + except ValueError: + # The nickname wasn't in the list + pass # Try to work around a change in the F-11 certutil where untrusted # CA's are not shown in the chain. This will make a default IPA # server installable. - if root_nickname is None and self.self_signed_ca: - return self.cacert_name + if len(root_nicknames) == 0 and self.self_signed_ca: + return [self.cacert_name] - return root_nickname + return root_nicknames def find_root_cert_from_pkcs12(self, pkcs12_fname, passwd_fname=None): """Given a PKCS#12 file, try to find any certificates that do @@ -723,11 +843,11 @@ class CertDB(object): newca = f.readlines() f.close() newca = "".join(newca) - newca = self.find_cert_from_txt(newca) + (newca, st) = find_cert_from_txt(newca) cacert = self.get_cert_from_db(self.cacert_name) if cacert != '': - cacert = self.find_cert_from_txt(cacert) + (cacert, st) = find_cert_from_txt(cacert) if newca == cacert: return @@ -765,11 +885,11 @@ class CertDB(object): raise RuntimeError("Could not find a CA cert in %s" % pkcs12_fname) self.cacert_name = ca_names[0] - for nickname in ca_names: - self.trust_root_cert(nickname) + for ca in ca_names: + self.trust_root_cert(ca) self.create_pin_file() - self.export_ca_cert(self.cacert_name, False) + self.export_ca_cert(nickname, False) self.self_signed_ca=False # This file implies that we have our own self-signed CA. Ensure diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index 90d64b112..eb0356289 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -35,7 +35,8 @@ import certs import ldap from ipaserver import ipaldap from ipaserver.install import ldapupdate -from ipalib import util, errors +from ipaserver.install import httpinstance +from ipalib import util SERVER_ROOT_64 = "/usr/lib64/dirsrv" SERVER_ROOT_32 = "/usr/lib/dirsrv" @@ -328,12 +329,15 @@ class DsInstance(service.Service): nickname = server_certs[0][0] else: nickname = "Server-Cert" + cadb = certs.CertDB(httpinstance.NSS_DIR, host_name=self.host_name) if self.self_signed_ca: - dsdb.create_self_signed() - dsdb.create_server_cert("Server-Cert", self.host_name) + cadb.create_self_signed() + dsdb.create_from_cacert(cadb.cacert_fname) + dsdb.create_server_cert("Server-Cert", self.host_name, cadb) + dsdb.create_pin_file() else: - cadb = certs.CertDB("/etc/httpd/alias", host_name=self.host_name) - cadb.export_ca_cert(cadb.cacert_name, False) + # FIXME, need to set this nickname in the RA plugin + cadb.export_ca_cert('ipaCert', False) dsdb.create_from_cacert(cadb.cacert_fname, passwd=None) dsdb.create_server_cert("Server-Cert", self.host_name, cadb) dsdb.create_pin_file() diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py index b6483fb3d..7e7adfa79 100644 --- a/ipaserver/install/httpinstance.py +++ b/ipaserver/install/httpinstance.py @@ -65,8 +65,7 @@ class HTTPInstance(service.Service): self.step("disabling mod_ssl in httpd", self.__disable_mod_ssl) self.step("Setting mod_nss port to 443", self.__set_mod_nss_port) - if not self_signed_ca: - self.step("Setting mod_nss password file", self.__set_mod_nss_passwordfile) + self.step("Setting mod_nss password file", self.__set_mod_nss_passwordfile) self.step("Adding URL rewriting rules", self.__add_include) self.step("configuring httpd", self.__configure_http) self.step("creating a keytab for httpd", self.__create_http_keytab) @@ -158,7 +157,7 @@ class HTTPInstance(service.Service): def __setup_ssl(self): if self.self_signed_ca: - ca_db = certs.CertDB(dsinstance.config_dirname(dsinstance.realm_to_serverid(self.realm))) + ca_db = certs.CertDB(NSS_DIR) else: ca_db = certs.CertDB(NSS_DIR, host_name=self.fqdn) db = certs.CertDB(NSS_DIR) @@ -176,6 +175,7 @@ class HTTPInstance(service.Service): else: if self.self_signed_ca: db.create_from_cacert(ca_db.cacert_fname) + db.create_password_conf() db.create_server_cert("Server-Cert", self.fqdn, ca_db) db.create_signing_cert("Signing-Cert", "Object Signing Cert", ca_db) else: @@ -184,14 +184,27 @@ class HTTPInstance(service.Service): db.create_password_conf() # Fix the database permissions - os.chmod(NSS_DIR + "/cert8.db", 0640) - os.chmod(NSS_DIR + "/key3.db", 0640) - os.chmod(NSS_DIR + "/secmod.db", 0640) + os.chmod(NSS_DIR + "/cert8.db", 0660) + os.chmod(NSS_DIR + "/key3.db", 0660) + os.chmod(NSS_DIR + "/secmod.db", 0660) + os.chmod(NSS_DIR + "/pwdfile.txt", 0660) pent = pwd.getpwnam("apache") os.chown(NSS_DIR + "/cert8.db", 0, pent.pw_gid ) os.chown(NSS_DIR + "/key3.db", 0, pent.pw_gid ) os.chown(NSS_DIR + "/secmod.db", 0, pent.pw_gid ) + os.chown(NSS_DIR + "/pwdfile.txt", 0, pent.pw_gid ) + + # Fix SELinux permissions on the database + ipautil.run(["/sbin/restorecon", NSS_DIR + "/cert8.db"]) + ipautil.run(["/sbin/restorecon", NSS_DIR + "/key3.db"]) + + # In case this got generated as part of the install, reset the + # context + if ipautil.file_exists(certs.CA_SERIALNO): + ipautil.run(["/sbin/restorecon", certs.CA_SERIALNO]) + os.chown(certs.CA_SERIALNO, 0, pent.pw_gid) + os.chmod(certs.CA_SERIALNO, 0664) def __setup_autoconfig(self): prefs_txt = ipautil.template_file(ipautil.SHARE_DIR + "preferences.html.template", self.sub_dict) @@ -202,19 +215,20 @@ class HTTPInstance(service.Service): # The signing cert is generated in __setup_ssl db = certs.CertDB(NSS_DIR) + pwdfile = open(db.passwd_fname) + pwd = pwdfile.read() + pwdfile.close() + tmpdir = tempfile.mkdtemp(prefix = "tmp-") shutil.copy("/usr/share/ipa/html/preferences.html", tmpdir) db.run_signtool(["-k", "Signing-Cert", "-Z", "/usr/share/ipa/html/configure.jar", - "-e", ".html", + "-e", ".html", "-p", pwd, tmpdir]) shutil.rmtree(tmpdir) def __publish_ca_cert(self): - if self.self_signed_ca: - ca_db = certs.CertDB(dsinstance.config_dirname(dsinstance.realm_to_serverid(self.realm))) - else: - ca_db = certs.CertDB(NSS_DIR) + ca_db = certs.CertDB(NSS_DIR) shutil.copy(ca_db.cacert_fname, "/usr/share/ipa/html/ca.crt") os.chmod("/usr/share/ipa/html/ca.crt", 0444) diff --git a/ipaserver/plugins/ra.py b/ipaserver/plugins/dogtag.py index 1238f5036..2b1e84e7a 100644 --- a/ipaserver/plugins/ra.py +++ b/ipaserver/plugins/dogtag.py @@ -20,39 +20,22 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ -Backend plugin for IPA-RA. - -The `ra` plugin provides access to the CA to issue, retrieve, and revoke -certificates via the following methods: - - * `ra.check_request_status()` - check certificate request status. - * `ra.get_certificate()` - retrieve an existing certificate. - * `ra.request_certificate()` - request a new certificate. - * `ra.revoke_certificate()` - revoke a certificate. - * `ra.take_certificate_off_hold()` - take a certificate off hold. +Backend plugin for RA using Dogtag. """ from ipalib import api, SkipPluginModule -if api.env.enable_ra is not True: +if api.env.ra_plugin != 'dogtag': # In this case, abort loading this plugin module... - raise SkipPluginModule(reason='env.enable_ra is not True') -import os, stat, subprocess -import array -import errno -import binascii + raise SkipPluginModule(reason='dogtag not selected as RA plugin') +import os from httplib import HTTPConnection -from urllib import urlencode, quote -from socket import gethostname +from urllib import urlencode +from ipaserver.plugins import rabase import socket -from ipalib import Backend from ipalib.errors import NetworkError -from ipaserver import servercore -from ipaserver import ipaldap from ipalib.constants import TYPE_ERROR from ipapython import nsslib import nss.nss as nss -import nss.ssl as ssl -from nss.error import NSPRError import xml.dom.minidom def get_xml_value(doc, tagname): @@ -62,7 +45,7 @@ def get_xml_value(doc, tagname): except IndexError: return None -class ra(Backend): +class ra(rabase.rabase): """ Request Authority backend plugin. """ @@ -110,29 +93,20 @@ class ra(Backend): self.debug('request data %s', data) return (status, reason, data) - def _sslget(self, url, **kw): + def _sslget(self, url, port, **kw): """ Perform an HTTPS request :param url: The URL to post to. :param kw: Keyword arguments to encode into POST body. """ - uri = 'https://%s:%d%s' % (self.env.ca_host, self.env.ca_ssl_port, url) + uri = 'https://%s:%d%s' % (self.env.ca_host, port, url) post = urlencode(kw) self.info('sslget %r', uri) self.debug('sslget post %r', post) - argv = [ - '/usr/bin/sslget', - '-n', self.ipa_certificate_nickname, # nickname - '-w', self.pwd_file, # pwfile - '-d', self.sec_dir, # dbdir - '-e', post, # post - '-r', url, # url - '%s:%d' % (self.env.ca_host, self.env.ca_ssl_port), - ] headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"} - conn = nsslib.NSSConnection(self.env.ca_host, self.env.ca_ssl_port, dbdir=self.sec_dir) + conn = nsslib.NSSConnection(self.env.ca_host, port, dbdir=self.sec_dir) conn.sslsock.set_client_auth_data_callback(nsslib.client_auth_data_callback, self.ipa_certificate_nickname, self.password, nss.get_default_certdb()) conn.set_debuglevel(10) conn.request("POST", url, post, headers) @@ -194,6 +168,7 @@ class ra(Backend): issued_certificate = None (status, reason, stdout) = self._sslget( '/ca/agent/ca/displayBySerial', + self.env.ca_agent_port, serialNumber=serial_number, xmlOutput='true', ) @@ -229,6 +204,7 @@ class ra(Backend): self.debug('%s.request_certificate()', self.fullname) certificate = None (status, reason, stdout) = self._sslget('/ca/ee/ca/profileSubmit', + self.env.ca_ee_port, profileId='caRAserverCert', cert_request_type=request_type, cert_request=csr, @@ -292,6 +268,7 @@ class ra(Backend): ) response = {} (status, reason, stdout) = self._sslget('/ca/agent/ca/doRevoke', + self.env.ca_agent_port, op='revoke', revocationReason=revocation_reason, revokeAll='(certRecordId=%s)' % serial_number, @@ -316,6 +293,7 @@ class ra(Backend): response = {} self.debug('%s.take_certificate_off_hold()', self.fullname) (status, reason, stdout) = self._sslget('/ca/agent/ca/doUnrevoke', + self.env.ca_agent_port, serialNumber=serial_number, ) if (status == 0): diff --git a/ipaserver/plugins/rabase.py b/ipaserver/plugins/rabase.py new file mode 100644 index 000000000..3fc0ec5cd --- /dev/null +++ b/ipaserver/plugins/rabase.py @@ -0,0 +1,113 @@ +# Authors: +# Rob Crittenden <rcritten@@redhat.com> +# +# Copyright (C) 2009 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" +Backend plugin for RA activities. + +The `ra` plugin provides access to the CA to issue, retrieve, and revoke +certificates via the following methods: + + * `ra.check_request_status()` - check certificate request status. + * `ra.get_certificate()` - retrieve an existing certificate. + * `ra.request_certificate()` - request a new certificate. + * `ra.revoke_certificate()` - revoke a certificate. + * `ra.take_certificate_off_hold()` - take a certificate off hold. +""" + +from ipalib import api +from ipalib import Backend +from ipalib import errors +from ipaserver.install import certs +import os + +class rabase(Backend): + """ + Request Authority backend plugin. + """ + def __init__(self): + if api.env.home: + self.sec_dir = api.env.dot_ipa + os.sep + 'alias' + self.pwd_file = self.sec_dir + os.sep + '.pwd' + self.serial_file = self.sec_dir + os.sep + 'ca_serialno' + else: + self.sec_dir = "/etc/httpd/alias" + self.pwd_file = "/etc/httpd/alias/pwdfile.txt" + self.serial_file = certs.CA_SERIALNO + super(rabase, self).__init__() + + + def check_request_status(self, request_id): + """ + Check status of a certificate signing request. + + :param request_id: request ID + """ + raise errors.NotImplementedError(name='%s.check_request_status' % self.name) + + def get_certificate(self, serial_number=None): + """ + Retrieve an existing certificate. + + :param serial_number: certificate serial number + """ + raise errors.NotImplementedError(name='%s.check_request_status' % self.name) + + def request_certificate(self, csr, request_type='pkcs10'): + """ + Submit certificate signing request. + + :param csr: The certificate signing request. + :param request_type: The request type (defaults to ``'pkcs10'``). + """ + raise errors.NotImplementedError(name='%s.check_request_status' % self.name) + + def revoke_certificate(self, serial_number, revocation_reason=0): + """ + Revoke a certificate. + + The integer ``revocation_reason`` code must have one of these values: + + * ``0`` - unspecified + * ``1`` - keyCompromise + * ``2`` - cACompromise + * ``3`` - affiliationChanged + * ``4`` - superseded + * ``5`` - cessationOfOperation + * ``6`` - certificateHold + * ``8`` - removeFromCRL + * ``9`` - privilegeWithdrawn + * ``10`` - aACompromise + + Note that reason code ``7`` is not used. See RFC 5280 for more details: + + http://www.ietf.org/rfc/rfc5280.txt + + :param serial_number: Certificate serial number. + :param revocation_reason: Integer code of revocation reason. + """ + raise errors.NotImplementedError(name='%s.check_request_status' % self.name) + + def take_certificate_off_hold(self, serial_number): + """ + Take revoked certificate off hold. + + :param serial_number: Certificate serial number. + """ + raise errors.NotImplementedError('%s.check_request_status' % self.name) + diff --git a/ipaserver/plugins/selfsign.py b/ipaserver/plugins/selfsign.py new file mode 100644 index 000000000..4bec0665a --- /dev/null +++ b/ipaserver/plugins/selfsign.py @@ -0,0 +1,126 @@ +# Authors: +# Rob Crittenden <rcritten@@redhat.com> +# +# Copyright (C) 2009 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" +Backend plugin for RA activities. + +The `ra` plugin provides access to the CA to issue, retrieve, and revoke +certificates via the following methods: + + * `ra.check_request_status()` - check certificate request status. + * `ra.get_certificate()` - retrieve an existing certificate. + * `ra.request_certificate()` - request a new certificate. + * `ra.revoke_certificate()` - revoke a certificate. + * `ra.take_certificate_off_hold()` - take a certificate off hold. +""" + +from ipalib import api, SkipPluginModule +if api.env.ra_plugin != 'selfsign': + # In this case, abort loading this plugin module... + raise SkipPluginModule(reason='selfsign is not selected as RA plugin, it is %s' % api.env.ra_plugin) +from ipalib import Backend +from ipalib import errors +import subprocess +import os +from ipaserver.plugins import rabase +from ipaserver.install import certs +import tempfile +from OpenSSL import crypto + +class ra(rabase.rabase): + """ + Request Authority backend plugin. + """ + + def request_certificate(self, csr, request_type='pkcs10'): + """ + Submit certificate signing request. + + :param csr: The certificate signing request. + :param request_type: The request type (defaults to ``'pkcs10'``). + """ + (csr_fd, csr_name) = tempfile.mkstemp() + os.write(csr_fd, csr) + os.close(csr_fd) + (cert_fd, cert_name) = tempfile.mkstemp() + os.close(cert_fd) + + serialno = certs.next_serial(self.serial_file) + + try: + args = [ + "/usr/bin/certutil", + "-C", + "-d", self.sec_dir, + "-c", "CA certificate", + "-i", csr_name, + "-o", cert_name, + "-m", str(serialno), + "-v", "60", + "-1", + "-5", + "-6", + "-a", + "-f", self.pwd_file] + self.log.debug("issue cert: %s" % str(args)) + p = subprocess.Popen(args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, close_fds=True) + p.stdin.write("0\n1\n2\n3\n9\ny\n") + p.stdin.write("0\n9\nn\n") + p.stdin.write("1\n9\nn\n") + (stdout, stderr) = p.communicate() + self.log.debug("stdout = %s" % stdout) + self.log.debug("stderr = %s" % stderr) + finally: + os.remove(csr_name) + + try: + cert_fd = open(cert_name) + cert = cert_fd.read() + cert_fd.close() + finally: + os.remove(cert_name) + + try: + # Grab the subject, reverse it, combine it and return it + x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert) + sub = x509.get_subject().get_components() + sub.reverse() + subject = "" + for s in sub: + subject = subject + "%s=%s," % (s[0], s[1]) + subject = subject[:-1] + + serial = x509.get_serial_number() + except crypto.Error, e: + raise errors.GenericError(format='Unable to decode certificate in entry: %s' % str(e)) + + # To make it look like dogtag return just the base64 data. + cert = cert.replace('\n','') + cert = cert.replace('\r','') + s = cert.find('-----BEGIN CERTIFICATE-----') + e = cert.find('-----END CERTIFICATE-----') + s = s + 27 + cert = cert[s:e] + + return {'status':0, 'subject': subject, 'certificate':cert, 'serial': "0x%x" % serial} + +api.register(ra) |