fe5dcbcef393cc0cb43cd99eb49c68d76b915a1c
[certmaster.git] / certmaster / certmaster.py
1 # FIXME: more intelligent fault raises
2
3 """
4 cert master listener
5
6 Copyright 2007, Red Hat, Inc
7 see AUTHORS
8
9 This software may be freely redistributed under the terms of the GNU
10 general public license.
11
12 You should have received a copy of the GNU General Public License
13 along with this program; if not, write to the Free Software
14 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
15 """
16
17 # standard modules
18 import SimpleXMLRPCServer
19 import sys
20 import os
21 import os.path
22 from OpenSSL import crypto
23 import sha
24 import glob
25 import socket
26 import exceptions
27
28 #from func.server import codes
29 import certs
30 import codes
31 import utils
32 from config import read_config
33 from commonconfig import CMConfig
34
35 CERTMASTER_LISTEN_PORT = 51235
36 CERTMASTER_CONFIG = "/etc/func/certmaster.conf"
37
38 class CertMaster(object):
39 def __init__(self, conf_file=CERTMASTER_CONFIG):
40 self.cfg = read_config(conf_file, CMConfig)
41
42 usename = utils.get_hostname()
43
44 mycn = '%s-CA-KEY' % usename
45 self.ca_key_file = '%s/funcmaster.key' % self.cfg.cadir
46 self.ca_cert_file = '%s/funcmaster.crt' % self.cfg.cadir
47 try:
48 if not os.path.exists(self.cfg.cadir):
49 os.makedirs(self.cfg.cadir)
50 if not os.path.exists(self.ca_key_file) and not os.path.exists(self.ca_cert_file):
51 certs.create_ca(CN=mycn, ca_key_file=self.ca_key_file, ca_cert_file=self.ca_cert_file)
52 except (IOError, OSError), e:
53 print 'Cannot make certmaster certificate authority keys/certs, aborting: %s' % e
54 sys.exit(1)
55
56
57 # open up the cakey and cacert so we have them available
58 self.cakey = certs.retrieve_key_from_file(self.ca_key_file)
59 self.cacert = certs.retrieve_cert_from_file(self.ca_cert_file)
60
61 for dirpath in [self.cfg.cadir, self.cfg.certroot, self.cfg.csrroot]:
62 if not os.path.exists(dirpath):
63 os.makedirs(dirpath)
64
65 # setup handlers
66 self.handlers = {
67 'wait_for_cert': self.wait_for_cert,
68 }
69
70 def _dispatch(self, method, params):
71 if method == 'trait_names' or method == '_getAttributeNames':
72 return self.handlers.keys()
73
74 if method in self.handlers.keys():
75 return self.handlers[method](*params)
76 else:
77 raise codes.InvalidMethodException
78
79 def _sanitize_cn(self, commonname):
80 commonname = commonname.replace('/', '')
81 commonname = commonname.replace('\\', '')
82 return commonname
83
84 def wait_for_cert(self, csrbuf):
85 """
86 takes csr as a string
87 returns True, caller_cert, ca_cert
88 returns False, '', ''
89 """
90
91 try:
92 csrreq = crypto.load_certificate_request(crypto.FILETYPE_PEM, csrbuf)
93 except crypto.Error, e:
94 #XXX need to raise a fault here and document it - but false is just as good
95 return False, '', ''
96
97 requesting_host = self._sanitize_cn(csrreq.get_subject().CN)
98
99 # get rid of dodgy characters in the filename we're about to make
100
101 certfile = '%s/%s.cert' % (self.cfg.certroot, requesting_host)
102 csrfile = '%s/%s.csr' % (self.cfg.csrroot, requesting_host)
103
104 # check for old csr on disk
105 # if we have it - compare the two - if they are not the same - raise a fault
106 if os.path.exists(csrfile):
107 oldfo = open(csrfile)
108 oldcsrbuf = oldfo.read()
109 oldsha = sha.new()
110 oldsha.update(oldcsrbuf)
111 olddig = oldsha.hexdigest()
112 newsha = sha.new()
113 newsha.update(csrbuf)
114 newdig = newsha.hexdigest()
115 if not newdig == olddig:
116 # XXX raise a proper fault
117 return False, '', ''
118
119 # look for a cert:
120 # if we have it, then return True, etc, etc
121 if os.path.exists(certfile):
122 slavecert = certs.retrieve_cert_from_file(certfile)
123 cert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, slavecert)
124 cacert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cacert)
125 return True, cert_buf, cacert_buf
126
127 # if we don't have a cert then:
128 # if we're autosign then sign it, write out the cert and return True, etc, etc
129 # else write out the csr
130
131 if self.cfg.autosign:
132 cert_fn = self.sign_this_csr(csrreq)
133 cert = certs.retrieve_cert_from_file(cert_fn)
134 cert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
135 cacert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cacert)
136 return True, cert_buf, cacert_buf
137
138 else:
139 # write the csr out to a file to be dealt with by the admin
140 destfo = open(csrfile, 'w')
141 destfo.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, csrreq))
142 destfo.close()
143 del destfo
144 return False, '', ''
145
146 return False, '', ''
147
148 def get_csrs_waiting(self):
149 hosts = []
150 csrglob = '%s/*.csr' % self.cfg.csrroot
151 csr_list = glob.glob(csrglob)
152 for f in csr_list:
153 hn = os.path.basename(f)
154 hn = hn[:-4]
155 hosts.append(hn)
156 return hosts
157
158 def remove_this_cert(self, hn):
159 """ removes cert for hostname using unlink """
160 cm = self
161 csrglob = '%s/%s.csr' % (cm.cfg.csrroot, hn)
162 csrs = glob.glob(csrglob)
163 certglob = '%s/%s.cert' % (cm.cfg.certroot, hn)
164 certs = glob.glob(certglob)
165 if not csrs and not certs:
166 # FIXME: should be an exception?
167 print 'No match for %s to clean up' % hn
168 return
169 for fn in csrs + certs:
170 print 'Cleaning out %s for host matching %s' % (fn, hn)
171 os.unlink(fn)
172
173 def sign_this_csr(self, csr):
174 """returns the path to the signed cert file"""
175 csr_unlink_file = None
176
177 if type(csr) is type(''):
178 if csr.startswith('/') and os.path.exists(csr): # we have a full path to the file
179 csrfo = open(csr)
180 csr_buf = csrfo.read()
181 csr_unlink_file = csr
182
183 elif os.path.exists('%s/%s' % (self.cfg.csrroot, csr)): # we have a partial path?
184 csrfo = open('%s/%s' % (self.cfg.csrroot, csr))
185 csr_buf = csrfo.read()
186 csr_unlink_file = '%s/%s' % (self.cfg.csrroot, csr)
187
188 # we have a string of some kind
189 else:
190 csr_buf = csr
191
192 try:
193 csrreq = crypto.load_certificate_request(crypto.FILETYPE_PEM, csr_buf)
194 except crypto.Error, e:
195 raise exceptions.Exception("Bad CSR: %s" % csr)
196
197 else: # assume we got a bare csr req
198 csrreq = csr
199 requesting_host = self._sanitize_cn(csrreq.get_subject().CN)
200
201 certfile = '%s/%s.cert' % (self.cfg.certroot, requesting_host)
202 thiscert = certs.create_slave_certificate(csrreq, self.cakey, self.cacert, self.cfg.cadir)
203 destfo = open(certfile, 'w')
204 destfo.write(crypto.dump_certificate(crypto.FILETYPE_PEM, thiscert))
205 destfo.close()
206 del destfo
207 if csr_unlink_file and os.path.exists(csr_unlink_file):
208 os.unlink(csr_unlink_file)
209
210 return certfile
211
212
213 class CertmasterXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
214 def __init__(self, args):
215 self.allow_reuse_address = True
216 SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, args)
217
218
219 def serve(xmlrpcinstance):
220
221 """
222 Code for starting the XMLRPC service.
223 """
224
225 server = CertmasterXMLRPCServer((xmlrpcinstance.cfg.listen_addr, CERTMASTER_LISTEN_PORT))
226 server.logRequests = 0 # don't print stuff to console
227 server.register_instance(xmlrpcinstance)
228 server.serve_forever()
229
230
231 def main(argv):
232
233 cm = CertMaster('/etc/func/certmaster.conf')
234
235 if "daemon" in argv or "--daemon" in argv:
236 utils.daemonize("/var/run/certmaster.pid")
237 else:
238 print "serving...\n"
239
240
241 # just let exceptions bubble up for now
242 serve(cm)
243
244
245 if __name__ == "__main__":
246 #textdomain(I18N_DOMAIN)
247 main(sys.argv)