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

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

Integrated SSL client authentication with OpenID based authentication for authz_lite integration test.

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