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