fix a bug where certmaster was writing out the client csr file over and over if it...
[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 import certs
29 import codes
30 import utils
31
32 import logger
33
34 from config import read_config
35 from commonconfig import CMConfig
36
37 CERTMASTER_LISTEN_PORT = 51235
38 CERTMASTER_CONFIG = "/etc/certmaster/certmaster.conf"
39
40 class CertMaster(object):
41 def __init__(self, conf_file=CERTMASTER_CONFIG):
42 self.cfg = read_config(conf_file, CMConfig)
43
44 usename = utils.get_hostname(talk_to_certmaster=False)
45
46 mycn = '%s-CA-KEY' % usename
47 self.ca_key_file = '%s/certmaster.key' % self.cfg.cadir
48 self.ca_cert_file = '%s/certmaster.crt' % self.cfg.cadir
49
50 self.logger = logger.Logger().logger
51 self.audit_logger = logger.AuditLogger()
52
53 try:
54 if not os.path.exists(self.cfg.cadir):
55 os.makedirs(self.cfg.cadir)
56 if not os.path.exists(self.ca_key_file) and not os.path.exists(self.ca_cert_file):
57 certs.create_ca(CN=mycn, ca_key_file=self.ca_key_file, ca_cert_file=self.ca_cert_file)
58 except (IOError, OSError), e:
59 print 'Cannot make certmaster certificate authority keys/certs, aborting: %s' % e
60 sys.exit(1)
61
62
63 # open up the cakey and cacert so we have them available
64 self.cakey = certs.retrieve_key_from_file(self.ca_key_file)
65 self.cacert = certs.retrieve_cert_from_file(self.ca_cert_file)
66
67 for dirpath in [self.cfg.cadir, self.cfg.certroot, self.cfg.csrroot]:
68 if not os.path.exists(dirpath):
69 os.makedirs(dirpath)
70
71 # setup handlers
72 self.handlers = {
73 'wait_for_cert': self.wait_for_cert,
74 }
75
76
77 def _dispatch(self, method, params):
78 if method == 'trait_names' or method == '_getAttributeNames':
79 return self.handlers.keys()
80
81
82 if method in self.handlers.keys():
83 return self.handlers[method](*params)
84 else:
85 self.logger.info("Unhandled method call for method: %s " % method)
86 raise codes.InvalidMethodException
87
88 def _sanitize_cn(self, commonname):
89 commonname = commonname.replace('/', '')
90 commonname = commonname.replace('\\', '')
91 return commonname
92
93 def wait_for_cert(self, csrbuf):
94 """
95 takes csr as a string
96 returns True, caller_cert, ca_cert
97 returns False, '', ''
98 """
99
100 try:
101 csrreq = crypto.load_certificate_request(crypto.FILETYPE_PEM, csrbuf)
102 except crypto.Error, e:
103 #XXX need to raise a fault here and document it - but false is just as good
104 return False, '', ''
105
106 requesting_host = self._sanitize_cn(csrreq.get_subject().CN)
107
108
109 self.logger.info("%s requested signing of cert %s" % (requesting_host,csrreq.get_subject().CN))
110 # get rid of dodgy characters in the filename we're about to make
111
112 certfile = '%s/%s.cert' % (self.cfg.certroot, requesting_host)
113 csrfile = '%s/%s.csr' % (self.cfg.csrroot, requesting_host)
114
115 # check for old csr on disk
116 # if we have it - compare the two - if they are not the same - raise a fault
117 self.logger.debug("csrfile: %s certfile: %s" % (csrfile, certfile))
118 if os.path.exists(csrfile):
119 oldfo = open(csrfile)
120 oldcsrbuf = oldfo.read()
121 oldsha = sha.new()
122 oldsha.update(oldcsrbuf)
123 olddig = oldsha.hexdigest()
124 newsha = sha.new()
125 newsha.update(csrbuf)
126 newdig = newsha.hexdigest()
127 if not newdig == olddig:
128 self.logger.info("A cert for %s already exists and does not match the requesting cert" % (requesting_host))
129 # XXX raise a proper fault
130 return False, '', ''
131
132
133 # look for a cert:
134 # if we have it, then return True, etc, etc
135 if os.path.exists(certfile):
136 slavecert = certs.retrieve_cert_from_file(certfile)
137 cert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, slavecert)
138 cacert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cacert)
139 return True, cert_buf, cacert_buf
140
141 # if we don't have a cert then:
142 # if we're autosign then sign it, write out the cert and return True, etc, etc
143 # else write out the csr
144
145 if self.cfg.autosign:
146 cert_fn = self.sign_this_csr(csrreq)
147 cert = certs.retrieve_cert_from_file(cert_fn)
148 cert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
149 cacert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cacert)
150 self.logger.info("cert for %s was autosigned" % (requesting_host))
151 return True, cert_buf, cacert_buf
152
153 else:
154 # write the csr out to a file to be dealt with by the admin
155 destfo = open(csrfile, 'w')
156 destfo.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, csrreq))
157 destfo.close()
158 del destfo
159 self.logger.info("cert for %s created and ready to be signed" % (requesting_host))
160 return False, '', ''
161
162 return False, '', ''
163
164 def get_csrs_waiting(self):
165 hosts = []
166 csrglob = '%s/*.csr' % self.cfg.csrroot
167 csr_list = glob.glob(csrglob)
168 for f in csr_list:
169 hn = os.path.basename(f)
170 hn = hn[:-4]
171 hosts.append(hn)
172 return hosts
173
174 def remove_this_cert(self, hn):
175 """ removes cert for hostname using unlink """
176 cm = self
177 csrglob = '%s/%s.csr' % (cm.cfg.csrroot, hn)
178 csrs = glob.glob(csrglob)
179 certglob = '%s/%s.cert' % (cm.cfg.certroot, hn)
180 certs = glob.glob(certglob)
181 if not csrs and not certs:
182 # FIXME: should be an exception?
183 print 'No match for %s to clean up' % hn
184 return
185 for fn in csrs + certs:
186 print 'Cleaning out %s for host matching %s' % (fn, hn)
187 self.logger.info('Cleaning out %s for host matching %s' % (fn, hn))
188 os.unlink(fn)
189
190 def sign_this_csr(self, csr):
191 """returns the path to the signed cert file"""
192 csr_unlink_file = None
193
194 if type(csr) is type(''):
195 if csr.startswith('/') and os.path.exists(csr): # we have a full path to the file
196 csrfo = open(csr)
197 csr_buf = csrfo.read()
198 csr_unlink_file = csr
199
200 elif os.path.exists('%s/%s' % (self.cfg.csrroot, csr)): # we have a partial path?
201 csrfo = open('%s/%s' % (self.cfg.csrroot, csr))
202 csr_buf = csrfo.read()
203 csr_unlink_file = '%s/%s' % (self.cfg.csrroot, csr)
204
205 # we have a string of some kind
206 else:
207 csr_buf = csr
208
209 try:
210 csrreq = crypto.load_certificate_request(crypto.FILETYPE_PEM, csr_buf)
211 except crypto.Error, e:
212 self.logger.info("Unable to sign %s: Bad CSR" % (csr))
213 raise exceptions.Exception("Bad CSR: %s" % csr)
214
215 else: # assume we got a bare csr req
216 csrreq = csr
217 requesting_host = self._sanitize_cn(csrreq.get_subject().CN)
218
219 certfile = '%s/%s.cert' % (self.cfg.certroot, requesting_host)
220 thiscert = certs.create_slave_certificate(csrreq, self.cakey, self.cacert, self.cfg.cadir)
221 destfo = open(certfile, 'w')
222 destfo.write(crypto.dump_certificate(crypto.FILETYPE_PEM, thiscert))
223 destfo.close()
224 del destfo
225 if csr_unlink_file and os.path.exists(csr_unlink_file):
226 os.unlink(csr_unlink_file)
227
228 return certfile
229
230
231
232
233 class CertmasterXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
234 def __init__(self, addr):
235 self.allow_reuse_address = True
236 SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, addr)
237
238
239 def serve(xmlrpcinstance):
240
241 """
242 Code for starting the XMLRPC service.
243 """
244
245
246 server = CertmasterXMLRPCServer((xmlrpcinstance.cfg.listen_addr, CERTMASTER_LISTEN_PORT))
247 server.logRequests = 0 # don't print stuff to console
248 server.register_instance(xmlrpcinstance)
249 xmlrpcinstance.logger.info("certmaster started")
250 xmlrpcinstance.audit_logger.logger.info("certmaster started")
251 server.serve_forever()
252
253
254 def main(argv):
255
256 cm = CertMaster('/etc/certmaster/certmaster.conf')
257
258 if "daemon" in argv or "--daemon" in argv:
259 utils.daemonize("/var/run/certmaster.pid")
260 else:
261 print "serving...\n"
262
263
264 # just let exceptions bubble up for now
265 serve(cm)
266
267
268 if __name__ == "__main__":
269 #textdomain(I18N_DOMAIN)
270 main(sys.argv)