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

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