Ignore:
Timestamp:
22/01/10 15:36:40 (11 years ago)
Author:
pjkersha
Message:

Refactoring base signature handler

Location:
TI12-security/trunk/WSSecurity/ndg/wssecurity
Files:
1 edited
1 copied

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/WSSecurity/ndg/wssecurity/common/wssecurity/signaturehandler/__init__.py

    r5441 r6378  
    44NERC DataGrid Project 
    55""" 
    6 __author__ = "C Byrom" 
     6__author__ = "Philip Kershaw and C Byrom" 
    77__date__ = "18/08/08" 
    8 __copyright__ = "" 
     8__copyright__ = "(C) 2009 Science and Technology Facilities Council" 
    99__license__ = "BSD - see LICENSE file in top-level directory" 
    1010__contact__ = "Philip.Kershaw@stfc.ac.uk" 
     
    2727# Enable settings from a config file 
    2828from ndg.security.common.wssecurity import WSSecurityConfig, WSSecurityError 
    29  
    30 from ndg.security.common.X509 import X509Cert, X509CertParse, X509CertRead, \ 
    31 X509Stack, X509StackParseFromDER 
     29from ndg.security.common.wssecurity.utils import ContextSensitiveConfigParser 
     30from ndg.security.common.wssecurity.pki import (X509Cert, X509CertParse,  
     31                                                X509CertRead, X509Stack,  
     32                                                X509StackParseFromDER) 
    3233 
    3334from datetime import datetime, timedelta 
     
    4142    UTILITY = ("http://docs.oasis-open.org/wss/2004/01/" 
    4243               "oasis-200401-wss-wssecurity-utility-1.0.xsd") 
     44 
    4345 
    4446class OASIS(_OASIS): 
     
    5557    verify a signature is not from a known CA""" 
    5658     
     59     
    5760class VerifyError(WSSecurityError): 
    5861    """Raised from SignatureHandler.verify if an error occurs in the signature 
    5962    verification""" 
    6063  
     64  
    6165class TimestampError(WSSecurityError): 
    6266    """Raised from SignatureHandler._verifyTimestamp if there is a problem with 
     
    6872    order to set a wsu:MessageExpired fault code""" 
    6973     
     74     
    7075class InvalidSignature(WSSecurityError): 
    7176    """Raised from verify method for an invalid signature""" 
     77 
    7278 
    7379class SignatureError(WSSecurityError): 
     
    107113        "X509v3":        OASIS.X509TOKEN.X509+"v3" 
    108114    } 
    109  
    110  
    111     def __init__(self, cfg=None, cfgFileSection='DEFAULT', cfgFilePrefix='', 
    112                  cfgClass=WSSecurityConfig, **kw): 
     115     
     116    CFG_PARSER_CLASS = CaseSensitiveConfigParser 
     117 
     118    PROPERTY_DEFAULTS = dict( 
     119        reqBinSecTokValType=(OASIS.X509TOKEN.X509,), 
     120        verifyingCert=(None,), 
     121        verifyingCertFilePath=(None, ''), 
     122        signingCert=(None,), 
     123        signingCertFilePath=(None, ''),  
     124        signingCertChain=[], 
     125        signingPriKey=(None,), 
     126        signingPriKeyFilePath=(None, ''),  
     127        signingPriKeyPwd=(None, ''), 
     128        caCertDirPath=(None, ''), 
     129        caCertFilePathList=([], ), 
     130        addTimestamp=(True,), 
     131        timestampClockSkew=(0., 0), 
     132        timestampMustBeSet=(False,), 
     133        createdElemMustBeSet=(True,), 
     134        expiresElemMustBeSet=(True,), 
     135        applySignatureConfirmation=(False,), 
     136        refC14nInclNS=([],), 
     137        signedInfoC14nInclNS=([],), 
     138        cfg=(None, CFG_PARSER_CLASS())) 
     139     
     140    TYPE_MAP = dict([(k, tuple([type(i) for i in v])) 
     141                     for k, v in PROPERTY_DEFAULTS.items()]) 
     142     
     143    # Special case to parse an option containing a comma separated list 
     144    LIST_PARAM_SEP_PAT = re.compile(',\s*') 
     145    _parseCfgGetList = lambda section, option: LIST_PARAM_SEP_PAT.split( 
     146                                        CFG_PARSER_CLASS.get(section, option)) 
     147    parseCfgGetList = classmethod(_parseCfgGetList) 
     148     
     149    CFG_PARSER_GET_FUNC_MAP = { 
     150        'basestring': CFG_PARSER_CLASS.get, 
     151        'bool': CFG_PARSER_CLASS.getboolean, 
     152        'float': CFG_PARSER_CLASS.getfloat, 
     153        'int': CFG_PARSER_CLASS.getint, 
     154        'list': parseCfgGetList 
     155    } 
     156     
     157    __slots__ = {}.update(TYPE_MAP) 
     158         
     159    def __init__(self, cfg=None): 
     160        '''Initialise settings from an existing config file object or the 
     161        given path to config file 
     162         
     163        @type cfg: SafeConfigParser or string 
     164        @param cfg: config object instance or file path to config file to be 
     165        parsed''' 
     166         
     167        # initialise attributes 
     168        for name, val in BaseSignatureHandler.PROPERTY_DEFAULTS: 
     169            setattr(self, name, val[0])   
     170             
     171        if cfg is not None: 
     172            self.cfg = cfg 
     173                   
     174    def __setattr__(self, name, val): 
     175        """Apply type checking on inputs""" 
     176        allowedType = BaseSignatureHandler.TYPE_MAP.get(name) 
     177        if allowedType: 
     178            if not isinstance(val, allowedType): 
     179                raise TypeError('Allowed type(s) for %r attribute are %r; ' 
     180                                'got %r' % (name, allowedType, type(val))) 
     181             
     182        super(BaseSignatureHandler).__setattr__(name, val) 
     183         
     184    def read(self, filePath): 
     185        '''Read ConfigParser object 
     186         
     187        @type filePath: basestring 
     188        @param filePath: file to read config from''' 
     189         
     190        # Expand environment variables in file path 
     191        expandedFilePath = os.path.expandvars(filePath) 
     192         
     193        # Add 'here' item to enable convenient path substitutions in the config 
     194        # file 
     195        defaultItems = dict(here=os.path.dirname(expandedFilePath)) 
     196        self.cfg.defaultitems().update(defaultItems) 
     197         
     198        readFilePaths = self._cfg.read(expandedFilePath) 
     199         
     200        # Check file was read in OK 
     201        if len(readFilePaths) == 0: 
     202            raise IOError('Missing config file: "%s"' % expandedFilePath) 
     203         
     204    def parse(self, section='DEFAULT', prefix=None, **kw): 
     205        '''Extract items from config file and place in dict 
     206        @type **kw: dict 
     207        @param **kw: this enables WS-Security params to be set in a config file 
     208        with other sections e.g. params could be under the section 'wssecurity' 
    113209        ''' 
    114         @param cfg: object from which to read config items - a file path, 
    115         config parser object or WSSecurityConfig object 
    116         @type cfg: basestring/RawConfigParser/WSSecurityConfig 
    117          
    118         @param cfgFileSection: section name in config file containing  
    119         parameters 
    120         @type cfgFileSection: basestring 
    121          
    122         @param cfgFilePrefix: prefix for parameter names in the config file. 
    123         This enables these parameters to be filtered from other unrelated 
    124         parameters in the same section 
    125         @type cfgFilePrefix: basestring 
    126          
    127         @param cfgClass: class used to parse the settings 
    128         @type cfgClass: WSSecurityConfig derived class type 
    129          
    130         @param kw: any config parameters as specified by WSSecurityConfig class 
    131         @type kw: dict 
    132         ''' 
    133         log.debug("BaseSignatureHandler.__init__ ...") 
    134  
    135         # WSSecurityConfig is the default class for reading config params but 
    136         # alternative derivative class may be passed in instead. 
    137         if not issubclass(cfgClass, WSSecurityConfig): 
    138             raise TypeError("%s is not a sub-class of WSSecurityConfig" %  
    139                             cfgClass) 
    140          
    141         # Read parameters from config file if set 
    142         if isinstance(cfg, basestring): 
    143             log.debug("BaseSignatureHandler.__init__: config file path input " 
    144                       "...") 
    145             self.cfg = cfgClass() 
    146             self.cfg.read(cfg) 
    147             self.cfg.parse(section=cfgFileSection, prefix=cfgFilePrefix) 
    148  
    149         elif isinstance(cfg, RawConfigParser): 
    150             log.debug("BaseSignatureHandler.__init__: config object input ...") 
    151             self.cfg = cfgClass(cfg=cfg) 
    152             self.cfg.parse(section=cfgFileSection, prefix=cfgFilePrefix) 
    153              
    154         elif isinstance(cfg, WSSecurityConfig): 
    155             log.debug("BaseSignatureHandler.__init__:  WSSSecurityConfig " 
    156                       "object input ...") 
    157             self.cfg = cfg 
     210        if prefix: 
     211            optNames = ["%s.%s" % (prefix, optName)  
     212                        for optName in BaseSignatureHandler.PROPERTY_DEFAULTS]  
    158213        else: 
    159             self.cfg = cfgClass() 
     214            optNames = BaseSignatureHandler.PROPERTY_DEFAULTS.keys() 
     215             
     216        nameTypeMap = zip(optNames, BaseSignatureHandler.TYPE_MAP)  
     217            
     218        for optName, expectedType in nameTypeMap: 
     219            # Parameters may be omitted and set later 
     220            if self.cfg.has_option(sectionName, optName): 
     221                getFunc = BaseSignatureHandler.CFG_PARSER_GET_FUNC_MAP.get( 
     222                                                                expectedType) 
     223                val = getFunc(self.cfg, section, optName) 
     224                setattr(self, name, val) 
     225 
     226    def update(self, prefix=None, **kw):    
     227        """Set attributes from a set of keywords or a dictionary""" 
     228        if prefix: 
     229            optNames = ["%s.%s" % (prefix, optName)  
     230                        for optName in BaseSignatureHandler.PROPERTY_DEFAULTS]  
     231        else: 
     232            optNames = BaseSignatureHandler.PROPERTY_DEFAULTS.keys() 
     233             
     234        nameTypeMap = zip(optNames, BaseSignatureHandler.TYPE_MAP)  
     235            
     236        for optName, expectedType in nameTypeMap: 
     237            # Parameters may be omitted and set later 
     238            if optName in kw: 
     239                setattr(self, optName, kw[optName]) 
    160240                 
    161         # Also update config from keywords set  
    162         log.debug("BaseSignatureHandler.__init__: updating config from " 
    163                   "keywords...") 
    164          
    165         # Filter keywords if a prefix is set removing any that don't start with 
    166         # the prefix given 
    167         self.cfg.update(kw, prefix=cfgFilePrefix) 
    168          
    169         # set default value type, if none specified in config file 
    170         if not self.cfg['reqBinSecTokValType']: 
    171             self.cfg['reqBinSecTokValType'] = "X509v3" 
    172              
    173         self.reqBinSecTokValType = self.cfg['reqBinSecTokValType'] 
    174  
    175         # Set keywords for canonicalization of SignedInfo and reference  
    176         # elements 
    177         self.refC14nKw = {'inclusive_namespaces': self.cfg['refC14nInclNS']} 
    178  
    179         self.signedInfoC14nKw = {'inclusive_namespaces':  
    180                                  self.cfg['signedInfoC14nInclNS']} 
    181  
    182         self.verifyingCert = self.cfg['verifyingCert'] 
    183         self.verifyingCertFilePath = self.cfg['verifyingCertFilePath'] 
    184          
    185         self.signingCert = self.cfg['signingCert'] 
    186         self.signingCertFilePath = self.cfg['signingCertFilePath'] 
    187  
    188         self.signingCertChain = self.cfg['signingCertChain'] 
    189               
    190         # MUST be set before _setSigningPriKeyFilePath / _setSigningPriKey 
    191         # are called 
    192         self.signingPriKeyPwd = self.cfg['signingPriKeyPwd'] 
    193          
    194         if self.cfg.get('signingPriKey'): 
    195             # Don't allow None for private key setting 
    196             self.signingPriKey = self.cfg['signingPriKey'] 
    197              
    198         self.signingPriKeyFilePath = self.cfg['signingPriKeyFilePath'] 
    199          
    200         # CA certificate(s) for verification of X.509 certificate used with 
    201         # signature. 
    202         if self.cfg.get('caCertDirPath'): 
    203             self.caCertDirPath = self.cfg['caCertDirPath'] 
    204              
    205         elif self.cfg.get('caCertFilePathList'): 
    206             self.caCertFilePathList = self.cfg['caCertFilePathList'] 
    207          
    208         # Configure signature generation to add/omit a timestamp 
    209         self.addTimestamp = self.cfg['addTimestamp'] 
    210          
    211         # Configure timestamp checking in Signature verification handler 
    212         self.timestampClockSkew = self.cfg['timestampClockSkew'] 
    213         self.timestampMustBeSet = self.cfg['timestampMustBeSet'] 
    214         self.createdElemMustBeSet = self.cfg['createdElemMustBeSet'] 
    215         self.expiresElemMustBeSet = self.cfg['expiresElemMustBeSet'] 
    216          
    217         # set default value, if none specified in config file 
    218         if not self.cfg['applySignatureConfirmation']: 
    219             self.cfg['applySignatureConfirmation'] = False 
    220  
    221         self.applySignatureConfirmation=self.cfg['applySignatureConfirmation'] 
    222         self.b64EncSignatureValue = None 
    223          
    224         log.debug("WSSE Config = %s" % self.cfg) 
    225  
    226                  
    227     def _setReqBinSecTokValType(self, value): 
    228         """Set ValueType attribute for BinarySecurityToken used in a request 
    229           
    230         @type value: string 
    231         @param value: name space for BinarySecurityToken ValueType check 
    232         'binSecTokValType' class variable for supported types.  Input can be  
    233         shortened to binSecTokValType keyword if desired. 
    234         """ 
    235         log.debug("Setting reqBinSecTokValType - to %s" %value) 
    236         if value in self.__class__.binSecTokValType: 
    237             self._reqBinSecTokValType = self.__class__.binSecTokValType[value] 
    238   
    239         elif value in self.__class__.binSecTokValType.values(): 
    240             self._reqBinSecTokValType = value 
    241         else: 
    242             raise WSSecurityError('Request BinarySecurityToken ValueType ' 
    243                                   '"%s" not recognised' % value) 
    244              
    245     def _getReqBinSecTokValType(self): 
    246         """ 
    247         Get ValueType attribute for BinarySecurityToken used in a request 
    248         """ 
    249         log.debug("Getting reqBinSecTokValType value") 
    250         if hasattr(self, '_reqBinSecTokValType'): 
    251             return self._reqBinSecTokValType 
    252         else: 
    253             return "" 
    254          
    255     reqBinSecTokValType = property(fset=_setReqBinSecTokValType, 
    256                                    fget=_getReqBinSecTokValType, 
    257          doc="ValueType attribute for BinarySecurityToken used in request") 
    258          
     241    @classmethod 
     242    def fromKeywords(cls, cfg=None, **kw): 
     243        """Instantiate from attribute values set in a list of keywords""" 
     244        handler = cls(cfg=cfg) 
     245        handler.update(**kw) 
     246        return handler 
     247     
     248    @classmethod 
     249    def fromConfigFile(cls, filePath, cfg=None, **kw): 
     250        """Instantiate from settings in configuration file""" 
     251        handler = cls(cfg=cfg) 
     252        handler.read(filePath) 
     253        handler.parse(**kw) 
     254        return handler        
    259255 
    260256    def __checkC14nKw(self, kw): 
     
    270266        if kw is not None and not isinstance(kw, dict): 
    271267            # Otherwise keywords must be a dictionary 
    272             raise AttributeError("Expecting dictionary type for reference " 
     268            raise TypeError("Expecting dictionary type for reference " 
    273269                                 "C14N keywords") 
    274270                 
    275271        elif kw.get('inclusive_namespaces') and \ 
    276272             not isinstance(kw['inclusive_namespaces'], (list, tuple)): 
    277             raise AttributeError('Expecting list or tuple of prefix names for ' 
     273            raise TypeError('Expecting list or tuple of prefix names for ' 
    278274                                 '"%s" keyword' % 'inclusive_namespaces') 
    279275         
    280                  
    281276    def _setRefC14nKw(self, kw): 
    282277        """Set keywords for canonicalization of reference elements in the  
     
    367362         
    368363        else: 
    369             raise AttributeError("X.509 Cert. must be type: ndg.security." 
     364            raise TypeError("X.509 Cert. must be type: ndg.security." 
    370365                                 "common.X509.X509Cert, M2Crypto.X509.X509 or " 
    371366                                 "a base64 encoded string") 
     
    410405                self._verifyingCert = X509CertRead(verifyingCertFilePath) 
    411406            else: 
    412                 raise AttributeError, "X.509 Cert file path is not a valid string" 
     407                raise TypeError, "X.509 Cert file path is not a valid string" 
    413408         
    414409        self._verifyingCertFilePath = verifyingCertFilePath 
     
    447442             
    448443        elif signingCertFilePath is not None: 
    449             raise AttributeError("Signature X.509 certificate file path must " 
     444            raise TypeError("Signature X.509 certificate file path must " 
    450445                                 "be a valid string") 
    451446         
     
    486481                                doc="Cert.s in chain of trust to cert. used " 
    487482                                    "to verify msg.") 
    488  
    489   
    490     def _setSigningPriKeyPwd(self, signingPriKeyPwd): 
    491         "Set method for private key file password used to sign message" 
    492         if signingPriKeyPwd is not None and \ 
    493            not isinstance(signingPriKeyPwd, basestring): 
    494             raise AttributeError("Signing private key password must be None " 
    495                                  "or a valid string") 
    496          
    497         self._signingPriKeyPwd = signingPriKeyPwd 
    498  
    499     def _getSigningPriKeyPwd(self): 
    500         if hasattr(self, '_signingPriKeyPwd'): 
    501             return self._signingPriKeyPwd 
    502         else: 
    503             return "" 
    504          
    505     signingPriKeyPwd = property(fset=_setSigningPriKeyPwd, 
    506                                 fget=_getSigningPriKeyPwd, 
    507                                 doc="Password protecting private key file " 
    508                                     "used to sign message") 
    509  
    510483  
    511484    def _setSigningPriKey(self, signingPriKey): 
     
    527500                    
    528501        else: 
    529             raise AttributeError("Signing private key must be a valid " 
    530                                   "M2Crypto.RSA.RSA type or a string") 
     502            raise TypeError("Signing private key must be a valid " 
     503                            "M2Crypto.RSA.RSA type or a string") 
    531504                 
    532505    def _getSigningPriKey(self): 
     
    550523                                                        callback=pwdCallback)            
    551524            except Exception, e: 
    552                 raise AttributeError("Setting private key for signature: %s"%e) 
     525                raise TypeError("Setting private key for signature: %s"%e) 
    553526         
    554527        elif signingPriKeyFilePath is not None: 
    555             raise AttributeError("Private key file path must be a valid " 
     528            raise TypeError("Private key file path must be a valid " 
    556529                                 "string or None") 
    557530         
     
    579552        for cert in caCertList: 
    580553            self._caX509Stack.push(cert) 
    581  
    582554 
    583555    def __setCAX509StackFromDir(self, caCertDir): 
     
    592564        reg = re.compile('\d+\.0') 
    593565        try: 
    594             caCertList = [X509CertRead(caFile) \ 
    595                           for caFile in os.listdir(caCertDir) \ 
     566            caCertList = [X509CertRead(caFile)  
     567                          for caFile in os.listdir(caCertDir)  
    596568                          if reg.match(caFile)] 
    597569        except Exception, e: 
     
    632604    caCertFilePathList = property(fset=__setCAX509StackFromCertFileList, 
    633605                      doc="List of CA cert. files used for verification")               
    634          
    635     def _get_timestampClockSkew(self): 
    636         return getattr(self, "_timestampClockSkew", 0.) 
    637  
    638     def _set_timestampClockSkew(self, val): 
    639         if isinstance(val, basestring): 
    640             self._timestampClockSkew = float(val) 
    641              
    642         elif isinstance(val, (float, int)): 
    643             self._timestampClockSkew = val 
    644              
    645         else: 
    646             raise TypeError("Expecting string, float or int type for " 
    647                             "timestampClockSkew attribute, got %r" %  
    648                             getattr(val, "__class__", val)) 
    649          
    650     timestampClockSkew = property(fset=_set_timestampClockSkew, 
    651                                   fget=_get_timestampClockSkew, 
    652                                   doc="adjust the current time calculated by " 
    653                                       "the number of seconds specified in " 
    654                                       "this parameter.  This enables " 
    655                                       "allowance to be made for clock skew " 
    656                                       "between a client and server system " 
    657                                       "clocks.") 
    658606     
    659607    def _setBool(self, val): 
     
    681629            raise TypeError("Invalid type for bool conversion: %r" %  
    682630                            val.__class__) 
    683          
    684     def _get_timestampMustBeSet(self): 
    685         return getattr(self, "_timestampMustBeSet", False) 
    686  
    687     def _set_timestampMustBeSet(self, val): 
    688         self._timestampMustBeSet = self._setBool(val) 
    689          
    690     timestampMustBeSet = property(fset=_set_timestampMustBeSet, 
    691                                   fget=_get_timestampMustBeSet, 
    692                                   doc="Set to True to raise an exception if a " 
    693                                       "message to be verified doesn't have a " 
    694                                       "timestamp element.  Set to False to " 
    695                                       "log a warning message and continue " 
    696                                       "processing") 
    697      
    698     def _get_createdElemMustBeSet(self): 
    699         return getattr(self, "_createdElemMustBeSet", False) 
    700  
    701     def _set_createdElemMustBeSet(self, val): 
    702         self._createdElemMustBeSet = self._setBool(val) 
    703          
    704     createdElemMustBeSet = property(fset=_set_createdElemMustBeSet, 
    705                                     fget=_get_createdElemMustBeSet, 
    706                                     doc="Set to True to raise an exception if " 
    707                                         "a message to be verified doesn't " 
    708                                         "have <wsu:Created/> element with its " 
    709                                         "timestamp element.  Set to False to " 
    710                                         "log a warning message and continue " 
    711                                         "processing") 
    712      
    713     def _get_expiresElemMustBeSet(self): 
    714         return getattr(self, "_expiresElemMustBeSet", False) 
    715  
    716     def _set_expiresElemMustBeSet(self, val): 
    717         self._expiresElemMustBeSet = self._setBool(val) 
    718          
    719     expiresElemMustBeSet = property(fset=_set_expiresElemMustBeSet, 
    720                                     fget=_get_expiresElemMustBeSet, 
    721                                     doc="Set to True to raise an exception if " 
    722                                         "a message to be verified doesn't " 
    723                                         "have <wsu:Expires/> element with its " 
    724                                         "timestamp element.  Set to False to " 
    725                                         "log a warning message and continue " 
    726                                         "processing")                                   
     631                                  
    727632 
    728633                               
Note: See TracChangeset for help on using the changeset viewer.