1 # FIXME: more intelligent fault raises
6 Copyright 2007, Red Hat, Inc
9 This software may be freely redistributed under the terms of the GNU
10 general public license.
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.
18 import SimpleXMLRPCServer
26 from OpenSSL
import crypto
31 # Python-2.4.z ... gah! (or even 2.3!)
37 # TODO: jude: was warnings even available in 2.4 ?
38 warnings
.warn("sha1 is deprecated", DeprecationWarning)
40 raise ValueError, "Bad checksum type"
52 from config
import read_config
53 from commonconfig
import CMConfig
55 CERTMASTER_LISTEN_PORT
= 51235
56 CERTMASTER_CONFIG
= "/etc/certmaster/certmaster.conf"
58 class CertMaster(object):
59 def __init__(self
, conf_file
=CERTMASTER_CONFIG
):
60 self
.cfg
= read_config(conf_file
, CMConfig
)
62 usename
= utils
.get_hostname(talk_to_certmaster
=False)
64 self
.logger
= logger
.Logger().logger
65 self
.audit_logger
= logger
.AuditLogger()
70 for (s_caname
,a_ca
) in self
.cfg
.ca
.iteritems():
74 mycn
= '%s-CA-KEY' % usename
76 mycn
= '%s-%s-CA-KEY' % (s_caname
.upper(),usename
)
78 s_ca_key_file
= '%s/certmaster.key' % s_cadir
79 s_ca_cert_file
= '%s/certmaster.crt' % s_cadir
81 # if ca_key_file exists and ca_cert_file is missing == minion only setup
82 if os
.path
.exists(s_ca_key_file
) and not os
.path
.exists(s_ca_cert_file
):
86 if not os
.path
.exists(s_cadir
):
88 if not os
.path
.exists(s_ca_key_file
) and not os
.path
.exists(s_ca_cert_file
):
89 certs
.create_ca(CN
=mycn
, ca_key_file
=s_ca_key_file
, ca_cert_file
=s_ca_cert_file
, hash_function
=a_ca
.hash_function
)
90 except (IOError, OSError), e
:
91 print 'Cannot make certmaster certificate authority keys/certs for CA %s, aborting: %s' % (s_caname
, e
)
94 # open up the cakey and cacert so we have them available
95 a_ca
.cakey
= certs
.retrieve_key_from_file(s_ca_key_file
)
96 a_ca
.cacert
= certs
.retrieve_cert_from_file(s_ca_cert_file
)
98 for dirpath
in [a_ca
.cadir
, a_ca
.certroot
, a_ca
.csrroot
, a_ca
.csrroot
]:
99 if not os
.path
.exists(dirpath
):
104 'wait_for_cert': self
.wait_for_cert
,
108 def _dispatch(self
, method
, params
):
109 if method
== 'trait_names' or method
== '_getAttributeNames':
110 return self
.handlers
.keys()
113 if method
in self
.handlers
.keys():
114 return self
.handlers
[method
](*params
)
116 self
.logger
.info("Unhandled method call for method: %s " % method
)
117 raise codes
.InvalidMethodException
119 def _sanitize_cn(self
, commonname
):
120 commonname
= commonname
.replace('/', '')
121 commonname
= commonname
.replace('\\', '')
124 def wait_for_cert(self
, csrbuf
, ca_name
, with_triggers
=True):
126 takes csr as a string
127 returns True, caller_cert, ca_cert, warning
128 returns False, '', '', ''
132 certauth
= self
.cfg
.ca
[ca_name
]
134 self
.logger
.info("Unknown cert authority: %s " % (ca_name
))
135 raise codes
.CMException("Unknown cert authority: %s" % ca_name
)
138 csrreq
= crypto
.load_certificate_request(crypto
.FILETYPE_PEM
, csrbuf
)
139 except crypto
.Error
, e
:
140 self
.logger
.info("crypto error: %s " % (e
))
141 #XXX need to raise a fault here and document it - but false is just as good
142 return False, '', '', ''
145 if certauth
.hash_function
== "md5":
148 ca_suffix
= ': ' + ca_name
149 fault
= "md5 hash function is unsupported%s" % ca_suffix
150 self
.logger
.error(fault
)
151 raise xmlrpclib
.Fault(1001,fault
)
152 elif certauth
.hash_function
== "sha1":
153 ret_warning
= "Deprecated hash function of sha1: %s\n" % ca_name
155 requesting_host
= self
._sanitize
_cn
(csrreq
.get_subject().CN
)
158 self
._run
_triggers
(requesting_host
, '/var/lib/certmaster/triggers/request/pre/*')
160 self
.logger
.info("%s requested signing of cert %s" % (requesting_host
,csrreq
.get_subject().CN
))
161 # get rid of dodgy characters in the filename we're about to make
163 certfile
= '%s/%s.cert' % (certauth
.certroot
, requesting_host
)
164 csrfile
= '%s/%s.csr' % (certauth
.csrroot
, requesting_host
)
166 # check for old csr on disk
167 # if we have it - compare the two - if they are not the same - raise a fault
168 self
.logger
.debug("csrfile: %s certfile: %s" % (csrfile
, certfile
))
170 if os
.path
.exists(csrfile
):
171 oldfo
= open(csrfile
)
172 oldcsrbuf
= oldfo
.read()
173 oldsha
= hashlib
.new(certauth
.hash_function
)
174 oldsha
.update(oldcsrbuf
)
175 olddig
= oldsha
.hexdigest()
176 newsha
= hashlib
.new(certauth
.hash_function
)
177 newsha
.update(csrbuf
)
178 newdig
= newsha
.hexdigest()
179 if not newdig
== olddig
:
180 self
.logger
.info("A cert for %s already exists and does not match the requesting cert" % (requesting_host
))
181 # XXX raise a proper fault
182 return False, '', '', ret_warning
186 # if we have it, then return True, etc, etc
187 if os
.path
.exists(certfile
):
188 slavecert
= certs
.retrieve_cert_from_file(certfile
)
189 cert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, slavecert
)
190 cacert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, certauth
.cacert
)
192 self
._run
_triggers
(requesting_host
,'/var/lib/certmaster/triggers/request/post/*')
193 return True, cert_buf
, cacert_buf
, ret_warning
195 # if we don't have a cert then:
196 # if we're autosign then sign it, write out the cert and return True, etc, etc
197 # else write out the csr
199 if certauth
.autosign
:
200 cert_fn
= self
.sign_this_csr(csrreq
,certauth
)
201 cert
= certs
.retrieve_cert_from_file(cert_fn
)
202 cert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, cert
)
203 cacert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, certauth
.cacert
)
204 self
.logger
.info("cert for %s for ca %s was autosigned" % (requesting_host
,ca_name
))
206 self
._run
_triggers
(None,'/var/lib/certmaster/triggers/request/post/*')
207 return True, cert_buf
, cacert_buf
, ret_warning
210 # write the csr out to a file to be dealt with by the admin
211 destfo
= open(csrfile
, 'w')
212 destfo
.write(crypto
.dump_certificate_request(crypto
.FILETYPE_PEM
, csrreq
))
215 self
.logger
.info("cert for %s for CA %s created and ready to be signed" % (requesting_host
, ca_name
))
217 self
._run
_triggers
(None,'/var/lib/certmaster/triggers/request/post/*')
218 return False, '', '', ret_warning
220 return False, '', '', ret_warning
222 def get_csrs_waiting(self
, certauth
):
224 csrglob
= '%s/*.csr' % certauth
.csrroot
225 csr_list
= glob
.glob(csrglob
)
227 hn
= os
.path
.basename(f
)
232 def remove_this_cert(self
, hn
, certauth
, with_triggers
=True):
233 """ removes cert for hostname using unlink """
235 csrglob
= '%s/%s.csr' % (certauth
.csrroot
, hn
)
236 csrs
= glob
.glob(csrglob
)
237 certglob
= '%s/%s.cert' % (certauth
.certroot
, hn
)
238 certs
= glob
.glob(certglob
)
239 if not csrs
and not certs
:
240 # FIXME: should be an exception?
241 print 'No match for %s to clean up' % hn
244 self
._run
_triggers
(hn
,'/var/lib/certmaster/triggers/remove/pre/*')
245 for fn
in csrs
+ certs
:
246 print 'Cleaning out %s for host matching %s' % (fn
, hn
)
247 self
.logger
.info('Cleaning out %s for host matching %s' % (fn
, hn
))
250 self
._run
_triggers
(hn
,'/var/lib/certmaster/triggers/remove/post/*')
252 def sign_this_csr(self
, csr
, certauth
,with_triggers
=True):
253 """returns the path to the signed cert file"""
254 csr_unlink_file
= None
256 if type(csr
) is type(''):
257 if csr
.startswith('/') and os
.path
.exists(csr
): # we have a full path to the file
259 csr_buf
= csrfo
.read()
260 csr_unlink_file
= csr
262 elif os
.path
.exists('%s/%s' % (certauth
.csrroot
, csr
)): # we have a partial path?
263 csrfo
= open('%s/%s' % (certauth
.csrroot
, csr
))
264 csr_buf
= csrfo
.read()
265 csr_unlink_file
= '%s/%s' % (certauth
.csrroot
, csr
)
267 # we have a string of some kind
272 csrreq
= crypto
.load_certificate_request(crypto
.FILETYPE_PEM
, csr_buf
)
273 except crypto
.Error
, e
:
274 self
.logger
.info("Unable to sign %s: Bad CSR" % (csr
))
275 raise exceptions
.Exception("Bad CSR: %s" % csr
)
277 else: # assume we got a bare csr req
281 requesting_host
= self
._sanitize
_cn
(csrreq
.get_subject().CN
)
283 self
._run
_triggers
(requesting_host
,'/var/lib/certmaster/triggers/sign/pre/*')
286 certfile
= '%s/%s.cert' % (certauth
.certroot
, requesting_host
)
287 self
.logger
.info("Signing for csr %s requested" % certfile
)
288 thiscert
= certs
.create_slave_certificate(csrreq
, certauth
.cakey
, certauth
.cacert
, certauth
.cadir
, hash_function
=certauth
.hash_function
)
290 destfo
= open(certfile
, 'w')
291 destfo
.write(crypto
.dump_certificate(crypto
.FILETYPE_PEM
, thiscert
))
296 self
.logger
.info("csr %s signed" % (certfile
))
298 self
._run
_triggers
(requesting_host
,'/var/lib/certmaster/triggers/sign/post/*')
301 if csr_unlink_file
and os
.path
.exists(csr_unlink_file
):
302 os
.unlink(csr_unlink_file
)
306 # return a list of already signed certs
307 def get_signed_certs(self
, certauth
,hostglobs
=None):
308 certglob
= "%s/*.cert" % (certauth
.certroot
)
315 for hostglob
in globs
:
316 certglob
= "%s/%s.cert" % (certauth
.certroot
, hostglob
)
317 certs
= certs
+ glob
.glob(certglob
)
321 # just want the hostname, so strip off path and ext
322 signed_certs
.append(os
.path
.basename(cert
).split(".cert", 1)[0])
326 def get_peer_certs(self
):
328 Returns a list of all certs under peerroot
330 myglob
= os
.path
.join(self
.cfg
.peerroot
, '*.%s' % self
.cfg
.cert_extension
)
331 return glob
.glob(myglob
)
333 # return a list of the cert hash string we use to identify systems
334 def get_cert_hashes(self
, certauth
, hostglobs
=None):
335 certglob
= "%s/*.cert" % (certauth
.certroot
)
342 for hostglob
in globs
:
343 certglob
= "%s/%s.cert" % (certauth
.certroot
, hostglob
)
344 certfiles
= certfiles
+ glob
.glob(certglob
)
347 for certfile
in certfiles
:
348 cert
= certs
.retrieve_cert_from_file(certfile
)
349 cert_hashes
.append("%s-%s" % (cert
.get_subject().CN
, cert
.subject_name_hash()))
353 def _run_triggers(self
, ref
, globber
):
354 return utils
.run_triggers(ref
, globber
)
357 class CertmasterXMLRPCServer(SimpleXMLRPCServer
.SimpleXMLRPCServer
):
358 def __init__(self
, addr
):
359 self
.allow_reuse_address
= True
360 SimpleXMLRPCServer
.SimpleXMLRPCServer
.__init
__(self
, addr
)
363 def serve(xmlrpcinstance
):
366 Code for starting the XMLRPC service.
370 config
= read_config(CERTMASTER_CONFIG
, CMConfig
)
371 listen_addr
= config
.listen_addr
372 listen_port
= config
.listen_port
373 if listen_port
== '':
374 listen_port
= CERTMASTER_LISTEN_PORT
375 server
= CertmasterXMLRPCServer((listen_addr
,listen_port
))
376 server
.logRequests
= 0 # don't print stuff to console
377 server
.register_instance(xmlrpcinstance
)
378 xmlrpcinstance
.logger
.info("certmaster started")
379 xmlrpcinstance
.audit_logger
.logger
.info("certmaster started")
380 server
.serve_forever()
382 def excepthook(exctype
, value
, tracebackobj
):
383 exctype_blurb
= "Exception occured: %s" % exctype
384 excvalue_blurb
= "Exception value: %s" % value
385 exctb_blurb
= "Exception Info:\n%s" % string
.join(traceback
.format_list(traceback
.extract_tb(tracebackobj
)))
391 log
= logger
.Logger().logger
392 log
.info(exctype_blurb
)
393 log
.info(excvalue_blurb
)
394 log
.info(exctb_blurb
)
399 sys
.excepthook
= excepthook
400 cm
= CertMaster('/etc/certmaster/certmaster.conf')
402 if "--version" in sys
.argv
or "-v" in sys
.argv
:
403 print >> sys
.stderr
, file("/etc/certmaster/version").read().strip()
406 if "daemon" in argv
or "--daemon" in argv
:
407 utils
.daemonize("/var/run/certmaster.pid")
411 # just let exceptions bubble up for now
415 if __name__
== "__main__":
416 #textdomain(I18N_DOMAIN)