source: TI12-security/trunk/WSSecurity/ndg/wssecurity/common/wssecurity/signaturehandler/foursuite.py @ 6378

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/WSSecurity/ndg/wssecurity/common/wssecurity/signaturehandler/foursuite.py@6378
Revision 6378, 30.7 KB checked in by pjkersha, 11 years ago (diff)

Refactoring base signature handler

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