Package arcjobtool :: Module CertUtils
[hide private]
[frames] | no frames]

Source Code for Module arcjobtool.CertUtils

  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  # Ported from Recipe 3.9 in Secure Programming Cookbook for C and C++ by 
 44  # John Viega and Matt Messier (O'Reilly 2003) 
 45   
 46  rfc822_specials = '()<>@,;:\\"[]' 
 47   
48 -def isValidEmail(addr):
49 # First we validate the name portion (name@domain) 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 # Next we validate the domain portion (name@domain) 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
86 -class CertificateRequest(object):
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 # Properties for sending signing request 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
131 - def __processLine(self, line):
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
141 - def __readCAInfo(self):
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
181 - def __readDirectionsText(self):
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
211 - def __createRandomData(self):
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
228 - def __setupDefaultDirs(self):
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
238 - def __createRequestDir(self):
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
255 - def checkPendingRequests(self):
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
274 - def loadRequest(self, requestDir):
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 # subject=/O=Grid/O=NorduGrid/OU=lunarc.lu.se/CN=Jonas Lindemann/emailAddress=jonas.lindemann@lunarc.lu.se 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
300 - def checkPassphrase(self, passphrase):
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
323 - def generate(self, passphrase = ""):
324 """ 325 Generates private key and certificate request. 326 """ 327 328 # Check if we have CA configuration 329 330 if not self.haveCAInfo: 331 wx.MessageBox("Could not find valid CA configuration files.", "Certificate request") 332 return 333 334 # We don't do anything if the passphrase is empty 335 336 if passphrase == "": 337 return 338 339 # Create directory for request and setup filenames 340 341 self.__createRequestDir() 342 343 # Create semi random data 344 345 self.__createRandomData() 346 347 # Create response string 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 # Generate private key and certificate request using OpenSSL 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 # Extract request subject 364 365 self.subject = Popen("openssl req -noout -in %s -subject" % (self.certRequestFilename), shell=True, stdout=PIPE).communicate()[0].strip() 366 367 # Remove random data file 368 369 if os.path.exists(self.randomFilename): 370 os.unlink(self.randomFilename) 371 372 # Read instructions template 373 374 self.__readDirectionsText() 375 376 # Read CA information 377 378 self.__readCAInfo() 379 380 # Create request header 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 # Read original request 386 387 requestFile = open(self.certRequestFilename, "r") 388 requestFileContents = requestFile.read() 389 requestFile.close() 390 391 # Add request header 392 393 requestFile = open(self.certRequestFilename, "w") 394 requestFile.write(requestHeader) 395 requestFile.write(requestFileContents) 396 requestFile.close() 397 398 self.updateStatus("GENERATED")
399
400 - def signingStatus(self):
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
418 - def isUnknown(self):
419 status, dateString = self.signingStatus() 420 return status == "UNKNOWN"
421
422 - def isSent(self):
423 status, dateString = self.signingStatus() 424 return status == "SENT"
425
426 - def isSigned(self):
427 status, dateString = self.signingStatus() 428 return status == "INSTALLED"
429
430 - def isGenerated(self):
431 status, dateString = self.signingStatus() 432 return status == "GENERATED"
433
434 - def isLoaded(self):
435 return self.currentRequestDir!=""
436
437 - def isGlobusConfigOk(self):
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
451 - def updateStatus(self, status):
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
461 - def sendSigningRequest(self):
462 """ 463 Send certificate request for signing. 464 """ 465 466 # Do we have a certificate request? 467 468 if self.currentRequestDir == "": 469 return "No request loaded or generated." 470 471 # Check for pending signing request 472 473 status, dateString = self.signingStatus() 474 475 if status == "SENT": 476 return "Certificate request was sent "+dateString+"." 477 478 # Check input 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 # Create mail message 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 #s.sendmail("jonas.lindemann@lunarc.lu.se", "jonas.lindemann@lunarc.lu.se", message.as_string()) 503 s.quit() 504 except: 505 return "Could not send email." 506 507 # Write a email receipt file. 508 509 self.updateStatus("SENT") 510 511 return ""
512
513 - def updateCertificate(self, certificateText):
514 """ 515 Update certificate file with signed certificate. 516 """ 517 userCertFile = open(self.certFilename, "w") 518 userCertFile.write(certificateText) 519 userCertFile.close()
520
521 - def hasExistingCredentials(self):
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
531 - def installCertAndKey(self):
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 # Rename existing certificate files if any 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 # Copy generated and signed certificate files from request dir 548 549 shutil.copyfile(self.certFilename, realUserCertFilename) 550 shutil.copyfile(self.keyFilename, realUserKeyFilename) 551 552 # Make sure the permissions are set correctly 553 554 os.chmod(realUserKeyFilename, 0400) 555 556 self.updateStatus("INSTALLED")
557
558 - def removeCurrentRequestDir(self):
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