source: TI12-security/trunk/WSSecurity/ndg/wssecurity/common/signaturehandler/dom.py @ 6387

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