3
########################################################################
5
# (c) by Michael Stroeder, michael@stroeder.com
6
########################################################################
10
########################################################################
11
# This script is typically run by CRON or a similar task manager.
12
# It does several jobs (some not implemented yet):
13
# - Mark expired certificates in OpenSSL certificate database
14
# - Sort in new certificates and inform user via e-mail where to
15
# download his certificate
16
# - Spool certificate requests and certificate revocation requests
17
# - Remove stale certificate requests from caPendCertReqDir
18
########################################################################
20
import sys, string, os, smtplib, getopt
22
from time import time,gmtime,localtime,strftime,mktime
24
def findoption(options,paramname):
30
def PrintUsage(ErrorMsg='',ErrorCode=1):
31
script_name = string.split(sys.argv[0],os.sep)[-1]
32
sys.stderr.write("""*** %s *** (C) by Michael Stroeder, 1999
39
Print out this message
42
Pathname of OpenSSL configuration file.
43
You may also use env variable OPENSSL_CONF.
44
Default: /etc/openssl/openssl.cnf
47
Specify directory containing the pyCA modules
48
Default: /usr/local/pyca/pylib
50
""" % (script_name,script_name))
52
sys.stderr.write('Error: %s\n' % ErrorMsg)
55
########################################################################
57
########################################################################
59
script_name=sys.argv[0]
62
options,args=getopt.getopt(sys.argv[1:],'h',['help','config=','pycalib='])
63
except getopt.error,e:
66
if findoption(options,'-h')!=() or findoption(options,'--help')!=():
69
if findoption(options,'--config')!=():
70
opensslcnfname = findoption(options,'--config')[1]
72
opensslcnfname = os.environ.get('OPENSSL_CONF','/etc/openssl/openssl.cnf')
74
if not os.path.isfile(opensslcnfname):
75
PrintUsage('Config file %s not found.' % (opensslcnfname))
77
if findoption(options,'--pycalib')!=():
78
pycalib = findoption(options,'--pycalib')[1]
80
pycalib = os.environ.get('PYCALIB','/usr/local/pyca/pylib')
82
if not os.path.exists(pycalib) or not os.path.isdir(pycalib):
83
PrintUsage('Module directory %s not exists or not a directory.' % (pycalib))
85
sys.path.append(pycalib)
88
import openssl,charset
89
from openssl.db import \
91
DB_type,DB_exp_date,DB_rev_date,DB_serial,DB_file,DB_name,DB_number, \
92
DB_TYPE_REV,DB_TYPE_EXP,DB_TYPE_VAL, \
93
dbtime2tuple,GetEntriesbyDN,SplitDN
95
PrintUsage('Required pyCA modules not found in directory %s!' % (pycalib))
97
# Read the configuration file
98
if os.path.isfile('%s.pickle' % (opensslcnfname)):
99
# Try to read OpenSSL's config file from a pickled copy
100
f=open('%s.pickle' % (opensslcnfname),'rb')
102
# first try to use the faster cPickle module
103
from cPickle import load
105
from pickle import load
109
# Parse OpenSSL's config file from source
110
opensslcnf=openssl.cnf.OpenSSLConfigClass(opensslcnfname)
112
pyca_section = opensslcnf.data.get('pyca',{})
114
openssl.bin_filename = pyca_section.get('OpenSSLExec','/usr/local/ssl/bin/openssl')
115
if not os.path.isfile(openssl.bin_filename):
116
PrintUsage('Did not find OpenSSL executable %s.' % (openssl.bin_filename))
118
MailRelay = pyca_section.get('MailRelay','localhost')
119
nsGetCertUrl = pyca_section.get('nsGetCertUrl','cgi-bin/get-cert.py')
120
nsViewCertUrl = pyca_section.get('nsViewCertUrl','cgi-bin/view-cert.py')
121
caPendCertReqValid = 3600*string.atoi(pyca_section.get('caPendCertReqValid','0'))
123
newcert_mailtext = r"""From: %s
125
Subject: Your certificate %d
127
The certificate you requested has been issued and is valid
130
You can retrieve your certificate from here:
134
Please use the same web browser on the same machine, with same
135
login and configuration data as when you created the certificate
136
request. Otherwise your software will likely refuse to install
139
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
140
! It is highly recommended to make a backup copy of your !
141
! private key and certificate right after installing it. !
142
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
144
If you have further questions simply reply to this e-mail.
146
----------------------------------------------------------
150
Detail view of your certificate:
156
gmtstr = strftime('%Y%m%d%H%M%S',gmtime(gmt))
158
######################################################################
159
# Spool certificates and certificate revocation lists
160
# from system holding the private keys
161
######################################################################
165
######################################################################
166
# CA specific actions
167
######################################################################
169
ca_names = opensslcnf.sectionkeys.get('ca',[])
171
# Lists of processed files and dirs to avoid double-processing
172
processed_ca_databases = []
173
processed_ca_crls = []
174
processed_pend_reqs_dirs = []
175
processed_new_reqs_dirs = []
176
processed_new_certs_dirs = []
178
for ca_name in ca_names:
180
sys.stdout.write('\n\nProcessing certificate authority "%s"\n' % (ca_name))
182
ca = opensslcnf.getcadata(ca_name)
185
######################################################################
186
# Processing certificate database
187
######################################################################
189
if not ca.database in processed_ca_databases:
191
if os.path.isfile(ca.database):
193
processed_ca_databases.append(ca.database)
194
# Certificate database not processed up to now
195
ca_db = openssl.db.OpenSSLcaDatabaseClass(ca.database)
197
# Mark expired certificates in OpenSSL certificate database
198
expired_db_entries = ca_db.Expire()
199
if expired_db_entries:
200
sys.stdout.write('The following entries were marked as expired:\n')
201
for db_entry in expired_db_entries:
202
sys.stdout.write('%s\n' % (charset.asn12iso(db_entry[DB_name])))
204
# Mark expired certificates in OpenSSL certificate database
205
expire_treshold=7*86400
206
expired_db_entries = ca_db.ExpireWarning(expire_treshold)
207
if expired_db_entries:
208
sys.stdout.write('The following entries will expire soon:\n')
209
for db_entry in expired_db_entries:
210
sys.stdout.write('%s, %s, %s\n' % (
212
strftime('%Y-%m-%d %H:%M',localtime(mktime(dbtime2tuple(db_entry[DB_exp_date])))),
213
charset.asn12iso(db_entry[DB_name])
218
sys.stderr.write('Warning: CA database file %s not found.\n' % (ca.database))
221
######################################################################
222
# Move expired CRLs to archive
223
######################################################################
225
if not ca.crl in processed_ca_crls:
227
if os.path.isfile(ca.crl):
229
processed_ca_crls.append(ca.crl)
230
ca_crl = openssl.cert.CRLClass(ca.crl)
232
if ca_crl.nextUpdate_secs<gmt:
234
ca_archived_crl = os.path.join(ca.crl_dir,os.path.basename('%s-%s.pem' % (os.path.splitext(ca.crl)[0],gmtstr)))
235
# Archive the copy in the preferred format
236
os.rename(ca.crl,ca_archived_crl)
237
sys.stdout.write('Archived expired CRL file %s.\n' % (ca_archived_crl))
240
sys.stderr.write('Warning: CRL file %s not found.\n' % (ca.crl))
243
######################################################################
244
# Remove stale certificate requests
245
######################################################################
247
if caPendCertReqValid and \
248
ca.pend_reqs_dir and \
249
(not ca.pend_reqs_dir in processed_pend_reqs_dirs):
251
processed_pend_reqs_dirs.append(ca.pend_reqs_dir)
252
# pend_certs_dir not processed up to now
253
if os.path.isdir(ca.pend_reqs_dir):
255
pendcertfilenames = os.listdir(ca.pend_reqs_dir)
257
for reqfilename in pendcertfilenames:
258
reqpathname = os.path.join(ca.pend_reqs_dir,reqfilename)
259
if gmt-caPendCertReqValid > os.path.getmtime(reqpathname):
260
os.remove(reqpathname)
261
stalecerts = stalecerts+1
264
sys.stdout.write('Removed %d stale certificate requests from %s.\n' % (stalecerts,ca.pend_reqs_dir))
267
pendcertfilenames = []
268
sys.stderr.write('Directory %s not found!\n' % (ca.pend_reqs_dir))
271
######################################################################
272
# New certificate requests
273
######################################################################
275
if ca.new_reqs_dir and \
276
(not ca.new_reqs_dir in processed_new_reqs_dirs):
278
processed_new_reqs_dirs.append(ca.new_reqs_dir)
279
# new_certs_dir not processed up to now
280
if os.path.isdir(ca.new_reqs_dir):
281
newreqfilenames = os.listdir(ca.new_reqs_dir)
283
for reqfilename in newreqfilenames:
284
if os.path.splitext(reqfilename)[1] in ['.spkac','.pem']:
285
newreqcounter = newreqcounter+1
288
sys.stdout.write('%d valid certificate requests in %s.\n' % (newreqcounter,ca.new_reqs_dir))
291
newcertfilenames = []
292
sys.stderr.write('Directory %s not found!\n' % (ca.new_reqs_dir))
295
######################################################################
296
# Publish new client certificates and inform user via e-mail where to
297
# download his certificate
298
######################################################################
300
if (not ca.new_certs_dir in processed_new_certs_dirs) and \
301
os.path.isfile(ca.certificate):
303
processed_new_certs_dirs.append(ca.new_certs_dir)
305
# new_certs_dir not processed up to now
306
newcertfilenames = os.listdir(ca.new_certs_dir)
309
sys.stdout.write('Publish %d new certificates in %s.\n' % (len(newcertfilenames),ca.new_certs_dir))
310
if ca.isservercert():
312
elif ca.isclientcert():
317
for certfilename in newcertfilenames:
319
newcertpathname = os.path.join(ca.new_certs_dir,certfilename)
320
cert = openssl.cert.X509CertificateClass(newcertpathname)
322
if openssl.db.GetEntrybySerial(ca.database,cert.serial):
324
certpathname = os.path.join(ca.certs,certfilename)
326
if not os.path.isfile(certpathname):
330
issuername = charset.asn12iso(cert.issuer.get('CN',''))
331
issueremail = cert.issuer.get('Email','root@localhost')
332
subjectname = charset.asn12iso(cert.subject.get('CN',''))
333
subjectemail = cert.subject.get('Email','')
334
from_addr = mimify.mime_encode_header('%s <%s>' % (issuername,issueremail))
336
to_name,to_email = subjectname,subjectemail
338
to_name,to_email = issuername,issueremail
339
to_addr = mimify.mime_encode_header('%s <%s>' % (to_name,to_email))
342
mail_msg = newcert_mailtext % (
346
strftime('%Y-%m-%d %H:%M',gmtime(cert.notBefore_secs)),
347
strftime('%Y-%m-%d %H:%M',gmtime(cert.notAfter_secs)),
348
ca.nsBaseUrl,nsGetCertUrl,ca_name,certtype,cert.serial,
350
ca.nsBaseUrl,nsViewCertUrl,ca_name,certtype,cert.serial,
353
smtpconn=smtplib.SMTP(MailRelay)
354
smtpconn.set_debuglevel(0)
356
smtpconn.sendmail(issueremail,to_email,mail_msg)
357
sys.stderr.write('Sent e-mail to %s <%s>.\n' % (to_name,to_email))
359
sys.stderr.write('Unable to send an e-mail to %s <%s>.\n' % (to_name,to_email))
362
# Move file from newcerts to certs dir
363
os.rename(newcertpathname,certpathname)
366
sys.stderr.write('Target file %s already exists. Maybe something went wrong?\n' % (certfilename))
369
sys.stderr.write('Did not find certificate with serial number %d in file %s! Certificate database up to date?\n' % (cert.serial,ca.database))
372
######################################################################
373
# Spool certificate requests and certificate revocation requests
374
# to system holding the private keys
375
######################################################################
380
######################################################################
381
# Check again if everything is in place and give out summary report
382
######################################################################
384
sys.stdout.write('\n\n##### Summary report #####\n\n')
386
for ca_name in ca_names:
388
ca = opensslcnf.getcadata(ca_name)
390
sys.stdout.write('CA definition "%s":\n' % (ca_name))
391
if not os.path.isfile(ca.crl):
392
sys.stderr.write('Warning: No valid CRL file %s after processing.\n' % (ca.crl))
394
ca_crl = openssl.cert.CRLClass(ca.crl)
395
sys.stdout.write('CRL file %s valid from %s until %s.\n' % (ca.crl,ca_crl.lastUpdate,ca_crl.nextUpdate))