source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/wssecurity/signaturehandler/dom.py @ 5433

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

Fixes for OAI editor security integration.

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