4
certs2ldap.py - Upload all EE certificates on LDAP server
5
(c) by Michael Stroeder, michael@stroeder.com
10
import sys, string, os, getopt
15
'emailAddress':'mail',
23
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
You may also use env variable PYCALIB.
49
Default: /usr/local/pyca/pylib
52
Specify an alternate host:port on which the ldap server
54
Default: localhost:389
57
Use searchbase as the starting point for the search
58
instead of the default.
62
Use binddn to bind to the LDAP directory.
63
Default: cn=root[,searchbase]
65
--bindpasswd=[password]
66
Use password to bind to the LDAP directory. For security
67
reasons it is better to set this with the env variable
68
LDAP_PASSWD if you really have to provide the password
69
in a non-interactive script.
72
--filtertemplate=[Python dict string]
73
A Python string used as template for searching the
74
LDAP entries of certificate owners.
75
E.g. (&(cn=%%(CN)s)(mail=%%(Email)s))
76
Default: (mail=%%(Email)s)
78
--certdnfilter=[regex]
79
Specify a filter as comma separated list of regular expressions
80
for DNs of the certificates which should be sent to the LDAP host.
81
E.g. C=DE,CN=.*,Email=.*@domain.my
84
--objectclasses=[objectClass]
85
Add objectclass: [objectClass] to the entry. Might be
86
a comma-separated list for specifying multiple object classes.
89
Replace existing userCertificate;binary attributes
92
Create LDAP entries if no entry for a user certificate
95
--dntemplate=[Python dict string]
96
A Python string used as template for the distinguished
97
name of LDAP entries to be created.
98
E.g. cn=%%(CN)s+mail=%%(Email)s,ou=Testing,dc=stroeder,dc=com
100
""" % (script_name,script_name))
102
sys.stderr.write('Error: %s\n' % ErrorMsg)
105
script_name=sys.argv[0]
108
options,args=getopt.getopt(
126
except getopt.error,e:
129
if findoption(options,'-h')!=() or findoption(options,'--help')!=():
132
if findoption(options,'--config')!=():
133
opensslcnfname = findoption(options,'--config')[1]
135
opensslcnfname = os.environ.get('OPENSSL_CONF','/etc/openssl/openssl.cnf')
137
if not os.path.isfile(opensslcnfname):
138
PrintUsage('Config file %s not found.' % (opensslcnfname))
141
if findoption(options,'--pycalib')!=():
142
pycalib = findoption(options,'--pycalib')[1]
144
pycalib = os.environ.get('PYCALIB','/usr/local/pyca/pylib')
146
if not os.path.exists(pycalib) or not os.path.isdir(pycalib):
147
PrintUsage('Directory %s with pyCA modules not found!' % (pycalib))
149
sys.path.append(pycalib)
154
PrintUsage('python-ldap module not found.' % (pycalib))
158
import openssl,charset
160
PrintUsage('pyCA modules not found in directory %s.' % (pycalib))
163
# Read the configuration file
164
if os.path.isfile('%s.pickle' % (opensslcnfname)):
165
# Try to read OpenSSL's config file from a pickled copy
166
f=open('%s.pickle' % (opensslcnfname),'rb')
168
# first try to use the faster cPickle module
169
from cPickle import load
171
from pickle import load
175
# Parse OpenSSL's config file from source
176
opensslcnf=openssl.cnf.OpenSSLConfigClass(opensslcnfname)
178
delete_reason = {openssl.db.DB_TYPE_EXP:'expired',openssl.db.DB_TYPE_REV:'revoked'}
180
pyca_section = opensslcnf.data.get('pyca',{})
181
openssl.bin_filename = pyca_section.get('OpenSSLExec','/usr/local/ssl/bin/openssl')
182
if not os.path.isfile(openssl.bin_filename):
183
sys.stderr.write('Did not find OpenSSL executable %s.\n' % (openssl.bin_filename))
186
if findoption(options,'--host')!=():
187
ldap_host = findoption(options,'--host')[1]
189
ldap_host = 'localhost:389'
191
if findoption(options,'--basedn')!=():
192
basedn = findoption(options,'--basedn')[1]
196
if findoption(options,'--binddn')!=():
197
binddn = findoption(options,'--binddn')[1]
200
binddn = 'cn=root,%s' % basedn
204
if findoption(options,'--bindpasswd')!=():
205
bindpasswd = findoption(options,'--bindpasswd')[1]
207
if os.environ.has_key('LDAP_PASSWD'):
208
bindpasswd = os.environ.get['LDAP_PASSWD']
210
from getpass import getpass
211
sys.stdout.write('Enter password for bind DN "%s".\n' % (binddn))
212
bindpasswd = getpass()
214
if findoption(options,'--filtertemplate')!=():
215
filtertemplate = findoption(options,'--filtertemplate')[1]
217
filtertemplate = r'(mail=%(Email)s)'
219
if findoption(options,'--objectclasses')!=():
223
None,string.split(findoption(options,'--objectclasses')[1],',')
229
replace = findoption(options,'--replace')!=()
231
if findoption(options,'--certdnfilter')!=():
232
certdnfilterlist = string.split(findoption(options,'--certdnfilter')[1],',')
234
for i in certdnfilterlist:
235
attr,filter = string.split(i,'=',1)
237
certdnfilter[attr]=filter
239
certdnfilter = {'Email':'.*'}
241
create = findoption(options,'--create')!=()
243
if findoption(options,'--dntemplate')!=():
244
dntemplate = findoption(options,'--dntemplate')[1]
246
dntemplate = r'mail=%(Email)s,'+basedn
248
print repr(dntemplate)
251
# This should be surrounded by a nice try: except: clause
252
# which catches specific exceptions and outputs
253
# nicer error messages.
254
l = ldap.open(ldap_host)
255
l.bind_s(binddn,bindpasswd,ldap.AUTH_SIMPLE)
257
ca_names = opensslcnf.sectionkeys.get('ca',[])
259
old_db_filenames = []
261
for ca_name in ca_names:
263
sys.stdout.write('*** Processing %s ***\n\n' % (ca_name))
265
ca = opensslcnf.getcadata(ca_name)
267
# Ist der Zertifikattyp 'S/MIME for client use' ?
268
if ca.isclientcert() and \
269
not ca.database in old_db_filenames and \
270
os.path.isfile(ca.database):
272
old_db_filenames.append(ca.database)
275
certs_found = openssl.db.GetEntriesbyDN(ca.database,certdnfilter,casesensitive=1,onlyvalid=0)
277
for cert_entry in certs_found:
279
certdn = charset.asn12iso(cert_entry[openssl.db.DB_name])
280
certdndict = openssl.db.SplitDN(charset.iso2utf(certdn))
281
ldap_filter = filtertemplate % certdndict
283
ldap_result = l.search_s(
287
['objectclass','userCertificate;binary','userSMIMECertificate;binary'],
290
except ldap.NO_SUCH_OBJECT:
291
sys.stdout.write('Certificate subject "%s" not found with filter "%s".\n' % (certdn,ldap_filter))
294
exc_obj,exc_value,exc_traceback = sys.exc_info()
295
sys.stderr.write('Unexpected error during searching with filter "%s":\n%s\n' % (ldap_filter,exc_value))
300
# Read certificate data
301
certfilename = os.path.join(ca.certs,'%s.pem' % (cert_entry[openssl.db.DB_serial]))
302
cert = openssl.cert.X509CertificateClass(certfilename)
303
local_cert = cert.readcertfile('der')
305
for entry in ldap_result:
309
old_objectclasses = {}
310
for oc in entry[1].get('objectClass',entry[1].get('objectclass',[])):
311
old_objectclasses[string.lower(oc)] = None
313
existing_usercert_attrtype = None
315
'userCertificate;binary','userCertificate',
316
'usercertificate;binary','usercertificate',
318
if entry[1].has_key(a):
319
existing_usercert_attrtype = a
322
old_usercertificate_attr = {}
323
if existing_usercert_attrtype!=None:
324
for ldap_cert in entry[1][existing_usercert_attrtype]:
325
old_usercertificate_attr[ldap_cert] = None
329
if cert_entry[openssl.db.DB_type]==openssl.db.DB_TYPE_VAL:
331
if existing_usercert_attrtype is None:
332
# Add new certificate attribute
333
ldap_modlist.append((ldap.MOD_ADD,'userCertificate;binary',[local_cert]))
334
sys.stdout.write('Adding new certificate attribute usercertificate;binary with certificate serial %s of LDAP entry "%s".\n' % (cert_entry[openssl.db.DB_serial],charset.utf2iso(ldap_dn)))
336
# Replace existing certificate attribute
337
ldap_modlist.append((ldap.MOD_DELETE,existing_usercert_attrtype,None))
338
ldap_modlist.append((ldap.MOD_ADD,existing_usercert_attrtype,[local_cert]))
339
sys.stdout.write('Replacing attribute %s of entry %s with certificate serial %s.\n' % (
340
existing_usercert_attrtype,
341
charset.utf2iso(ldap_dn),
342
cert_entry[openssl.db.DB_serial]
345
elif not old_usercertificate_attr.has_key(local_cert):
346
# Add new certificate attribute value
347
ldap_modlist.append((ldap.MOD_DELETE,existing_usercert_attrtype,None))
348
ldap_modlist.append((ldap.MOD_ADD,existing_usercert_attrtype,old_usercertificate_attr.keys()+[local_cert]))
350
'Adding certificate with certificate serial %s to existing attribute %s of LDAP entry "%s".\n' % (
351
cert_entry[openssl.db.DB_serial],
352
existing_usercert_attrtype,
353
charset.utf2iso(ldap_dn)
357
sys.stdout.write('Leaving attribute %s of entry %s untouched.\n' % (
358
existing_usercert_attrtype,
359
charset.utf2iso(ldap_dn)
363
if ldap_modlist and objectclasses:
364
# New object classes were specified at command-line
365
# => add to modify list if necessary
366
new_objectclasses = []
367
for oc in objectclasses:
368
if not old_objectclasses.has_key(string.lower(oc)):
369
new_objectclasses.append(oc)
370
if new_objectclasses:
371
ldap_modlist.append((ldap.MOD_ADD,'objectClass',new_objectclasses))
374
elif (cert_entry[openssl.db.DB_type]==openssl.db.DB_TYPE_EXP) or \
375
(cert_entry[openssl.db.DB_type]==openssl.db.DB_TYPE_REV):
377
sys.stdout.write('Certificate (serial %s) %s.\n' % (cert_entry[openssl.db.DB_serial],delete_reason[cert_entry[openssl.db.DB_type]]))
379
if old_usercertificate_attr.has_key(local_cert):
380
del old_usercertificate_attr[local_cert]
381
sys.stdout.write('Deleting certificate with certificate serial %s from attribute usercertificate;binary of LDAP entry "%s".\n' % (cert_entry[openssl.db.DB_serial],charset.utf2iso(ldap_dn)))
382
ldap_modlist.append((ldap.MOD_REPLACE,existing_usercert_attrtype,old_usercertificate_attr.keys()))
384
if ldap_modlist and objectclasses:
385
new_objectclasses = []
386
for oc in objectclasses:
387
if old_objectclasses.has_key(string.lower(oc)):
388
new_objectclasses.append(oc)
389
if new_objectclasses:
390
ldap_modlist.append((ldap.MOD_DELETE,'objectClass',new_objectclasses))
393
# Do modifications on directory if modlist is not empty
396
l.modify_s(ldap_dn,ldap_modlist)
397
except ldap.NO_SUCH_OBJECT:
398
sys.stderr.write('No such object "%s": Probably a parent entry is missing.\n' % (
399
charset.utf2iso(ldap_dn)
402
except ldap.INSUFFICIENT_ACCESS,e:
403
sys.stderr.write('You are not allowed to modify entry "%s": %s.\n' % (
404
charset.utf2iso(ldap_dn),str(e)
407
except ldap.LDAPError,e:
408
sys.stderr.write('LDAPError: %s.\n' % str(e))
412
if cert_entry[openssl.db.DB_type]==openssl.db.DB_TYPE_VAL:
416
# Read certificate data
417
certfilename = os.path.join(ca.certs,'%s.pem' % (cert_entry[openssl.db.DB_serial]))
418
cert = openssl.cert.X509CertificateClass(certfilename)
419
local_cert = cert.readcertfile('der')
421
ldap_dn = dntemplate % certdndict
424
('objectClass',['person','organizationalPerson','inetOrgPerson']),
425
('userCertificate;binary',[local_cert]),
428
for k in certdndict.keys():
430
ldap_modlist.append((ldap_attrtype[k],certdndict[k]))
436
l.add_s(ldap_dn,ldap_modlist)
437
except ldap.NO_SUCH_OBJECT:
438
sys.stderr.write('No such object "%s": Probably a parent entry is missing.\n' % (
439
charset.utf2iso(ldap_dn)
442
except ldap.INSUFFICIENT_ACCESS,e:
443
sys.stderr.write('You are not allowed to add entry "%s": %s.\n' % (
444
charset.utf2iso(ldap_dn),str(e)
447
except ldap.LDAPError,e:
448
sys.stderr.write('LDAPError: %s.\n' % str(e))
450
sys.stdout.write('Added new entry "%s" for certificate serial %s.\n' % (charset.utf2iso(ldap_dn),cert_entry[openssl.db.DB_serial]))
453
sys.stderr.write('No entry found with filter "%s" for %s.\n' % (ldap_filter,certdn))