Ignore:
Timestamp:
16/12/08 10:45:01 (11 years ago)
Author:
pjkersha
Message:

#884: add capability to X509Cert.isValidTime to warn when X.509 certificates are due to expire within a certain time limit (default 30 days). isValidTime is now called from read and parsing routines. warnings.warn and logging.warning are called so logs from security services will display the messages.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/ndg.security.common/ndg/security/common/X509.py

    r4606 r4654  
    1111__contact__ = "Philip.Kershaw@stfc.ac.uk" 
    1212__revision__ = '$Id$' 
    13  
     13import logging 
     14log = logging.getLogger(__name__) 
     15from warnings import warn # warn of impendiong certificate expiry 
    1416 
    1517import types 
     
    2729    """Exception handling for NDG X.509 Certificate handling class.""" 
    2830 
     31class X509CertReadError(X509CertError): 
     32    """Error reading in certificate from file""" 
     33     
    2934class X509CertInvalidNotBeforeTime(X509CertError): 
    3035    """Call from X509Cert.isValidTime if certificates not before time is 
     
    5560         
    5661 
    57     def read(self, filePath=None): 
    58         """Read a certificate from file""" 
     62    def read(self, filePath=None, **isValidTimeKw): 
     63        """Read a certificate from PEM encoded file 
     64         
     65        @type filePath: basestring 
     66        @param filePath: file path of PEM format file to be read 
     67         
     68        @type isValidTimeKw: dict 
     69        @param isValidTimeKw: keywords to isValidTime() call""" 
    5970         
    6071        # Check for optional input certificate file path 
    6172        if filePath is not None: 
    6273            if not isinstance(filePath, basestring): 
    63                 raise X509CertError( 
    64                     "Certificate File Path input must be a valid string") 
     74                raise X509CertError("Certificate File Path input must be a " 
     75                                    "valid string") 
    6576             
    6677            self.__filePath = filePath 
     
    6980            self.__m2CryptoX509 = M2Crypto.X509.load_cert(self.__filePath) 
    7081        except Exception, e: 
    71             raise X509CertError("Error loading certificate \"%s\": %s" % \ 
    72                                 (self.__filePath, str(e))) 
     82            raise X509CertReadError("Error loading certificate \"%s\": %s" % 
     83                                    (self.__filePath, e)) 
    7384 
    7485        # Update DN and validity times from M2Crypto X509 object just 
    7586        # created 
    7687        self.__setM2CryptoX509() 
    77  
    78  
    79     def parse(self, certTxt): 
    80         """Read a certificate input as a string""" 
     88         
     89        if 'warningStackLevel'not in isValidTimeKw: 
     90            isValidTimeKw['warningStackLevel'] = 3 
     91             
     92        self.isValidTime(**isValidTimeKw) 
     93 
     94 
     95    def parse(self, certTxt, **isValidTimeKw): 
     96        """Read a certificate input as a string 
     97         
     98        @type certTxt: basestring 
     99        @param certTxt: PEM encoded certificate to parse  
     100         
     101        @type isValidTimeKw: dict 
     102        @param isValidTimeKw: keywords to isValidTime() call""" 
    81103 
    82104        try: 
     
    90112             
    91113        except Exception, e: 
    92             raise X509CertError, "Error loading certificate: %s" % str(e) 
     114            raise X509CertError("Error loading certificate: %s" % e) 
    93115 
    94116        # Update DN and validity times from M2Crypto X509 object just 
    95117        # created 
    96118        self.__setM2CryptoX509() 
     119         
     120         
     121        if 'warningStackLevel'not in isValidTimeKw: 
     122            isValidTimeKw['warningStackLevel'] = 3 
     123                     
     124        self.isValidTime(**isValidTimeKw) 
    97125 
    98126       
     
    103131        if m2CryptoX509 is not None: 
    104132            if not isinstance(m2CryptoX509, M2Crypto.X509.X509): 
    105                 raise TypeError, \ 
    106                     "Incorrect type for input M2Crypto.X509.X509 object" 
     133                raise TypeError("Incorrect type for input M2Crypto.X509.X509 " 
     134                                "object") 
    107135                     
    108136            self.__m2CryptoX509 = m2CryptoX509 
     
    127155                                         
    128156        except Exception, e: 
    129             raise X509CertError, "Not Before time: " + str(e) 
     157            raise X509CertError("Not Before time: %s" % e) 
    130158 
    131159         
     
    135163                                     
    136164        except Exception, e: 
    137             raise X509CertError, "Not After time: " + str(e) 
     165            raise X509CertError("Not After time: %s" % e) 
    138166 
    139167 
     
    259287 
    260288 
    261     def isValidTime(self, raiseExcep=False): 
     289    def isValidTime(self,  
     290                    raiseExcep=False,  
     291                    expiryWarning=True,  
     292                    nDaysBeforeExpiryLimit=30, 
     293                    warningStackLevel=2): 
    262294        """Check Certificate for expiry 
    263295 
    264         raiseExcep: set True to raise an exception if certificate is invalid""" 
     296        @type raiseExcep: bool 
     297        @param raiseExcep: set True to raise an exception if certificate is  
     298        invalid 
     299         
     300        @type expiryWarning: bool 
     301        @param expiryWarning: set to True to output a warning message if the  
     302        certificate is due to expire in less than nDaysBeforeExpiryLimit days.  
     303        Message is sent using warnings.warn and through logging.warning.  No  
     304        message is set if the certificate has an otherwise invalid time 
     305         
     306        @type nDaysBeforeExpiryLimit: int 
     307        @param nDaysBeforeExpiryLimit: used in conjunction with the  
     308        expiryWarning flag.  Set the number of days in advance of certificate 
     309        expiry from which to start outputing warnings 
     310         
     311        @type warningStackLevel: int 
     312        @param warningStackLevel: set where in the stack to flag the warning 
     313        from.  Level 2 will flag it at the level of the caller of this  
     314        method.  Level 3 would flag at the level of the caller of the caller 
     315        and so on. 
     316         
     317        @raise X509CertInvalidNotBeforeTime: current time is before the  
     318        certificate's notBefore time 
     319        @raise X509CertExpired: current time is after the certificate's  
     320        notAfter time""" 
    265321 
    266322        if not isinstance(self.__dtNotBefore, datetime): 
     
    271327        
    272328        dtNow = datetime.utcnow() 
    273  
    274         if raiseExcep: 
    275             if dtNow < self.__dtNotBefore: 
    276                 raise X509CertInvalidNotBeforeTime, \ 
    277                     "Current time is before the certificate's Not Before Time" 
    278              
    279             elif dtNow > self.__dtNotAfter: 
    280                 raise X509CertExpired, \ 
    281                     "Certificate, %s, has expired: the time now is %s  \ 
    282                     and the certificate expiry is %s." \ 
    283                     %(self.__filePath, dtNow, self.__dtNotAfter) 
     329        isValidTime = dtNow > self.__dtNotBefore and dtNow < self.__dtNotAfter 
     330 
     331        # Helper string for message output 
     332        if self.__filePath: 
     333            fileInfo = ' "%s"' % self.__filePath 
    284334        else: 
    285             return dtNow > self.__dtNotBefore and dtNow < self.__dtNotAfter 
     335            fileInfo = '' 
     336              
     337         
     338        # Set a warning message for impending expiry of certificate but only 
     339        # if the certificate is not any other way invalid - see below 
     340        if isValidTime and expiryWarning: 
     341            dtTime2Expiry = self.__dtNotAfter - dtNow 
     342            if dtTime2Expiry.days < nDaysBeforeExpiryLimit: 
     343                msg = ('Certificate%s with DN "%s" will expire in %d days on: ' 
     344                       '%s' % (fileInfo,  
     345                               self.dn,  
     346                               dtTime2Expiry.days,  
     347                               self.__dtNotAfter)) 
     348                warn(msg, stacklevel=warningStackLevel) 
     349                log.warning(msg) 
     350         
     351                      
     352        if dtNow < self.__dtNotBefore: 
     353            msg = ("Current time %s is before the certificate's Not Before " 
     354                   'Time %s for certificate%s with DN "%s"' %  
     355                   (dtNow, self.__dtNotBefore, fileInfo, self.dn)) 
     356            log.error(msg) 
     357            if raiseExcep: 
     358                raise X509CertInvalidNotBeforeTime(msg) 
     359             
     360        elif dtNow > self.__dtNotAfter: 
     361            msg = ('Certificate%s with DN "%s" has expired: the time now is ' 
     362                   '%s and the certificate expiry is %s.' %(fileInfo, 
     363                                                            self.dn,  
     364                                                            dtNow,  
     365                                                            self.__dtNotAfter)) 
     366            if raiseExcep: 
     367                raise X509CertExpired(msg) 
     368 
     369        # If exception flag is not set return validity as bool 
     370        return isValidTime 
    286371 
    287372 
     
    330415 
    331416    @classmethod 
    332     def Read(cls, filePath): 
     417    def Read(cls, filePath, **isValidTimeKw): 
    333418        """Create a new X509 certificate read in from a file""" 
    334419     
    335420        x509Cert = cls(filePath=filePath) 
    336         x509Cert.read() 
     421         
     422        if 'warningStackLevel' not in isValidTimeKw: 
     423            isValidTimeKw['warningStackLevel'] = 4 
     424             
     425        x509Cert.read(**isValidTimeKw) 
    337426         
    338427        return x509Cert 
    339428     
    340429    @classmethod 
    341     def Parse(cls, x509CertTxt): 
     430    def Parse(cls, x509CertTxt, **isValidTimeKw): 
    342431        """Create a new X509 certificate from string of file content""" 
    343432     
    344433        x509Cert = cls() 
    345         x509Cert.parse(x509CertTxt) 
     434         
     435        if 'warningStackLevel' not in isValidTimeKw: 
     436            isValidTimeKw['warningStackLevel'] = 4 
     437             
     438        x509Cert.parse(x509CertTxt, **isValidTimeKw) 
    346439         
    347440        return x509Cert 
    348441         
    349442# Alternative AttCert constructors 
    350 def X509CertRead(filePath): 
     443def X509CertRead(filePath, **isValidTimeKw): 
    351444    """Create a new X509 certificate read in from a file""" 
    352445 
    353446    x509Cert = X509Cert(filePath=filePath) 
    354     x509Cert.read() 
     447     
     448    if 'warningStackLevel' not in isValidTimeKw: 
     449        isValidTimeKw['warningStackLevel'] = 4 
     450         
     451    x509Cert.read(**isValidTimeKw) 
    355452     
    356453    return x509Cert 
    357454 
    358 def X509CertParse(x509CertTxt): 
     455def X509CertParse(x509CertTxt, **isValidTimeKw): 
    359456    """Create a new X509 certificate from string of file content""" 
    360457 
    361458    x509Cert = X509Cert() 
    362     x509Cert.parse(x509CertTxt) 
     459    if 'warningStackLevel' not in isValidTimeKw: 
     460        isValidTimeKw['warningStackLevel'] = 4 
     461             
     462    x509Cert.parse(x509CertTxt, **isValidTimeKw) 
    363463     
    364464    return x509Cert 
     
    430530                                       X509CertParse(x509Cert).m2CryptoX509)             
    431531        else: 
    432             raise X509StackError, "Expecting M2Crypto.X509.X509, " + \ 
    433                 "ndg.security.common.X509.X509Cert or string type" 
     532            raise X509StackError("Expecting M2Crypto.X509.X509, ndg.security." 
     533                                 "common.X509.X509Cert or string type") 
    434534                 
    435535    def pop(self): 
Note: See TracChangeset for help on using the changeset viewer.