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

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