1 """
2 Module for certificate utilities.
3 """
4
5 import sys, os, getpass, datetime, re, smtplib, shutil
6
7 from subprocess import *
8 from email.MIMEText import MIMEText
9
10 directionsTemplate = """Please read the instructions below carefully!
11
12 This is a Certificate Request file. It should be mailed to your local
13 registration authority (RA) for identity validation. You can find the
14 list of approved RAs at:
15
16 http://hep.nbi.dk/CA/
17
18 =========================================================================
19
20 A private key and a certificate request has been generated with the
21 subject:
22
23 %(SUBJECT)
24
25 The above string (without the emailaddress field if present) is known
26 as your %(SERVICE) certificate subject, and uniquely identifies
27 this %(SERVICE) inside the NorduGrid CA.
28
29 If the subject above is not appropriate, rerun this script with
30 the -force and -int options.
31
32 Your private key is stored in %(KEY_FILE)
33 Your request is stored in %(REQUEST_FILE)
34
35 Your certificate will be mailed to you within 7 working days after it
36 has been recieved by the NorduGrid CA.
37
38 If you have any questions about the certificate contact
39 the %(GSI_CA_NAME) at %(GSI_CA_EMAIL_ADDR)
40
41 """
42
43
44
45
46 rfc822_specials = '()<>@,;:\\"[]'
47
49
50 c = 0
51 while c < len(addr):
52 if addr[c] == '"' and (not c or addr[c - 1] == '.' or addr[c - 1] == '"'):
53 c = c + 1
54 while c < len(addr):
55 if addr[c] == '"': break
56 if addr[c] == '\\' and addr[c + 1] == ' ':
57 c = c + 2
58 continue
59 if ord(addr[c]) < 32 or ord(addr[c]) >= 127: return 0
60 c = c + 1
61 else: return 0
62 if addr[c] == '@': break
63 if addr[c] != '.': return 0
64 c = c + 1
65 continue
66 if addr[c] == '@': break
67 if ord(addr[c]) <= 32 or ord(addr[c]) >= 127: return 0
68 if addr[c] in rfc822_specials: return 0
69 c = c + 1
70 if not c or addr[c - 1] == '.': return 0
71
72
73 domain = c = c + 1
74 if domain >= len(addr): return 0
75 count = 0
76 while c < len(addr):
77 if addr[c] == '.':
78 if c == domain or addr[c - 1] == '.': return 0
79 count = count + 1
80 if ord(addr[c]) <= 32 or ord(addr[c]) >= 127: return 0
81 if addr[c] in rfc822_specials: return 0
82 c = c + 1
83
84 return count >= 1
85
87 """
88 Class for generating a grid certificate request.
89
90 Basic structure and procedures are modeled after the grid-cert-request
91 script from the Globus project.
92 """
93 - def __init__(self, domain="", name="", email=""):
94 """
95 Class constructor
96 """
97 self.domain = domain
98 self.name = name
99 self.email = email
100 self.CAName = ""
101 self.CAEmail = ""
102
103
104
105 self.emailTo = ""
106 self.emailFrom = ""
107 self.emailSubject = "Certificate Request"
108 self.emailSMTPServer = "localhost"
109
110 self.keyFilename = "userkey.pem"
111 self.certFilename = "usercert.pem"
112 self.certRequestFilename = "usercert_request.pem"
113 self.randomFilename = "uicertreq.random"
114 self.passphrase = ""
115 self.globusUserConfig = "/etc/grid-security/globus-user-ssl.conf"
116 self.gridSecurityConfig = "/etc/grid-security/grid-security.conf"
117 self.directionsFilename = "/etc/grid-security/directions"
118 self.directionsStartHeader = "----- BEGIN REQUEST HEADER TEXT -----"
119 self.directionsEndHeader = "----- END REQUEST HEADER TEXT -----"
120
121 self.currentRequestDir = ""
122
123 self.directionsTemplate = directionsTemplate
124
125 self.requestCmdTemplate = "openssl req -new -keyout %s -out %s -rand %s -config %s -passout stdin 2> /dev/null"
126
127 self.haveCAInfo = self.__readCAInfo()
128
129 self.__setupDefaultDirs()
130
132 """
133 Convert characters and variables to a Python format.
134 """
135
136 line = line.replace('"', '')
137 line = line.replace("${", "%(")
138 line = line.replace("}", ")s")
139 return line
140
142 """
143 Extract CA info from /etc/grid-security.conf
144 """
145
146 if not os.path.exists(self.gridSecurityConfig):
147 print "No security configuration found. Using program defaults."
148 return False
149
150 gridSecurityFile = open(self.gridSecurityConfig, "r")
151 lines = gridSecurityFile.readlines()
152 gridSecurityFile.close()
153
154 defaultCAName = ""
155 defaultCAEmail = ""
156 setupCAName = ""
157 setupCAEmail = ""
158
159 for line in lines:
160 if line.find("SETUP_GSI_CA_NAME")!=-1 and setupCAName=="":
161 setupCAName = line.split("=")[1].replace('"','').strip()
162 if line.find("SETUP_GSI_CA_EMAIL")!=-1 and setupCAEmail=="":
163 setupCAEmail = line.split("=")[1].replace('"','').strip()
164 if line.find("DEFAULT_GSI_CA_NAME")!=-1 and defaultCAName=="":
165 defaultCAName = line.split("=")[1].replace('"','').strip()
166 if line.find("DEFAULT_GSI_CA_EMAIL")!=-1 and defaultCAEmail=="":
167 defaultCAEmail = line.split("=")[1].replace('"','').strip()
168
169 if setupCAName!="":
170 self.CAName = setupCAName
171 elif defaultCAName!="":
172 self.CAName = defaultCAName
173
174 if setupCAEmail!="":
175 self.CAEmail = setupCAEmail
176 elif defaultCAEmail!="":
177 self.CAEmail = defaultCAEmail
178
179 return True
180
182 """
183 Read directions template from /etc/grid-security if it exists.
184 """
185
186 if os.path.exists(self.directionsFilename):
187
188 directionsFile = open(self.directionsFilename, "r")
189 lines = directionsFile.readlines()
190 directionsFile.close()
191
192 directionsTemplate = ""
193
194 addLines = False
195
196 for line in lines:
197 if line.strip() == self.directionsEndHeader:
198 addLines = False
199
200 if addLines:
201 line = self.__processLine(line)
202 directionsTemplate += line
203
204 if line.strip() == self.directionsStartHeader:
205 addLines = True
206
207 self.directionsTemplate = directionsTemplate
208 else:
209 self.directionsTemplate = directionsTemplate
210
212 """
213 Create semi random data for use when generating keys.
214 """
215
216 randomData = ""
217 randomData += Popen("head -1000 /dev/urandom 2>&1", shell=True, stdout=PIPE).communicate()[0]
218 randomData += Popen("date 2>&1", shell=True, stdout=PIPE).communicate()[0]
219 randomData += Popen("netstat -in 2>&1", shell=True, stdout=PIPE).communicate()[0]
220 randomData += Popen("ps -ef 2>&1", shell=True, stdout=PIPE).communicate()[0]
221 randomData += Popen("ls -ln ${HOME} 2>&1", shell=True, stdout=PIPE).communicate()[0]
222 randomData += Popen("ls -ln /tmp 2>&1", shell=True, stdout=PIPE).communicate()[0]
223
224 randomFile = open(self.randomFilename, "w")
225 randomFile.write(randomData)
226 randomFile.close()
227
229 """
230 Setup default dirs for cert request generation.
231 """
232
233 self.homeDir = os.getenv("HOME")
234 self.globusDir = os.path.join(self.homeDir, ".globus")
235 if not os.path.exists(self.globusDir):
236 os.mkdir(self.globusDir)
237
239 """
240 Create temporary request directory for storing the new certificate
241 request. Format $HOME/.globus/uicertreq.request.XXXXXX
242 """
243
244 timeStamp = datetime.datetime.now().strftime("%Y%m%d-%H%M")
245 self.requestDir = os.path.join(self.globusDir, "uicertreq.request.%s" % timeStamp)
246 os.mkdir(self.requestDir)
247
248 self.currentRequestDir = self.requestDir
249
250 self.certFilename = os.path.join(self.requestDir, "usercert.pem")
251 self.certRequestFilename = os.path.join(self.requestDir, "usercert_request.pem")
252 self.keyFilename = os.path.join(self.requestDir, "userkey.pem")
253 self.randomFilename = os.path.join(self.requestDir, "uicertreq.random")
254
256 """
257 Check for existing certificate requests.
258 """
259
260 requestList = []
261
262 if os.path.exists(self.globusDir):
263
264 dirItems = os.listdir(self.globusDir)
265
266 for item in dirItems:
267 fullPath = os.path.join(self.globusDir, item)
268 if os.path.isdir(fullPath):
269 if fullPath.find("uicertreq.request")!=-1:
270 requestList.append(fullPath)
271
272 return requestList
273
275 """
276 Load existing request information.
277 """
278
279 self.currentRequestDir = requestDir
280
281 self.certRequestFilename = os.path.join(requestDir, "usercert_request.pem")
282 self.certFilename = os.path.join(requestDir, "usercert.pem")
283 self.keyFilename = os.path.join(requestDir, "userkey.pem")
284
285
286
287 self.subject = Popen("openssl req -noout -in %s -subject" % (self.certRequestFilename), shell=True, stdout=PIPE).communicate()[0].strip()
288
289 self.subject = self.subject[9:]
290 parts = self.subject.split("/")
291
292 for part in parts:
293 if part.find("CN=")!=-1:
294 self.name = part.split("CN=")[1]
295 elif part.find("OU=")!=-1:
296 self.domain = part.split("OU=")[1]
297 elif part.find("emailAddress=")!=-1:
298 self.email = part.split("emailAddress=")[1]
299
301 strength = ['Blank','Very Weak','Weak','Medium','Strong','Very Strong']
302 score = 1
303
304 if len(passphrase) < 1:
305 return strength[0]
306 if len(passphrase) < 4:
307 return strength[1]
308
309 if len(passphrase) >=6:
310 score = score + 1
311 if len(passphrase) >=12:
312 score = score + 1
313
314 if re.search('\d+',passphrase):
315 score = score + 1
316 if re.search('[a-z]',passphrase) and re.search('[A-Z]',passphrase):
317 score = score + 1
318 if re.search('.[!,@,#,$,%,^,&,*,?,_,~,-,?,(,)]',passphrase):
319 score = score + 1
320
321 return strength[score]
322
324 """
325 Generates private key and certificate request.
326 """
327
328
329
330 if not self.haveCAInfo:
331 wx.MessageBox("Could not find valid CA configuration files.", "Certificate request")
332 return
333
334
335
336 if passphrase == "":
337 return
338
339
340
341 self.__createRequestDir()
342
343
344
345 self.__createRandomData()
346
347
348
349 inputData = passphrase + "\n"
350 inputData += "\n\n"
351 inputData += self.domain+"\n"
352 inputData += self.name+"\n"
353 inputData += self.email+"\n"
354
355
356
357 Popen(self.requestCmdTemplate % (self.keyFilename, self.certRequestFilename, self.randomFilename, self.globusUserConfig),
358 shell=True, stdout= PIPE, stdin=PIPE).communicate(inputData)[0]
359
360 os.chmod(self.keyFilename, 0400)
361 os.chmod(self.certRequestFilename, 0600)
362
363
364
365 self.subject = Popen("openssl req -noout -in %s -subject" % (self.certRequestFilename), shell=True, stdout=PIPE).communicate()[0].strip()
366
367
368
369 if os.path.exists(self.randomFilename):
370 os.unlink(self.randomFilename)
371
372
373
374 self.__readDirectionsText()
375
376
377
378 self.__readCAInfo()
379
380
381
382 requestHeader = self.directionsTemplate % {"SUBJECT":self.subject, "SERVICE":"user", "GSI_CA_NAME":self.CAName,
383 "GSI_CA_EMAIL_ADDR":self.CAEmail, "KEY_FILE":self.keyFilename, "REQUEST_FILE":self.certRequestFilename}
384
385
386
387 requestFile = open(self.certRequestFilename, "r")
388 requestFileContents = requestFile.read()
389 requestFile.close()
390
391
392
393 requestFile = open(self.certRequestFilename, "w")
394 requestFile.write(requestHeader)
395 requestFile.write(requestFileContents)
396 requestFile.close()
397
398 self.updateStatus("GENERATED")
399
401 """
402 Return current signing status for loaded request
403 """
404
405 statusFilename = os.path.join(self.currentRequestDir, "signing.status")
406
407 if os.path.exists(statusFilename):
408 requestStatusFile = open(statusFilename, "r")
409 requestStatus = requestStatusFile.read()
410 requestStatusFile.close()
411
412 dateString = requestStatus.split(":")[0].strip()
413 status = requestStatus.split(":")[1].strip()
414 return status, dateString
415 else:
416 return "UNKNOWN", ""
417
419 status, dateString = self.signingStatus()
420 return status == "UNKNOWN"
421
423 status, dateString = self.signingStatus()
424 return status == "SENT"
425
427 status, dateString = self.signingStatus()
428 return status == "INSTALLED"
429
431 status, dateString = self.signingStatus()
432 return status == "GENERATED"
433
435 return self.currentRequestDir!=""
436
438 """
439 Check that we have needed request configuration files.
440 """
441 configOk = True
442 if not os.path.exists(self.directionsFilename):
443 configOk = False
444 if not os.path.exists(self.globusUserConfig):
445 configOk = False
446 if not os.path.exists(self.gridSecurityConfig):
447 configOk = False
448
449 return configOk
450
452 """
453 Update the signing process status.
454 """
455 statusFilename = os.path.join(self.currentRequestDir, "signing.status")
456 timeStamp = datetime.datetime.now().strftime("%Y%m%d-%H%M")
457 statusFile = open(statusFilename, "w")
458 statusFile.write(timeStamp+":"+status)
459 statusFile.close()
460
462 """
463 Send certificate request for signing.
464 """
465
466
467
468 if self.currentRequestDir == "":
469 return "No request loaded or generated."
470
471
472
473 status, dateString = self.signingStatus()
474
475 if status == "SENT":
476 return "Certificate request was sent "+dateString+"."
477
478
479
480 if not isValidEmail(self.emailFrom):
481 return "Not a valid from address."
482
483 if not isValidEmail(self.emailTo):
484 return "Not av valid to address."
485
486 if self.emailSubject == "":
487 return "No subject given"
488
489
490
491 requestFile = open(self.certRequestFilename, "r")
492 message = MIMEText(requestFile.read())
493 requestFile.close()
494
495 message['Subject'] = self.emailSubject
496 message['From'] = self.emailFrom
497 message['To'] = self.emailTo
498
499 try:
500 s = smtplib.SMTP(self.emailSMTPServer)
501 s.sendmail(self.emailFrom, self.emailTo, message.as_string())
502
503 s.quit()
504 except:
505 return "Could not send email."
506
507
508
509 self.updateStatus("SENT")
510
511 return ""
512
514 """
515 Update certificate file with signed certificate.
516 """
517 userCertFile = open(self.certFilename, "w")
518 userCertFile.write(certificateText)
519 userCertFile.close()
520
522 """
523 Check for existing credentials.
524 """
525
526 hasKey = os.path.exists(os.path.join(self.globusDir, "userkey.pem"))
527 hasCert = os.path.exists(os.path.join(self.globusDir, "usercert.pem"))
528
529 return hasKey or hasCert
530
532 """
533 Copy user key and certificate into certificate directory.
534 Remove certificate request directory.
535 """
536 realUserKeyFilename = os.path.join(self.globusDir, "userkey.pem")
537 realUserCertFilename = os.path.join(self.globusDir, "usercert.pem")
538
539
540
541 if os.path.exists(realUserKeyFilename):
542 os.rename(realUserKeyFilename, realUserKeyFilename+".old."+datetime.datetime.now().strftime("%Y%m%d-%H%M"))
543
544 if os.path.exists(realUserCertFilename):
545 os.rename(realUserCertFilename, realUserCertFilename+".old."+datetime.datetime.now().strftime("%Y%m%d-%H%M"))
546
547
548
549 shutil.copyfile(self.certFilename, realUserCertFilename)
550 shutil.copyfile(self.keyFilename, realUserKeyFilename)
551
552
553
554 os.chmod(realUserKeyFilename, 0400)
555
556 self.updateStatus("INSTALLED")
557
559 """
560 Remove current request directory
561 """
562 if os.path.exists(self.currentRequestDir):
563 shutil.rmtree(self.currentRequestDir)
564 sself.currentRequestDir = ""
565 self.keyFilename = "userkey.pem"
566 self.certFilename = "usercert.pem"
567 self.certRequestFilename = "usercert_request.pem"
568 self.__setupDefaultDirs()
569