source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/wssecurity/etree.py @ 4038

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/wssecurity/etree.py@4038
Revision 4038, 54.3 KB checked in by pjkersha, 12 years ago (diff)

Completed ElementTree - ZSI integration for WS-Security SignatureHandler? but still problems with ET C14N for signature generation: C14N produces superfluous xmlns declarations.

Line 
1"""WS-Security digital signature handler for ElementTree XML package
2
3NERC Data Grid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "02/07/08"
7__copyright__ = "(C) 2008 STFC & NERC"
8__license__ = \
9"""This software may be distributed under the terms of the Q Public
10License, version 1.0 or later."""
11__contact__ = "P.J.Kershaw@rl.ac.uk"
12__revision__ = '$Id: wsSecurity.py 3790 2008-04-17 12:46:30Z pjkersha $'
13
14import re
15
16# Digest and signature/verify
17from sha import sha
18from M2Crypto import X509, BIO, RSA
19import base64
20
21from elementtree import ElementTree, ElementC14N
22from StringIO import StringIO
23
24# Conditional import as this is required for the encryption
25# handler
26try:
27    # For shared key encryption
28    from Crypto.Cipher import AES, DES3
29except:
30    from warnings import warn
31    warn('Crypto.Cipher not available: EncryptionHandler disabled!',
32         RuntimeWarning)
33    class AES:
34        MODE_ECB = None
35        MODE_CBC = None
36       
37    class DES3: 
38        MODE_CBC = None
39
40import os
41
42import ZSI
43from ZSI.wstools.Namespaces import DSIG, ENCRYPTION, WSU, WSA200403, \
44                                   SOAP, SCHEMA # last included for xsi
45                                   
46from ZSI.wstools.Namespaces import WSU as _WSU
47from ZSI.wstools.Namespaces import OASIS as _OASIS
48                                 
49from ZSI.TC import ElementDeclaration,TypeDefinition
50from ZSI.generate.pyclass import pyclass_type
51
52from ZSI.wstools.Utility import DOMException
53from ZSI.wstools.Utility import NamespaceError, MessageInterface, ElementProxy
54
55# Canonicalization
56from ZSI.wstools.c14n import Canonicalize
57
58from xml.dom import Node
59from xml.xpath.Context import Context
60from xml import xpath
61
62# Enable settings from a config file
63from ndg.security.common.wssecurity import WSSecurityConfig
64
65from ndg.security.common.X509 import X509Cert, X509CertParse, X509CertRead, \
66X509Stack, X509StackParseFromDER
67
68from datetime import datetime, timedelta
69import logging
70log = logging.getLogger(__name__)
71
72
73class _ENCRYPTION(ENCRYPTION):
74    '''Derived from ENCRYPTION class to add in extra 'tripledes-cbc' - is this
75    any different to 'des-cbc'?  ENCRYPTION class implies that it is the same
76    because it's assigned to 'BLOCK_3DES' ??'''
77    BLOCK_TRIPLEDES = "http://www.w3.org/2001/04/xmlenc#tripledes-cbc"
78
79class WSU(_WSU):
80    '''Try different utility namespace for use with WebSphere'''
81    #UTILITY = "http://schemas.xmlsoap.org/ws/2003/06/utility"
82    UTILITY = \
83"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
84
85class OASIS(_OASIS):
86    # wss4j 1.5.3
87    WSSE11 = \
88    "http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd"
89    # wss4j 1.5.1
90    #WSSE11 = "http://docs.oasis-open.org/wss/2005/xx/oasis-2005xx-wss-wssecurity-secext-1.1.xsd"
91       
92def getElements(node, nameList):
93    '''DOM Helper function for getting child elements from a given node'''
94    # Avoid sub-string matches
95    nameList = isinstance(nameList, basestring) and [nameList] or nameList
96    return [n for n in node.childNodes if str(n.localName) in nameList]
97
98
99def getChildNodes(node, nodeList=None):
100    if nodeList is None:
101        nodeList = [node] 
102    return _getChildNodes(node, nodeList)
103           
104def _getChildNodes(node, nodeList):
105
106    if node.attributes is not None:
107        nodeList += node.attributes.values() 
108    nodeList += node.childNodes
109    for childNode in node.childNodes:
110        _getChildNodes(childNode, nodeList)
111    return nodeList
112
113
114class WSSecurityError(Exception):
115    """For WS-Security generic exceptions not covered by other exception
116    classes in this module"""
117
118class InvalidCertChain(WSSecurityError):   
119    """Raised from SignatureHandler.verify if the certificate submitted to
120    verify a signature is not from a known CA"""
121   
122class VerifyError(WSSecurityError):
123    """Raised from SignatureHandler.verify if an error occurs in the signature
124    verification"""
125
126class NoSignatureFound(WSSecurityError): 
127    """Incoming message to be verified was not signed"""
128   
129class TimestampError(WSSecurityError):
130    """Raised from SignatureHandler._verifyTimestamp if there is a problem with
131    the created or expiry times in an input message Timestamp"""
132   
133class InvalidSignature(WSSecurityError):
134    """Raised from verify method for an invalid signature"""
135
136class SignatureError(WSSecurityError):
137    """Flag if an error occurs during signature generation"""
138       
139class SignatureHandler(object):
140    """Class to handle signature and verification of signature with
141    WS-Security
142   
143    @cvar binSecTokValType: supported ValueTypes for BinarySecurityToken
144    element in WSSE header
145    @type binSecTokValType: dict
146   
147    @ivar addTimestamp: set to true to add a timestamp to outbound messages
148    @type addTimestamp: bool
149
150    @ivar applySignatureConfirmation: for servers - set this flag to enable the
151    signature value of a request to be recorded and included with a
152    SignatureConfirmation element in the response.
153    @type applySignatureConfirmation: bool
154   
155    @param b64EncSignatureValue: base 64 encoded signature value for the last
156    message verified
157    @type b64EncSignatureValue: string/None"""
158
159   
160    binSecTokValType = {
161        "X509PKIPathv1": OASIS.X509TOKEN.X509PKIPathv1,
162        "X509":          OASIS.X509TOKEN.X509,
163        "X509v3":        OASIS.X509TOKEN.X509+"v3"
164    }
165
166    binSecTokEncType = \
167"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
168
169    #_________________________________________________________________________
170    def __init__(self, cfgFilePath=None, cfgFileSection='DEFAULT',
171                 cfgClass=WSSecurityConfig, **kw):
172        '''
173        @reqBinSecTokValType: set the ValueType for the BinarySecurityToken
174        added to the WSSE header for a signed message.  See
175        __setReqBinSecTokValType method and binSecTokValType class variable
176        for options.  binSecTokValType determines whether signingCert or
177        signingCertChain attributes will be used.       
178        @type binSecTokValType: string
179       
180        @param verifyingCert: X.509 certificate used by verify method to
181        verify a message.  This argument can be omitted if the message to
182        be verified contains the X.509 certificate in the
183        BinarySecurityToken element.  In this case, the cert read from the
184        message will be assigned to the verifyingCert attribute.
185        @type verifyingCert: M2Crypto.X509.X509 /
186        ndg.security.common.X509.X509Cert
187       
188        @param verifyingCertFilePath: alternative input to the above, pass
189        the file path to the certificate stored in a file
190        @type verifyingCertFilePath: string
191       
192        @param signingCert: certificate associated with private key used to
193        sign a message.  The sign method will add this to the
194        BinarySecurityToken element of the WSSE header.  binSecTokValType
195        attribute must be set to 'X509' or 'X509v3' ValueTyep.  As an
196        alternative, use signingCertChain - see below...
197        @type signingCert: M2Crypto.X509.X509 /
198        ndg.security.common.X509.X509Cert
199       
200        @param signingCertFilePath: alternative input to the above, pass
201        the file path to the certificate stored in a file
202        @type signingCertFilePath: string
203       
204        @param signingCertChain: pass a list of certificates constituting a
205        chain of trust from the certificate used to verifying the signature
206        backward to the CA cert.  The CA cert need not be included.  To use
207        this option, reqBinSecTokValType must be set to the 'X509PKIPathv1'
208        ValueType
209        @type signingCertChain: list or tuple
210       
211        @param signingPriKey: private key used to be sign method to sign
212        message
213        @type signingPriKey: M2Crypto.RSA.
214       
215        @param signingPriKeyFilePath: equivalent to the above but pass
216        private key from PEM file
217        @type signingPriKeyFilePath: string
218       
219        @param signingPriKeyPwd: password protecting private key.  Set /
220        default to None if there is no password.
221        @type signingPriKeyPwd: string or None
222       
223        @param caCertDirPath: establish trust for signature verification.
224        This is a directory containing CA certificates.  These are used to
225        verify the certificate used to verify the message signature.
226        @type caCertDirPath: string
227       
228        @param caCertFilePathList: same as above except pass in a list of
229        file paths instead of a single directory name.
230        @type caCertFilePathList: list or tuple
231       
232        @param addTimestamp: set to true to add a timestamp to outbound
233        messages
234        @type addTimestamp: bool
235       
236        @param : for servers - set this flag to enable the signature value of a
237        request to be recorded and included with a SignatureConfirmation
238        element in the response.
239        @type : bool
240       
241        @param refC14nKw: dictionary of keywords to reference
242        Canonicalization.  Use 'inclusive_namespaces' keyword to set
243        inclusive_namespaces.
244        @type refC14nKw: dict
245       
246        @param signedInfoC14nKw: keywords to Signed Info Canonicalization.
247        It uses the same format as refC14nKw above.
248        @type signedInfoC14nKw: dict
249        '''
250        log.debug("SignatureHandler.__init__ ...")
251       
252        # WSSecurityConfig is the default class for reading config params but
253        # alternative derivative class may be passed in instead.
254        if not issubclass(cfgClass, WSSecurityConfig):
255            raise TypeError("%s is not a sub-class of WSSecurityConfig" % \
256                            cfgClass)
257        self.cfg = cfgClass()
258       
259        # Read parameters from config file if set
260        if cfgFilePath:
261            log.debug("SignatureHandler.__init__: Processing config file...")
262            self.cfg.read(cfgFilePath)
263            self.cfg.parse(section=cfgFileSection)
264       
265        # Also update config from keywords set
266        log.debug("SignatureHandler.__init__: setting config from keywords...")
267        self.cfg.update(kw)
268       
269       
270        self.__setReqBinSecTokValType(self.cfg['reqBinSecTokValType'])
271       
272        # Set keywords for canonicalization of SignedInfo and reference
273        # elements
274        # TODO: get rid of refC14nKw and signedInfoC14nKw options
275        if len(self.cfg.get('refC14nInclNS', [])):
276            self.__setRefC14nKw({'inclusive_namespaces':
277                                 self.cfg['refC14nInclNS']})
278        else:
279            self.__setRefC14nKw(self.cfg['refC14nKw'])
280
281   
282        if len(self.cfg.get('signedInfoC14nNS', [])):
283            self.__setSignedInfoC14nKw({'inclusive_namespaces':
284                                        self.cfg['signedInfoC14nNS']})
285        else:
286            self.__setSignedInfoC14nKw(self.cfg['signedInfoC14nKw'])
287           
288
289        self.__setVerifyingCert(self.cfg['verifyingCert'])
290        self.__setVerifyingCertFilePath(self.cfg['verifyingCertFilePath'])
291       
292        self.__setSigningCert(self.cfg['signingCert'])
293        self.__setSigningCertFilePath(self.cfg['signingCertFilePath'])
294
295        if self.cfg.get('signingCertChain'):
296            self.__setSigningCertChain(self.cfg['signingCertChain'])
297        else:
298            self.__signingCertChain = None   
299             
300        # MUST be set before __setSigningPriKeyFilePath / __setSigningPriKey
301        # are called
302        self.__setSigningPriKeyPwd(self.cfg['signingPriKeyPwd'])
303       
304        if self.cfg.get('signingPriKey'):
305            # Don't allow None for private key setting
306            self.__setSigningPriKey(self.cfg['signingPriKey'])
307           
308        self.__setSigningPriKeyFilePath(self.cfg['signingPriKeyFilePath'])
309       
310        # CA certificate(s) for verification of X.509 certificate used with
311        # signature.
312        if self.cfg.get('caCertDirPath'):
313            self.caCertDirPath = self.cfg['caCertDirPath']
314           
315        elif self.cfg.get('caCertFilePathList'):
316            self.caCertFilePathList = self.cfg['caCertFilePathList']
317           
318        self.addTimestamp = self.cfg['addTimestamp']
319        self.applySignatureConfirmation=self.cfg['applySignatureConfirmation']
320        self.b64EncSignatureValue = None
321       
322        log.debug("WSSE Config = %s" % self.cfg)
323
324               
325    #_________________________________________________________________________
326    def __setReqBinSecTokValType(self, value):
327        """Set ValueType attribute for BinarySecurityToken used in a request
328         
329        @type value: string
330        @param value: name space for BinarySecurityToken ValueType check
331        'binSecTokValType' class variable for supported types.  Input can be
332        shortened to binSecTokValType keyword if desired.
333        """
334       
335        if value in self.__class__.binSecTokValType:
336            self.__reqBinSecTokValType = self.__class__.binSecTokValType[value]
337 
338        elif value in self.__class__.binSecTokValType.values():
339            self.__reqBinSecTokValType = value
340        else:
341            raise WSSecurityError, \
342                'Request BinarySecurityToken ValueType "%s" not recognised' %\
343                value
344           
345       
346    reqBinSecTokValType = property(fset=__setReqBinSecTokValType,
347         doc="ValueType attribute for BinarySecurityToken used in request")
348       
349
350    #_________________________________________________________________________
351    def __checkC14nKw(self, kw):
352        """Check keywords for canonicalization in signing process - generic
353        method for setting keywords for reference element and SignedInfo
354        element c14n
355       
356        @type kw: dict
357        @param kw: keyword used with ZSI.wstools.Utility.Canonicalization"""
358       
359        # Check for dict/None - Set to None in order to use inclusive
360        # canonicalization
361        if kw is not None and not isinstance(kw, dict):
362            # Otherwise keywords must be a dictionary
363            raise AttributeError, \
364                "Expecting dictionary type for reference c14n keywords"
365               
366        elif kw.get('inclusive_namespaces') and \
367             not isinstance(kw['inclusive_namespaces'], list) and \
368             not isinstance(kw['inclusive_namespaces'], tuple):
369            raise AttributeError, \
370                'Expecting list or tuple of prefix names for "%s" keyword' % \
371                'inclusive_namespaces'
372       
373               
374    #_________________________________________________________________________
375    def __setRefC14nKw(self, kw):
376        """Set keywords for canonicalization of reference elements in the
377        signing process"""
378        self.__checkC14nKw(kw)                   
379        self.__refC14nKw = kw
380       
381    refC14nKw = property(fset=__setRefC14nKw,
382                         doc="Keywords for c14n of reference elements")
383       
384               
385    #_________________________________________________________________________
386    def __setSignedInfoC14nKw(self, kw):
387        """Set keywords for canonicalization of SignedInfo element in the
388        signing process"""
389        self.__checkC14nKw(kw)                   
390        self.__signedInfoC14nKw = kw
391       
392    signedInfoC14nKw = property(fset=__setSignedInfoC14nKw,
393                                doc="Keywords for c14n of SignedInfo element")
394
395
396    #_________________________________________________________________________
397    def __refC14nIsExcl(self):
398        return isinstance(self.__refC14nKw, dict) and \
399               isinstance(self.__refC14nKw.get('inclusive_namespaces'), list)
400               
401    refC14nIsExcl = property(fget=__refC14nIsExcl,
402    doc="Return True/False c14n for reference elements set to exclusive type")
403     
404
405    #_________________________________________________________________________
406    def __signedInfoC14nIsExcl(self):
407        return isinstance(self.__signedInfoC14nKw, dict) and \
408        isinstance(self.__signedInfoC14nKw.get('inclusive_namespaces'), list)
409               
410    signedInfoC14nIsExcl = property(fget=__signedInfoC14nIsExcl,
411    doc="Return True/False c14n for SignedInfo element set to exclusive type")
412   
413   
414    #_________________________________________________________________________
415    def __setCert(self, cert):
416        """filter and convert input cert to signing verifying cert set
417        property methods.  For signingCert, set to None if it is not to be
418        included in the SOAP header.  For verifyingCert, set to None if this
419        cert can be expected to be retrieved from the SOAP header of the
420        message to be verified
421       
422        @type: ndg.security.common.X509.X509Cert / M2Crypto.X509.X509 /
423        string or None
424        @param cert: X.509 certificate. 
425       
426        @rtype ndg.security.common.X509.X509Cert
427        @return X.509 certificate object"""
428       
429        if cert is None or isinstance(cert, X509Cert):
430            # ndg.security.common.X509.X509Cert type / None
431            return cert
432           
433        elif isinstance(cert, X509.X509):
434            # M2Crypto.X509.X509 type
435            return X509Cert(m2CryptoX509=cert)
436           
437        elif isinstance(cert, basestring):
438            return X509CertParse(cert)
439       
440        else:
441            raise AttributeError, "X.509 Cert. must be type: " + \
442                "ndg.security.common.X509.X509Cert, M2Crypto.X509.X509 or " +\
443                "a base64 encoded string"
444
445   
446    #_________________________________________________________________________
447    def __getVerifyingCert(self):
448        '''Return X.509 cert object corresponding to cert used to verify the
449        signature in the last call to verify
450       
451         * Cert will correspond to one used in the LATEST call to verify, on
452         the next call it will be replaced
453         * if verify hasn't been called, the cert will be None
454       
455        @rtype: M2Crypto.X509.X509
456        @return: certificate object
457        '''
458        return self.__verifyingCert
459
460
461    #_________________________________________________________________________
462    def __setVerifyingCert(self, verifyingCert):
463        "Set property method for X.509 cert. used to verify a signature"
464        self.__verifyingCert = self.__setCert(verifyingCert)
465   
466        # Reset file path as it may no longer apply
467        self.__verifyingCertFilePath = None
468       
469    verifyingCert = property(fset=__setVerifyingCert,
470                             fget=__getVerifyingCert,
471                             doc="Set X.509 Cert. for verifying signature")
472
473
474    #_________________________________________________________________________
475    def __setVerifyingCertFilePath(self, verifyingCertFilePath):
476        "Set method for Service X.509 cert. file path property"
477       
478        if isinstance(verifyingCertFilePath, basestring):
479            self.__verifyingCert = X509CertRead(verifyingCertFilePath)
480           
481        elif verifyingCertFilePath is not None:
482            raise AttributeError, \
483            "Verifying X.509 Cert. file path must be None or a valid string"
484       
485        self.__verifyingCertFilePath = verifyingCertFilePath
486       
487    verifyingCertFilePath = property(fset=__setVerifyingCertFilePath,
488                    doc="file path of X.509 Cert. for verifying signature")
489
490   
491    #_________________________________________________________________________
492    def __getSigningCert(self):
493        '''Return X.509 cert object corresponding to cert used with
494        signature
495       
496        @rtype: M2Crypto.X509.X509
497        @return: certificate object
498        '''
499        return self.__signingCert
500
501
502    #_________________________________________________________________________
503    def __setSigningCert(self, signingCert):
504        "Set property method for X.509 cert. to be included with signature"
505        self.__signingCert = self.__setCert(signingCert)
506   
507        # Reset file path as it may no longer apply
508        self.__signingCertFilePath = None
509       
510    signingCert = property(fget=__getSigningCert,
511                           fset=__setSigningCert,
512                           doc="X.509 Cert. to include signature")
513
514 
515    #_________________________________________________________________________
516    def __setSigningCertFilePath(self, signingCertFilePath):
517        "Set signature X.509 cert property method"
518       
519        if isinstance(signingCertFilePath, basestring):
520            self.__signingCert = X509CertRead(signingCertFilePath)
521           
522        elif signingCertFilePath is not None:
523            raise AttributeError, \
524                "Signature X.509 cert. file path must be a valid string"
525       
526        self.__signingCertFilePath = signingCertFilePath
527       
528       
529    signingCertFilePath = property(fset=__setSigningCertFilePath,
530                   doc="File path X.509 cert. to include with signed message")
531
532   
533    #_________________________________________________________________________
534    def __setSigningCertChain(self, signingCertChain):
535        '''Signature set-up with "X509PKIPathv1" BinarySecurityToken
536        ValueType.  Use an X.509 Stack to store certificates that make up a
537        chain of trust to certificate used to verify a signature
538       
539        @type signingCertChain: list or tuple of M2Crypto.X509.X509Cert or
540        ndg.security.common.X509.X509Cert types.
541        @param signingCertChain: list of certificate objects making up the
542        chain of trust.  The last certificate is the one associated with the
543        private key used to sign the message.'''
544       
545        if not isinstance(signingCertChain, list) and \
546           not isinstance(signingCertChain, tuple):
547            raise WSSecurityError, \
548                        'Expecting a list or tuple for "signingCertChain"'
549       
550        self.__signingCertChain = X509Stack()
551           
552        for cert in signingCertChain:
553            self.__signingCertChain.push(cert)
554           
555    signingCertChain = property(fset=__setSigningCertChain,
556               doc="Cert.s in chain of trust to cert. used to verify msg.")
557
558 
559    #_________________________________________________________________________
560    def __setSigningPriKeyPwd(self, signingPriKeyPwd):
561        "Set method for private key file password used to sign message"
562        if signingPriKeyPwd is not None and \
563           not isinstance(signingPriKeyPwd, basestring):
564            raise AttributeError, \
565                "Signing private key password must be None or a valid string"
566       
567        self.__signingPriKeyPwd = signingPriKeyPwd
568       
569    signingPriKeyPwd = property(fset=__setSigningPriKeyPwd,
570             doc="Password protecting private key file used to sign message")
571
572 
573    #_________________________________________________________________________
574    def __setSigningPriKey(self, signingPriKey):
575        """Set method for client private key
576       
577        Nb. if input is a string, signingPriKeyPwd will need to be set if
578        the key is password protected.
579       
580        @type signingPriKey: M2Crypto.RSA.RSA / string
581        @param signingPriKey: private key used to sign message"""
582       
583        if isinstance(signingPriKey, basestring):
584            pwdCallback = lambda *ar, **kw: self.__signingPriKeyPwd
585            self.__signingPriKey = RSA.load_key_string(signingPriKey,
586                                                       callback=pwdCallback)
587
588        elif isinstance(signingPriKey, RSA.RSA):
589            self.__signingPriKey = signingPriKey
590                   
591        else:
592            raise AttributeError, "Signing private key must be a valid " + \
593                                  "M2Crypto.RSA.RSA type or a string"
594               
595    signingPriKey = property(fset=__setSigningPriKey,
596                             doc="Private key used to sign outbound message")
597
598 
599    #_________________________________________________________________________
600    def __setSigningPriKeyFilePath(self, signingPriKeyFilePath):
601        """Set method for client private key file path
602       
603        signingPriKeyPwd MUST be set prior to a call to this method"""
604        if isinstance(signingPriKeyFilePath, basestring):                           
605            try:
606                # Read Private key to sign with   
607                priKeyFile = BIO.File(open(signingPriKeyFilePath)) 
608                pwdCallback = lambda *ar, **kw: self.__signingPriKeyPwd                                           
609                self.__signingPriKey = RSA.load_key_bio(priKeyFile, 
610                                                        callback=pwdCallback)           
611            except Exception, e:
612                raise AttributeError, \
613                                "Setting private key for signature: %s" % e
614       
615        elif signingPriKeyFilePath is not None:
616            raise AttributeError, \
617                        "Private key file path must be a valid string or None"
618       
619        self.__signingPriKeyFilePath = signingPriKeyFilePath
620       
621    signingPriKeyFilePath = property(fset=__setSigningPriKeyFilePath,
622                      doc="File path for private key used to sign message")
623
624    def __caCertIsSet(self):
625        '''Check for CA certificate set (X.509 Stack has been created)'''
626        return hasattr(self, '_SignatureHandler__caX509Stack')
627   
628    caCertIsSet = property(fget=__caCertIsSet,
629           doc='Check for CA certificate set (X.509 Stack has been created)')
630   
631    #_________________________________________________________________________
632    def __appendCAX509Stack(self, caCertList):
633        '''Store CA certificates in an X.509 Stack
634       
635        @param caCertList: list or tuple
636        @type caCertList: M2Crypto.X509.X509 certificate objects'''
637       
638        if not self.caCertIsSet:
639            self.__caX509Stack = X509Stack()
640           
641        for cert in caCertList:
642            self.__caX509Stack.push(cert)
643
644
645    #_________________________________________________________________________
646    def __setCAX509StackFromDir(self, caCertDir):
647        '''Read CA certificates from directory and add them to the X.509
648        stack
649       
650        @param caCertDir: string
651        @type caCertDir: directory from which to read CA certificate files'''
652       
653        # Mimic OpenSSL -CApath option which expects directory of CA files
654        # of form <Hash cert subject name>.0
655        reg = re.compile('\d+\.0')
656        try:
657            caCertList = [X509CertRead(caFile) \
658                          for caFile in os.listdir(caCertDir) \
659                          if reg.match(caFile)]
660        except Exception, e:
661            raise WSSecurityError, \
662                'Loading CA certificate "%s" from CA directory: %s' % \
663                                                        (caFile, str(e))
664                   
665        # Add to stack
666        self.__appendCAX509Stack(caCertList)
667       
668    caCertDirPath = property(fset=__setCAX509StackFromDir,
669                      doc="Dir. containing CA cert.s used for verification")
670
671
672    #_________________________________________________________________________
673    def __setCAX509StackFromCertFileList(self, caCertFilePathList):
674        '''Read CA certificates from file and add them to the X.509
675        stack
676       
677        @type caCertFilePathList: list or tuple
678        @param caCertFilePathList: list of file paths for CA certificates to
679        be used to verify certificate used to sign message'''
680       
681        if not isinstance(caCertFilePathList, list) and \
682           not isinstance(caCertFilePathList, tuple):
683            raise WSSecurityError, \
684                        'Expecting a list or tuple for "caCertFilePathList"'
685
686        # Mimic OpenSSL -CApath option which expects directory of CA files
687        # of form <Hash cert subject name>.0
688        try:
689            caCertList = [X509CertRead(caFile) \
690                          for caFile in caCertFilePathList]
691        except Exception, e:
692            raise WSSecurityError, \
693                    'Loading CA certificate "%s" from file list: %s' % \
694                                                        (caFile, str(e))
695                   
696        # Add to stack
697        self.__appendCAX509Stack(caCertList)
698       
699    caCertFilePathList = property(fset=__setCAX509StackFromCertFileList,
700                      doc="List of CA cert. files used for verification")
701               
702
703    def _applySignatureConfirmation(self, wsseElem):
704        '''Add SignatureConfirmation element - as specified in WS-Security 1.1
705        - to outbound message on receipt of a signed message from a client
706       
707        This has been added in through tests vs. Apache Axis Rampart client
708       
709        @type wsseElem:
710        @param wsseElem: wsse:Security element'''
711        if self.b64EncSignatureValue is None:
712            log.info(\
713"SignatureConfirmation element requested but no request signature was cached")
714            return
715       
716        sigConfirmElem = ElementTree.Element("{%s}%s" % (OASIS.WSSE11, 
717                                                      'SignatureConfirmation'))
718        wsseElem.append(sigConfirmElem)
719       
720        # Add ID so that the element can be included in the signature
721        sigConfirmElem.set('{%s}Id' % WSU.UTILITY, "signatureConfirmation")
722
723        # Add ID so that the element can be included in the signature
724        # Following line is a hack to avoid appearance of #x when serialising
725        # \n chars
726        # TODO: Fix #x problem with sig value?
727        b64EncSignatureValue = ''.join(self.b64EncSignatureValue.split('\n'))
728        sigConfirmElem.set('Value', b64EncSignatureValue)
729       
730       
731    def _addTimeStamp(self, wsseElem, elapsedSec=60*5):
732        '''Add a timestamp to wsse:Security section of message to be signed
733        e.g.
734            <wsu:Timestamp wsu:Id="timestamp">
735               <wsu:Created>2008-03-25T14:40:37.319Z</wsu:Created>
736               <wsu:Expires>2008-03-25T14:45:37.319Z</wsu:Expires>
737            </wsu:Timestamp>
738       
739        @type wsseElem:
740        @param wsseElem: wsse:Security element
741        @type elapsedSec: int   
742        @param elapsedSec: time interval in seconds between Created and Expires
743        time stamp values
744        '''
745        # Nb. wsu ns declaration is in the SOAP header elem
746        timestampElem = ElementTree.Element("{%s}%s"%(WSU.UTILITY,'Timestamp'))
747        wsseElem.append(timestampElem)
748       
749        # Add ID so that the timestamp element can be included in the signature
750        timestampElem.set('{%s}Id' % WSU.UTILITY, "timestamp")
751       
752        # Value type can be any be any one of those supported via
753        # binSecTokValType
754        createdElem = ElementTree.Element("{%s}%s" % (WSU.UTILITY,'Created'))
755        timestampElem.append(createdElem)
756       
757        dtCreatedTime = datetime.utcnow()
758        createdElem.text = dtCreatedTime.isoformat('T') + 'Z'
759       
760        dtExpiryTime = dtCreatedTime + timedelta(seconds=elapsedSec)
761        expiresElem = ElementTree.Element("{%s}%s" % (WSU.UTILITY, 'Expires'))
762        timestampElem.append(expiresElem)
763       
764        expiresElem.text = dtExpiryTime.isoformat('T') + 'Z'
765       
766
767    def _verifyTimeStamp(self, parsedSOAP, ctxt):
768        """Call from verify to check timestamp if found. 
769       
770        TODO: refactor input args - maybe these should by object attributes
771       
772        @type parsedSOAP: ZSI.parse.ParsedSoap
773        @param parsedSOAP: object contain parsed SOAP message received from
774        sender
775        @type ctxt:
776        @param ctxt: XPath context object"""
777
778        try:
779            timestampNode = xpath.Evaluate('//wsse:Timestamp',
780                                           contextNode=parsedSOAP.dom,
781                                           context=ctxt)[0]
782        except:
783            log.warning("Verifying message - No timestamp element found")
784            return
785       
786        # Time now
787        dtNow = datetime.utcnow()
788       
789        createdNode = timestampNode.getElementsByTagName("Created")
790       
791        # Workaround for fractions of second
792        try:
793            [createdDateTime, createdSecFraction]=createdNode.nodeValue.split()
794        except ValueError, e:
795            raise ValueError("Parsing timestamp Created element: %s" % e)
796       
797        dtCreated = datetime.strptime(createdDateTime, '%Y-%m-%dT%H:%M:%S')
798        dtCreated += timedelta(seconds=int(createdSecFraction))
799        if dtCreated >= dtNow:
800            raise TimestampError(\
801        "Timestamp created time %s is equal to or after the current time %s" %\
802                (dtCreated, dtNow))
803       
804        expiresNode = timestampNode.getElementsByTagName("Expires")
805        if expiresNode is None:
806            log.warning(\
807                "Verifying message - No Expires element found in Timestamp")
808            return
809
810        try:
811            [expiresDateTime, expiresSecFraction]=expiresNode.nodeValue.split()
812        except ValueError, e:
813            raise ValueError("Parsing timestamp Expires element: %s" % e)
814       
815        dtCreated = datetime.strptime(expiresDateTime, '%Y-%m-%dT%H:%M:%S')
816        dtCreated += timedelta(seconds=int(createdSecFraction))
817        if dtExpiry > dtNow:
818            raise TimestampError(\
819                "Timestamp expiry time %s is after the current time %s" % \
820                (dtCreated, dtNow))
821           
822                   
823    #_________________________________________________________________________
824    def sign(self, soapWriter):
825        '''Sign the message body and binary security token of a SOAP message
826       
827        @type soapWriter: ZSI.writer.SoapWriter
828        @param soapWriter: ZSI object to write SOAP message
829        '''
830       
831        # Namespaces for XPath searches
832        processorNSs = \
833        {
834            'ds':       DSIG.BASE, 
835            'wsu':      WSU.UTILITY, 
836            'wsse':     OASIS.WSSE, 
837            'SOAP-ENV': SOAP.ENV
838        }
839
840        # Add X.509 cert as binary security token
841        # TODO: sub encodestring with b64encode?
842        if self.__reqBinSecTokValType==self.binSecTokValType['X509PKIPathv1']:
843            binSecTokVal = base64.encodestring(self.__signingCertChain.asDER())
844        else:
845            # Assume X.509 / X.509 vers 3
846            binSecTokVal = base64.encodestring(self.__signingCert.asDER())
847
848        self._soapEnvElem = soapWriter.dom._elem
849        soapHdrElem = soapWriter._header._elem
850        soapBodyElem = soapWriter.body._elem
851       
852        self._soapEnvElem.set("xmlns:%s" % 'ds', DSIG.BASE)
853        #self._soapEnvElem.set("xmlns:%s" % 'wsse', OASIS.WSSE)
854       
855        soapHdrElem.set("xmlns:%s" % 'wsse', OASIS.WSSE)
856        soapHdrElem.set("xmlns:%s" % 'wsse11', OASIS.WSSE11)
857        soapHdrElem.set("xmlns:%s" % 'wsu', WSU.UTILITY)
858        soapHdrElem.set("xmlns:%s" % 'ds', DSIG.BASE)
859       
860        try:
861            refC14nPfxSet = len(self.__refC14nKw['inclusive_namespaces']) > 0
862        except KeyError:
863            refC14nPfxSet = False
864
865        try:
866            signedInfoC14nPfxSet = \
867                len(self.__signedInfoC14nKw['inclusive_namespaces']) > 0
868        except KeyError:
869            signedInfoC14nPfxSet = False
870               
871        if refC14nPfxSet:
872            soapHdrElem.set("xmlns:%s" % 'ec', DSIG.C14N_EXCL)
873           
874           
875        # Check <wsse:security> isn't already present in header
876        wsseElems = self._soapEnvElem.findall('.//wsse:security',
877                                              namespaces=processorNSs)
878        if len(wsseElems) > 1:
879            raise SignatureError('wsse:Security element is already present')
880       
881        # Add WSSE element
882        wsseElem = ElementTree.Element("{%s}%s" % (OASIS.WSSE, 'Security'))
883        soapHdrElem.append(wsseElem)
884       
885        wsseElem.set("xmlns:%s" % 'wsse', OASIS.WSSE)
886       
887        # Recipient MUST parse and check this signature
888        wsseElem.set('SOAP-ENV:mustUnderstand', "1")
889       
890        # Binary Security Token element will contain the X.509 cert
891        # corresponding to the private key used to sing the message
892        binSecTokElem = ElementTree.Element("{%s}%s" % (OASIS.WSSE, 
893                                                        'BinarySecurityToken'))
894        wsseElem.append(binSecTokElem)
895       
896        # Value type can be any be any one of those supported via
897        # binSecTokValType
898        binSecTokElem.set('ValueType', self.__reqBinSecTokValType)
899        binSecTokElem.set('EncodingType', SignatureHandler.binSecTokEncType)
900       
901        # Add ID so that the binary token can be included in the signature
902        binSecTokElem.set('{%s}Id' % WSU.UTILITY, "binaryToken")
903       
904        binSecTokElem.text = binSecTokVal
905
906
907        # Timestamp
908        if self.addTimestamp:
909            self._addTimeStamp(wsseElem)
910           
911        # Signature Confirmation
912        if self.applySignatureConfirmation: 
913            self._applySignatureConfirmation(wsseElem)
914       
915        # Signature
916        signatureElem = ElementTree.Element("{%s}%s" % (DSIG.BASE,'Signature'))
917        wsseElem.append(signatureElem)
918       
919        # Signature - Signed Info
920        signedInfoElem = ElementTree.Element("{%s}%s"%(DSIG.BASE,'SignedInfo'))
921        signatureElem.append(signedInfoElem)
922       
923        # Signed Info - Canonicalization method
924        c14nMethodElem = ElementTree.Element("{%s}%s" % (DSIG.BASE,
925                                                    'CanonicalizationMethod'))
926        signedInfoElem.append(c14nMethodElem)
927       
928        # Set based on 'signedInfoIsExcl' property
929        c14nAlgOpt = (DSIG.C14N, DSIG.C14N_EXCL)
930        signedInfoC14nAlg = c14nAlgOpt[int(self.signedInfoC14nIsExcl)]
931       
932        c14nMethodElem.set('Algorithm', signedInfoC14nAlg)
933       
934        if signedInfoC14nPfxSet:
935            c14nInclNamespacesElem = ElementTree.Element("{%s}%s" % \
936                                                    (signedInfoC14nAlg,
937                                                    'InclusiveNamespaces'))
938            c14nMethodElem.append(c14nInclNamespacesElem)
939           
940            pfxList = ' '.join(self.__signedInfoC14nKw['inclusive_namespaces'])
941            c14nInclNamespacesElem.set('PrefixList', pfxList)
942
943       
944        # Signed Info - Signature method
945        sigMethodElem = ElementTree.Element("{%s}%s" % \
946                                            (DSIG.BASE, 'SignatureMethod'))
947        signedInfoElem.append(sigMethodElem)
948        sigMethodElem.set('Algorithm', DSIG.SIG_RSA_SHA1)
949       
950        # Signature - Signature value
951        signatureValueElem = ElementTree.Element("{%s}%s" % (DSIG.BASE, 
952                                                             'SignatureValue'))
953        signatureElem.append(signatureValueElem)
954       
955        # Key Info
956        KeyInfoElem = ElementTree.Element("{%s}%s" % (DSIG.BASE, 'KeyInfo'))
957        signatureElem.append(KeyInfoElem)
958       
959        secTokRefElem = ElementTree.Element("{%s}%s" % (OASIS.WSSE, 
960                                                    'SecurityTokenReference'))
961        KeyInfoElem.append(secTokRefElem)
962       
963        # Reference back to the binary token included earlier
964        wsseRefElem = ElementTree.Element("{%s}%s" % (OASIS.WSSE, 'Reference'))
965        secTokRefElem.append(wsseRefElem)
966       
967        wsseRefElem.set('URI', "#binaryToken")
968       
969        # Add Reference to body so that it can be included in the signature
970        soapBodyElem.set('xmlns:wsu', WSU.UTILITY)
971        soapBodyElem.set('{%s}Id' % WSU.UTILITY, 'body')
972
973
974        # Set Reference Canonicalization algorithm based on 'refC14nIsExcl'
975        # property
976        refC14nAlg = c14nAlgOpt[self.refC14nIsExcl]
977       
978        # Pick up all the wsu:Id tagged elements set in the above
979        refElems = self._soapEnvElem.findall('.//*[@wsu:Id]',
980                                             namespaces=processorNSs)
981
982        # 1) Reference Generation
983        #
984        # Find references
985        for refElem in refElems:
986           
987            # Set URI attribute to point to reference to be signed
988            uri = '#' + refElem.get('{%s}%s' % (WSU.UTILITY, 'Id'))
989           
990            # Canonicalize reference
991#            refC14n = self.canonicalize(subset=refElem,
992#                                        exclusive=self.refC14nIsExcl,
993#                                        **self.__refC14nKw)
994            refC14n = soapWriter.dom.canonicalize(subset=refElem,
995                                                  exclusive=self.refC14nIsExcl,
996                                                  **self.__refC14nKw)
997            log.debug('Canonicalisation for URI "%s": %s', uri, refC14n)
998            import pdb;pdb.set_trace()
999           
1000            # Calculate digest for reference and base 64 encode
1001            #
1002            # Nb. encodestring adds a trailing newline char
1003            # Use b64encode instead - encodestring puts in newline chars at
1004            # 76 char intervals
1005            #digestValue = base64.encodestring(sha(refC14n).digest()).strip()
1006            digestValue = base64.b64encode(sha(refC14n).digest())
1007           
1008            # Add a new reference element to SignedInfo
1009            signedInfoRefElem = ElementTree.Element("{%s}Reference"%DSIG.BASE)
1010            signedInfoElem.append(signedInfoRefElem)
1011            signedInfoRefElem.set('URI', uri)
1012           
1013            # Use ds:Transforms or wsse:TransformationParameters?
1014            transformsElem = ElementTree.Element("{%s}Transforms" % DSIG.BASE)
1015            signedInfoRefElem.append(transformsElem)
1016           
1017            transformElem = ElementTree.Element("{%s}Transform" % DSIG.BASE)
1018            transformsElem.append(transformElem)
1019
1020            # Set Canonicalization algorithm type
1021            transformElem.set('Algorithm', refC14nAlg)
1022            if refC14nPfxSet:
1023                # Exclusive C14N requires inclusive namespace elements
1024                inclNamespacesElem = transformElem.createAppendElement(\
1025                                                       refC14nAlg,
1026                                                       'InclusiveNamespaces')
1027                refPfxList = ' '.join(self.__refC14nKw['inclusive_namespaces'])
1028                inclNamespacesElem.set('PrefixList', refPfxList)
1029           
1030            # Digest Method
1031            digestMethodElem = ElementTree.Element("{%s}%s" % (DSIG.BASE, 
1032                                                               'DigestMethod'))
1033            signedInfoRefElem.append(digestMethodElem)
1034           
1035            digestMethodElem.set('Algorithm', DSIG.DIGEST_SHA1)
1036           
1037            # Digest Value
1038            digestValueElem = ElementTree.Element("{%s}%s" % (DSIG.BASE, 
1039                                                              'DigestValue'))
1040            signedInfoRefElem.append(digestValueElem)
1041            digestValueElem.text = digestValue
1042   
1043        # 2) Signature Generation
1044        #       
1045        # Canonicalize the signedInfo node
1046        c14nSignedInfo = soapWriter.dom.canonicalize(subset=signedInfoElem,
1047                                           exclusive=self.signedInfoC14nIsExcl,
1048                                           **self.__signedInfoC14nKw)
1049        log.debug('Canonicalisation for <ds:signedInfo>: %s', c14nSignedInfo)
1050       
1051        # Calculate digest of SignedInfo
1052        signedInfoDigestValue = sha(c14nSignedInfo).digest()
1053       
1054        # Sign using the private key and base 64 encode the result
1055        signatureValue = self.__signingPriKey.sign(signedInfoDigestValue)
1056       
1057        # encodestring puts newline markers at 76 char intervals otherwise no
1058        # difference
1059        # b64EncSignatureValue = base64.encodestring(signatureValue).strip()
1060        b64EncSignatureValue = base64.b64encode(signatureValue)
1061
1062        # Add to <SignatureValue>
1063        signatureValueElem.text = b64EncSignatureValue
1064        log.debug("Signature generation complete")
1065
1066
1067    def verify(self, parsedSOAP):
1068        """Verify signature
1069       
1070        @type parsedSOAP: ZSI.parse.ParsedSoap
1071        @param parsedSOAP: object contain parsed SOAP message received from
1072        sender"""
1073
1074        processorNSs = \
1075        {
1076            'ds':       DSIG.BASE, 
1077            'wsu':      WSU.UTILITY, 
1078            'wsse':     OASIS.WSSE, 
1079            'soapenv':  SOAP.ENV
1080        }
1081       
1082        self._soapEnvElem = parsedSOAP.dom._elem
1083#        soapHdrElem = parsedSOAP._header._elem
1084#        soapBodyElem = parsedSOAP.body._elem
1085
1086        signatureElems = self._soapEnvElem.findall('.//ds:Signature', 
1087                                                   namespaces=processorNSs)       
1088        if len(signatureElems) > 1:
1089            raise VerifyError('Multiple <ds:Signature/> elements found')
1090       
1091        try:
1092            signatureElems = signatureElems[0]
1093        except:
1094            # Message wasn't signed - may be possible if peer raised a SOAP
1095            # fault
1096            raise NoSignatureFound("Input message wasn't signed!")
1097
1098       
1099        # Two stage process: reference validation followed by signature
1100        # validation
1101       
1102        # 1) Reference Validation
1103       
1104        # Check for canonicalization set via ds:CanonicalizationMethod -
1105        # Use this later as a back up in case no Canonicalization was set in
1106        # the transforms elements
1107        c14nMethodElem = self._soapEnvElem.find('.//ds:CanonicalizationMethod', 
1108                                                namespaces=processorNSs)
1109        if c14nMethodElem is None:
1110            raise VerifyError("No <ds:Canonicalization/> element found") 
1111             
1112        refElems = self._soapEnvElem.findall('.//ds:Reference', 
1113                                             namespaces=processorNSs)
1114        for refElem in refElems:
1115            # Get the URI for the reference
1116            refURI = refElem.get('URI')
1117                         
1118            transformElem = refElem.find('ds:Transforms/ds:Transform',
1119                                         namespaces=processorNSs)
1120            if transformElem is None:
1121                raise VerifyError(
1122            'Failed to get transform algorithm for <ds:Reference URI="%s">'%\
1123                        refURI)
1124               
1125            refAlgorithm = transformElem.get("Algorithm")
1126               
1127            # Add extra keyword for Exclusive canonicalization method
1128            refC14nKw = dict(exclusive=refAlgorithm == DSIG.C14N_EXCL)
1129            if refC14nKw['exclusive']:
1130                # Check for no inclusive namespaces set
1131                inclusiveNSElem = transformElem.find("InclusiveNamespaces",
1132                                                namespaces=processorNSs)                   
1133                if inclusiveNSElem is not None:
1134                    pfxListTxt = inclusiveNSElem.get('PrefixList')
1135                    if pfxListTxt is None:
1136                        raise VerifyError('Empty InclusiveNamespaces list for'\
1137                                          ' <ds:Reference URI="%s">' % refURI)
1138                                                 
1139                    refC14nKw['inclusive_namespaces'] = pfxListTxt.split()
1140                else:
1141                    # Set to empty list to ensure Exclusive C14N is set for
1142                    # Canonicalize call
1143                    refC14nKw['inclusive_namespaces'] = []
1144       
1145            # Canonicalize the reference data and calculate the digest
1146            if refURI[0] != "#":
1147                raise VerifyError('Expecting # identifier for Reference URI' \
1148                                  ' "%s"' % refURI)
1149                   
1150            # XPath reference
1151            uriXPath = './/*[@wsu:Id="%s"]' % refURI[1:]
1152            uriElem=self._soapEnvElem.findall(uriXPath,namespaces=processorNSs)
1153            if len(uriElem) > 1:
1154                raise VerifyError("Multiple elements matching '%s' " % \
1155                                  uriXPath + \
1156                                  "search path: %s" % uriElem)
1157               
1158            refC14n=parsedSOAP.dom.canonicalize(subset=uriElem[0],**refC14nKw)
1159           
1160            # encodestring adds line delimiters at 76 char intervals - avoid
1161            # and use b64encode instead           
1162            calculatedDigestValue = base64.b64encode(sha(refC14n).digest())
1163           
1164            # Extract the digest value that was stored in the SOAP request         
1165            digestElem = refElem.find('ds:DigestValue',namespaces=processorNSs)
1166            if digestElem is None:
1167                raise VerifyError('Failed to get digestValue for ' \
1168                                  '<ds:Reference URI="%s">' % refURI)
1169               
1170            # Need to check here for value split into separate lines?
1171            retrievedDigestValue = str(digestElem.text).strip()   
1172           
1173            # Reference validates if the two digest values are the same
1174            if retrievedDigestValue != calculatedDigestValue:
1175                log.error("Digest values don't match")
1176                log.error('Canonicalisation for URI: "%s": %s' % \
1177                          (refURI, refC14n))
1178                import pdb;pdb.set_trace()
1179                raise InvalidSignature('Digest Values do not match for URI:' \
1180                                       ' "%s"' % refURI)
1181           
1182            log.debug("Verified canonicalization for element %s" % refURI[1:])
1183           
1184        else:
1185            raise VerifyError("No <ds:Reference/> elements found") 
1186
1187
1188        # 2) Signature Validation
1189        signedInfoElem = self._soapEnvElem.find('.//ds:SignedInfo',
1190                                                namespaces=processorNSs)
1191        if signedInfoElem is None:
1192            raise VerifyError("No <ds:signedInfo/> section found")
1193       
1194        # Get algorithm used for canonicalization of the SignedInfo
1195        # element.  Nb. This is NOT necessarily the same as that used to
1196        # canonicalize the reference elements checked above!
1197        signedInfoC14nAlg = c14nMethodElem.get("Algorithm")
1198        if signedInfoC14nAlg is None:
1199            raise VerifyError('No Algorithm attribute set for <signedInfo/>' \
1200                              ' section')
1201           
1202        signedInfoC14nKw = dict(exclusive=signedInfoC14nAlg == DSIG.C14N_EXCL)
1203        if signedInfoC14nKw['exclusive']:
1204
1205            # Check for no inclusive namespaces set
1206            inclusiveNSElem = c14nMethodElem.find("InclusiveNamespaces",
1207                                                  namespaces=processorNSs)                   
1208            if inclusiveNSElem is not None:
1209                pfxListTxt = inclusiveNSElem.get('PrefixList')
1210                if pfxListTxt is None:
1211                    raise VerifyError('Empty InclusiveNamespaces list for'\
1212                                      ' <ds:Reference URI="%s">' % refURI)
1213                                             
1214                signedInfoC14nKw['inclusive_namespaces'] = pfxListTxt.split()
1215            else:
1216                # Set to empty list to ensure Exclusive C14N is set for
1217                # Canonicalize call
1218                signedInfoC14nKw['inclusive_namespaces'] = []
1219
1220        # Canonicalize the SignedInfo node and take digest
1221        c14nSignedInfo = parsedSOAP.dom.canonicalize(subset=signedInfoElem, 
1222                                                     **signedInfoC14nKw)                     
1223        signedInfoDigestValue = sha(c14nSignedInfo).digest()
1224       
1225        # Get the signature value in order to check against the digest just
1226        # calculated
1227        signatureValueElem = self._soapEnvElem.find('//ds:SignatureValue',
1228                                                    namespaces=processorNSs)
1229
1230        # Remove base 64 encoding
1231        b64EncSignatureValue = signatureValueElem.text
1232        signatureValue = base64.decodestring(b64EncSignatureValue)
1233
1234        # Cache Signature Value here so that a response can include it
1235        if self.applySignatureConfirmation:
1236            # re-encode string to avoid possible problems with interpretation
1237            # of line breaks
1238            self.b64EncSignatureValue = b64EncSignatureValue
1239        else:
1240            self.b64EncSignatureValue = None
1241         
1242        # Look for X.509 Cert in wsse:BinarySecurityToken element -
1243        # Signature may not have included the Binary Security Token in
1244        # which case the verifying cert will need to have been set
1245        # elsewhere
1246        binSecTokElem = self._soapEnvElem.find('.//wsse:BinarySecurityToken',
1247                                               namespaces=processorNSs)       
1248        if binSecTokElem is not None:
1249            x509CertTxt = str(binSecTokElem.text)
1250           
1251            valueType = binSecTokElem.get("ValueType")
1252            if valueType in (SignatureHandler.binSecTokValType['X509v3'],
1253                             SignatureHandler.binSecTokValType['X509']):
1254                # Remove base 64 encoding
1255                derString = base64.decodestring(x509CertTxt)
1256               
1257                # Load from DER format into M2Crypto.X509
1258                m2X509Cert = X509.load_cert_string(derString,
1259                                                   format=X509.FORMAT_DER)
1260                self.__setVerifyingCert(m2X509Cert)
1261               
1262                x509Stack = X509Stack()
1263
1264            elif valueType == \
1265                SignatureHandler.binSecTokValType['X509PKIPathv1']:
1266               
1267                derString = base64.decodestring(x509CertTxt)
1268                x509Stack = X509StackParseFromDER(derString)
1269               
1270                # TODO: Check ordering - is the last off the stack the
1271                # one to use to verify the message?
1272                self.__verifyingCert = x509Stack[-1]
1273            else:
1274                raise WSSecurityError('BinarySecurityToken ValueType ' \
1275                    'attribute is not recognised: "%s"' % valueType)
1276
1277        if self.__verifyingCert is None:
1278            raise VerifyError("No certificate set for verification of the " \
1279                              "signature")
1280       
1281        # Extract RSA public key from the cert
1282        rsaPubKey = self.__verifyingCert.pubKey.get_rsa()
1283
1284        # Apply the signature verification
1285        try:
1286            verify = rsaPubKey.verify(signedInfoDigestValue, signatureValue)
1287        except RSA.RSAError, e:
1288            raise VerifyError("Error in Signature: " + str(e))
1289       
1290        if not verify:
1291            raise InvalidSignature("Invalid signature")
1292       
1293        # Verify chain of trust
1294        x509Stack.verifyCertChain(x509Cert2Verify=self.__verifyingCert,
1295                                  caX509Stack=self.__caX509Stack)
1296       
1297        self._verifyTimeStamp(parsedSOAP, ctxt) 
1298        log.info("Signature OK")       
1299
1300
1301    def canonicalize(self, **kw):
1302        '''ElementTree based Canonicalization - See ElementC14N for keyword
1303        info'''
1304        f = StringIO()
1305        ElementC14N.write(ElementC14N.build_scoped_tree(self._soapEnvElem), 
1306                          f, 
1307                          **kw)
1308        return f.getvalue()
Note: See TracBrowser for help on using the repository browser.