Package ndg :: Package saml :: Package utils :: Module m2crypto
[hide private]

Source Code for Module ndg.saml.utils.m2crypto

   1  """SAML 2.0 Utilities module for M2Crypto SSL functionality  
   2   
   3  NERC DataGrid Project 
   4  """ 
   5  __author__ = "P J Kershaw" 
   6  __date__ = "02/07/07" 
   7  __copyright__ = "(C) 2009 Science and Technology Facilities Council" 
   8  __license__ = "http://www.apache.org/licenses/LICENSE-2.0" 
   9  __contact__ = "Philip.Kershaw@stfc.ac.uk" 
  10  __revision__ = '$Id: m2crypto.py 8049 2012-03-28 15:57:38Z pjkersha $' 
  11  import logging 
  12  log = logging.getLogger(__name__) 
  13   
  14  from warnings import warn # warn of impending certificate expiry 
  15   
  16  import os 
  17  import re 
  18   
  19  # Handle not before and not after strings 
  20  from time import strptime 
  21  from datetime import datetime 
  22   
  23  import M2Crypto 
  24  from M2Crypto import SSL, X509 
  25  from M2Crypto.httpslib import HTTPSConnection as _HTTPSConnection 
26 27 28 -class X500DNError(Exception):
29 """Exception handling for NDG X.500 DN class."""
30
31 32 -class X500DN(dict):
33 "NDG X500 Distinguished name" 34 35 # Class attribute - look-up mapping short name attributes to their long 36 # name equivalents 37 # * private * 38 __shortNameLUT = { 39 'commonName': 'CN', 40 'organisationalUnitName': 'OU', 41 'organisation': 'O', 42 'countryName': 'C', 43 'emailAddress': 'EMAILADDRESS', 44 'localityName': 'L', 45 'stateOrProvinceName': 'ST', 46 'streetAddress': 'STREET', 47 'domainComponent': 'DC', 48 'userid': 'UID' 49 } 50 SLASH_PARSER_RE_STR = '/(%s)=' % '|'.join(__shortNameLUT.keys() + 51 __shortNameLUT.values()) 52 SLASH_PARSER_RE = re.compile(SLASH_PARSER_RE_STR) 53 54 COMMA_PARSER_RE_STR = '[,]?\s*(%s)=' % '|'.join(__shortNameLUT.keys() + 55 __shortNameLUT.values()) 56 COMMA_PARSER_RE = re.compile(COMMA_PARSER_RE_STR) 57
58 - def __init__(self, dn=None, m2CryptoX509Name=None, separator=None):
59 60 """Create a new X500 Distinguished Name 61 62 @type m2CryptoX509Name: M2Crypto.X509.X509_Name 63 @param m2CryptoX509Name: initialise using using an 64 M2Crypto.X509.X509_Name 65 @type dn: basestring 66 @param dn: initialise using a distinguished name string 67 @type separator: basestring 68 @param: separator: separator used to delimit dn fields - usually '/' 69 or ','. If dn is input and separator is omitted the separator 70 character will be automatically parsed from the dn string. 71 """ 72 73 # Private key data 74 self.__dat = {}.fromkeys(X500DN.__shortNameLUT.values(), '') 75 76 dict.__init__(self) 77 78 self.__separator = None 79 80 # Check for separator from input 81 if separator is not None: 82 if not isinstance(separator, basestring): 83 raise X500DNError("dn Separator must be a valid string") 84 85 # Check for single character but allow trailing space chars 86 if len(separator.lstrip()) is not 1: 87 raise X500DNError("dn separator must be a single character") 88 89 self.__separator = separator 90 91 if m2CryptoX509Name is not None: 92 # the argument is an x509 dn in m2crypto format 93 self.deserialise(str(m2CryptoX509Name)) 94 95 elif dn is not None: 96 # Separator can be parsed from the input DN string - only attempt 97 # if no explict separator was input 98 if self.__separator is None: 99 self.__separator = self.parseSeparator(dn) 100 101 # Split Distinguished name string into constituent fields 102 self.deserialise(dn)
103 104 @classmethod
105 - def fromString(cls, dn):
106 """Convenience method for parsing DN string into a new instance 107 """ 108 return cls(dn=dn)
109
110 - def __repr__(self):
111 """Give representation based on underlying dict object""" 112 return repr(self.__dat)
113
114 - def __str__(self):
115 """Behaviour for print and string statements - convert DN into 116 serialised format.""" 117 return self.serialise()
118
119 - def __eq__(self, x500dn):
120 """Return true if the all the fields of the two DNs are equal""" 121 122 if not isinstance(x500dn, X500DN): 123 return False 124 125 return self.__dat.items() == x500dn.items()
126
127 - def __ne__(self, x500dn):
128 """Return true if the all the fields of the two DNs are equal""" 129 130 if not isinstance(x500dn, X500DN): 131 return False 132 133 return self.__dat.items() != x500dn.items()
134
135 - def __delitem__(self, key):
136 """Prevent keys from being deleted.""" 137 raise X500DNError('Keys cannot be deleted from the X500DN')
138
139 - def __getitem__(self, key):
140 141 # Check input key 142 if self.__dat.has_key(key): 143 144 # key recognised 145 return self.__dat[key] 146 147 elif X500DN.__shortNameLUT.has_key(key): 148 149 # key not recognised - but a long name version of the key may 150 # have been passed 151 shortName = X500DN.__shortNameLUT[key] 152 return self.__dat[shortName] 153 154 else: 155 # key not recognised as a short or long name version 156 raise KeyError('Key "' + key + '" not recognised for X500DN')
157
158 - def __setitem__(self, key, item):
159 160 # Check input key 161 if self.__dat.has_key(key): 162 163 # key recognised 164 self.__dat[key] = item 165 166 elif X500DN.__shortNameLUT.has_key(key): 167 168 # key not recognised - but a long name version of the key may 169 # have been passed 170 shortName = X500DN.__shortNameLUT[key] 171 self.__dat[shortName] = item 172 173 else: 174 # key not recognised as a short or long name version 175 raise KeyError('Key "' + key + '" not recognised for X500DN')
176
177 - def clear(self):
178 raise X500DNError("Data cannot be cleared from X500DN")
179
180 - def copy(self):
181 import copy 182 return copy.copy(self)
183
184 - def keys(self):
185 return self.__dat.keys()
186
187 - def items(self):
188 return self.__dat.items()
189
190 - def values(self):
191 return self.__dat.values()
192
193 - def has_key(self, key):
194 return self.__dat.has_key(key)
195 196 # 'in' operator
197 - def __contains__(self, key):
198 return self.has_key(key)
199
200 - def get(self, *arg):
201 return self.__dat.get(*arg)
202
203 - def serialise(self, separator=None):
204 """Combine fields in Distinguished Name into a single string.""" 205 206 if separator: 207 if not isinstance(separator, basestring): 208 raise X500DNError("Separator must be a valid string") 209 210 self.__separator = separator 211 212 else: 213 # Default to / if no separator is set 214 separator = '/' 215 216 217 # If using '/' then prepend DN with an initial '/' char 218 if separator == '/': 219 sDN = separator 220 else: 221 sDN = '' 222 223 dnList = [] 224 for (key, val) in self.__dat.items(): 225 if val: 226 if isinstance(val, tuple): 227 dnList += [separator.join(["%s=%s" % (key, valSub) \ 228 for valSub in val])] 229 else: 230 dnList += ["%s=%s" % (key, val)] 231 232 sDN += separator.join(dnList) 233 234 return sDN
235 236 serialize = serialise 237
238 - def deserialise(self, dn, separator=None):
239 """Break up a DN string into it's constituent fields and use to 240 update the object's dictionary""" 241 242 if separator: 243 if not isinstance(separator, basestring): 244 raise X500DNError("Separator must be a valid string") 245 246 self.__separator = separator 247 248 249 # If no separator has been set, parse if from the DN string 250 if self.__separator is None: 251 self.__separator = self.parseSeparator(dn) 252 253 if self.__separator == '/': 254 parserRe = self.__class__.SLASH_PARSER_RE 255 256 elif self.__separator == ',': 257 parserRe = self.__class__.COMMA_PARSER_RE 258 else: 259 raise X500DNError("DN field separator %r not recognised" % 260 self.__separator) 261 262 try: 263 dnFields = parserRe.split(dn) 264 if len(dnFields) < 2: 265 raise X500DNError("Error parsing DN string: \"%s\"" % dn) 266 267 items = zip(dnFields[1::2], dnFields[2::2]) 268 269 # Reset existing dictionary values 270 self.__dat.fromkeys(self.__dat, '') 271 272 # Strip leading and trailing space chars and convert into a 273 # dictionary 274 parsedDN = {} 275 for key, val in items: 276 key = key.strip() 277 if key in parsedDN: 278 if isinstance(parsedDN[key], tuple): 279 parsedDN[key] = tuple(list(parsedDN[key]) + [val]) 280 else: 281 parsedDN[key] = (parsedDN[key], val) 282 else: 283 parsedDN[key] = val 284 285 # Copy matching DN fields 286 for key, val in parsedDN.items(): 287 if key not in self.__dat and key not in self.__shortNameLUT: 288 raise X500DNError('Invalid field "%s" in input DN string' % 289 key) 290 291 self.__dat[key] = val 292 293 294 except Exception, excep: 295 raise X500DNError("Error de-serialising DN \"%s\": %s" % \ 296 (dn, str(excep)))
297 298 deserialize = deserialise 299
300 - def parseSeparator(self, dn):
301 """Attempt to parse the separator character from a given input 302 DN string. If not found, return None 303 304 DNs don't use standard separators e.g. 305 306 /C=UK/O=eScience/OU=CLRC/L=DL/CN=AN Other 307 CN=SUM Oneelse,L=Didcot, O=RAL,OU=SSTD 308 309 This function isolates and identifies the character. - In the above, 310 '/' and ',' respectively""" 311 312 313 # Make a regular expression containing all the possible field 314 # identifiers with equal sign appended and 'or'ed together. \W should 315 # match the separator which preceeds the field name. \s* allows any 316 # whitespace between field name and field separator to be taken into 317 # account. 318 # 319 # The resulting match should be a list. The first character in each 320 # element in the list should be the field separator and should be the 321 # same 322 regExpr = '|'.join(['\W\s*'+i+'=' for i in self.__dat.keys()]) 323 match = re.findall(regExpr, dn) 324 325 # In the first example above, the resulting match is: 326 # ['/C=', '/O=', '/OU=', '/L='] 327 # In each element the first character is the separator 328 sepList = [i[0:1] for i in match] 329 330 # All separators should be the same character - return None if they 331 # don't match 332 if not [i for i in sepList if i != sepList[0]]: 333 return sepList[0] 334 else: 335 return None
336 337 @classmethod
338 - def Parse(cls, dn):
339 """Convenience method to create an X500DN object from a DN string 340 @type dn: basestring 341 @param dn: Distinguished Name 342 """ 343 return cls(dn=dn)
344 345 Deserialise = Deserialize = Parse
346
347 348 -class X509CertError(Exception):
349 """Exception handling for NDG X.509 Certificate handling class."""
350
351 -class X509CertReadError(X509CertError):
352 """Error reading in certificate from file"""
353
354 -class X509CertParseError(X509CertError):
355 """Error parsing a certificate"""
356
357 -class X509CertInvalidNotBeforeTime(X509CertError):
358 """Call from X509Cert.isValidTime if certificates not before time is 359 BEFORE the current system time"""
360
361 -class X509CertExpired(X509CertError):
362 """Call from X509Cert.isValidTime if certificate has expired"""
363
364 365 -class X509Cert(object):
366 "NDG X509 Certificate Handling" 367 368 formatPEM = M2Crypto.X509.FORMAT_PEM 369 formatDER = M2Crypto.X509.FORMAT_DER 370
371 - def __init__(self, filePath=None, m2CryptoX509=None):
372 373 # Set certificate file path 374 if filePath is not None: 375 if not isinstance(filePath, basestring): 376 raise X509CertError("Certificate File Path input must be a " 377 "valid string") 378 379 self.__filePath = filePath 380 self.__dn = None 381 self.__dtNotBefore = None 382 self.__dtNotAfter = None 383 384 if m2CryptoX509: 385 self.__setM2CryptoX509(m2CryptoX509) 386 else: 387 self.__m2CryptoX509 = None
388
389 - def read(self, 390 filePath=None, 391 format=None, 392 warningStackLevel=3, 393 **isValidTimeKw):
394 """Read a certificate from PEM encoded DER format file 395 396 @type filePath: basestring 397 @param filePath: file path of PEM format file to be read 398 399 @type format: int 400 @param format: format of input file - PEM is the default. Set to 401 X509Cert.formatDER for DER format 402 403 @type isValidTimeKw: dict 404 @param isValidTimeKw: keywords to isValidTime() call""" 405 406 if format is None: 407 format = X509Cert.formatPEM 408 409 # Check for optional input certificate file path 410 if filePath is not None: 411 if not isinstance(filePath, basestring): 412 raise X509CertError("Certificate File Path input must be a " 413 "valid string") 414 415 self.__filePath = filePath 416 417 try: 418 self.__m2CryptoX509 = M2Crypto.X509.load_cert(self.__filePath, 419 format=format) 420 except Exception, e: 421 raise X509CertReadError("Error loading certificate \"%s\": %s" % 422 (self.__filePath, e)) 423 424 # Update DN and validity times from M2Crypto X509 object just 425 # created 426 self.__setM2CryptoX509() 427 428 self.isValidTime(warningStackLevel=warningStackLevel, **isValidTimeKw)
429
430 - def parse(self, 431 certTxt, 432 format=None, 433 warningStackLevel=3, 434 **isValidTimeKw):
435 """Read a certificate input as a string 436 437 @type certTxt: basestring 438 @param certTxt: PEM encoded certificate to parse 439 440 @type format: int 441 @param format: format of input file - PEM is the default. Set to 442 X509Cert.formatDER for DER format 443 444 @type isValidTimeKw: dict 445 @param isValidTimeKw: keywords to isValidTime() call""" 446 447 if format is None: 448 format = X509Cert.formatPEM 449 450 try: 451 # Create M2Crypto memory buffer and pass to load certificate 452 # method 453 # 454 # Nb. input converted to standard string - buffer method won't 455 # accept unicode type strings 456 # certBIO = M2Crypto.BIO.MemoryBuffer(str(certTxt)) 457 # self.__m2CryptoX509 = M2Crypto.X509.load_cert_bio(certBIO) 458 self.__m2CryptoX509 = M2Crypto.X509.load_cert_string(str(certTxt), 459 format=format) 460 except Exception, e: 461 raise X509CertParseError("Error loading certificate: %s" % e) 462 463 # Update DN and validity times from M2Crypto X509 object just 464 # created 465 self.__setM2CryptoX509() 466 467 self.isValidTime(warningStackLevel=warningStackLevel, **isValidTimeKw)
468
469 - def __setM2CryptoX509(self, m2CryptoX509=None):
470 """Private method allows class members to be updated from the 471 current M2Crypto object. __m2CryptoX509 must have been set.""" 472 473 if m2CryptoX509 is not None: 474 if not isinstance(m2CryptoX509, M2Crypto.X509.X509): 475 raise TypeError("Incorrect type for input M2Crypto.X509.X509 " 476 "object") 477 478 self.__m2CryptoX509 = m2CryptoX509 479 480 # Get distinguished name 481 m2CryptoX509Name = self.__m2CryptoX509.get_subject() 482 483 # Instantiate X500 Distinguished name 484 self.__dn = X500DN(m2CryptoX509Name=m2CryptoX509Name) 485 486 # Get not before and not after validity times 487 # 488 # Only option for M2Crypto seems to be to return the times as 489 # formatted strings and then parse them in order to create a datetime 490 # type 491 492 try: 493 m2CryptoNotBefore = self.__m2CryptoX509.get_not_before() 494 self.__dtNotBefore=self.__m2CryptoUTC2datetime(m2CryptoNotBefore) 495 496 except Exception, e: 497 raise X509CertError("Not Before time: %s" % e) 498 499 try: 500 m2CryptoNotAfter = self.__m2CryptoX509.get_not_after() 501 self.__dtNotAfter = self.__m2CryptoUTC2datetime(m2CryptoNotAfter) 502 503 except Exception, e: 504 raise X509CertError("Not After time: %s" % e)
505
506 - def __getM2CryptoX509(self, m2CryptoX509=None):
507 "Return M2Crypto X.509 cert object" 508 return self.__m2CryptoX509
509 510 m2CryptoX509 = property(fset=__setM2CryptoX509, 511 fget=__getM2CryptoX509, 512 doc="M2Crypto.X509.X509 type") 513
514 - def toString(self, **kw):
515 """Return certificate file content as a PEM format 516 string""" 517 return self.asPEM(**kw)
518
519 - def asPEM(self, filePath=None):
520 """Return certificate file content as a PEM format 521 string""" 522 523 # Check M2Crypto.X509 object has been instantiated - if not call 524 # read method 525 if self.__m2CryptoX509 is None: 526 self.read(filePath) 527 528 return self.__m2CryptoX509.as_pem()
529
530 - def asDER(self):
531 """Return certificate file content in DER format""" 532 533 # Check M2Crypto.X509 object has been instantiated 534 assert(self.__m2CryptoX509) 535 return self.__m2CryptoX509.as_der()
536 537 # Make some attributes accessible as read-only
538 - def __getDN(self):
539 """Get X500 Distinguished Name.""" 540 return self.__dn
541 542 dn = property(fget=__getDN, doc="X.509 Distinguished Name") 543
544 - def __getVersion(self):
545 """Get X.509 Certificate version""" 546 if self.__m2CryptoX509 is None: 547 return None 548 549 return self.__m2CryptoX509.get_version()
550 551 version = property(fget=__getVersion, doc="X.509 Certificate version") 552
553 - def __getSerialNumber(self):
554 """Get Serial Number""" 555 if self.__m2CryptoX509 is None: 556 return None 557 558 return self.__m2CryptoX509.get_serial_number()
559 560 serialNumber = property(fget=__getSerialNumber, 561 doc="X.509 Certificate Serial Number") 562
563 - def __getNotBefore(self):
564 """Get not before validity time as datetime type""" 565 if self.__m2CryptoX509 is None: 566 return None 567 568 return self.__dtNotBefore
569 570 notBefore = property(fget=__getNotBefore, 571 doc="Not before validity time as datetime type") 572
573 - def __getNotAfter(self):
574 """Get not after validity time as datetime type""" 575 if self.__m2CryptoX509 is None: 576 return None 577 578 return self.__dtNotAfter
579 580 notAfter = property(fget=__getNotAfter, 581 doc="Not after validity time as datetime type") 582
583 - def __getPubKey(self):
584 """Get public key 585 586 @return: RSA public key for certificate 587 @rtype: M2Crypto.RSA.RSA_pub""" 588 if self.__m2CryptoX509 is None: 589 return None 590 591 return self.__m2CryptoX509.get_pubkey()
592 593 pubKey = property(fget=__getPubKey, doc="Public Key") 594
595 - def __getIssuer(self):
596 """Get Certificate issuer""" 597 if self.__m2CryptoX509 is None: 598 return None 599 600 # Return as X500DN type 601 return X500DN(m2CryptoX509Name=self.__m2CryptoX509.get_issuer())
602 603 issuer = property(fget=__getIssuer, doc="Certificate Issuer") 604
605 - def __getSubject(self):
606 """Get Certificate subject""" 607 if self.__m2CryptoX509 is None: 608 return None 609 610 # Return as X500DN type 611 return X500DN(m2CryptoX509Name=self.__m2CryptoX509.get_subject())
612 613 subject = property(fget=__getSubject, doc="Certificate subject") 614
615 - def isValidTime(self, 616 raiseExcep=False, 617 expiryWarning=True, 618 nDaysBeforeExpiryLimit=30, 619 warningStackLevel=2):
620 """Check Certificate for expiry 621 622 @type raiseExcep: bool 623 @param raiseExcep: set True to raise an exception if certificate is 624 invalid 625 626 @type expiryWarning: bool 627 @param expiryWarning: set to True to output a warning message if the 628 certificate is due to expire in less than nDaysBeforeExpiryLimit days. 629 Message is sent using warnings.warn and through logging.warning. No 630 message is set if the certificate has an otherwise invalid time 631 632 @type nDaysBeforeExpiryLimit: int 633 @param nDaysBeforeExpiryLimit: used in conjunction with the 634 expiryWarning flag. Set the number of days in advance of certificate 635 expiry from which to start outputing warnings 636 637 @type warningStackLevel: int 638 @param warningStackLevel: set where in the stack to flag the warning 639 from. Level 2 will flag it at the level of the caller of this 640 method. Level 3 would flag at the level of the caller of the caller 641 and so on. 642 643 @raise X509CertInvalidNotBeforeTime: current time is before the 644 certificate's notBefore time 645 @raise X509CertExpired: current time is after the certificate's 646 notAfter time""" 647 648 if not isinstance(self.__dtNotBefore, datetime): 649 raise X509CertError("Not Before datetime is not set") 650 651 if not isinstance(self.__dtNotAfter, datetime): 652 raise X509CertError("Not After datetime is not set") 653 654 dtNow = datetime.utcnow() 655 isValidTime = dtNow > self.__dtNotBefore and dtNow < self.__dtNotAfter 656 657 # Helper string for message output 658 if self.__filePath: 659 fileInfo = ' "%s"' % self.__filePath 660 else: 661 fileInfo = '' 662 663 664 # Set a warning message for impending expiry of certificate but only 665 # if the certificate is not any other way invalid - see below 666 if isValidTime and expiryWarning: 667 dtTime2Expiry = self.__dtNotAfter - dtNow 668 if dtTime2Expiry.days < nDaysBeforeExpiryLimit: 669 msg = ('Certificate%s with DN "%s" will expire in %d days on: ' 670 '%s' % (fileInfo, 671 self.dn, 672 dtTime2Expiry.days, 673 self.__dtNotAfter)) 674 warn(msg, stacklevel=warningStackLevel) 675 log.warning(msg) 676 677 678 if dtNow < self.__dtNotBefore: 679 msg = ("Current time %s is before the certificate's Not Before " 680 'Time %s for certificate%s with DN "%s"' % 681 (dtNow, self.__dtNotBefore, fileInfo, self.dn)) 682 log.error(msg) 683 if raiseExcep: 684 raise X509CertInvalidNotBeforeTime(msg) 685 686 elif dtNow > self.__dtNotAfter: 687 msg = ('Certificate%s with DN "%s" has expired: the time now is ' 688 '%s and the certificate expiry is %s.' %(fileInfo, 689 self.dn, 690 dtNow, 691 self.__dtNotAfter)) 692 log.error(msg) 693 if raiseExcep: 694 raise X509CertExpired(msg) 695 696 # If exception flag is not set return validity as bool 697 return isValidTime
698
699 - def __m2CryptoUTC2datetime(self, m2CryptoUTC):
700 """Convert M2Crypto UTC time string as returned by get_not_before/ 701 get_not_after methods into datetime type""" 702 703 datetimeRE = "([a-zA-Z]{3} {1,2}\d{1,2} \d{2}:\d{2}:\d{2} \d{4}).*" 704 sM2CryptoUTC = None 705 706 try: 707 # Convert into string 708 sM2CryptoUTC = str(m2CryptoUTC) 709 710 # Check for expected format - string may have trailing GMT - ignore 711 sTime = re.findall(datetimeRE, sM2CryptoUTC)[0] 712 713 # Convert into a tuple 714 lTime = strptime(sTime, "%b %d %H:%M:%S %Y")[0:6] 715 716 return datetime(lTime[0], lTime[1], lTime[2], 717 lTime[3], lTime[4], lTime[5]) 718 719 except Exception: 720 msg = "Error parsing M2Crypto UTC" 721 if sM2CryptoUTC is not None: 722 msg += ": " + sM2CryptoUTC 723 724 raise X509CertError(msg)
725
726 - def verify(self, pubKey, **kw):
727 """Verify a certificate against the public key of the 728 issuer 729 730 @param pubKey: public key of cert that issued self 731 @type pubKey: M2Crypto.RSA.RSA_pub 732 @param **kw: keywords to pass to M2Crypto.X509.X509 - 733 'pkey' 734 @type: dict 735 @return: True if verifies OK, False otherwise 736 @rtype: bool 737 """ 738 return bool(self.__m2CryptoX509.verify(pubKey, **kw))
739 740 @classmethod
741 - def Read(cls, filePath, warningStackLevel=4, **isValidTimeKw):
742 """Create a new X509 certificate read in from a file""" 743 x509Cert = cls(filePath=filePath) 744 x509Cert.read(warningStackLevel=warningStackLevel, **isValidTimeKw) 745 746 return x509Cert
747 748 @classmethod
749 - def Parse(cls, x509CertTxt, warningStackLevel=4, **isValidTimeKw):
750 """Create a new X509 certificate from string of file content""" 751 x509Cert = cls() 752 x509Cert.parse(x509CertTxt, 753 warningStackLevel=warningStackLevel, 754 **isValidTimeKw) 755 756 return x509Cert
757 758 @classmethod
759 - def fromM2Crypto(cls, m2CryptoX509):
760 """Convenience method to instantiate a new object from an M2Crypto 761 X.509 certificate object""" 762 x509Cert = cls(m2CryptoX509=m2CryptoX509) 763 return x509Cert
764
765 766 -class X509StackError(X509CertError):
767 """Error from X509Stack type"""
768
769 770 -class X509StackEmptyError(X509CertError):
771 """Expecting non-zero length X509Stack"""
772
773 774 -class X509CertIssuerNotFound(X509CertError):
775 """Raise from verifyCertChain if no certificate can be found to verify the 776 input"""
777
778 779 -class SelfSignedCert(X509CertError):
780 """Raise from verifyCertChain if cert. is self-signed and 781 rejectSelfSignedCert=True"""
782
783 784 -class X509CertInvalidSignature(X509CertError):
785 """X.509 Certificate has an invalid signature"""
786
787 -class X509Stack(object):
788 """Wrapper for M2Crypto X509_Stack""" 789
790 - def __init__(self, m2X509Stack=None):
791 """Initialise from an M2Crypto stack object 792 793 @param m2X509Stack: M2Crypto X.509 stack object 794 @type m2X509Stack: M2Crypto.X509.X509_Stack""" 795 796 self.__m2X509Stack = m2X509Stack or M2Crypto.X509.X509_Stack()
797
798 - def __len__(self):
799 """@return: length of stack 800 @rtype: int""" 801 return self.__m2X509Stack.__len__()
802
803 - def __getitem__(self, idx):
804 """Index stack as an array 805 @param idx: stack index 806 @type idx: int 807 @return: X.509 cert object 808 @rtype: ndg.security.common.X509.X509Cert""" 809 810 return X509Cert(m2CryptoX509=self.__m2X509Stack.__getitem__(idx))
811
812 - def __iter__(self):
813 """@return: stack iterator 814 @rtype: listiterator""" 815 return iter([X509Cert(m2CryptoX509=i) for i in self.__m2X509Stack])
816
817 - def push(self, x509Cert):
818 """Push an X509 certificate onto the stack. 819 820 @param x509Cert: X509 object. 821 @type x509Cert: M2Crypto.X509.X509, 822 ndg.security.common.X509.X509Cert or basestring 823 @return: The number of X509 objects currently on the stack. 824 @rtype: int""" 825 if isinstance(x509Cert, M2Crypto.X509.X509): 826 return self.__m2X509Stack.push(x509Cert) 827 828 elif isinstance(x509Cert, X509Cert): 829 return self.__m2X509Stack.push(x509Cert.m2CryptoX509) 830 831 elif isinstance(x509Cert, basestring): 832 return self.__m2X509Stack.push( 833 X509Cert.Parse(x509Cert).m2CryptoX509) 834 else: 835 raise X509StackError("Expecting M2Crypto.X509.X509, ndg.security." 836 "common.X509.X509Cert or string type")
837
838 - def pop(self):
839 """Pop a certificate from the stack. 840 841 @return: X509 object that was popped, or None if there is nothing 842 to pop. 843 @rtype: ndg.security.common.X509.X509Cert 844 """ 845 return X509Cert(m2CryptoX509=self.__m2X509Stack.pop())
846
847 - def asDER(self):
848 """Return the stack as a DER encoded string 849 @return: DER string 850 @rtype: string""" 851 return self.__m2X509Stack.as_der()
852
853 - def verifyCertChain(self, 854 x509Cert2Verify=None, 855 caX509Stack=None, 856 rejectSelfSignedCert=True):
857 """Treat stack as a list of certificates in a chain of 858 trust. Validate the signatures through to a single root issuer. 859 860 @param x509Cert2Verify: X.509 certificate to be verified default is 861 last in the stack 862 @type x509Cert2Verify: X509Cert 863 864 @param caX509Stack: X.509 stack containing CA certificates that are 865 trusted. 866 @type caX509Stack: X509Stack 867 868 @param rejectSelfSignedCert: Set to True (default) to raise an 869 SelfSignedCert exception if a certificate in self's stack is 870 self-signed. 871 @type rejectSelfSignedCert: bool""" 872 873 if caX509Stack is None: 874 caX509Stack = [] 875 876 n2Validate = len(self) 877 if x509Cert2Verify: 878 # One more to validate in addition to stack content 879 n2Validate += 1 880 else: 881 # Validate starting from last on stack - but check first that it's 882 # populated 883 if n2Validate == 0: 884 raise X509StackEmptyError("Empty stack and no x509Cert2Verify " 885 "set: no cert.s to verify") 886 887 x509Cert2Verify = self[-1] 888 889 # Exit loop if all certs have been validated or if find a self 890 # signed cert. 891 nValidated = 0 892 issuerX509Cert = None 893 while nValidated < n2Validate: 894 issuerX509Cert = None 895 issuerDN = x509Cert2Verify.issuer 896 897 # Search for issuing certificate in stack 898 for x509Cert in self: 899 if x509Cert.dn == issuerDN: 900 # Match found - the cert.'s issuer has been found in the 901 # stack 902 issuerX509Cert = x509Cert 903 break 904 905 if issuerX509Cert: 906 # An issuing cert. has been found - use it to check the 907 # signature of the cert. to be verified 908 if not x509Cert2Verify.verify(issuerX509Cert.pubKey): 909 X509CertInvalidSignature('Signature is invalid for cert. ' 910 '"%s"' % x509Cert2Verify.dn) 911 912 # In the next iteration the issuer cert. will be checked: 913 # 1) search for a cert. in the stack that issued it 914 # 2) If found use the issuing cert. to verify 915 x509Cert2Verify = issuerX509Cert 916 nValidated += 1 917 else: 918 # All certs in the stack have been searched 919 break 920 921 922 if issuerX509Cert: 923 # Check for self-signed certificate 924 if (nValidated == 1 and rejectSelfSignedCert and 925 issuerX509Cert.dn == issuerX509Cert.issuer): 926 927 # If only one iteration occurred then it must be a self 928 # signed certificate 929 raise SelfSignedCert("Certificate is self signed: [DN=%s]" % 930 issuerX509Cert.dn) 931 932 if not caX509Stack: 933 caX509Stack = [issuerX509Cert] 934 935 elif not caX509Stack: 936 raise X509CertIssuerNotFound('No issuer certificate found for ' 937 'certificate "%s"' % 938 x509Cert2Verify.dn) 939 940 for caCert in caX509Stack: 941 issuerDN = x509Cert2Verify.issuer 942 if caCert.dn == issuerDN: 943 issuerX509Cert = caCert 944 break 945 946 if issuerX509Cert: 947 if not x509Cert2Verify.verify(issuerX509Cert.pubKey): 948 X509CertInvalidSignature('Signature is invalid for cert. "%s"' % 949 x509Cert2Verify.dn) 950 951 # Chain is validated through to CA cert 952 return 953 else: 954 raise X509CertIssuerNotFound('No issuer cert. found for ' 955 'certificate "%s"'%x509Cert2Verify.dn) 956 957 # If this point is reached then an issuing cert is missing from the 958 # chain 959 raise X509CertIssuerNotFound('Can\'t find issuer cert "%s" for ' 960 'certificate "%s"' % 961 (x509Cert2Verify.issuer, 962 x509Cert2Verify.dn))
963
964 965 -class InvalidCertSignature(SSL.Checker.SSLVerificationError):
966 """Raise if verification against CA cert public key fails"""
967
968 969 -class InvalidCertDN(SSL.Checker.SSLVerificationError):
970 """Raise if verification against a list acceptable DNs fails"""
971
972 973 -class HostCheck(SSL.Checker.Checker, object):
974 """Override SSL.Checker.Checker to enable alternate Common Name 975 setting match for peer cert""" 976
977 - def __init__(self, 978 peerCertDN=None, 979 peerCertCN=None, 980 acceptedDNs=None, 981 caCertList=None, 982 caCertFilePathList=None, 983 **kw):
984 """Override parent class __init__ to enable setting of myProxyServerDN 985 setting 986 987 @type peerCertDN: string/list 988 @param peerCertDN: Set the expected Distinguished Name of the 989 server to avoid errors matching hostnames. This is useful 990 where the hostname is not fully qualified. 991 992 *param acceptedDNs: a list of acceptable DNs. This enables validation 993 where the expected DN is where against a limited list of certs. 994 995 @type peerCertCN: string 996 @param peerCertCN: enable alternate Common Name to peer 997 hostname 998 999 @type caCertList: list type of M2Crypto.X509.X509 types 1000 @param caCertList: CA X.509 certificates - if set the peer cert's 1001 CA signature is verified against one of these. At least one must 1002 verify 1003 1004 @type caCertFilePathList: list string types 1005 @param caCertFilePathList: same as caCertList except input as list 1006 of CA cert file paths""" 1007 1008 if acceptedDNs is None: 1009 acceptedDNs = [] 1010 1011 if caCertList is None: 1012 caCertList = [] 1013 1014 if caCertFilePathList is None: 1015 caCertFilePathList = [] 1016 1017 SSL.Checker.Checker.__init__(self, **kw) 1018 1019 self.peerCertDN = peerCertDN 1020 self.peerCertCN = peerCertCN 1021 self.acceptedDNs = acceptedDNs 1022 1023 if caCertList: 1024 self.caCertList = caCertList 1025 elif caCertFilePathList: 1026 self.caCertFilePathList = caCertFilePathList 1027 else: 1028 # Set default to enable len() test in __call__ 1029 self.__caCertStack = ()
1030
1031 - def __call__(self, peerCert, host=None):
1032 """Carry out checks on server ID 1033 @param peerCert: MyProxy server host certificate as M2Crypto.X509.X509 1034 instance 1035 @param host: name of host to check 1036 """ 1037 if peerCert is None: 1038 raise SSL.Checker.NoCertificate('SSL Peer did not return ' 1039 'certificate') 1040 1041 peerCertDN = '/'+peerCert.get_subject().as_text().replace(', ', '/') 1042 try: 1043 SSL.Checker.Checker.__call__(self, peerCert, host=self.peerCertCN) 1044 1045 except SSL.Checker.WrongHost, e: 1046 # Try match against peerCertDN set 1047 if peerCertDN != self.peerCertDN: 1048 raise e 1049 1050 # At least one match should be found in the list - first convert to 1051 # NDG X500DN type to allow per field matching for DN comparison 1052 peerCertX500DN = X500DN(dn=peerCertDN) 1053 1054 if self.acceptedDNs: 1055 matchFound = False 1056 for dn in self.acceptedDNs: 1057 x500dn = X500DN(dn=dn) 1058 if x500dn == peerCertX500DN: 1059 matchFound = True 1060 break 1061 1062 if not matchFound: 1063 raise InvalidCertDN('Peer cert DN "%s" doesn\'t match ' 1064 'verification list' % peerCertDN) 1065 1066 if len(self.__caCertStack) > 0: 1067 try: 1068 self.__caCertStack.verifyCertChain( 1069 x509Cert2Verify=X509Cert(m2CryptoX509=peerCert)) 1070 except Exception, e: 1071 raise InvalidCertSignature("Peer certificate verification " 1072 "against CA certificate failed: %s" 1073 % e) 1074 1075 # They match - drop the exception and return all OK instead 1076 return True
1077
1078 - def __setCACertList(self, caCertList):
1079 """Set list of CA certs - peer cert must validate against at least one 1080 of these""" 1081 self.__caCertStack = X509Stack() 1082 for caCert in caCertList: 1083 self.__caCertStack.push(caCert)
1084 1085 caCertList = property(fset=__setCACertList, 1086 doc="list of CA certificates - the peer certificate " 1087 "must validate against one") 1088
1089 - def __setCACertsFromFileList(self, caCertFilePathList):
1090 '''Read CA certificates from file and add them to the X.509 1091 stack 1092 1093 @type caCertFilePathList: basestring, list or tuple 1094 @param caCertFilePathList: list of file paths for CA certificates to 1095 be used to verify certificate used to sign message. If a single 1096 string item is input then this is converted into a tuple 1097 ''' 1098 if isinstance(caCertFilePathList, basestring): 1099 caCertFilePathList = (caCertFilePathList,) 1100 1101 elif not isinstance(caCertFilePathList, (list, tuple)): 1102 raise TypeError('Expecting a basestring, list or tuple type for ' 1103 '"caCertFilePathList"') 1104 1105 self.__caCertStack = X509Stack() 1106 1107 for caCertFilePath in caCertFilePathList: 1108 self.__caCertStack.push(X509.load_cert(caCertFilePath))
1109 1110 caCertFilePathList = property(fset=__setCACertsFromFileList, 1111 doc="list of CA certificate file paths - " 1112 "peer certificate must validate against " 1113 "one")
1114
1115 1116 -class HTTPSConnection(_HTTPSConnection):
1117 """Modified version of M2Crypto equivalent to enable custom checks with 1118 the peer and timeout settings 1119 1120 @type defReadTimeout: M2Crypto.SSL.timeout 1121 @cvar defReadTimeout: default timeout for read operations 1122 @type defWriteTimeout: M2Crypto.SSL.timeout 1123 @cvar defWriteTimeout: default timeout for write operations""" 1124 defReadTimeout = SSL.timeout(sec=20.) 1125 defWriteTimeout = SSL.timeout(sec=20.) 1126
1127 - def __init__(self, *args, **kw):
1128 '''Overload to enable setting of post connection check 1129 callback to SSL.Connection 1130 1131 type *args: tuple 1132 param *args: args which apply to M2Crypto.httpslib.HTTPSConnection 1133 type **kw: dict 1134 param **kw: additional keywords 1135 @type postConnectionCheck: SSL.Checker.Checker derivative 1136 @keyword postConnectionCheck: set class for checking peer 1137 @type readTimeout: M2Crypto.SSL.timeout 1138 @keyword readTimeout: readTimeout - set timeout for read 1139 @type writeTimeout: M2Crypto.SSL.timeout 1140 @keyword writeTimeout: similar to read timeout''' 1141 1142 self._postConnectionCheck = kw.pop('postConnectionCheck', 1143 SSL.Checker.Checker) 1144 1145 if 'readTimeout' in kw: 1146 if not isinstance(kw['readTimeout'], SSL.timeout): 1147 raise AttributeError("readTimeout must be of type " 1148 "M2Crypto.SSL.timeout") 1149 self.readTimeout = kw.pop('readTimeout') 1150 else: 1151 self.readTimeout = HTTPSConnection.defReadTimeout 1152 1153 if 'writeTimeout' in kw: 1154 if not isinstance(kw['writeTimeout'], SSL.timeout): 1155 raise AttributeError("writeTimeout must be of type " 1156 "M2Crypto.SSL.timeout") 1157 self.writeTimeout = kw.pop('writeTimeout') 1158 else: 1159 self.writeTimeout = HTTPSConnection.defWriteTimeout 1160 1161 self._clntCertFilePath = kw.pop('clntCertFilePath', None) 1162 self._clntPriKeyFilePath = kw.pop('clntPriKeyFilePath', None) 1163 1164 _HTTPSConnection.__init__(self, *args, **kw) 1165 1166 # load up certificate stuff 1167 if (self._clntCertFilePath is not None and 1168 self._clntPriKeyFilePath is not None): 1169 self.ssl_ctx.load_cert(self._clntCertFilePath, 1170 self._clntPriKeyFilePath)
1171 1172
1173 - def connect(self):
1174 '''Overload M2Crypto.httpslib.HTTPSConnection to enable 1175 custom post connection check of peer certificate and socket timeout''' 1176 1177 self.sock = SSL.Connection(self.ssl_ctx) 1178 self.sock.set_post_connection_check_callback(self._postConnectionCheck) 1179 1180 self.sock.set_socket_read_timeout(self.readTimeout) 1181 self.sock.set_socket_write_timeout(self.writeTimeout) 1182 1183 self.sock.connect((self.host, self.port))
1184
1185 - def putrequest(self, method, url, **kw):
1186 '''Overload to work around bug with unicode type URL''' 1187 url = str(url) 1188 _HTTPSConnection.putrequest(self, method, url, **kw)
1189
1190 1191 -class SSLContextProxy(object):
1192 """Holder for M2Crypto.SSL.Context parameters""" 1193 PRE_VERIFY_FAIL, PRE_VERIFY_OK = range(2) 1194 1195 SSL_CERT_FILEPATH_OPTNAME = "sslCertFilePath" 1196 SSL_PRIKEY_FILEPATH_OPTNAME = "sslPriKeyFilePath" 1197 SSL_PRIKEY_PWD_OPTNAME = "sslPriKeyPwd" 1198 SSL_CACERT_FILEPATH_OPTNAME = "sslCACertFilePath" 1199 SSL_CACERT_DIRPATH_OPTNAME = "sslCACertDir" 1200 SSL_VALID_DNS_OPTNAME = "sslValidDNs" 1201 1202 OPTNAMES = ( 1203 SSL_CERT_FILEPATH_OPTNAME, 1204 SSL_PRIKEY_FILEPATH_OPTNAME, 1205 SSL_PRIKEY_PWD_OPTNAME, 1206 SSL_CACERT_FILEPATH_OPTNAME, 1207 SSL_CACERT_DIRPATH_OPTNAME, 1208 SSL_VALID_DNS_OPTNAME 1209 ) 1210 1211 __slots__ = tuple(["__%s" % name for name in OPTNAMES]) 1212 del name 1213 1214 VALID_DNS_PAT = re.compile(',\s*') 1215
1216 - def __init__(self):
1217 self.__sslCertFilePath = None 1218 self.__sslPriKeyFilePath = None 1219 self.__sslPriKeyPwd = None 1220 self.__sslCACertFilePath = None 1221 self.__sslCACertDir = None 1222 self.__sslValidDNs = []
1223
1224 - def createCtx(self, depth=9, **kw):
1225 """Create an M2Crypto SSL Context from this objects properties 1226 @type depth: int 1227 @param depth: max. depth of certificate to verify against 1228 @type kw: dict 1229 @param kw: M2Crypto.SSL.Context keyword arguments 1230 @rtype: M2Crypto.SSL.Context 1231 @return M2Crypto SSL context object 1232 """ 1233 ctx = SSL.Context(**kw) 1234 1235 # Configure context according to this proxy's attributes 1236 if self.sslCertFilePath and self.sslPriKeyFilePath: 1237 # Pass client certificate (optionally with chain) 1238 ctx.load_cert_chain(self.sslCertFilePath, 1239 self.__sslPriKeyFilePath, 1240 lambda *arg, **kw: self.sslPriKeyPwd) 1241 log.debug("Set client certificate and key in SSL Context") 1242 else: 1243 log.debug("No client certificate or key set in SSL Context") 1244 1245 if self.sslCACertFilePath or self.sslCACertDir: 1246 # Set CA certificates in order to verify peer 1247 ctx.load_verify_locations(self.sslCACertFilePath, 1248 self.sslCACertDir) 1249 mode = SSL.verify_peer 1250 else: 1251 mode = SSL.verify_none 1252 log.warning('No CA certificate files set: mode set to ' 1253 '"verify_none"! No verification of the server ' 1254 'certificate will be enforced') 1255 1256 if len(self.sslValidDNs) > 0: 1257 # Set custom callback in order to verify peer certificate DN 1258 # against whitelist 1259 mode = SSL.verify_peer 1260 callback = self.createVerifySSLPeerCertCallback() 1261 log.debug('Set peer certificate Distinguished Name check set in ' 1262 'SSL Context') 1263 else: 1264 callback = None 1265 log.warning('No peer certificate Distinguished Name check set in ' 1266 'SSL Context') 1267 1268 ctx.set_verify(mode, 9, callback=callback) 1269 1270 return ctx
1271
1272 - def copy(self, sslCtxProxy):
1273 """Copy settings from another context object 1274 """ 1275 if not isinstance(sslCtxProxy, SSLContextProxy): 1276 raise TypeError('Expecting %r for copy method input object; ' 1277 'got %r' % (SSLContextProxy, type(sslCtxProxy))) 1278 1279 for name in SSLContextProxy.OPTNAMES: 1280 setattr(self, name, getattr(sslCtxProxy, name))
1281
1283 """Create a callback function to enable the DN of the peer in an SSL 1284 connection to be verified against a whitelist. 1285 1286 Nb. Making this function within the scope of a method of the class to 1287 enables to access instance variables 1288 """ 1289 1290 def _verifySSLPeerCertCallback(preVerifyOK, x509StoreCtx): 1291 '''SSL verify callback function used to control the behaviour when 1292 the SSL_VERIFY_PEER flag is set. See: 1293 1294 http://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html 1295 1296 This implementation applies verification in order to check the DN 1297 of the peer certificate against a whitelist 1298 1299 @type preVerifyOK: int 1300 @param preVerifyOK: If a verification error is found, this 1301 parameter will be set to 0 1302 @type x509StoreCtx: M2Crypto.X509.X509_Store_Context 1303 @param x509StoreCtx: locate the certificate to be verified and 1304 perform additional verification steps as needed 1305 @rtype: int 1306 @return: controls the strategy of the further verification process. 1307 - If verify_callback returns 0, the verification process is 1308 immediately stopped with "verification failed" state. If 1309 SSL_VERIFY_PEER is set, a verification failure alert is sent to the 1310 peer and the TLS/SSL handshake is terminated. 1311 - If verify_callback returns 1, the verification process is 1312 continued. 1313 If verify_callback always returns 1, the TLS/SSL handshake will not 1314 be terminated with respect to verification failures and the 1315 connection 1316 will be established. The calling process can however retrieve the 1317 error code of the last verification error using 1318 SSL_get_verify_result or by maintaining its own error storage 1319 managed by verify_callback. 1320 ''' 1321 if preVerifyOK == 0: 1322 # Something is wrong with the certificate don't bother 1323 # proceeding any further 1324 log.error("verifyCallback: pre-verify OK flagged an error " 1325 "with the peer certificate, returning error state " 1326 "to caller ...") 1327 return preVerifyOK 1328 1329 x509CertChain = x509StoreCtx.get1_chain() 1330 for cert in x509CertChain: 1331 x509Cert = X509Cert.fromM2Crypto(cert) 1332 if x509Cert.dn in self.sslValidDNs: 1333 return preVerifyOK 1334 1335 subject = cert.get_subject() 1336 dn = subject.as_text() 1337 log.debug("verifyCallback: dn = %r", dn) 1338 1339 # No match found so return fail status 1340 log.debug("No match for peer certificate %s in DN whitelist %r", 1341 x509Cert.dn, self.sslValidDNs) 1342 return SSLContextProxy.PRE_VERIFY_FAIL
1343 1344 return _verifySSLPeerCertCallback
1345
1346 - def _getSSLCertFilePath(self):
1347 return self.__sslCertFilePath
1348
1349 - def _setSSLCertFilePath(self, filePath):
1350 "Set X.509 cert/cert chian file path property method" 1351 1352 if isinstance(filePath, basestring): 1353 filePath = os.path.expandvars(filePath) 1354 1355 elif filePath is not None: 1356 raise TypeError("X.509 cert. file path must be a valid string") 1357 1358 self.__sslCertFilePath = filePath
1359 1360 sslCertFilePath = property(fset=_setSSLCertFilePath, 1361 fget=_getSSLCertFilePath, 1362 doc="File path to X.509 cert. / cert. chain") 1363
1364 - def _getSSLCACertFilePath(self):
1365 """Get file path for list of CA cert or certs used to validate SSL 1366 connections 1367 1368 @rtype sslCACertFilePath: basestring 1369 @return sslCACertFilePathList: file path to file containing concatenated 1370 PEM encoded CA certificates.""" 1371 return self.__sslCACertFilePath
1372
1373 - def _setSSLCACertFilePath(self, value):
1374 """Set CA cert file path 1375 1376 @type sslCACertFilePath: basestring, list, tuple or None 1377 @param sslCACertFilePath: file path to CA certificate file. If None 1378 then the input is quietly ignored.""" 1379 if isinstance(value, basestring): 1380 self.__sslCACertFilePath = os.path.expandvars(value) 1381 1382 elif value is None: 1383 self.__sslCACertFilePath = value 1384 1385 else: 1386 raise TypeError("Input CA Certificate file path must be " 1387 "a valid string or None type: %r" % type(value))
1388 1389 1390 sslCACertFilePath = property(fget=_getSSLCACertFilePath, 1391 fset=_setSSLCACertFilePath, 1392 doc="Path to file containing concatenated PEM " 1393 "encoded CA Certificates - used for " 1394 "verification of peer certs in SSL " 1395 "connection") 1396
1397 - def _getSSLCACertDir(self):
1398 """Get file path for list of CA cert or certs used to validate SSL 1399 connections 1400 1401 @rtype sslCACertDir: basestring 1402 @return sslCACertDirList: directory containing PEM encoded CA 1403 certificates.""" 1404 return self.__sslCACertDir
1405
1406 - def _setSSLCACertDir(self, value):
1407 """Set CA cert or certs to validate AC signatures, signatures 1408 of Attribute Authority SOAP responses and SSL connections where 1409 AA SOAP service is run over SSL. 1410 1411 @type sslCACertDir: basestring 1412 @param sslCACertDir: directory containing CA certificate files. 1413 """ 1414 if isinstance(value, basestring): 1415 self.__sslCACertDir = os.path.expandvars(value) 1416 elif value is None: 1417 self.__sslCACertDir = value 1418 else: 1419 raise TypeError("Input CA Certificate directroy must be " 1420 "a valid string or None type: %r" % type(value))
1421 1422 sslCACertDir = property(fget=_getSSLCACertDir, 1423 fset=_setSSLCACertDir, 1424 doc="Path to directory containing PEM encoded CA " 1425 "Certificates used for verification of peer " 1426 "certs in SSL connection. Files in the " 1427 "directory must be named with the form " 1428 "<hash>.0 where <hash> can be obtained using " 1429 "openssl x509 -in cert -hash -noout or using " 1430 "the c_rehash OpenSSL script") 1431
1432 - def _getSslValidDNs(self):
1433 return self.__sslValidDNs
1434
1435 - def _setSslValidDNs(self, value):
1436 if isinstance(value, basestring): 1437 pat = SSLContextProxy.VALID_DNS_PAT 1438 self.__sslValidDNs = [X500DN.fromString(dn) 1439 for dn in pat.split(value)] 1440 1441 elif isinstance(value, (tuple, list)): 1442 self.__sslValidDNs = [X500DN.fromString(dn) for dn in value] 1443 else: 1444 raise TypeError('Expecting list/tuple or basestring type for "%s" ' 1445 'attribute; got %r' % 1446 (SSLContextProxy.SSL_VALID_DNS_OPTNAME, 1447 type(value)))
1448 1449 sslValidDNs = property(_getSslValidDNs, 1450 _setSslValidDNs, 1451 doc="whitelist of acceptable certificate " 1452 "Distinguished Names for peer certificates in " 1453 "SSL requests") 1454
1455 - def _getSSLPriKeyFilePath(self):
1456 return self.__sslPriKeyFilePath
1457
1458 - def _setSSLPriKeyFilePath(self, filePath):
1459 "Set ssl private key file path property method" 1460 1461 if isinstance(filePath, basestring): 1462 filePath = os.path.expandvars(filePath) 1463 1464 elif filePath is not None: 1465 raise TypeError("Private key file path must be a valid " 1466 "string or None type") 1467 1468 self.__sslPriKeyFilePath = filePath
1469 1470 sslPriKeyFilePath = property(fget=_getSSLPriKeyFilePath, 1471 fset=_setSSLPriKeyFilePath, 1472 doc="File path to SSL private key") 1473
1474 - def _setSSLPriKeyPwd(self, sslPriKeyPwd):
1475 "Set method for ssl private key file password" 1476 if not isinstance(sslPriKeyPwd, (type(None), basestring)): 1477 raise TypeError("Signing private key password must be None " 1478 "or a valid string") 1479 1480 # Explicitly convert to string as M2Crypto OpenSSL wrapper fails with 1481 # unicode type 1482 self.__sslPriKeyPwd = str(sslPriKeyPwd)
1483
1484 - def _getSSLPriKeyPwd(self):
1485 "Get property method for SSL private key" 1486 return self.__sslPriKeyPwd
1487 1488 sslPriKeyPwd = property(fset=_setSSLPriKeyPwd, 1489 fget=_getSSLPriKeyPwd, 1490 doc="Password protecting SSL private key file") 1491
1492 - def __getstate__(self):
1493 '''Enable pickling for use with beaker.session''' 1494 _dict = {} 1495 for attrName in SSLContextProxy.__slots__: 1496 # Ugly hack to allow for derived classes setting private member 1497 # variables 1498 if attrName.startswith('__'): 1499 attrName = "_SSLContextProxy" + attrName 1500 1501 _dict[attrName] = getattr(self, attrName) 1502 1503 return _dict
1504
1505 - def __setstate__(self, attrDict):
1506 '''Enable pickling for use with beaker.session''' 1507 for attr, val in attrDict.items(): 1508 setattr(self, attr, val)
1509