summaryrefslogtreecommitdiffstats
path: root/ipaserver
diff options
context:
space:
mode:
Diffstat (limited to 'ipaserver')
-rw-r--r--ipaserver/install/cainstance.py211
-rw-r--r--ipaserver/install/certs.py294
-rw-r--r--ipaserver/install/dsinstance.py14
-rw-r--r--ipaserver/install/httpinstance.py36
-rw-r--r--ipaserver/plugins/dogtag.py (renamed from ipaserver/plugins/ra.py)50
-rw-r--r--ipaserver/plugins/rabase.py113
-rw-r--r--ipaserver/plugins/selfsign.py126
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)