source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/wssecurity/etree.py @ 4129

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/wssecurity/etree.py@4129
Revision 4129, 29.8 KB checked in by cbyrom, 11 years ago (diff)

General refactoring and updating of code, including:

Removal of refC14nKw and singnedInfoC14nKw keywords in wsssecurity session manager config
(the refC14nInclNS and signedInfoC14nInclNS keywords are sufficient);
Creation of new DOM signature handler class, dom.py, based on the wsSecurity
class;
Abstraction of common code between dom.py and etree.py into new parent
class, BaseSignatureHandler?.py.
Fixing and extending use of properties in the SignatureHandler? code.
Fixing a few bugs with the original SignatureHandler? code.
Updating of test cases to new code/code structure.

Line 
1"""WS-Security digital signature handler for ElementTree XML package
2
3NERC Data Grid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "02/07/08"
7__copyright__ = "(C) 2008 STFC & NERC"
8__license__ = \
9"""This software may be distributed under the terms of the Q Public
10License, version 1.0 or later."""
11__contact__ = "P.J.Kershaw@rl.ac.uk"
12__revision__ = '$Id:  $'
13
14from BaseSignatureHandler import _WSU, OASIS, BaseSignatureHandler
15import re
16
17# Digest and signature/verify
18from sha import sha
19from M2Crypto import X509, BIO, RSA
20import base64
21
22from elementtree import ElementTree, ElementC14N
23from StringIO import StringIO
24
25# Conditional import as this is required for the encryption
26# handler
27try:
28    # For shared key encryption
29    from Crypto.Cipher import AES, DES3
30except:
31    from warnings import warn
32    warn('Crypto.Cipher not available: EncryptionHandler disabled!',
33         RuntimeWarning)
34    class AES:
35        MODE_ECB = None
36        MODE_CBC = None
37       
38    class DES3: 
39        MODE_CBC = None
40
41import os
42
43import ZSI
44from ZSI.wstools.Namespaces import DSIG, WSA200403, \
45                                   SOAP, SCHEMA # last included for xsi
46
47from ZSI.TC import ElementDeclaration,TypeDefinition
48from ZSI.generate.pyclass import pyclass_type
49
50from ZSI.wstools.Utility import DOMException
51from ZSI.wstools.Utility import NamespaceError, MessageInterface, ElementProxy
52
53# Canonicalization
54from ZSI.wstools.c14n import Canonicalize
55
56from xml.dom import Node
57from xml.xpath.Context import Context
58from xml import xpath
59
60# Enable settings from a config file
61from ndg.security.common.wssecurity import WSSecurityConfig
62
63# Check SoapWriter.dom attribute is ElementTreeProxy type
64from ndg.security.common.zsi_utils.elementtreeproxy import ElementTreeProxy
65
66from ndg.security.common.X509 import X509Cert, X509CertParse, X509CertRead, \
67X509Stack, X509StackParseFromDER
68
69from datetime import datetime, timedelta
70import logging
71log = logging.getLogger(__name__)
72
73class SignatureHandler(BaseSignatureHandler):
74    """Class to handle signature and verification of signature with
75    WS-Security
76    """
77
78    _binSecTokEncType = \
79"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
80
81    # Namespaces for XPath searches
82    _processorNSs = \
83    {
84        'ds':       DSIG.BASE, 
85        'wsu':      _WSU.UTILITY, 
86        'wsse':     OASIS.WSSE, 
87        'SOAP-ENV': SOAP.ENV
88    }
89
90               
91
92    def _applySignatureConfirmation(self, wsseElem):
93        '''Add SignatureConfirmation element - as specified in WS-Security 1.1
94        - to outbound message on receipt of a signed message from a client
95       
96        This has been added in through tests vs. Apache Axis Rampart client
97       
98        @type wsseElem:
99        @param wsseElem: wsse:Security element'''
100        if self.b64EncSignatureValue is None:
101            log.info(\
102"SignatureConfirmation element requested but no request signature was cached")
103            return
104       
105        sigConfirmElem = ElementTree.Element("{%s}%s" % (OASIS.WSSE11, 
106                                                      'SignatureConfirmation'))
107        wsseElem.append(sigConfirmElem)
108       
109        # Add ID so that the element can be included in the signature
110        sigConfirmElem.set('{%s}Id' % _WSU.UTILITY, "signatureConfirmation")
111
112        # Add ID so that the element can be included in the signature
113        # Following line is a hack to avoid appearance of #x when serialising
114        # \n chars
115        # TODO: Fix #x problem with sig value?
116        b64EncSignatureValue = ''.join(self.b64EncSignatureValue.split('\n'))
117        sigConfirmElem.set('Value', b64EncSignatureValue)
118       
119       
120    def _addTimeStamp(self, wsseElem, elapsedSec=60*5):
121        '''Add a timestamp to wsse:Security section of message to be signed
122        e.g.
123            <wsu:Timestamp wsu:Id="timestamp">
124               <wsu:Created>2008-03-25T14:40:37.319Z</wsu:Created>
125               <wsu:Expires>2008-03-25T14:45:37.319Z</wsu:Expires>
126            </wsu:Timestamp>
127       
128        @type wsseElem:
129        @param wsseElem: wsse:Security element
130        @type elapsedSec: int   
131        @param elapsedSec: time interval in seconds between Created and Expires
132        time stamp values
133        '''
134        # Nb. wsu ns declaration is in the SOAP header elem
135        timestampElem = ElementTree.Element("{%s}%s"%(_WSU.UTILITY,'Timestamp'))
136        wsseElem.append(timestampElem)
137       
138        # Add ID so that the timestamp element can be included in the signature
139        timestampElem.set('{%s}Id' % _WSU.UTILITY, "timestamp")
140       
141        # Value type can be any be any one of those supported via
142        # binSecTokValType
143        createdElem = ElementTree.Element("{%s}%s" % (_WSU.UTILITY,'Created'))
144        timestampElem.append(createdElem)
145       
146        dtCreatedTime = datetime.utcnow()
147        createdElem.text = dtCreatedTime.isoformat('T') + 'Z'
148       
149        dtExpiryTime = dtCreatedTime + timedelta(seconds=elapsedSec)
150        expiresElem = ElementTree.Element("{%s}%s" % (_WSU.UTILITY, 'Expires'))
151        timestampElem.append(expiresElem)
152       
153        expiresElem.text = dtExpiryTime.isoformat('T') + 'Z'
154       
155
156    def _verifyTimeStamp(self, parsedSOAP, timestampMustBeSet=False):
157        """Call from verify to check timestamp if found. 
158       
159        TODO: refactor input args - maybe these should by object attributes
160       
161        @type parsedSOAP: ZSI.parse.ParsedSoap
162        @param parsedSOAP: object contain parsed SOAP message received from
163        sender
164        @type timestampMustBeSet: bool
165        @param timestampMustBeSet: set to True to raise an exception if no
166        timestamp element is present in the message to be checked"""
167
168        timestampElems = self._soapEnvElem.findall('.//wsu:Timestamp', 
169                                                namespaces=self._processorNSs)
170        nTimestampElems = len(timestampElems)       
171        if nTimestampElems > 1:
172            raise TimestampError("Expecting only one timestamp element")
173       
174        elif nTimestampElems == 0:
175            msg = "Verifying message - No timestamp element found"
176            if timestampMustBeSet:
177                raise VerifyError(msg)
178            else:
179                log.warning(msg)
180                return
181       
182        timestampElem = timestampElems[0]
183       
184        # Time now
185        dtNow = datetime.utcnow()
186       
187        createdElem = timestampElem.find("wsu:Created", 
188                                         namespaces=self._processorNSs)
189        if createdElem is None:
190            raise TimestampError("No 'Created' element found in timestamp")
191
192        # Workaround for fractions of second with datetime module
193        try:
194            createdDateTime,strCreatedSecFraction = createdElem.text.split('.')
195            strCreatedSecFraction = strCreatedSecFraction.split('Z')[0]
196            createdExp = -int(len(strCreatedSecFraction))
197            createdSecFraction = int(strCreatedSecFraction) * 10 ** createdExp
198           
199        except ValueError, e:
200            raise ValueError("Parsing timestamp Created element: %s" % e)
201       
202
203        dtCreated = datetime.strptime(createdDateTime, '%Y-%m-%dT%H:%M:%S')
204        dtCreated += timedelta(seconds=createdSecFraction)
205        if dtCreated >= dtNow:
206            raise TimestampError(\
207        "Timestamp created time %s is equal to or after the current time %s" %\
208                (dtCreated, dtNow))
209       
210        expiresElem = timestampElem.find("wsu:Expires",
211                                         namespaces=self._processorNSs)
212        if expiresElem is None:
213            raise TimestampError("Verifying message - No Expires element "
214                                 "found in Timestamp")
215
216        try:
217            expiryDateTime, strExpirySecFraction = expiresElem.text.split('.')
218            strExpirySecFraction = strExpirySecFraction.split('Z')[0]
219            expiryExp = -int(len(strExpirySecFraction))
220            expirySecFraction = int(strExpirySecFraction) * 10 ** expiryExp
221        except ValueError, e:
222            raise ValueError("Parsing timestamp Expires element: %s" % e)
223       
224        dtExpiry = datetime.strptime(expiryDateTime, '%Y-%m-%dT%H:%M:%S')
225        dtExpiry += timedelta(seconds=expirySecFraction)
226        if dtExpiry < dtNow:
227            raise TimestampError(\
228                "Timestamp expiry time %s is before the current time %s" % \
229                (dtCreated, dtNow))
230           
231        log.debug("Verified timestamp")
232           
233                   
234    #_________________________________________________________________________
235    def sign(self, soapWriter):
236        '''Sign the message body and binary security token of a SOAP message
237       
238        @type soapWriter: ZSI.writer.SoapWriter
239        @param soapWriter: ZSI object to write SOAP message
240        '''
241       
242        # Check for expected soapWriter DOM class
243        if not isinstance(soapWriter.dom, ElementTreeProxy):
244            raise SignatureError("Expecting ElementTreeProxy type for "
245                                 "ZSI.writer.SoapWriter.dom instance")
246           
247        # Add X.509 cert as binary security token
248        # TODO: sub encodestring with b64encode?
249        if self.reqBinSecTokValType==self.binSecTokValType['X509PKIPathv1']:
250            binSecTokVal = base64.encodestring(self.signingCertChain.asDER())
251        else:
252            # Assume X.509 / X.509 vers 3
253            binSecTokVal = base64.encodestring(self.signingCert.asDER())
254
255        self._soapEnvElem = soapWriter.dom._elem
256        soapHdrElem = soapWriter._header._elem
257        soapBodyElem = soapWriter.body._elem
258       
259        self._soapEnvElem.set("xmlns:%s" % 'ds', DSIG.BASE)
260        #self._soapEnvElem.set("xmlns:%s" % 'wsse', OASIS.WSSE)
261       
262        soapHdrElem.set("xmlns:%s" % 'wsse', OASIS.WSSE)
263        soapHdrElem.set("xmlns:%s" % 'wsse11', OASIS.WSSE11)
264#        soapHdrElem.set("xmlns:%s" % 'wsu', _WSU.UTILITY)
265        self._soapEnvElem.set("xmlns:%s" % 'wsu', _WSU.UTILITY)
266        soapHdrElem.set("xmlns:%s" % 'ds', DSIG.BASE)
267       
268        refC14nPfxSet = False
269        if isinstance(self.refC14nKw.get('inclusive_namespaces'), list) and \
270            len(self.refC14nKw['inclusive_namespaces']) > 0:
271            refC14nPfxSet = True 
272
273        signedInfoC14nPfxSet = False
274        if isinstance(self.signedInfoC14nKw.get('inclusive_namespaces'), list) and \
275            len(self.signedInfoC14nKw['inclusive_namespaces']) > 0:
276            signedInfoC14nPfxSet = True
277               
278        if refC14nPfxSet or signedInfoC14nPfxSet:
279            soapHdrElem.set("xmlns:%s" % 'ec', DSIG.C14N_EXCL)
280           
281           
282        # Check <wsse:security> isn't already present in header
283        wsseElems = self._soapEnvElem.findall('.//wsse:security',
284                                              namespaces=self._processorNSs)
285        if len(wsseElems) > 1:
286            raise SignatureError('wsse:Security element is already present')
287       
288        # Add WSSE element
289        wsseElem = ElementTree.Element("{%s}%s" % (OASIS.WSSE, 'Security'))
290        soapHdrElem.append(wsseElem)
291       
292        wsseElem.set("xmlns:%s" % 'wsse', OASIS.WSSE)
293       
294        # Recipient MUST parse and check this signature
295        wsseElem.set('SOAP-ENV:mustUnderstand', "1")
296       
297        # Binary Security Token element will contain the X.509 cert
298        # corresponding to the private key used to sing the message
299        binSecTokElem = ElementTree.Element("{%s}%s" % (OASIS.WSSE, 
300                                                        'BinarySecurityToken'))
301        wsseElem.append(binSecTokElem)
302       
303        # Value type can be any be any one of those supported via
304        # binSecTokValType
305        binSecTokElem.set('ValueType', self.reqBinSecTokValType)
306        binSecTokElem.set('EncodingType', self._binSecTokEncType)
307       
308        # Add ID so that the binary token can be included in the signature
309        binSecTokElem.set('{%s}Id' % _WSU.UTILITY, "binaryToken")
310       
311        binSecTokElem.text = binSecTokVal
312
313
314        # Timestamp
315        if self.addTimestamp:
316            self._addTimeStamp(wsseElem)
317           
318        # Signature Confirmation
319        if self.applySignatureConfirmation: 
320            self._applySignatureConfirmation(wsseElem)
321       
322        # Signature
323        signatureElem = ElementTree.Element("{%s}%s" % (DSIG.BASE,'Signature'))
324        wsseElem.append(signatureElem)
325       
326        # Signature - Signed Info
327        signedInfoElem = ElementTree.Element("{%s}%s"%(DSIG.BASE,'SignedInfo'))
328        signatureElem.append(signedInfoElem)
329       
330        # Signed Info - Canonicalization method
331        c14nMethodElem = ElementTree.Element("{%s}%s" % (DSIG.BASE,
332                                                    'CanonicalizationMethod'))
333        signedInfoElem.append(c14nMethodElem)
334       
335        # Set based on 'signedInfoIsExcl' property
336        c14nAlgOpt = (DSIG.C14N, DSIG.C14N_EXCL)
337        signedInfoC14nAlg = c14nAlgOpt[int(self.signedInfoC14nIsExcl)]
338       
339        c14nMethodElem.set('Algorithm', signedInfoC14nAlg)
340       
341        if signedInfoC14nPfxSet:
342            c14nInclNamespacesElem = ElementTree.Element("{%s}%s" % \
343                                                    (signedInfoC14nAlg,
344                                                    'InclusiveNamespaces'))
345            c14nMethodElem.append(c14nInclNamespacesElem)
346           
347            pfxList = ' '.join(self.signedInfoC14nKw['inclusive_namespaces'])
348            c14nInclNamespacesElem.set('PrefixList', pfxList)
349
350       
351        # Signed Info - Signature method
352        sigMethodElem = ElementTree.Element("{%s}%s" % \
353                                            (DSIG.BASE, 'SignatureMethod'))
354        signedInfoElem.append(sigMethodElem)
355        sigMethodElem.set('Algorithm', DSIG.SIG_RSA_SHA1)
356       
357        # Signature - Signature value
358        signatureValueElem = ElementTree.Element("{%s}%s" % (DSIG.BASE, 
359                                                             'SignatureValue'))
360        signatureElem.append(signatureValueElem)
361       
362        # Key Info
363        KeyInfoElem = ElementTree.Element("{%s}%s" % (DSIG.BASE, 'KeyInfo'))
364        signatureElem.append(KeyInfoElem)
365       
366        secTokRefElem = ElementTree.Element("{%s}%s" % (OASIS.WSSE, 
367                                                    'SecurityTokenReference'))
368        KeyInfoElem.append(secTokRefElem)
369       
370        # Reference back to the binary token included earlier
371        wsseRefElem = ElementTree.Element("{%s}%s" % (OASIS.WSSE, 'Reference'))
372        secTokRefElem.append(wsseRefElem)
373       
374        wsseRefElem.set('URI', "#binaryToken")
375       
376        # Add Reference to body so that it can be included in the signature
377        #soapBodyElem.set('xmlns:wsu', _WSU.UTILITY) - results in duplicate xmlns declarations
378        soapBodyElem.set('{%s}Id' % _WSU.UTILITY, 'body')
379
380
381        # Set Reference Canonicalization algorithm based on 'refC14nIsExcl'
382        # property
383        refC14nAlg = c14nAlgOpt[self.refC14nIsExcl]
384       
385        # Pick up all the wsu:Id tagged elements set in the above
386        refElems = self._soapEnvElem.findall('.//*[@wsu:Id]',
387                                             namespaces=self._processorNSs)
388
389        # 1) Reference Generation
390        #
391        # Find references
392        for refElem in refElems:
393           
394            # Set URI attribute to point to reference to be signed
395            uri = '#' + refElem.get('{%s}%s' % (_WSU.UTILITY, 'Id'))
396           
397            # Canonicalize reference
398            refC14n = soapWriter.dom.canonicalize(subset=refElem,
399                                                  exclusive=self.refC14nIsExcl,
400                                                  **self.refC14nKw)
401            log.debug('Canonicalisation for URI "%s": %s', uri, refC14n)
402           
403            # Calculate digest for reference and base 64 encode
404            #
405            # Nb. encodestring adds a trailing newline char
406            # Use b64encode instead - encodestring puts in newline chars at
407            # 76 char intervals
408            #digestValue = base64.encodestring(sha(refC14n).digest()).strip()
409            digestValue = base64.b64encode(sha(refC14n).digest())
410           
411            # Add a new reference element to SignedInfo
412            signedInfoRefElem = ElementTree.Element("{%s}Reference"%DSIG.BASE)
413            signedInfoElem.append(signedInfoRefElem)
414            signedInfoRefElem.set('URI', uri)
415           
416            # Use ds:Transforms or wsse:TransformationParameters?
417            transformsElem = ElementTree.Element("{%s}Transforms" % DSIG.BASE)
418            signedInfoRefElem.append(transformsElem)
419           
420            transformElem = ElementTree.Element("{%s}Transform" % DSIG.BASE)
421            transformsElem.append(transformElem)
422
423            # Set Canonicalization algorithm type
424            transformElem.set('Algorithm', refC14nAlg)
425            if refC14nPfxSet:
426                # Exclusive C14N requires inclusive namespace elements
427                inclNamespacesElem = transformElem.createAppendElement(\
428                                                       refC14nAlg,
429                                                       'InclusiveNamespaces')
430                refPfxList = ' '.join(self.refC14nKw['inclusive_namespaces'])
431                inclNamespacesElem.set('PrefixList', refPfxList)
432           
433            # Digest Method
434            digestMethodElem = ElementTree.Element("{%s}%s" % (DSIG.BASE, 
435                                                               'DigestMethod'))
436            signedInfoRefElem.append(digestMethodElem)
437           
438            digestMethodElem.set('Algorithm', DSIG.DIGEST_SHA1)
439           
440            # Digest Value
441            digestValueElem = ElementTree.Element("{%s}%s" % (DSIG.BASE, 
442                                                              'DigestValue'))
443            signedInfoRefElem.append(digestValueElem)
444            digestValueElem.text = digestValue
445   
446        # 2) Signature Generation
447        #       
448        # Canonicalize the signedInfo node
449        c14nSignedInfo = soapWriter.dom.canonicalize(subset=signedInfoElem,
450                                           exclusive=self.signedInfoC14nIsExcl,
451                                           **self.signedInfoC14nKw)
452        log.debug('Canonicalisation for <ds:signedInfo>: %s', c14nSignedInfo)
453       
454        # Calculate digest of SignedInfo
455        signedInfoDigestValue = sha(c14nSignedInfo).digest()
456       
457        # Sign using the private key and base 64 encode the result
458        signatureValue = self.signingPriKey.sign(signedInfoDigestValue)
459       
460        # encodestring puts newline markers at 76 char intervals otherwise no
461        # difference
462        # b64EncSignatureValue = base64.encodestring(signatureValue).strip()
463        b64EncSignatureValue = base64.b64encode(signatureValue)
464
465        # Add to <SignatureValue>
466        signatureValueElem.text = b64EncSignatureValue
467        log.debug("Signature generation complete")
468
469
470    def verify(self, parsedSOAP):
471        """Verify signature
472       
473        @type parsedSOAP: ZSI.parse.ParsedSoap
474        @param parsedSOAP: object contain parsed SOAP message received from
475        sender"""
476
477        if not isinstance(parsedSOAP.dom, ElementTreeProxy):
478            raise VerifyError("Expecting ElementTreeProxy type for "
479                              "ZSI.parse.ParsedSoap.dom")
480       
481        self._soapEnvElem = parsedSOAP.dom._elem
482
483        signatureElems = self._soapEnvElem.findall('.//ds:Signature', 
484                                                namespaces=self._processorNSs)       
485        if len(signatureElems) > 1:
486            raise VerifyError('Multiple <ds:Signature/> elements found')
487       
488        try:
489            signatureElems = signatureElems[0]
490        except:
491            # Message wasn't signed - may be possible if peer raised a SOAP
492            # fault
493            raise NoSignatureFound("Input message wasn't signed!")
494
495       
496        # Two stage process: reference validation followed by signature
497        # validation
498       
499        # 1) Reference Validation
500       
501        # Check for canonicalization set via ds:CanonicalizationMethod -
502        # Use this later as a back up in case no Canonicalization was set in
503        # the transforms elements
504        c14nMethodElem = self._soapEnvElem.find('.//ds:CanonicalizationMethod', 
505                                                namespaces=self._processorNSs)
506        if c14nMethodElem is None:
507            raise VerifyError("No <ds:Canonicalization/> element found") 
508             
509        refElems = self._soapEnvElem.findall('.//ds:Reference', 
510                                             namespaces=self._processorNSs)
511        if len(refElems) == 0:
512            raise VerifyError("No <ds:Reference/> elements found") 
513       
514        for refElem in refElems:
515            # Get the URI for the reference
516            refURI = refElem.get('URI')
517                         
518            transformElem = refElem.find('ds:Transforms/ds:Transform',
519                                         namespaces=self._processorNSs)
520            if transformElem is None:
521                raise VerifyError('Failed to get transform algorithm for '
522                                  '<ds:Reference URI="%s">' % refURI)
523               
524            refAlgorithm = transformElem.get("Algorithm")
525               
526            # Add extra keyword for Exclusive canonicalization method
527            refC14nKw = dict(exclusive=refAlgorithm == DSIG.C14N_EXCL)
528            if refC14nKw['exclusive']:
529                # Check for no inclusive namespaces set
530                inclusiveNSElem = transformElem.find("InclusiveNamespaces",
531                                                namespaces=self._processorNSs)                   
532                if inclusiveNSElem is not None:
533                    pfxListTxt = inclusiveNSElem.get('PrefixList')
534                    if pfxListTxt is None:
535                        raise VerifyError('Empty InclusiveNamespaces list for'\
536                                          ' <ds:Reference URI="%s">' % refURI)
537                                                 
538                    refC14nKw['inclusive_namespaces'] = pfxListTxt.split()
539                else:
540                    # Set to empty list to ensure Exclusive C14N is set for
541                    # Canonicalize call
542                    refC14nKw['inclusive_namespaces'] = []
543       
544            # Canonicalize the reference data and calculate the digest
545            if refURI[0] != "#":
546                raise VerifyError('Expecting # identifier for Reference URI' \
547                                  ' "%s"' % refURI)
548                   
549            # XPath reference
550            uriXPath = './/*[@wsu:Id="%s"]' % refURI[1:]
551            uriElem=self._soapEnvElem.findall(uriXPath,
552                                              namespaces=self._processorNSs)
553            if len(uriElem) > 1:
554                raise VerifyError("Multiple elements matching '%s' search" 
555                                  " path: %s" % (uriXPath, uriElem))
556
557            refC14n=parsedSOAP.dom.canonicalize(subset=uriElem[0], **refC14nKw)
558           
559            # encodestring adds line delimiters at 76 char intervals - avoid
560            # and use b64encode instead           
561            calculatedDigestValue = base64.b64encode(sha(refC14n).digest())
562           
563            # Extract the digest value that was stored in the SOAP request         
564            digestElem = refElem.find('ds:DigestValue',
565                                      namespaces=self._processorNSs)
566            if digestElem is None:
567                raise VerifyError('Failed to get digestValue for ' \
568                                  '<ds:Reference URI="%s">' % refURI)
569               
570            # Need to check here for value split into separate lines?
571            retrievedDigestValue = str(digestElem.text).strip()   
572           
573            # Reference validates if the two digest values are the same
574            if retrievedDigestValue != calculatedDigestValue:
575                log.error("Digest values don't match")
576                log.error('Canonicalisation for URI: "%s": %s' % \
577                          (refURI, refC14n))
578                raise InvalidSignature('Digest Values do not match for URI:' \
579                                       ' "%s"' % refURI)
580           
581            log.debug("Verified canonicalization for element '%s'"%refURI[1:])
582           
583
584        # 2) Signature Validation
585        signedInfoElem = self._soapEnvElem.find('.//ds:SignedInfo',
586                                                namespaces=self._processorNSs)
587        if signedInfoElem is None:
588            raise VerifyError("No <ds:signedInfo/> section found")
589       
590        # Get algorithm used for canonicalization of the SignedInfo
591        # element.  Nb. This is NOT necessarily the same as that used to
592        # canonicalize the reference elements checked above!
593        signedInfoC14nAlg = c14nMethodElem.get("Algorithm")
594        if signedInfoC14nAlg is None:
595            raise VerifyError('No Algorithm attribute set for <signedInfo/>' \
596                              ' section')
597           
598        signedInfoC14nKw = dict(exclusive=signedInfoC14nAlg == DSIG.C14N_EXCL)
599        if signedInfoC14nKw['exclusive']:
600
601            # Check for no inclusive namespaces set
602            inclusiveNSElem = c14nMethodElem.find("InclusiveNamespaces",
603                                                namespaces=self._processorNSs)                   
604            if inclusiveNSElem is not None:
605                pfxListTxt = inclusiveNSElem.get('PrefixList')
606                if pfxListTxt is None:
607                    raise VerifyError('Empty InclusiveNamespaces list for'\
608                                      ' <ds:Reference URI="%s">' % refURI)
609                                             
610                signedInfoC14nKw['inclusive_namespaces'] = pfxListTxt.split()
611            else:
612                # Set to empty list to ensure Exclusive C14N is set for
613                # Canonicalize call
614                signedInfoC14nKw['inclusive_namespaces'] = []
615
616        # Canonicalize the SignedInfo node and take digest
617        c14nSignedInfo = parsedSOAP.dom.canonicalize(subset=signedInfoElem, 
618                                                     **signedInfoC14nKw)                     
619        signedInfoDigestValue = sha(c14nSignedInfo).digest()
620       
621        # Get the signature value in order to check against the digest just
622        # calculated
623        signatureValueElem = self._soapEnvElem.find('.//ds:SignatureValue',
624                                                namespaces=self._processorNSs)
625
626        # Remove base 64 encoding
627        b64EncSignatureValue = signatureValueElem.text
628        signatureValue = base64.decodestring(b64EncSignatureValue)
629
630        # Cache Signature Value here so that a response can include it
631        if self.applySignatureConfirmation:
632            # re-encode string to avoid possible problems with interpretation
633            # of line breaks
634            self.b64EncSignatureValue = b64EncSignatureValue
635        else:
636            self.b64EncSignatureValue = None
637         
638        # Look for X.509 Cert in wsse:BinarySecurityToken element -
639        # Signature may not have included the Binary Security Token in
640        # which case the verifying cert will need to have been set
641        # elsewhere
642        binSecTokElem = self._soapEnvElem.find('.//wsse:BinarySecurityToken',
643                                               namespaces=self._processorNSs)       
644        if binSecTokElem is not None:
645            x509CertTxt = str(binSecTokElem.text)
646           
647            valueType = binSecTokElem.get("ValueType")
648            if valueType in (self.binSecTokValType['X509v3'],
649                             self.binSecTokValType['X509']):
650                # Remove base 64 encoding
651                derString = base64.decodestring(x509CertTxt)
652               
653                # Load from DER format into M2Crypto.X509
654                m2X509Cert = X509.load_cert_string(derString,
655                                                   format=X509.FORMAT_DER)
656                self.__setVerifyingCert(m2X509Cert)
657               
658                x509Stack = X509Stack()
659
660            elif valueType == \
661                self.binSecTokValType['X509PKIPathv1']:
662               
663                derString = base64.decodestring(x509CertTxt)
664                x509Stack = X509StackParseFromDER(derString)
665               
666                # TODO: Check ordering - is the last off the stack the
667                # one to use to verify the message?
668                self.verifyingCert = x509Stack[-1]
669            else:
670                raise WSSecurityError('BinarySecurityToken ValueType ' \
671                    'attribute is not recognised: "%s"' % valueType)
672
673        if self.verifyingCert is None:
674            raise VerifyError("No certificate set for verification of the " \
675                              "signature")
676       
677        # Extract RSA public key from the cert
678        rsaPubKey = self.verifyingCert.pubKey.get_rsa()
679
680        # Apply the signature verification
681        try:
682            verify = rsaPubKey.verify(signedInfoDigestValue, signatureValue)
683        except RSA.RSAError, e:
684            raise VerifyError("Error in Signature: " + str(e))
685       
686        if not verify:
687            raise InvalidSignature("Invalid signature")
688       
689        log.debug("Verified signature")
690       
691        # Verify chain of trust
692        x509Stack.verifyCertChain(x509Cert2Verify=self.verifyingCert,
693                                  caX509Stack=self.__caX509Stack)
694
695        log.debug("Verified certificate chain of trust")
696       
697        self._verifyTimeStamp(parsedSOAP) 
698
699        log.info("Signature OK")       
700
701
702    def canonicalize(self, **kw):
703        '''ElementTree based Canonicalization - See ElementC14N for keyword
704        info'''
705        f = StringIO()
706        ElementC14N.write(ElementC14N.build_scoped_tree(self._soapEnvElem), 
707                          f, 
708                          **kw)
709        return f.getvalue()
Note: See TracBrowser for help on using the repository browser.