source: TI12-security/trunk/WSSecurity/ndg/wssecurity/common/signaturehandler/__init__.py @ 6403

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/WSSecurity/ndg/wssecurity/common/signaturehandler/__init__.py@6403
Revision 6403, 32.2 KB checked in by pjkersha, 10 years ago (diff)
Line 
1""" Base class for the WS-Security digital signature handlers - to allow
2sharing of common code
3
4NERC DataGrid Project
5"""
6__author__ = "C Byrom, Philip Kershaw"
7__date__ = "18/08/08"
8__copyright__ = ""
9__license__ = "BSD - see LICENSE file in top-level directory"
10__contact__ = "Philip.Kershaw@stfc.ac.uk"
11__revision__ = '$Id: $'
12import logging
13log = logging.getLogger(__name__)
14
15import os
16import re
17import base64
18import traceback
19from datetime import datetime, timedelta
20from sha import sha # Digest and signature/verify
21
22from M2Crypto import X509, BIO, RSA
23
24import ZSI
25from ZSI.wstools.Namespaces import ENCRYPTION, WSU
26from ZSI.wstools.Namespaces import OASIS as _OASIS
27
28from ndg.wssecurity.common import WSSecurityConfigError, WSSecurityError
29from ndg.wssecurity.common.utils import classfactory
30from ndg.wssecurity.common.utils.configfileparsers import (
31                                            CaseSensitiveConfigParser, 
32                                            WithGetListConfigParser)
33from ndg.wssecurity.common.utils.pki import (X509Cert, X509Stack, 
34                                             X509StackParseFromDER)
35
36
37class _WSU(WSU):
38    '''Try different utility namespace for use with WebSphere'''
39    #UTILITY = "http://schemas.xmlsoap.org/ws/2003/06/utility"
40    UTILITY = ("http://docs.oasis-open.org/wss/2004/01/"
41               "oasis-200401-wss-wssecurity-utility-1.0.xsd")
42
43
44class OASIS(_OASIS):
45    # wss4j 1.5.3
46    WSSE11 = ("http://docs.oasis-open.org/wss/"
47              "oasis-wss-wssecurity-secext-1.1.xsd")
48    # wss4j 1.5.1
49#    WSSE11 = ("http://docs.oasis-open.org/wss/2005/xx/"
50#              "oasis-2005xx-wss-wssecurity-secext-1.1.xsd")
51   
52   
53class VerifyError(WSSecurityError):
54    """Raised from SignatureHandler.verify if an error occurs in the signature
55    verification"""
56   
57   
58class InvalidSignature(WSSecurityError):
59    """Raised from verify method for an invalid signature"""
60
61
62class SignatureError(WSSecurityError):
63    """Flag if an error occurs during signature generation"""
64
65
66class NoSignatureFound(WSSecurityError):
67    """Raise from SignatureHandler.verify if inbound message is not signed"""
68
69
70_isIterable = lambda obj: getattr(obj, '__iter__', False) 
71 
72
73class BaseSignatureHandler(object):
74    """Class to handle signature and verification of signature with
75    WS-Security
76   
77    @cvar BINARY_SECURITY_TOK_VAL_TYPE: supported ValueTypes for
78    BinarySecurityToken
79    element in WSSE header
80    @type BINARY_SECURITY_TOK_VAL_TYPE: dict
81   
82    @ivar addTimestamp: set to true to add a timestamp to outbound messages
83    @type addTimestamp: bool
84
85    @ivar applySignatureConfirmation: for servers - set this flag to enable the
86    signature value of a request to be recorded and included with a
87    SignatureConfirmation element in the response.
88    @type applySignatureConfirmation: bool
89   
90    @param b64EncSignatureValue: base 64 encoded signature value for the last
91    message verified
92    @type b64EncSignatureValue: string/None"""
93    isIterable = staticmethod(_isIterable)
94
95    BINARY_SECURITY_TOK_ENCODING_TYPE = (
96        "http://docs.oasis-open.org/wss/2004/01/"
97        "oasis-200401-wss-soap-message-security-1.0#Base64Binary"
98    )
99   
100    BINARY_SECURITY_TOK_VAL_TYPE = {
101        "X509PKIPathv1": OASIS.X509TOKEN.X509PKIPathv1,
102        "X509":          OASIS.X509TOKEN.X509,
103        "X509v3":        OASIS.X509TOKEN.X509+"v3"
104    }
105
106    # keyword used with ZSI.wstools.Utility.Canonicalization
107    ZSI_C14N_KEYWORD_NAME = 'inclusive_namespaces'
108   
109    CFG_PARSER_CLASS = WithGetListConfigParser
110    PROPERTY_DEFAULTS = dict(
111        className=('',),
112        reqBinarySecurityTokValType=(OASIS.X509TOKEN.X509,),
113        verifyingCert=(None, ''),
114        verifyingCertFilePath=(None, ''),
115        signingCert=(None, ''),
116        signingCertFilePath=(None, ''), 
117        signingCertChain=([],),
118        signingPriKey=(None, ''),
119        signingPriKeyFilePath=(None, ''), 
120        signingPriKeyPwd=(None, ''),
121        caCertDirPath=(None, ''),
122        caCertFilePathList=([],),
123        addTimestamp=(True,),
124        timestampClockSkew=(0.,),
125        timestampMustBeSet=(False,),
126        createdElemMustBeSet=(True,),
127        expiresElemMustBeSet=(True,),
128        applySignatureConfirmation=(False,),
129        refC14nInclNS=([],),
130        signedInfoC14nInclNS=([],),
131        cfg=(CFG_PARSER_CLASS(),))
132   
133    CFG_PARSER_GET_FUNC_MAP = {
134        str: CFG_PARSER_CLASS.get,
135        unicode: CFG_PARSER_CLASS.get,
136        bool: CFG_PARSER_CLASS.getboolean,
137        float: CFG_PARSER_CLASS.getfloat,
138        int: CFG_PARSER_CLASS.getint,
139        list: CFG_PARSER_CLASS.getlist
140    }
141   
142    TYPE_MAP = dict([(k, tuple([type(i) for i in v]))
143                     for k,v in PROPERTY_DEFAULTS.items()])
144   
145    __slots__ = TYPE_MAP.copy()
146    __slots__.update({}.fromkeys(['__%s' % i for i in TYPE_MAP.keys()]))   
147    __slots__.update(
148        __caX509Stack=None,
149        __refC14nKw=None,
150        __signedInfoC14nKw=None
151    )
152   
153    def __init__(self):
154        ''''''
155        log.debug("BaseSignatureHandler.__init__ ...")
156        for name, val in BaseSignatureHandler.PROPERTY_DEFAULTS.items():
157            setattr(self, name, val[0])
158
159        self.__caX509Stack = X509Stack()
160        self.__refC14nKw = {}
161        self.__signedInfoC14nKw = {}
162
163    @classmethod
164    def fromConfigFile(cls, filePath, **kw):
165        """Instantiate from settings in a config file"""
166        signatureHandler = cls()
167        signatureHandler.read(filePath)
168        signatureHandler.parse(**kw)
169        return signatureHandler
170
171    @classmethod
172    def fromKeywords(cls, *arg, **kw):
173        """Instantiate from keyword settings - this is useful when integrating
174        with Paste WSGI apps app_conf settings"""
175        signatureHandler = cls()
176        signatureHandler.update(**kw)
177        return signatureHandler
178   
179    @classmethod
180    def expandVars(cls, val):
181        if cls.isIterable(val):
182            for i, v in zip(range(len(val)), val):
183                if isinstance(v, basestring):
184                    val[i] = os.path.expandvars(v) 
185            return val
186       
187        elif isinstance(val, basestring):
188            return os.path.expandvars(val)
189        else:
190            return val
191       
192    def __setattr__(self, name, val):
193        expectedTypes = BaseSignatureHandler.TYPE_MAP.get(name)
194        if expectedTypes is not None:
195            if not isinstance(val, expectedTypes):
196                raise TypeError('Expected type(s) for % attribute are %r; '
197                                'got %r' % (name, expectedTypes, type(val)))
198                               
199        super(BaseSignatureHandler, self).__setattr__(name, val)
200
201    def read(self, filePath):
202        '''Read ConfigParser object
203       
204        @type filePath: basestring
205        @param filePath: file to read config from'''
206       
207        # Expand environment variables in file path
208        expandedFilePath = os.path.expandvars(filePath)
209       
210        # Add 'here' item to enable convenient path substitutions in the config
211        # file
212        defaultItems = dict(here=os.path.dirname(expandedFilePath))
213        self.cfg.defaults().update(defaults=defaultItems)
214       
215        readFilePaths = self.cfg.read(expandedFilePath)
216       
217        # Check file was read in OK
218        if len(readFilePaths) == 0:
219            raise IOError('Missing config file: "%s"' % expandedFilePath)
220
221    def parse(self, sectionName='DEFAULT', prefix=None):
222        '''Extract items from config file and assign to instance attributes
223       
224        @type prefix: None type or basestring
225        @param prefix: Prefix for option names - optNames = name as they appear
226        in the config file
227        '''
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        paramSettings = zip(optNames, 
235                            BaseSignatureHandler.PROPERTY_DEFAULTS.keys()) 
236           
237        for optName, attrName in paramSettings:
238           
239            # Parameters may be omitted and set later
240            if self.cfg.has_option(sectionName, optName):
241                types = BaseSignatureHandler.TYPE_MAP.get(attrName)
242                if types is None:
243                    raise WSSecurityConfigError("No type set for %r attribute" %
244                                                attrName)
245                   
246                getFunc = BaseSignatureHandler.CFG_PARSER_GET_FUNC_MAP.get(
247                                                                    types[-1])
248                if getFunc is None:
249                    raise WSSecurityConfigError("No Config parser get method "
250                                                "configured for attribute %r "
251                                                "with type %r" %
252                                                (attrName, types[-1]))
253                     
254                val = getFunc(self.cfg, sectionName, optName)
255                setattr(self, attrName, BaseSignatureHandler.expandVars(val))
256                 
257    def update(self, prefix=None, **kw):
258        '''Extract items from a dictionary and assign to instance attributes
259       
260        @type prefix: None type or basestring
261        @param prefix: Prefix for option names - optNames = name as they appear
262        in the config file
263        @type **kw: dict
264        @param **kw: this enables WS-Security params to be set in a config file
265        with other sections e.g. params could be under the section 'wssecurity'
266        '''           
267        for optName, val in kw.items():
268            # Parameters may be omitted and set later
269            if prefix:
270                optName = optName.replace(prefix, '', 1)
271                setattr(self, optName, BaseSignatureHandler.expandVars(val))
272               
273    def sign(self, soapWriter):
274        '''Sign the message body and binary security token of a SOAP message
275       
276        Derived class must implement
277       
278        @type soapWriter: ZSI.writer.SoapWriter
279        @param soapWriter: ZSI object to write SOAP message
280        '''
281        raise NotImplementedError()
282   
283    def verify(self, parsedSOAP):
284        """Verify signature.  Derived class must implement
285       
286        @type parsedSOAP: ZSI.parse.ParsedSoap
287        @param parsedSOAP: object contain parsed SOAP message received from
288        sender"""       
289        raise NotImplementedError()
290                                     
291    def _setReqBinarySecurityTokValType(self, value):
292        """Set ValueType attribute for BinarySecurityToken used in a request
293         
294        @type value: string
295        @param value: name space for BinarySecurityToken ValueType check
296        'BINARY_SECURITY_TOK_VAL_TYPE' class variable for supported types. 
297        Input can be shortened to BINARY_SECURITY_TOK_VAL_TYPE keyword if
298        desired.
299        """
300        if value in self.__class__.BINARY_SECURITY_TOK_VAL_TYPE:
301            self.__reqBinarySecurityTokValType = \
302                self.__class__.BINARY_SECURITY_TOK_VAL_TYPE[value]
303 
304        elif value in self.__class__.BINARY_SECURITY_TOK_VAL_TYPE.values():
305            self.__reqBinarySecurityTokValType = value
306        else:
307            raise TypeError('Request BinarySecurityToken ValueType %r not '
308                            'recognised' % value)
309           
310    def _getReqBinarySecurityTokValType(self):
311        """
312        Get ValueType attribute for BinarySecurityToken used in a request
313        """
314        return self.__reqBinarySecurityTokValType
315       
316    reqBinarySecurityTokValType = property(fset=_setReqBinarySecurityTokValType,
317                                           fget=_getReqBinarySecurityTokValType,
318                                           doc="ValueType attribute for "
319                                               "BinarySecurityToken used in "
320                                               "request")
321   
322    @classmethod
323    def __checkC14nKw(cls, kw):
324        """Check keywords for canonicalization in signing process - generic
325        method for setting keywords for reference element and SignedInfo
326        element C14N
327       
328        @type kw: dict
329        @param kw: keyword used with ZSI.wstools.Utility.Canonicalization"""
330       
331        # Check for dict/None - Set to None in order to use inclusive
332        # canonicalization
333        if not isinstance(kw, (dict, type(None))):
334            # Otherwise keywords must be a dictionary
335            raise TypeError("Expecting dictionary type for reference C14N "
336                            "keywords")
337               
338        elif not isinstance(kw.get(cls.ZSI_C14N_KEYWORD_NAME), (list, tuple)):
339            raise TypeError('Expecting list or tuple of prefix names for '
340                            '"%s" keyword' % cls.ZSI_C14N_KEYWORD_NAME)
341       
342               
343    def _setRefC14nKw(self, kw):
344        """Set keywords for canonicalization of reference elements in the
345        signing process"""
346        self.__checkC14nKw(kw)                   
347        self.__refC14nKw = kw
348       
349    def _getRefC14nKw(self):
350        return self.__refC14nKw
351       
352    refC14nKw = property(fset=_setRefC14nKw,
353                         fget=_getRefC14nKw,
354                         doc="Keywords for C14N of reference elements")
355       
356    def _setSignedInfoC14nKw(self, kw):
357        """Set keywords for canonicalization of SignedInfo element in the
358        signing process"""
359        self.__checkC14nKw(kw)                   
360        self.__signedInfoC14nKw = kw
361       
362    def _getSignedInfoC14nKw(self):
363        if hasattr(self, '_signedInfoC14nKw'):
364            return self.__signedInfoC14nKw
365        else:
366            return {}
367       
368    signedInfoC14nKw = property(fset=_setSignedInfoC14nKw,
369                                fget=_getSignedInfoC14nKw,
370                                doc="Keywords for C14N of SignedInfo element")
371
372    def _refC14nIsExcl(self):
373        '''
374        @rtype: bool
375        @return: true if Exclusive C14N is set as algorithm to apply to
376        reference elements
377        '''
378        # TODO: alter logic here if inclusive C14N is re-instated.
379        return True
380               
381    refC14nIsExcl = property(fget=_refC14nIsExcl,
382    doc="Return True/False C14N for reference elements set to exclusive type")
383     
384    def _signedInfoC14nIsExcl(self):
385        '''
386        @rtype: bool
387        @return: true if Exclusive C14N is set as algorithm to apply to
388        the signed info elements of the XML Digital Signature
389        '''
390        # TODO: alter logic here if inclusive C14N is re-instated.
391        return True
392       
393    signedInfoC14nIsExcl = property(fget=_signedInfoC14nIsExcl,
394                                    doc="Return True/False C14N for "
395                                    "SignedInfo element set to exclusive type")
396   
397    def _setCert(self, cert):
398        """filter and convert input cert to signing verifying cert set
399        property methods.  For signingCert, set to None if it is not to be
400        included in the SOAP header.  For verifyingCert, set to None if this
401        cert can be expected to be retrieved from the SOAP header of the
402        message to be verified
403       
404        @type: ndg.security.common.X509.X509Cert / M2Crypto.X509.X509 /
405        PEM encoded string or None
406        @param cert: X.509 certificate. 
407       
408        @rtype ndg.security.common.X509.X509Cert
409        @return X.509 certificate object"""
410       
411        if not cert or isinstance(cert, X509Cert):
412            # ndg.security.common.X509.X509Cert type / None
413            x509Cert = cert
414           
415        elif isinstance(cert, X509.X509):
416            # M2Crypto.X509.X509 type
417            x509Cert = X509Cert(m2CryptoX509=cert)
418           
419        elif isinstance(cert, basestring):
420            # Nb. Assume PEM encoded string!
421            x509Cert = X509Cert.Parse(cert)
422       
423        else:
424            raise TypeError("X.509 Cert. must be type: ndg.security."
425                            "common.X509.X509Cert, M2Crypto.X509.X509 or "
426                            "a base64 encoded string")
427       
428        # Check for expired certificate
429        if x509Cert:   
430            x509Cert.isValidTime(raiseExcep=True)
431           
432        return x509Cert
433
434    def _getVerifyingCert(self):
435        '''Return X.509 cert object corresponding to cert used to verify the
436        signature in the last call to verify
437       
438         * Cert will correspond to one used in the LATEST call to verify, on
439         the next call it will be replaced
440         * if verify hasn't been called, the cert will be None
441       
442        @rtype: M2Crypto.X509.X509
443        @return: certificate object
444        '''
445        return self.__verifyingCert
446
447    def _setVerifyingCert(self, verifyingCert):
448        "Set property method for X.509 cert. used to verify a signature"
449        self.__verifyingCert = self._setCert(verifyingCert)
450       
451        # Reset file path as it may no longer apply
452        self.__verifyingCertFilePath = None
453       
454    verifyingCert = property(fset=_setVerifyingCert,
455                             fget=_getVerifyingCert,
456                             doc="Set X.509 Cert. for verifying signature")
457
458    def _setVerifyingCertFilePath(self, verifyingCertFilePath):
459        "Set method for Service X.509 cert. file path property"
460        if verifyingCertFilePath:
461            if isinstance(verifyingCertFilePath, basestring):
462                self.__verifyingCert = X509Cert.Read(verifyingCertFilePath)
463            else:
464                raise TypeError("X.509 Cert file path is not a valid string")
465       
466        self.__verifyingCertFilePath = verifyingCertFilePath
467       
468    verifyingCertFilePath = property(fset=_setVerifyingCertFilePath,
469                    doc="file path of X.509 Cert. for verifying signature")
470
471    def _getSigningCert(self):
472        '''Return X.509 certificate object corresponding to certificate used
473        with signature
474       
475        @rtype: M2Crypto.X509.X509
476        @return: certificate object
477        '''
478        return self.__signingCert
479
480    def _setSigningCert(self, signingCert):
481        "Set property method for X.509 cert. to be included with signature"
482        self.__signingCert = self._setCert(signingCert)
483   
484        # Reset file path as it may no longer apply
485        self.__signingCertFilePath = None
486       
487    signingCert = property(fget=_getSigningCert,
488                           fset=_setSigningCert,
489                           doc="X.509 Certificate to include signature")
490
491    def _getSigningCertFilePath(self):
492        "Get signature X.509 certificate property method"
493        return self.__signingCertFilePath
494   
495    def _setSigningCertFilePath(self, signingCertFilePath):
496        "Set signature X.509 certificate property method"
497       
498        if isinstance(signingCertFilePath, basestring):
499            self.__signingCert = X509Cert.Read(signingCertFilePath)
500           
501        elif signingCertFilePath is not None:
502            raise AttributeError("Signature X.509 certificate file path must "
503                                 "be a valid string")
504       
505        self.__signingCertFilePath = signingCertFilePath
506       
507    signingCertFilePath = property(fget=_getSigningCertFilePath,
508                                   fset=_setSigningCertFilePath,
509                                   doc="File path X.509 cert. to include with "
510                                       "signed message")
511
512    def _setSigningCertChain(self, signingCertChain):
513        '''Signature set-up with "X509PKIPathv1" BinarySecurityToken
514        ValueType.  Use an X.509 Stack to store certificates that make up a
515        chain of trust to certificate used to verify a signature
516       
517        @type signingCertChain: list or tuple of M2Crypto.X509.X509Cert or
518        ndg.security.common.X509.X509Cert types.
519        @param signingCertChain: list of certificate objects making up the
520        chain of trust.  The last certificate is the one associated with the
521        private key used to sign the message.'''
522        self.__signingCertChain = X509Stack()
523       
524        for cert in signingCertChain:
525            if cert:
526                self.__signingCertChain.push(cert)
527           
528    def _getSigningCertChain(self):
529        return self.__signingCertChain
530   
531    signingCertChain = property(fset=_setSigningCertChain,
532                                fget=_getSigningCertChain,
533                                doc="Certificates in the chain of trust to "
534                                    "verify the certificate provided in an "
535                                    "incoming message.")
536
537    def _setSigningPriKeyPwd(self, signingPriKeyPwd):
538        "Set method for private key file password used to sign message"
539        if (signingPriKeyPwd is not None and 
540            not isinstance(signingPriKeyPwd, basestring)):
541            raise AttributeError("Signing private key password must be None "
542                                 "or a valid string")
543       
544        self.__signingPriKeyPwd = signingPriKeyPwd
545
546    def _getSigningPriKeyPwd(self):       
547        return self.__signingPriKeyPwd
548
549    signingPriKeyPwd = property(fset=_setSigningPriKeyPwd,
550                                fget=_getSigningPriKeyPwd,
551                                doc="Password protecting private key file "
552                                    "used to sign message")
553
554    def _setSigningPriKey(self, signingPriKey):
555        """Set method for client private key
556       
557        Nb. if input is a string, signingPriKeyPwd will need to be set if
558        the key is password protected.
559       
560        @type signingPriKey: M2Crypto.RSA.RSA / string / None
561        @param signingPriKey: private key used to sign message"""
562        if not signingPriKey:
563            self.__signingPriKey = None
564           
565        elif isinstance(signingPriKey, basestring):
566            pwdCallback = lambda *ar, **kw: self.__signingPriKeyPwd
567            self.__signingPriKey = RSA.load_key_string(signingPriKey,
568                                                       callback=pwdCallback)
569        elif isinstance(signingPriKey, RSA.RSA):
570            self.__signingPriKey = signingPriKey
571           
572        else:
573            raise TypeError("Signing private key must be a valid "
574                            "M2Crypto.RSA.RSA type or a string")
575               
576    def _getSigningPriKey(self):
577        return self.__signingPriKey
578
579    signingPriKey = property(fset=_setSigningPriKey,
580                             fget=_getSigningPriKey,
581                             doc="Private key used to sign outbound message")
582
583 
584    def _setSigningPriKeyFilePath(self, signingPriKeyFilePath):
585        """Set method for client private key file path
586       
587        signingPriKeyPwd MUST be set prior to a call to this method"""
588        if isinstance(signingPriKeyFilePath, basestring):                           
589            try:
590                # Read Private key to sign with   
591                priKeyFile = BIO.File(open(signingPriKeyFilePath)) 
592                pwdCallback = lambda *ar, **kw: self.__signingPriKeyPwd                                           
593                self.__signingPriKey = RSA.load_key_bio(priKeyFile, 
594                                                        callback=pwdCallback)           
595            except Exception, e:
596                raise AttributeError("Setting private key for signature: %s"%e)
597       
598        elif signingPriKeyFilePath is not None:
599            raise TypeError("Private key file path must be a valid string or "
600                            "None")
601       
602        self.__signingPriKeyFilePath = signingPriKeyFilePath
603       
604    signingPriKeyFilePath = property(fset=_setSigningPriKeyFilePath,
605                      doc="File path for private key used to sign message")
606
607    def __caCertIsSet(self):
608        '''Check for CA certificate set (X.509 Stack has been created)'''
609        return hasattr(self, '_caX509Stack')
610   
611    caCertIsSet = property(fget=__caCertIsSet,
612           doc='Check for CA certificate set (X.509 Stack has been created)')
613   
614    def __appendCAX509Stack(self, caCertList):
615        '''Store CA certificates in an X.509 Stack
616       
617        @param caCertList: list or tuple
618        @type caCertList: M2Crypto.X509.X509 certificate objects'''
619        for cert in caCertList:
620            self.__caX509Stack.push(cert)
621
622
623    def __setCAX509StackFromDir(self, caCertDir):
624        '''Read CA certificates from directory and add them to the X.509
625        stack
626       
627        @param caCertDir: string / None type
628        @type caCertDir: directory from which to read CA certificate files'''
629       
630        if not caCertDir:
631            return
632       
633        # Mimic OpenSSL -CApath option which expects directory of CA files
634        # of form <Hash cert subject name>.0
635        reg = re.compile('\d+\.0')
636        try:
637            caCertList = [X509Cert.Read(caFile) 
638                          for caFile in os.listdir(caCertDir) 
639                          if reg.match(caFile)]
640        except Exception, e:
641            raise WSSecurityError('Loading CA certificate "%s" from CA '
642                                  'directory: %s' % (caFile, str(e)))
643                   
644        # Add to stack
645        self.__appendCAX509Stack(caCertList)
646       
647    caCertDirPath = property(fset=__setCAX509StackFromDir,
648                             doc="Dir. containing CA cert.s used for "
649                                "verification")
650
651    def __setCAX509StackFromCertFileList(self, caCertFilePathList):
652        '''Read CA certificates from file and add them to the X.509
653        stack
654       
655        @type caCertFilePathList: list or tuple
656        @param caCertFilePathList: list of file paths for CA certificates to
657        be used to verify certificate used to sign message'''
658
659        # Mimic OpenSSL -CApath option which expects directory of CA files
660        # of form <Hash cert subject name>.0
661        try:
662            caCertList = [X509Cert.Read(caFile) for caFile in caCertFilePathList]
663        except Exception:
664            raise WSSecurityError('Loading CA certificate "%s" from file '
665                                  'list: %s' % (caFile, traceback.format_exc()))
666                   
667        # Add to stack
668        self.__appendCAX509Stack(caCertList)
669       
670    caCertFilePathList = property(fset=__setCAX509StackFromCertFileList,
671                                  doc="List of CA cert. files used for "
672                                      "verification")             
673       
674    def _get_timestampClockSkew(self):
675        return self.__timestampClockSkew
676
677    def _set_timestampClockSkew(self, val):
678        if isinstance(val, basestring):
679            self.__timestampClockSkew = float(val)
680           
681        elif isinstance(val, (float, int)):
682            self.__timestampClockSkew = val
683           
684        else:
685            raise TypeError("Expecting string, float or int type for "
686                            "timestampClockSkew attribute, got %r" % 
687                            type(val))
688       
689    timestampClockSkew = property(fset=_set_timestampClockSkew,
690                                  fget=_get_timestampClockSkew,
691                                  doc="adjust the current time calculated by "
692                                      "the number of seconds specified in "
693                                      "this parameter.  This enables "
694                                      "allowance to be made for clock skew "
695                                      "between a client and server system "
696                                      "clocks.")
697   
698    def _setBool(self, val):
699        """Convert input string, float or int to bool type
700       
701        @type val: int, float or basestring
702        @param val: input value to be converted
703        @rtype: bool
704        @return: input value converted to bool type
705        """     
706        if isinstance(val, bool):
707            return val
708       
709        elif isinstance(val, basestring):
710            val = val.lower()
711            if val not in ("true", "false"):
712                raise ValueError("String conversion failed for input: %r"%val)
713           
714            return val == "true"
715           
716        elif isinstance(val, (int, float)): 
717            return bool(val)
718        else:
719            raise TypeError("Invalid type for bool conversion: %r" % 
720                            val.__class__)
721       
722    def _get_timestampMustBeSet(self):
723        return self.__timestampMustBeSet
724
725    def _set_timestampMustBeSet(self, val):
726        self.__timestampMustBeSet = self._setBool(val)
727       
728    timestampMustBeSet = property(fset=_set_timestampMustBeSet,
729                                  fget=_get_timestampMustBeSet,
730                                  doc="Set to True to raise an exception if a "
731                                      "message to be verified doesn't have a "
732                                      "timestamp element.  Set to False to "
733                                      "log a warning message and continue "
734                                      "processing")
735   
736    def _get_createdElemMustBeSet(self):
737        return self.__createdElemMustBeSet
738
739    def _set_createdElemMustBeSet(self, val):
740        self.__createdElemMustBeSet = self._setBool(val)
741       
742    createdElemMustBeSet = property(fset=_set_createdElemMustBeSet,
743                                    fget=_get_createdElemMustBeSet,
744                                    doc="Set to True to raise an exception if "
745                                        "a message to be verified doesn't "
746                                        "have <wsu:Created/> element with its "
747                                        "timestamp element.  Set to False to "
748                                        "log a warning message and continue "
749                                        "processing")
750   
751    def _get_expiresElemMustBeSet(self):
752        return self.__expiresElemMustBeSet
753
754    def _set_expiresElemMustBeSet(self, val):
755        self.__expiresElemMustBeSet = self._setBool(val)
756       
757    expiresElemMustBeSet = property(fset=_set_expiresElemMustBeSet,
758                                    fget=_get_expiresElemMustBeSet,
759                                    doc="Set to True to raise an exception if "
760                                        "a message to be verified doesn't "
761                                        "have <wsu:Expires/> element with its "
762                                        "timestamp element.  Set to False to "
763                                        "log a warning message and continue "
764                                        "processing")           
765   
766   
767class SignatureHandlerFactory(object):
768    """Create a new signature handler from the given class name and other
769    configuration settings
770    """
771    CLASS_NAME_OPTNAME = 'className'
772   
773    @classmethod
774    def fromConfigFile(cls, filePath, sectionName='DEFAULT', prefix=''):
775        """Instantiate a new signature handler from a config file"""
776       
777        # Expand environment variables in file path
778        expandedFilePath = os.path.expandvars(filePath)
779       
780        # Add 'here' item to enable convenient path substitutions in the config
781        # file
782        defaultItems = dict(here=os.path.dirname(expandedFilePath))
783        cfg = CaseSensitiveConfigParser(defaults=defaultItems)
784       
785        readFilePaths = cfg.read(expandedFilePath)
786       
787        # Check file was read in OK
788        if len(readFilePaths) == 0:
789            raise IOError('Missing config file: "%s"' % expandedFilePath) 
790               
791        optName = prefix + cls.CLASS_NAME_OPTNAME
792        className = cfg.get(sectionName, optName)
793        signatureHandlerClass = classfactory.importClass(className, 
794                                            objectType=BaseSignatureHandler)
795       
796        return signatureHandlerClass.fromConfigFile(filePath,
797                                                    sectionName=sectionName, 
798                                                    prefix=prefix)
799   
800    @classmethod
801    def fromKeywords(cls, prefix='', **kw):
802        """Instantiate a new signature handler from keyword settings"""
803        optName = prefix + cls.CLASS_NAME_OPTNAME
804        className = kw.get(optName)
805        if className is None:
806            raise KeyError("No %r keyword setting" % cls.CLASS_NAME_OPTNAME)
807       
808        signatureHandlerClass = classfactory.importClass(className, 
809                                            objectType=BaseSignatureHandler)
810       
811        return signatureHandlerClass.fromKeywords(prefix, **kw)
812   
Note: See TracBrowser for help on using the repository browser.