source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/wssecurity/signaturehandler/foursuite.py @ 5359

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/wssecurity/signaturehandler/foursuite.py@5359
Revision 5359, 30.2 KB checked in by pjkersha, 12 years ago (diff)
  • ndg.security.test.unit.wssecurity.foursuite: WS-Security signature handler - added a unit test case for a client not setting a timestamp in the input message and the server configured to catch it and raise an exception (using timestampMustBeSet config param)
  • Property svn:executable set to *
Line 
1"""4Suite XML based WS-Security digital signature handler
2
3NERC DataGrid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "27/02/09"
7__copyright__ = "(C) 2009 Science and Technology Facilities Council"
8__license__ = "BSD - see LICENSE file in top-level directory"
9__contact__ = "Philip.Kershaw@stfc.ac.uk"
10__revision__ = '$Id: $'
11import logging
12log = logging.getLogger(__name__)
13
14import os
15import re
16from cStringIO import StringIO
17
18# Digest and signature/verify
19from sha import sha
20from M2Crypto import X509, BIO, RSA
21import base64
22from datetime import datetime, timedelta
23
24# Workaround for lack of datetime.strptime in Python < 2.5
25if hasattr(datetime, 'strptime'):
26    _strptime = datetime.strptime
27else:
28    from time import strptime
29    _strptime = lambda datetimeStr, format: datetime(*(strptime(datetimeStr, 
30                                                                format)[0:6]))
31   
32from ZSI.wstools.Namespaces import DSIG, SOAP
33
34# Canonicalization
35from Ft.Xml.Domlette import CanonicalPrint
36
37from ndg.security.common.wssecurity import WSSecurityError
38from ndg.security.common.wssecurity.signaturehandler import _WSU, OASIS, \
39    BaseSignatureHandler, NoSignatureFound, InvalidSignature, TimestampError, \
40    MessageExpired, VerifyError, SignatureError
41
42from ndg.security.common.X509 import X509Cert, X509CertParse, X509CertRead, \
43    X509Stack, X509StackParseFromDER
44
45
46def getElements(node, nameList):
47    '''DOM Helper function for getting child elements from a given node'''
48    # Avoid sub-string matches
49    nameList = isinstance(nameList, basestring) and [nameList] or nameList
50    return [n for n in node.childNodes if str(n.localName) in nameList]
51
52
53class SignatureHandler(BaseSignatureHandler):
54    """Class to handle signature and verification of signature with
55    WS-Security
56    """
57
58    def _applySignatureConfirmation(self, wsseElem):
59        '''Add SignatureConfirmation element - as specified in WS-Security 1.1
60        - to outbound message on receipt of a signed message from a client
61       
62        This has been added in through tests vs. Apache Axis Rampart client
63       
64        @type wsseElem:
65        @param wsseElem: wsse:Security element'''
66        if self.b64EncSignatureValue is None:
67            log.info("SignatureConfirmation element requested but no request "
68                     "signature was cached")
69            return
70       
71        sigConfirmElem = wsseElem.createAppendElement(OASIS.WSSE11, 
72                                                      'SignatureConfirmation')
73       
74        # Add ID so that the element can be included in the signature
75        sigConfirmElem.setAttributeNS(_WSU.UTILITY, 
76                                      'Id', 
77                                      'signatureConfirmation')
78       
79        # Add ID so that the element can be included in the signature
80        # Following line is a hck to avoid appearance of #x when serialising \n
81        # chars TODO: why is this happening??
82        b64EncSignatureValue = ''.join(self.b64EncSignatureValue.split('\n'))
83        sigConfirmElem.setAttributeNS(None, 'Value', b64EncSignatureValue)
84       
85    def _addTimeStamp(self, wsseElem, elapsedSec=60*5):
86        '''Add a timestamp to wsse:Security section of message to be signed
87        e.g.
88            <wsu:Timestamp wsu:Id="timestamp">
89               <wsu:Created>2008-03-25T14:40:37.319Z</wsu:Created>
90               <wsu:Expires>2008-03-25T14:45:37.319Z</wsu:Expires>
91            </wsu:Timestamp>
92       
93        @type wsseElem:
94        @param wsseElem: wsse:Security element
95        @type elapsedSec: int   
96        @param elapsedSec: time interval in seconds between Created and Expires
97        time stamp values
98        '''
99        # Nb. wsu ns declaration is in the SOAP header elem
100        timestampElem = wsseElem.createAppendElement(_WSU.UTILITY, 'Timestamp')
101
102        # Add ID so that the timestamp element can be included in the signature
103        timestampElem.setAttributeNS(_WSU.UTILITY, 'Id', "timestamp")
104       
105        # Value type can be any be any one of those supported via
106        # binSecTokValType
107        createdElem = timestampElem.createAppendElement(_WSU.UTILITY,'Created')
108        dtCreatedTime = datetime.utcnow()
109        createdElem.createAppendTextNode(dtCreatedTime.isoformat('T')+'Z')
110       
111        dtExpiryTime = dtCreatedTime + timedelta(seconds=elapsedSec)
112        expiresElem = timestampElem.createAppendElement(_WSU.UTILITY,'Expires')
113        expiresElem.createAppendTextNode(dtExpiryTime.isoformat('T')+'Z')
114       
115
116    def _verifyTimeStamp(self, 
117                         parsedSOAP, 
118                         processorNss,
119                         timestampMustBeSet=False,
120                         createdElemMustBeSet=True,
121                         expiresElemMustBeSet=True):
122        """Call from verify to check timestamp if found.  The WS-Security 1.1
123        specification allows elements to be optional.  Hence, the bool flags
124        enable their configuration.  The default behaviour is, send a warning
125        if a timestamp is not found but if a timestamp is present raise
126        an exception if the created or the expired elements are missing.
127       
128        @type parsedSOAP: ZSI.parse.ParsedSoap
129        @param parsedSOAP: object contain parsed SOAP message received from
130        sender
131        @type processorNss: dict
132        @param processorNss: namespaces to be used in XPath query to locate
133        timestamp.
134        @type timestampMustBeSet: bool
135        @param timestampMustBeSet: if set to True, raise an exception if no
136        timestamp element is found
137        @type createdElemMustBeSet: bool
138        @param createdElemMustBeSet: if True. raise an exception if no
139        <wsu:Created/> element is present
140        @param expiresElemMustBeSet: if True. raise an exception if no
141        <wsu:Expires/> element is present
142        """
143
144        try:
145            timestampElem = parsedSOAP.dom.xpath('//wsu:Timestamp', 
146                                                 explicitNss=processorNss)[0]
147        except IndexError:
148            # Catch TypeError raised from attempt to reference element 0 of
149            # None type
150            msg = "Verifying message - No timestamp element found"
151            if timestampMustBeSet:
152                raise TimestampError(msg)
153            else:
154                log.warning(msg)
155                return
156       
157        # Time now
158        dtNow = datetime.utcnow()
159
160        createdElem = getElements(timestampElem, "Created")           
161        if len(createdElem) == 0:
162            msg = ("Verifying message: no <wsu:Created/> timestamp "
163                   "sub-element found")
164            if createdElemMustBeSet:
165                raise TimestampError(msg)
166            else:
167                log.warning(msg)
168        else:               
169            # Workaround for fractions of second
170            try:
171                createdDateTime, createdSecFraction = \
172                            createdElem[0].childNodes[0].nodeValue.split('.')
173                dtCreated = _strptime(createdDateTime, '%Y-%m-%dT%H:%M:%S')
174                createdSeconds = float("0."+createdSecFraction.replace('Z',''))
175                dtCreated += timedelta(seconds=createdSeconds)
176                                               
177            except ValueError, e:
178                raise TimestampError("Failed to parse timestamp Created "
179                                     "element: %s" % e)
180           
181            if dtCreated >= dtNow:
182                raise TimestampError("Timestamp created time %s is equal to "
183                                     "or after the current time %s" %
184                                     (dtCreated, dtNow))
185       
186        expiresElem = getElements(timestampElem, "Expires")
187        if len(expiresElem) == 0:
188            msg = ("Verifying message: no <wsu:Expires/> element found in "
189                   "Timestamp")
190            if expiresElemMustBeSet:
191                raise TimeStampError(msg)
192            else:
193                log.warning(warning)
194        else:
195            try:
196                expiresDateTime, expiresSecFraction = \
197                            expiresElem[0].childNodes[0].nodeValue.split('.')
198                dtExpiry = _strptime(expiresDateTime, '%Y-%m-%dT%H:%M:%S')
199                expirySeconds = float("0."+expiresSecFraction.replace('Z', ''))
200                dtExpiry += timedelta(seconds=expirySeconds)
201   
202            except ValueError, e:
203                raise TimestampError("Failed to parse timestamp Expires "
204                                     "element: %s" % e)
205   
206            if dtExpiry < dtNow:
207                raise MessageExpired("Message has expired: timestamp expiry "
208                                     "time %s is before the current time %s." %
209                                     (dtExpiry, dtNow))
210           
211                   
212    def sign(self, soapWriter):
213        '''Sign the message body and binary security token of a SOAP message
214       
215        @type soapWriter: ZSI.writer.SoapWriter
216        @param soapWriter: ZSI object to write SOAP message
217        '''
218       
219        # Namespaces for XPath searches
220        processorNss = \
221        {
222            'ds':     DSIG.BASE, 
223            'wsu':    _WSU.UTILITY, 
224            'wsse':   OASIS.WSSE, 
225            'soapenv':"http://schemas.xmlsoap.org/soap/envelope/" 
226        }
227
228        # Add X.509 cert as binary security token
229        if self.reqBinSecTokValType==self.binSecTokValType['X509PKIPathv1']:
230            if self.signingCertChain is None:
231                msg = 'SignatureHandler signingCertChain attribute is not set'
232                log.error(msg)
233                raise AttributeError(msg)
234           
235            binSecTokVal = base64.encodestring(self.signingCertChain.asDER())
236        else:
237            # Assume X.509 / X.509 vers 3
238            if self.signingCert is None:
239                msg = 'SignatureHandler signingCert attribute is not set'
240                log.error(msg)
241                raise AttributeError(msg)
242           
243            binSecTokVal = base64.encodestring(self.signingCert.asDER())
244
245        soapWriter._header.setNamespaceAttribute('wsse', OASIS.WSSE)
246        soapWriter._header.setNamespaceAttribute('wsse11', OASIS.WSSE11)
247        soapWriter._header.setNamespaceAttribute('wsu', _WSU.UTILITY)
248        soapWriter._header.setNamespaceAttribute('ds', DSIG.BASE)
249       
250        # Flag if inclusive namespace prefixes are set for the signature or
251        # reference elements
252        refC14nPfxSet = len(self.refC14nKw['inclusive_namespaces']) > 0
253        signedInfoC14nPfxSet = \
254                        len(self.signedInfoC14nKw['inclusive_namespaces']) > 0
255               
256        if refC14nPfxSet or signedInfoC14nPfxSet:
257           soapWriter._header.setNamespaceAttribute('ec', DSIG.C14N_EXCL)
258       
259        # Check <wsse:security> isn't already present in header
260        wsseElems = soapWriter._header.evaluate('//wsse:security', 
261                                                processorNss=processorNss)
262        if len(wsseElems) > 1:
263            raise SignatureError('wsse:Security element is already present')
264
265        # Add WSSE element
266        wsseElem = soapWriter._header.createAppendElement(OASIS.WSSE, 
267                                                          'Security')
268        wsseElem.setNamespaceAttribute('wsse', OASIS.WSSE)
269       
270        # Flag to recipient - they MUST parse and check this signature
271        wsseElem.setAttributeNS(SOAP.ENV, 'mustUnderstand', "1")
272       
273        # Binary Security Token element will contain the X.509 cert
274        # corresponding to the private key used to sing the message
275        binSecTokElem = wsseElem.createAppendElement(OASIS.WSSE, 
276                                                     'BinarySecurityToken')
277       
278        # Value type can be any be any one of those supported via
279        # binSecTokValType
280        binSecTokElem.setAttributeNS(None, 
281                                     'ValueType', 
282                                     self.reqBinSecTokValType)
283
284        binSecTokElem.setAttributeNS(None, 
285                                     'EncodingType',
286                                     self._binSecTokEncType)
287       
288        # Add ID so that the binary token can be included in the signature
289        binSecTokElem.setAttributeNS(_WSU.UTILITY, 'Id', "binaryToken")
290
291        binSecTokElem.createAppendTextNode(binSecTokVal)
292
293        # Timestamp
294        if self.addTimestamp:
295            self._addTimeStamp(wsseElem)
296           
297        # Signature Confirmation
298        if self.applySignatureConfirmation: 
299            self._applySignatureConfirmation(wsseElem)
300       
301        # Signature
302        signatureElem = wsseElem.createAppendElement(DSIG.BASE, 'Signature')
303        signatureElem.setNamespaceAttribute('ds', DSIG.BASE)
304       
305        # Signature - Signed Info
306        signedInfoElem = signatureElem.createAppendElement(DSIG.BASE, 
307                                                           'SignedInfo')
308       
309        # Signed Info - Canonicalization method
310        c14nMethodElem = signedInfoElem.createAppendElement(DSIG.BASE,
311                                                    'CanonicalizationMethod')
312       
313        # Set based on 'signedInfoIsExcl' property
314        c14nAlgOpt = (DSIG.C14N, DSIG.C14N_EXCL)
315        signedInfoC14nAlg = c14nAlgOpt[int(self.signedInfoC14nIsExcl)]
316       
317        c14nMethodElem.setAttributeNS(None, 'Algorithm', signedInfoC14nAlg)
318       
319        if signedInfoC14nPfxSet:
320            c14nInclNamespacesElem = c14nMethodElem.createAppendElement(
321                                                    signedInfoC14nAlg,
322                                                    'InclusiveNamespaces')
323            inclNsPfx = ' '.join(self.signedInfoC14nKw['inclusive_namespaces'])
324            c14nInclNamespacesElem.setAttributeNS(None,'PrefixList',inclNsPfx)
325       
326        # Signed Info - Signature method
327        sigMethodElem = signedInfoElem.createAppendElement(DSIG.BASE,
328                                                           'SignatureMethod')
329        sigMethodElem.setAttributeNS(None, 'Algorithm', DSIG.SIG_RSA_SHA1)
330       
331        # Signature - Signature value
332        signatureValueElem = signatureElem.createAppendElement(DSIG.BASE, 
333                                                             'SignatureValue')
334       
335        # Key Info
336        KeyInfoElem = signatureElem.createAppendElement(DSIG.BASE, 'KeyInfo')
337        secTokRefElem = KeyInfoElem.createAppendElement(OASIS.WSSE, 
338                                                  'SecurityTokenReference')
339       
340        # Reference back to the binary token included earlier
341        wsseRefElem = secTokRefElem.createAppendElement(OASIS.WSSE, 
342                                                        'Reference')
343        wsseRefElem.setAttributeNS(None, 'URI', "#binaryToken")
344       
345        # Add Reference to body so that it can be included in the signature
346        soapWriter.body.setAttributeNS(_WSU.UTILITY, 'Id', "body")
347
348        refElems = soapWriter.body.evaluate('//*[@wsu:Id]', 
349                                            processorNss=processorNss)
350       
351        # Set based on 'signedInfoIsExcl' property
352        refC14nAlg = c14nAlgOpt[int(self.refC14nIsExcl)]
353       
354        # 1) Reference Generation
355        #
356        # Find references
357        for refElem in refElems:
358           
359            refID = refElem.getAttributeValue(_WSU.UTILITY, 'Id')
360           
361            # Set URI attribute to point to reference to be signed
362            uri = u"#" + refID
363           
364            # Canonicalize reference
365            inclusiveNsKWs = self.createUnsupressedPrefixKW(self.refC14nKw)
366            refC14n = refElem.canonicalize(algorithm=refC14nAlg, 
367                                           **inclusiveNsKWs)
368
369            # Calculate digest for reference and base 64 encode
370            #
371            # Nb. encodestring adds a trailing newline char
372            digestValue = base64.encodestring(sha(refC14n).digest()).strip()
373
374
375            # Add a new reference element to SignedInfo
376            refElem = signedInfoElem.createAppendElement(DSIG.BASE, 
377                                                         'Reference')
378            refElem.setAttributeNS(None, 'URI', uri)
379           
380            # Use ds:Transforms or wsse:TransformationParameters?
381            transformsElem = refElem.createAppendElement(DSIG.BASE, 
382                                                         'Transforms')
383            transformElem = transformsElem.createAppendElement(DSIG.BASE, 
384                                                               'Transform')
385
386            # Set Canonicalization algorithm type
387            transformElem.setAttributeNS(None, 'Algorithm', refC14nAlg)
388            if refC14nPfxSet:
389                # Exclusive C14N requires inclusive namespace elements
390                inclNamespacesElem = transformElem.createAppendElement(
391                                                                                   refC14nAlg,
392                                                       'InclusiveNamespaces')
393                refInclNsPfx = ' '.join(self.refC14nKw['inclusive_namespaces'])
394                inclNamespacesElem.setAttributeNS(None, 'PrefixList', 
395                                                  refInclNsPfx)
396           
397            # Digest Method
398            digestMethodElem = refElem.createAppendElement(DSIG.BASE, 
399                                                           'DigestMethod')
400            digestMethodElem.setAttributeNS(None, 'Algorithm',DSIG.DIGEST_SHA1)
401           
402            # Digest Value
403            digestValueElem = refElem.createAppendElement(DSIG.BASE, 
404                                                          'DigestValue')
405            digestValueElem.createAppendTextNode(digestValue)
406
407   
408        # 2) Signature Generation
409        #       
410        # Canonicalize the signedInfo node
411        signedInfoInclusiveNsKWs = self.createUnsupressedPrefixKW(
412                                                        self.signedInfoC14nKw)
413        try:
414            signedInfoElem = soapWriter.body.evaluate('//ds:SignedInfo',
415                                                  processorNss=processorNss)[0]
416        except TypeError, e:
417            log.error("Error locating SignedInfo element for signature")
418            raise 
419       
420        c14nSignedInfo=signedInfoElem.canonicalize(algorithm=signedInfoC14nAlg,
421                                                   **signedInfoInclusiveNsKWs)
422        # Calculate digest of SignedInfo
423        signedInfoDigestValue = sha(c14nSignedInfo).digest()
424       
425        # Sign using the private key and base 64 encode the result
426        signatureValue = self.signingPriKey.sign(signedInfoDigestValue)
427        b64EncSignatureValue = base64.encodestring(signatureValue).strip()
428
429        # Add to <SignatureValue>
430        signatureValueElem.createAppendTextNode(b64EncSignatureValue)
431
432        log.info("Signature generation complete")
433
434
435    def createUnsupressedPrefixKW(self, dictToConvert):
436        """
437        Convert a dictionary to use keys with names, 'inclusive_namespaces' in
438        place of keys with names 'unsupressedPrefixes'
439        NB, this is required for the ZSI canonicalize method
440        @type dictToConvert: dict
441        @param dictToConvert: dictionary to convert
442        @rtype: dict
443        @return: dictionary with corrected keys
444        """
445        nsList = []
446        newDict = dictToConvert.copy()
447        if isinstance(newDict, dict) and \
448            isinstance(newDict.get('inclusive_namespaces'), list):
449            nsList = newDict.get('inclusive_namespaces')
450            newDict.pop('inclusive_namespaces')
451
452        newDict['unsuppressedPrefixes'] = nsList
453        return newDict
454
455    def verify(self, parsedSOAP, raiseNoSignatureFound=True):
456        """Verify signature
457       
458        @type parsedSOAP: ZSI.parse.ParsedSoap
459        @param parsedSOAP: object contain parsed SOAP message received from
460        sender"""
461
462        processorNss = {
463            'ds':     DSIG.BASE, 
464            'wsu':    _WSU.UTILITY, 
465            'wsse':   OASIS.WSSE, 
466            'soapenv':SOAP.ENV
467        }
468        signatureElem = parsedSOAP.dom.xpath('//ds:Signature', 
469                                             explicitNss=processorNss)
470        if len(signatureElem) > 1:
471            raise VerifyError('Multiple <ds:Signature/> elements found')
472       
473        try:
474            signatureElem = signatureElem[0]
475        except IndexError:
476            # Message wasn't signed
477            msg = "Input message wasn't signed!"
478            if raiseNoSignatureFound:
479                raise NoSignatureFound(msg)
480            else: 
481                log.warning(msg)
482                return
483       
484        # Two stage process: reference validation followed by signature
485        # validation
486       
487        # 1) Reference Validation
488       
489        # Check for canonicalization set via ds:CanonicalizationMethod -
490        # Use this later as a back up in case no Canonicalization was set in
491        # the transforms elements
492        try:
493            c14nMethodElem=parsedSOAP.dom.xpath('//ds:CanonicalizationMethod',
494                                                explicitNss=processorNss)[0]
495        except TypeError, e:
496            log.error("XPath query error locating "
497                      "<ds:CanonicalizationMethod/>: %s" % e)
498            raise
499       
500        refElems = parsedSOAP.dom.xpath('//ds:Reference',
501                                        explicitNss=processorNss)
502
503        for refElem in refElems:
504            # Get the URI for the reference
505            refURI = refElem.getAttributeNS(None, 'URI')
506           
507            try:
508                transformsElem = getElements(refElem, "Transforms")[0]
509                transformElems = getElements(transformsElem, "Transform")
510   
511                refAlgorithm = transformElems[0].getAttributeNS(None,
512                                                                'Algorithm')
513            except Exception, e:
514                raise VerifyError('failed to get transform algorithm for '
515                                  '<ds:Reference URI="%s">' % 
516                                  (refURI, e))
517               
518            # Add extra keyword for Exclusive canonicalization method
519            refC14nKw = {}
520            refC14nIsExcl = refAlgorithm == DSIG.C14N_EXCL
521            if refC14nIsExcl:
522                try:
523                    # Check for no inclusive namespaces set
524                    inclusiveNS = getElements(transformElems[0], 
525                                              "InclusiveNamespaces")                   
526                    if len(inclusiveNS) > 0:
527                        pfxListAttElem=inclusiveNS[0].getAttributeNodeNS(None, 
528                                                                'PrefixList')
529                           
530                        refC14nKw['inclusivePrefixes'] = \
531                                                pfxListAttElem.value.split()
532                    else:
533                        refC14nKw['inclusivePrefixes'] = None
534                except Exception, e:
535                    raise VerifyError('failed to handle transform (%s) in '
536                                      '<ds:Reference URI="%s">: %s' % \
537                                      (transformElems[0], refURI, e))
538       
539            # Canonicalize the reference data and calculate the digest
540            if refURI[0] != "#":
541                raise VerifyError("Expecting # identifier for Reference URI "
542                                  "\"%s\"" % refURI)
543                   
544            # XPath reference
545            uriXPath = '//*[@wsu:Id="%s"]' % refURI[1:]
546            uriElem = parsedSOAP.dom.xpath(uriXPath,
547                                           explicitNss=processorNss)[0]
548
549            f = StringIO()
550            CanonicalPrint(uriElem, stream=f, exclusive=refC14nIsExcl,
551                           **refC14nKw)
552            refC14n = f.getvalue()
553            digestValue = base64.encodestring(sha(refC14n).digest()).strip()
554           
555            # Extract the digest value that was stored           
556            digestNode = getElements(refElem, "DigestValue")[0]
557            nodeDigestValue = str(digestNode.childNodes[0].nodeValue).strip()   
558           
559            # Reference validates if the two digest values are the same
560            if digestValue != nodeDigestValue:
561                raise InvalidSignature('Digest Values do not match for URI: '
562                                       '"%s"' % refURI)
563           
564            log.info("Verified canonicalization for element %s" % refURI[1:])
565               
566        # 2) Signature Validation
567        signedInfoElem = parsedSOAP.dom.xpath('//ds:SignedInfo',
568                                              explicitNss=processorNss)[0]
569
570        # Get algorithm used for canonicalization of the SignedInfo
571        # element.  Nb. This is NOT necessarily the same as that used to
572        # canonicalize the reference elements checked above!
573        signedInfoC14nAlg = c14nMethodElem.getAttributeNS(None, "Algorithm")
574        signedInfoC14nKw = {}
575        signedInfoC14nIsExcl = signedInfoC14nAlg == DSIG.C14N_EXCL
576        if signedInfoC14nIsExcl:
577            try:
578                # Check for inclusive namespaces
579                inclusiveNsElem = getElements(c14nMethodElem,
580                                              "InclusiveNamespaces")
581                if len(inclusiveNsElem) > 0:                   
582                    pfxListAttElem=inclusiveNsElem[0].getAttributeNodeNS(None,
583                                                                 'PrefixList')
584                    signedInfoC14nKw['inclusivePrefixes'] = \
585                                                pfxListAttElem.value.split()
586                else:
587                    signedInfoC14nKw['inclusivePrefixes'] = None
588            except Exception, e:
589                raise VerifyError('failed to handle exclusive '
590                                  'canonicalisation for SignedInfo: %s' % e)
591
592        # Canonicalize the SignedInfo node and take digest
593        f = StringIO()
594        CanonicalPrint(signedInfoElem,
595                       stream=f,
596                       exclusive=signedInfoC14nIsExcl, 
597                       **signedInfoC14nKw)       
598        c14nSignedInfo = f.getvalue() 
599                       
600        signedInfoDigestValue = sha(c14nSignedInfo).digest()
601       
602        # Get the signature value in order to check against the digest just
603        # calculated
604        signatureValueElem = parsedSOAP.dom.xpath('//ds:SignatureValue',
605                                                  explicitNss=processorNss)[0]
606
607        # Remove base 64 encoding
608        b64EncSignatureValue = signatureValueElem.childNodes[0].nodeValue
609        signatureValue = base64.decodestring(b64EncSignatureValue)
610
611        # Cache Signature Value here so that a response can include it.
612        #
613        # Nb. If the sign method is called from a separate SignatureHandler
614        # object then the signature value must be passed from THIS object to
615        # the other SignatureHandler otherwise signature confirmation will
616        # fail
617        if self.applySignatureConfirmation:
618            # re-encode string to avoid possible problems with interpretation
619            # of line breaks
620            self.b64EncSignatureValue = b64EncSignatureValue
621        else:
622            self.b64EncSignatureValue = None
623         
624        # Look for X.509 Cert in wsse:BinarySecurityToken node
625        try:
626            binSecTokElem = parsedSOAP.dom.xpath('//wsse:BinarySecurityToken',
627                                                 explicitNss=processorNss)[0]
628        except:
629            # Signature may not have included the Binary Security Token in
630            # which case the verifying cert will need to have been set
631            # elsewhere
632            log.info("No Binary Security Token found in WS-Security header")
633            binSecTokElem = None
634       
635        if binSecTokElem:
636            try:
637                x509CertTxt=str(binSecTokElem.childNodes[0].nodeValue)
638               
639                valueType = binSecTokElem.getAttributeNS(None, "ValueType")
640                if valueType in (self.__class__.binSecTokValType['X509v3'],
641                                 self.__class__.binSecTokValType['X509']):
642                    # Remove base 64 encoding
643                    derString = base64.decodestring(x509CertTxt)
644                    self.verifyingCert = X509Cert.Parse(derString, 
645                                                    format=X509Cert.formatDER)
646                    x509Stack = X509Stack()
647
648                elif valueType == \
649                    self.__class__.binSecTokValType['X509PKIPathv1']:
650                   
651                    derString = base64.decodestring(x509CertTxt)
652                    x509Stack = X509StackParseFromDER(derString)
653                   
654                    # TODO: Check ordering - is the last off the stack the
655                    # one to use to verify the message?
656                    self.verifyingCert = x509Stack[-1]
657                else:
658                    raise WSSecurityError("BinarySecurityToken ValueType "
659                                          'attribute is not recognised: "%s"' %
660                                          valueType)
661                               
662            except Exception, e:
663                raise VerifyError("Error extracting BinarySecurityToken "
664                                  "from WSSE header: %s" % e)
665
666        if self.verifyingCert is None:
667            raise VerifyError("No certificate set for verification of the "
668                              "signature")
669       
670        # Extract RSA public key from the cert
671        rsaPubKey = self.verifyingCert.pubKey.get_rsa()
672
673        # Apply the signature verification
674        try:
675            verify = rsaPubKey.verify(signedInfoDigestValue, signatureValue)
676        except RSA.RSAError, e:
677            raise VerifyError("Error in Signature: " % e)
678       
679        if not verify:
680            raise InvalidSignature("Invalid signature")
681       
682        # Verify chain of trust
683        x509Stack.verifyCertChain(x509Cert2Verify=self.verifyingCert,
684                                  caX509Stack=self._caX509Stack)
685       
686        self._verifyTimeStamp(parsedSOAP, 
687                              processorNss,
688                              timestampMustBeSet=self.timestampMustBeSet,
689                              createdElemMustBeSet=self.createdElemMustBeSet,
690                              expiresElemMustBeSet=self.expiresElemMustBeSet) 
691        log.info("Signature OK")       
Note: See TracBrowser for help on using the repository browser.