summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorGraham Hagger <g.hagger@gmail.com>2010-10-22 14:00:29 -0400
committerGraham Hagger <g.hagger@gmail.com>2010-10-22 14:00:29 -0400
commita845a6d856f60876967258dfd5c39f8f97e8afd2 (patch)
treed03136ab0f5ad5b56b2fb0b118159605d5719709 /src
parent7e7529c1f14bf7c421d78a0ad93958504d728f92 (diff)
downloadbcfg2-a845a6d856f60876967258dfd5c39f8f97e8afd2.tar.gz
bcfg2-a845a6d856f60876967258dfd5c39f8f97e8afd2.tar.bz2
bcfg2-a845a6d856f60876967258dfd5c39f8f97e8afd2.zip
initial add of SSLCA plugin
Diffstat (limited to 'src')
-rw-r--r--src/lib/Server/Plugins/SSLCA.py209
1 files changed, 209 insertions, 0 deletions
diff --git a/src/lib/Server/Plugins/SSLCA.py b/src/lib/Server/Plugins/SSLCA.py
new file mode 100644
index 000000000..294f82f3f
--- /dev/null
+++ b/src/lib/Server/Plugins/SSLCA.py
@@ -0,0 +1,209 @@
+import Bcfg2.Server.Plugin
+import Bcfg2.Options
+import os
+from ConfigParser import ConfigParser, NoSectionError, NoOptionError
+from M2Crypto import RSA, EVP, X509, m2
+
+"""
+How this should work....
+
+V1.0 - Only handles localhost.key and localhost.crt, therefor
+assuming we only care about a cert for www, or all ssl services
+will use the same cert
+
+Initialiazation:
+Grab options from bcfg2.conf
+load cakey, cacert
+cache other options
+
+Req comes in for key & cert
+If key exists:
+ load key
+ cache key
+ return key
+Else:
+ gen key
+ cache key
+ save key
+ return key
+If cert exists:
+ load cert
+ If fails to verify against key:
+ gen cert
+ save cert
+ return cert
+ If aliases fail don't match
+ gen cert
+ save cert
+ return cert
+ return cert
+Else:
+ gen cert
+ save cert
+ return cert
+
+V2.0 - Maybe create additional types, SSLCertPath, SSLKeyPath,
+to allow generation of multiple certs/keys in arbitrary locations
+"""
+
+
+class SSLbase(Bcfg2.Server.Plugin.Plugin,
+ Bcfg2.Server.Plugin.Generator,
+ Bcfg2.Server.Plugin.DirectoryBacked):
+ """
+ The sslca generator manages ssl certificates
+ and keys
+ """
+
+ name = 'SSLbase'
+ __version__ = '0.00000000001'
+ __author__ = 'ghagger@wgen.net'
+
+ hostkey = 'localhost.key'
+ hostcert = 'localhost.crt'
+
+ def __init__(self, core, datastore):
+ Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
+ Bcfg2.Server.Plugin.Generator.__init__(self)
+ try:
+ Bcfg2.Server.Plugin.DirectoryBacked.__init__(self, self.data,
+ self.core.fam)
+ except OSError, ioerr:
+ self.logger.error("Failed to load SSLbase repository from %s" \
+ % (self.data))
+ self.logger.error(ioerr)
+ raise Bcfg2.Server.Plugin.PluginInitError
+ self.Entries = {'Path':
+ {'/etc/pki/tls/private/localhost.key': self.get_key,
+ '/etc/pki/tls/certs/localhost.crt': self.get_cert}}
+ # grab essential sslca configuration from bcfg2.conf
+ cp = ConfigParser()
+ cp.read(Bcfg2.Options.CFILE.value)
+ try:
+ ca_cert_filename = cp.get('sslca', 'ca_cert')
+ ca_key_filename = cp.get('sslca', 'ca_key')
+ self.ca_key_passphrase = cp.get('sslca', 'ca_key_passphrase')
+ self.cert_subject = cp.get('sslca', 'cert_subject')
+ self.cert_days = cp.get('sslca', 'cert_days')
+ self.pkey_bits = cp.get('sslca', 'pkey_bits')
+ except:
+ raise NoOptionError
+ self.ca_cert = X509.load_cert(ca_cert_filename)
+ self.ca_key = EVP.load_key(ca_key_filename, lambda x: self.ca_key_passphrase)
+ self._newkey = False
+
+ def get_key(self, entry, metadata):
+ filename = self.hostkey+".H_%s" % metadata.hostname
+ if filename in self.entries.keys():
+ entry.text = self.entries[filename].data
+ self.pkey = EVP.load_key_string(entry.text)
+ else:
+ (self.pkey, entry.text) = self.build_key(filename)
+ keyfile = open(self.data + '/' +filename, 'w')
+ keyfile.write(entry.text)
+ keyfile.close()
+ self._newkey = True
+
+ def build_key(self, filename):
+ """Generate new private key for client."""
+ rsa_key = RSA.gen_key(int(self.pkey_bits), m2.RSA_F4)
+ pkey = EVP.PKey()
+ pkey.assign_rsa(rsa_key)
+ keyfile = open(self.data + '/' +filename, 'w')
+ keyfile.write(pkey.as_pem(cipher=None))
+ keyfile.close()
+ self._newkey = True
+ return pkey, pkey.as_pem(cipher=None)
+
+ def get_cert(self, entry, metadata):
+ filename = self.hostcert + ".H_%s" % metadata.hostname
+ # load prexisting cert, if any
+ if filename in self.entries.keys() and self._newkey == False:
+ cert = X509.load_cert_string(self.entries[filename].data)
+ # check cert subjectAltNames match current aliases
+ cert_aliases = cert.get_ext('subjectAltName')
+ if cert_aliases:
+ if metadata.aliases != [alias.lstrip('DNS:') for alias in cert_aliases.get_value().split(', ')]:
+ entry.text = self.build_cert(filename, metadata)
+ return
+ entry.text = cert.as_text()+cert.as_string()
+ else:
+ entry.text = self.build_cert(filename, metadata)
+
+ def get_serial(self):
+ serialpath = self.data + '/serial'
+ serial = 0
+ if os.path.isfile(serialpath):
+ serialfile = open(serialpath, 'r')
+ serial = int(serialfile.read())
+ serialfile.close()
+ serialfile = open(serialpath, 'w')
+ serial += 1
+ serialfile.write(str(serial))
+ serialfile.close()
+ return serial
+
+ def build_cert(self, filename, metadata):
+ req = self.make_request(self.pkey, metadata)
+ serial = self.get_serial()
+ cert = self.make_cert(req, serial, metadata.aliases)
+ cert_out = cert.as_text()+cert.as_pem()
+ certfile = open(self.data + '/' +filename, 'w')
+ certfile.write(cert_out)
+ certfile.close()
+ cert_store = self.data + '/certstore'
+ if not os.path.isdir(cert_store):
+ os.mkdir(cert_store)
+ storefile = open(cert_store + '/' + str(serial) + '.pem', 'w')
+ storefile.write(cert_out)
+ storefile.close()
+ return cert_out
+
+ def make_request(self, key, metadata):
+ req = X509.Request()
+ req.set_version(2)
+ req.set_pubkey(key)
+ name = X509.X509_Name()
+ parts = [a.split('=') for a in self.cert_subject.split(',')]
+ [setattr(name, k, v) for k,v in parts]
+ name.CN = metadata.hostname
+ req.set_subject_name(name)
+ req.sign(key, 'sha1')
+ return req
+
+ def make_cert(self, req, serial, aliases):
+ pkey = req.get_pubkey()
+ if not req.verify(pkey):
+ raise ValueError, 'Error verifying request'
+ sub = req.get_subject()
+ cert = X509.X509()
+ cert.set_serial_number(serial)
+ cert.set_version(2)
+ cert.set_subject(sub)
+ cert.set_issuer(self.ca_cert)
+ cert.set_pubkey(pkey)
+ notBefore = m2.x509_get_not_before(cert.x509)
+ notAfter = m2.x509_get_not_after(cert.x509)
+ m2.x509_gmtime_adj(notBefore, 0)
+ m2.x509_gmtime_adj(notAfter, 60*60*24*long(self.cert_days))
+ exts = [
+ ('basicConstraints','CA:FALSE'),
+ ('subjectKeyIdentifier','hash'),
+ ('authorityKeyIdentifier','keyid,issuer:always'),
+ ('nsCertType','SSL Server'),
+ ]
+ if aliases:
+ exts.append(('subjectAltName', ','.join(['DNS:'+alias for alias in aliases])))
+ for ext in exts:
+ cert.add_ext(X509.new_extension(ext[0],ext[1]))
+ cert.sign(self.ca_key, 'sha1')
+ return cert
+
+ def HandleEvent(self, event=None):
+ """Local event handler that does something...."""
+ Bcfg2.Server.Plugin.DirectoryBacked.HandleEvent(self, event)
+
+ def HandlesEntry(self, entry, _):
+ """Handle entries dynamically."""
+ return entry.tag == 'Path' and (entry.get('name').endswith(self.hostkey) or entry.get('name').endswith(self.hostcert))
+