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

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

Fixes following update to NOCS deployment.

  • m2CryptoSSLUtility.HTTPSConnection now overrides putrequest in order to ensure that the URL path is string type. unicode type gives an error
  • added a unit test for BrowsePDP - gatekeeper for MOLES/CSML access control.
Line 
1"""WS-Security test class includes digital signature handler
2
3NERC Data Grid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "01/09/06"
7__copyright__ = "(C) 2007 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: wsSecurity.py 3790 2008-04-17 12:46:30Z pjkersha $'
13
14import re
15
16# Digest and signature/verify
17from sha import sha
18from M2Crypto import X509, BIO, RSA
19import base64
20
21# Conditional import as this is required for the encryption
22# handler
23try:
24    # For shared key encryption
25    from Crypto.Cipher import AES, DES3
26except:
27    from warnings import warn
28    warn('Crypto.Cipher not available: EncryptionHandler disabled!',
29         RuntimeWarning)
30    class AES:
31        MODE_ECB = None
32        MODE_CBC = None
33       
34    class DES3: 
35        MODE_CBC = None
36
37import os
38
39import ZSI
40from ZSI.wstools.Namespaces import DSIG, ENCRYPTION, WSU, WSA200403, \
41                                   SOAP, SCHEMA # last included for xsi
42
43from ZSI.wstools.Namespaces import OASIS as _OASIS
44                                 
45from ZSI.TC import ElementDeclaration,TypeDefinition
46from ZSI.generate.pyclass import pyclass_type
47
48from ZSI.wstools.Utility import DOMException
49from ZSI.wstools.Utility import NamespaceError, MessageInterface, ElementProxy
50
51# Canonicalization
52from ZSI.wstools.c14n import Canonicalize
53
54from xml.dom import Node
55from xml.xpath.Context import Context
56from xml import xpath
57
58# Include for re-parsing doc ready for canonicalization in sign method - see
59# associated note
60from xml.dom.ext.reader.PyExpat import Reader
61
62# Enable settings from a config file
63from ndg.security.common.wssecurity import WSSecurityConfig
64
65from ndg.security.common.X509 import X509Cert, X509CertParse, X509CertRead, \
66X509Stack, X509StackParseFromDER
67
68from datetime import datetime, timedelta
69import logging
70log = logging.getLogger(__name__)
71
72
73class _ENCRYPTION(ENCRYPTION):
74    '''Derived from ENCRYPTION class to add in extra 'tripledes-cbc' - is this
75    any different to 'des-cbc'?  ENCRYPTION class implies that it is the same
76    because it's assigned to 'BLOCK_3DES' ??'''
77    BLOCK_TRIPLEDES = "http://www.w3.org/2001/04/xmlenc#tripledes-cbc"
78
79class _WSU(WSU):
80    '''Try different utility namespace for use with WebSphere'''
81    #UTILITY = "http://schemas.xmlsoap.org/ws/2003/06/utility"
82    UTILITY = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
83
84class OASIS(_OASIS):
85    # wss4j 1.5.3
86    WSSE11 = "http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd"
87    # wss4j 1.5.1
88    #WSSE11 = "http://docs.oasis-open.org/wss/2005/xx/oasis-2005xx-wss-wssecurity-secext-1.1.xsd"
89       
90def getElements(node, nameList):
91    '''DOM Helper function for getting child elements from a given node'''
92    # Avoid sub-string matches
93    nameList = isinstance(nameList, basestring) and [nameList] or nameList
94    return [n for n in node.childNodes if str(n.localName) in nameList]
95
96
97def getChildNodes(node, nodeList=None):
98    if nodeList is None:
99        nodeList = [node] 
100    return _getChildNodes(node, nodeList)
101           
102def _getChildNodes(node, nodeList):
103
104    if node.attributes is not None:
105        nodeList += node.attributes.values() 
106    nodeList += node.childNodes
107    for childNode in node.childNodes:
108        _getChildNodes(childNode, nodeList)
109    return nodeList
110
111
112
113class WSSecurityError(Exception):
114    """For WS-Security generic exceptions not covered by other exception
115    classes in this module"""
116
117class InvalidCertChain(Exception):   
118    """Raised from SignatureHandler.verify if the certificate submitted to
119    verify a signature is not from a known CA"""
120   
121class VerifyError(Exception):
122    """Raised from SignatureHandler.verify if an error occurs in the signature
123    verification"""
124 
125class TimestampError(Exception):
126    """Raised from SignatureHandler._verifyTimestamp if there is a problem with
127    the created or expiry times in an input message Timestamp"""
128   
129class InvalidSignature(Exception):
130    """Raised from verify method for an invalid signature"""
131
132class SignatureError(Exception):
133    """Flag if an error occurs during signature generation"""
134       
135class SignatureHandler(object):
136    """Class to handle signature and verification of signature with
137    WS-Security
138   
139    @cvar binSecTokValType: supported ValueTypes for BinarySecurityToken
140    element in WSSE header
141    @type binSecTokValType: dict
142   
143    @ivar addTimestamp: set to true to add a timestamp to outbound messages
144    @type addTimestamp: bool
145
146    @ivar applySignatureConfirmation: for servers - set this flag to enable the
147    signature value of a request to be recorded and included with a
148    SignatureConfirmation element in the response.
149    @type applySignatureConfirmation: bool
150   
151    @param b64EncSignatureValue: base 64 encoded signature value for the last
152    message verified
153    @type b64EncSignatureValue: string/None"""
154
155   
156    binSecTokValType = {
157        "X509PKIPathv1": OASIS.X509TOKEN.X509PKIPathv1,
158        "X509":          OASIS.X509TOKEN.X509,
159        "X509v3":        OASIS.X509TOKEN.X509+"v3"
160    }
161
162
163    #_________________________________________________________________________
164    def __init__(self, cfgFilePath=None, cfgFileSection='DEFAULT',
165                 cfgClass=WSSecurityConfig, **kw):
166        '''
167        @reqBinSecTokValType: set the ValueType for the BinarySecurityToken
168        added to the WSSE header for a signed message.  See
169        __setReqBinSecTokValType method and binSecTokValType class variable
170        for options.  binSecTokValType determines whether signingCert or
171        signingCertChain attributes will be used.       
172        @type binSecTokValType: string
173       
174        @param verifyingCert: X.509 certificate used by verify method to
175        verify a message.  This argument can be omitted if the message to
176        be verified contains the X.509 certificate in the
177        BinarySecurityToken element.  In this case, the cert read from the
178        message will be assigned to the verifyingCert attribute.
179        @type verifyingCert: M2Crypto.X509.X509 /
180        ndg.security.common.X509.X509Cert
181       
182        @param verifyingCertFilePath: alternative input to the above, pass
183        the file path to the certificate stored in a file
184        @type verifyingCertFilePath: string
185       
186        @param signingCert: certificate associated with private key used to
187        sign a message.  The sign method will add this to the
188        BinarySecurityToken element of the WSSE header.  binSecTokValType
189        attribute must be set to 'X509' or 'X509v3' ValueTyep.  As an
190        alternative, use signingCertChain - see below...
191        @type signingCert: M2Crypto.X509.X509 /
192        ndg.security.common.X509.X509Cert
193       
194        @param signingCertFilePath: alternative input to the above, pass
195        the file path to the certificate stored in a file
196        @type signingCertFilePath: string
197       
198        @param signingCertChain: pass a list of certificates constituting a
199        chain of trust from the certificate used to verifying the signature
200        backward to the CA cert.  The CA cert need not be included.  To use
201        this option, reqBinSecTokValType must be set to the 'X509PKIPathv1'
202        ValueType
203        @type signingCertChain: list or tuple
204       
205        @param signingPriKey: private key used to be sign method to sign
206        message
207        @type signingPriKey: M2Crypto.RSA.
208       
209        @param signingPriKeyFilePath: equivalent to the above but pass
210        private key from PEM file
211        @type signingPriKeyFilePath: string
212       
213        @param signingPriKeyPwd: password protecting private key.  Set /
214        default to None if there is no password.
215        @type signingPriKeyPwd: string or None
216       
217        @param caCertDirPath: establish trust for signature verification.
218        This is a directory containing CA certificates.  These are used to
219        verify the certificate used to verify the message signature.
220        @type caCertDirPath: string
221       
222        @param caCertFilePathList: same as above except pass in a list of
223        file paths instead of a single directory name.
224        @type caCertFilePathList: list or tuple
225       
226        @param addTimestamp: set to true to add a timestamp to outbound
227        messages
228        @type addTimestamp: bool
229       
230        @param : for servers - set this flag to enable the signature value of a
231        request to be recorded and included with a SignatureConfirmation
232        element in the response.
233        @type : bool
234       
235        @param refC14nKw: dictionary of keywords to reference
236        Canonicalization.  Use 'unsuppressedPrefixes' keyword to set
237        unsuppressedPrefixes.
238        @type refC14nKw: dict
239       
240        @param signedInfoC14nKw: keywords to Signed Info Canonicalization.
241        It uses the same format as refC14nKw above.
242        @type signedInfoC14nKw: dict
243        '''
244        log.debug("SignatureHandler.__init__ ...")
245       
246        # WSSecurityConfig is the default class for reading config params but
247        # alternative derivative class may be passed in instead.
248        if not issubclass(cfgClass, WSSecurityConfig):
249            raise TypeError("%s is not a sub-class of WSSecurityConfig" % \
250                            cfgClass)
251        self.cfg = cfgClass()
252       
253        # Read parameters from config file if set
254        if cfgFilePath:
255            log.debug("SignatureHandler.__init__: Processing config file...")
256            self.cfg.read(cfgFilePath)
257            self.cfg.parse(section=cfgFileSection)
258       
259        # Also update config from keywords set
260        log.debug("SignatureHandler.__init__: setting config from keywords...")
261        self.cfg.update(kw)
262       
263       
264        self.__setReqBinSecTokValType(self.cfg['reqBinSecTokValType'])
265       
266        # Set keywords for canonicalization of SignedInfo and reference
267        # elements
268        # TODO: get rid of refC14nKw and signedInfoC14nKw options
269        if len(self.cfg.get('refC14nInclNS', [])):
270            self.__setRefC14nKw({'unsuppressedPrefixes':
271                                 self.cfg['refC14nInclNS']})
272        else:
273            self.__setRefC14nKw(self.cfg['refC14nKw'])
274
275   
276        if len(self.cfg.get('signedInfoC14nNS', [])):
277            self.__setSignedInfoC14nKw({'unsuppressedPrefixes':
278                                        self.cfg['signedInfoC14nNS']})
279        else:
280            self.__setSignedInfoC14nKw(self.cfg['signedInfoC14nKw'])
281           
282
283        self.__setVerifyingCert(self.cfg['verifyingCert'])
284        self.__setVerifyingCertFilePath(self.cfg['verifyingCertFilePath'])
285       
286        self.__setSigningCert(self.cfg['signingCert'])
287        self.__setSigningCertFilePath(self.cfg['signingCertFilePath'])
288
289        if self.cfg.get('signingCertChain'):
290            self.__setSigningCertChain(self.cfg['signingCertChain'])
291        else:
292            self.__signingCertChain = None   
293             
294        # MUST be set before __setSigningPriKeyFilePath / __setSigningPriKey
295        # are called
296        self.__setSigningPriKeyPwd(self.cfg['signingPriKeyPwd'])
297       
298        if self.cfg.get('signingPriKey'):
299            # Don't allow None for private key setting
300            self.__setSigningPriKey(self.cfg['signingPriKey'])
301           
302        self.__setSigningPriKeyFilePath(self.cfg['signingPriKeyFilePath'])
303       
304        # CA certificate(s) for verification of X.509 certificate used with
305        # signature.
306        if self.cfg.get('caCertDirPath'):
307            self.caCertDirPath = self.cfg['caCertDirPath']
308           
309        elif self.cfg.get('caCertFilePathList'):
310            self.caCertFilePathList = self.cfg['caCertFilePathList']
311           
312        self.addTimestamp = self.cfg['addTimestamp']
313        self.applySignatureConfirmation=self.cfg['applySignatureConfirmation']
314        self.b64EncSignatureValue = None
315       
316        log.debug("WSSE Config = %s" % self.cfg)
317
318               
319    #_________________________________________________________________________
320    def __setReqBinSecTokValType(self, value):
321        """Set ValueType attribute for BinarySecurityToken used in a request
322         
323        @type value: string
324        @param value: name space for BinarySecurityToken ValueType check
325        'binSecTokValType' class variable for supported types.  Input can be
326        shortened to binSecTokValType keyword if desired.
327        """
328       
329        if value in self.__class__.binSecTokValType:
330            self.__reqBinSecTokValType = self.__class__.binSecTokValType[value]
331 
332        elif value in self.__class__.binSecTokValType.values():
333            self.__reqBinSecTokValType = value
334        else:
335            raise WSSecurityError, \
336                'Request BinarySecurityToken ValueType "%s" not recognised' %\
337                value
338           
339       
340    reqBinSecTokValType = property(fset=__setReqBinSecTokValType,
341         doc="ValueType attribute for BinarySecurityToken used in request")
342       
343
344    #_________________________________________________________________________
345    def __checkC14nKw(self, kw):
346        """Check keywords for canonicalization in signing process - generic
347        method for setting keywords for reference element and SignedInfo
348        element c14n
349       
350        @type kw: dict
351        @param kw: keyword used with ZSI.wstools.Utility.Canonicalization"""
352       
353        # Check for dict/None - Set to None in order to use inclusive
354        # canonicalization
355        if kw is not None and not isinstance(kw, dict):
356            # Otherwise keywords must be a dictionary
357            raise AttributeError, \
358                "Expecting dictionary type for reference c14n keywords"
359               
360        elif kw.get('unsuppressedPrefixes') and \
361             not isinstance(kw['unsuppressedPrefixes'], list) and \
362             not isinstance(kw['unsuppressedPrefixes'], tuple):
363            raise AttributeError, \
364                'Expecting list or tuple of prefix names for "%s" keyword' % \
365                'unsuppressedPrefixes'
366       
367               
368    #_________________________________________________________________________
369    def __setRefC14nKw(self, kw):
370        """Set keywords for canonicalization of reference elements in the
371        signing process"""
372        self.__checkC14nKw(kw)                   
373        self.__refC14nKw = kw
374       
375    refC14nKw = property(fset=__setRefC14nKw,
376                         doc="Keywords for c14n of reference elements")
377       
378               
379    #_________________________________________________________________________
380    def __setSignedInfoC14nKw(self, kw):
381        """Set keywords for canonicalization of SignedInfo element in the
382        signing process"""
383        self.__checkC14nKw(kw)                   
384        self.__signedInfoC14nKw = kw
385       
386    signedInfoC14nKw = property(fset=__setSignedInfoC14nKw,
387                                doc="Keywords for c14n of SignedInfo element")
388
389
390    #_________________________________________________________________________
391    def __refC14nIsExcl(self):
392        return isinstance(self.__refC14nKw, dict) and \
393               isinstance(self.__refC14nKw.get('unsuppressedPrefixes'), list)
394               
395    refC14nIsExcl = property(fget=__refC14nIsExcl,
396    doc="Return True/False c14n for reference elements set to exclusive type")
397     
398
399    #_________________________________________________________________________
400    def __signedInfoC14nIsExcl(self):
401        return isinstance(self.__signedInfoC14nKw, dict) and \
402        isinstance(self.__signedInfoC14nKw.get('unsuppressedPrefixes'), list)
403               
404    signedInfoC14nIsExcl = property(fget=__signedInfoC14nIsExcl,
405    doc="Return True/False c14n for SignedInfo element set to exclusive type")
406   
407   
408    #_________________________________________________________________________
409    def __setCert(self, cert):
410        """filter and convert input cert to signing verifying cert set
411        property methods.  For signingCert, set to None if it is not to be
412        included in the SOAP header.  For verifyingCert, set to None if this
413        cert can be expected to be retrieved from the SOAP header of the
414        message to be verified
415       
416        @type: ndg.security.common.X509.X509Cert / M2Crypto.X509.X509 /
417        string or None
418        @param cert: X.509 certificate. 
419       
420        @rtype ndg.security.common.X509.X509Cert
421        @return X.509 certificate object"""
422       
423        if cert is None or isinstance(cert, X509Cert):
424            # ndg.security.common.X509.X509Cert type / None
425            return cert
426           
427        elif isinstance(cert, X509.X509):
428            # M2Crypto.X509.X509 type
429            return X509Cert(m2CryptoX509=cert)
430           
431        elif isinstance(cert, basestring):
432            return X509CertParse(cert)
433       
434        else:
435            raise AttributeError, "X.509 Cert. must be type: " + \
436                "ndg.security.common.X509.X509Cert, M2Crypto.X509.X509 or " +\
437                "a base64 encoded string"
438
439   
440    #_________________________________________________________________________
441    def __getVerifyingCert(self):
442        '''Return X.509 cert object corresponding to cert used to verify the
443        signature in the last call to verify
444       
445         * Cert will correspond to one used in the LATEST call to verify, on
446         the next call it will be replaced
447         * if verify hasn't been called, the cert will be None
448       
449        @rtype: M2Crypto.X509.X509
450        @return: certificate object
451        '''
452        return self.__verifyingCert
453
454
455    #_________________________________________________________________________
456    def __setVerifyingCert(self, verifyingCert):
457        "Set property method for X.509 cert. used to verify a signature"
458        self.__verifyingCert = self.__setCert(verifyingCert)
459   
460        # Reset file path as it may no longer apply
461        self.__verifyingCertFilePath = None
462       
463    verifyingCert = property(fset=__setVerifyingCert,
464                             fget=__getVerifyingCert,
465                             doc="Set X.509 Cert. for verifying signature")
466
467
468    #_________________________________________________________________________
469    def __setVerifyingCertFilePath(self, verifyingCertFilePath):
470        "Set method for Service X.509 cert. file path property"
471       
472        if isinstance(verifyingCertFilePath, basestring):
473            self.__verifyingCert = X509CertRead(verifyingCertFilePath)
474           
475        elif verifyingCertFilePath is not None:
476            raise AttributeError, \
477            "Verifying X.509 Cert. file path must be None or a valid string"
478       
479        self.__verifyingCertFilePath = verifyingCertFilePath
480       
481    verifyingCertFilePath = property(fset=__setVerifyingCertFilePath,
482                    doc="file path of X.509 Cert. for verifying signature")
483
484   
485    #_________________________________________________________________________
486    def __getSigningCert(self):
487        '''Return X.509 cert object corresponding to cert used with
488        signature
489       
490        @rtype: M2Crypto.X509.X509
491        @return: certificate object
492        '''
493        return self.__signingCert
494
495
496    #_________________________________________________________________________
497    def __setSigningCert(self, signingCert):
498        "Set property method for X.509 cert. to be included with signature"
499        self.__signingCert = self.__setCert(signingCert)
500   
501        # Reset file path as it may no longer apply
502        self.__signingCertFilePath = None
503       
504    signingCert = property(fget=__getSigningCert,
505                           fset=__setSigningCert,
506                           doc="X.509 Cert. to include signature")
507
508 
509    #_________________________________________________________________________
510    def __setSigningCertFilePath(self, signingCertFilePath):
511        "Set signature X.509 cert property method"
512       
513        if isinstance(signingCertFilePath, basestring):
514            self.__signingCert = X509CertRead(signingCertFilePath)
515           
516        elif signingCertFilePath is not None:
517            raise AttributeError, \
518                "Signature X.509 cert. file path must be a valid string"
519       
520        self.__signingCertFilePath = signingCertFilePath
521       
522       
523    signingCertFilePath = property(fset=__setSigningCertFilePath,
524                   doc="File path X.509 cert. to include with signed message")
525
526   
527    #_________________________________________________________________________
528    def __setSigningCertChain(self, signingCertChain):
529        '''Signature set-up with "X509PKIPathv1" BinarySecurityToken
530        ValueType.  Use an X.509 Stack to store certificates that make up a
531        chain of trust to certificate used to verify a signature
532       
533        @type signingCertChain: list or tuple of M2Crypto.X509.X509Cert or
534        ndg.security.common.X509.X509Cert types.
535        @param signingCertChain: list of certificate objects making up the
536        chain of trust.  The last certificate is the one associated with the
537        private key used to sign the message.'''
538       
539        if not isinstance(signingCertChain, list) and \
540           not isinstance(signingCertChain, tuple):
541            raise WSSecurityError, \
542                        'Expecting a list or tuple for "signingCertChain"'
543       
544        self.__signingCertChain = X509Stack()
545           
546        for cert in signingCertChain:
547            self.__signingCertChain.push(cert)
548           
549    signingCertChain = property(fset=__setSigningCertChain,
550               doc="Cert.s in chain of trust to cert. used to verify msg.")
551
552 
553    #_________________________________________________________________________
554    def __setSigningPriKeyPwd(self, signingPriKeyPwd):
555        "Set method for private key file password used to sign message"
556        if signingPriKeyPwd is not None and \
557           not isinstance(signingPriKeyPwd, basestring):
558            raise AttributeError, \
559                "Signing private key password must be None or a valid string"
560       
561        self.__signingPriKeyPwd = signingPriKeyPwd
562       
563    signingPriKeyPwd = property(fset=__setSigningPriKeyPwd,
564             doc="Password protecting private key file used to sign message")
565
566 
567    #_________________________________________________________________________
568    def __setSigningPriKey(self, signingPriKey):
569        """Set method for client private key
570       
571        Nb. if input is a string, signingPriKeyPwd will need to be set if
572        the key is password protected.
573       
574        @type signingPriKey: M2Crypto.RSA.RSA / string
575        @param signingPriKey: private key used to sign message"""
576       
577        if isinstance(signingPriKey, basestring):
578            pwdCallback = lambda *ar, **kw: self.__signingPriKeyPwd
579            self.__signingPriKey = RSA.load_key_string(signingPriKey,
580                                                       callback=pwdCallback)
581
582        elif isinstance(signingPriKey, RSA.RSA):
583            self.__signingPriKey = signingPriKey
584                   
585        else:
586            raise AttributeError, "Signing private key must be a valid " + \
587                                  "M2Crypto.RSA.RSA type or a string"
588               
589    signingPriKey = property(fset=__setSigningPriKey,
590                             doc="Private key used to sign outbound message")
591
592 
593    #_________________________________________________________________________
594    def __setSigningPriKeyFilePath(self, signingPriKeyFilePath):
595        """Set method for client private key file path
596       
597        signingPriKeyPwd MUST be set prior to a call to this method"""
598        if isinstance(signingPriKeyFilePath, basestring):                           
599            try:
600                # Read Private key to sign with   
601                priKeyFile = BIO.File(open(signingPriKeyFilePath)) 
602                pwdCallback = lambda *ar, **kw: self.__signingPriKeyPwd                                           
603                self.__signingPriKey = RSA.load_key_bio(priKeyFile, 
604                                                        callback=pwdCallback)           
605            except Exception, e:
606                raise AttributeError, \
607                                "Setting private key for signature: %s" % e
608       
609        elif signingPriKeyFilePath is not None:
610            raise AttributeError, \
611                        "Private key file path must be a valid string or None"
612       
613        self.__signingPriKeyFilePath = signingPriKeyFilePath
614       
615    signingPriKeyFilePath = property(fset=__setSigningPriKeyFilePath,
616                      doc="File path for private key used to sign message")
617
618    def __caCertIsSet(self):
619        '''Check for CA certificate set (X.509 Stack has been created)'''
620        return hasattr(self, '_SignatureHandler__caX509Stack')
621   
622    caCertIsSet = property(fget=__caCertIsSet,
623           doc='Check for CA certificate set (X.509 Stack has been created)')
624   
625    #_________________________________________________________________________
626    def __appendCAX509Stack(self, caCertList):
627        '''Store CA certificates in an X.509 Stack
628       
629        @param caCertList: list or tuple
630        @type caCertList: M2Crypto.X509.X509 certificate objects'''
631       
632        if not self.caCertIsSet:
633            self.__caX509Stack = X509Stack()
634           
635        for cert in caCertList:
636            self.__caX509Stack.push(cert)
637
638
639    #_________________________________________________________________________
640    def __setCAX509StackFromDir(self, caCertDir):
641        '''Read CA certificates from directory and add them to the X.509
642        stack
643       
644        @param caCertDir: string
645        @type caCertDir: directory from which to read CA certificate files'''
646       
647        # Mimic OpenSSL -CApath option which expects directory of CA files
648        # of form <Hash cert subject name>.0
649        reg = re.compile('\d+\.0')
650        try:
651            caCertList = [X509CertRead(caFile) \
652                          for caFile in os.listdir(caCertDir) \
653                          if reg.match(caFile)]
654        except Exception, e:
655            raise WSSecurityError, \
656                'Loading CA certificate "%s" from CA directory: %s' % \
657                                                        (caFile, str(e))
658                   
659        # Add to stack
660        self.__appendCAX509Stack(caCertList)
661       
662    caCertDirPath = property(fset=__setCAX509StackFromDir,
663                      doc="Dir. containing CA cert.s used for verification")
664
665
666    #_________________________________________________________________________
667    def __setCAX509StackFromCertFileList(self, caCertFilePathList):
668        '''Read CA certificates from file and add them to the X.509
669        stack
670       
671        @type caCertFilePathList: list or tuple
672        @param caCertFilePathList: list of file paths for CA certificates to
673        be used to verify certificate used to sign message'''
674       
675        if not isinstance(caCertFilePathList, list) and \
676           not isinstance(caCertFilePathList, tuple):
677            raise WSSecurityError, \
678                        'Expecting a list or tuple for "caCertFilePathList"'
679
680        # Mimic OpenSSL -CApath option which expects directory of CA files
681        # of form <Hash cert subject name>.0
682        try:
683            caCertList = [X509CertRead(caFile) \
684                          for caFile in caCertFilePathList]
685        except Exception, e:
686            raise WSSecurityError, \
687                    'Loading CA certificate "%s" from file list: %s' % \
688                                                        (caFile, str(e))
689                   
690        # Add to stack
691        self.__appendCAX509Stack(caCertList)
692       
693    caCertFilePathList = property(fset=__setCAX509StackFromCertFileList,
694                      doc="List of CA cert. files used for verification")
695               
696
697    def _applySignatureConfirmation(self, wsseElem):
698        '''Add SignatureConfirmation element - as specified in WS-Security 1.1
699        - to outbound message on receipt of a signed message from a client
700       
701        This has been added in through tests vs. Apache Axis Rampart client
702       
703        @type wsseElem:
704        @param wsseElem: wsse:Security element'''
705        if self.b64EncSignatureValue is None:
706            log.info(\
707"SignatureConfirmation element requested but no request signature was cached")
708            return
709       
710        sigConfirmElem = wsseElem.createAppendElement(OASIS.WSSE11, 
711                                                      'SignatureConfirmation')
712       
713        # Add ID so that the element can be included in the signature
714        sigConfirmElem._etree.set('wsu:Id', "signatureConfirmation")
715
716        # Add ID so that the element can be included in the signature
717        # Following line is a hck to avoid appearance of #x when serialising \n
718        # chars TODO: why is this happening??
719        b64EncSignatureValue = ''.join(self.b64EncSignatureValue.split('\n'))
720        sigConfirmElem._etree.set('Value', b64EncSignatureValue)
721       
722       
723    def _addTimeStamp(self, wsseElem, elapsedSec=60*5):
724        '''Add a timestamp to wsse:Security section of message to be signed
725        e.g.
726            <wsu:Timestamp wsu:Id="timestamp">
727               <wsu:Created>2008-03-25T14:40:37.319Z</wsu:Created>
728               <wsu:Expires>2008-03-25T14:45:37.319Z</wsu:Expires>
729            </wsu:Timestamp>
730       
731        @type wsseElem:
732        @param wsseElem: wsse:Security element
733        @type elapsedSec: int   
734        @param elapsedSec: time interval in seconds between Created and Expires
735        time stamp values
736        '''
737        # Nb. wsu ns declaration is in the SOAP header elem
738        timestampElem = wsseElem.createAppendElement(_WSU.UTILITY, 'Timestamp')
739
740        # Add ID so that the timestamp element can be included in the signature
741        timestampElem._etree.set('wsu:Id', "timestamp")
742       
743        # Value type can be any be any one of those supported via
744        # binSecTokValType
745        createdElem = timestampElem.createAppendElement(_WSU.UTILITY,'Created')
746        dtCreatedTime = datetime.utcnow()
747        createdElem.createAppendTextNode(dtCreatedTime.isoformat('T')+'Z')
748       
749        dtExpiryTime = dtCreatedTime + timedelta(seconds=elapsedSec)
750        expiresElem = timestampElem.createAppendElement(_WSU.UTILITY,'Expires')
751        expiresElem.createAppendTextNode(dtExpiryTime.isoformat('T')+'Z')
752       
753
754    def _verifyTimeStamp(self, parsedSOAP, ctxt):
755        """Call from verify to check timestamp if found. 
756       
757        TODO: refactor input args - maybe these should by object attributes
758       
759        @type parsedSOAP: ZSI.parse.ParsedSoap
760        @param parsedSOAP: object contain parsed SOAP message received from
761        sender
762        @type ctxt:
763        @param ctxt: XPath context object"""
764
765        try:
766            timestampNode = xpath.Evaluate('//wsse:Timestamp',
767                                           contextNode=parsedSOAP.dom,
768                                           context=ctxt)[0]
769        except:
770            log.warning("Verifying message - No timestamp element found")
771            return
772       
773        # Time now
774        dtNow = datetime.utcnow()
775       
776        createdNode = timestampNode.getElementsByTagName("Created")
777       
778        # Workaround for fractions of second
779        try:
780            [createdDateTime, createdSecFraction]=createdNode.nodeValue.split()
781        except ValueError, e:
782            raise ValueError("Parsing timestamp Created element: %s" % e)
783       
784        dtCreated = datetime.strptime(createdDateTime, '%Y-%m-%dT%H:%M:%S')
785        dtCreated += timedelta(seconds=int(createdSecFraction))
786        if dtCreated >= dtNow:
787            raise TimestampError(\
788        "Timestamp created time %s is equal to or after the current time %s" %\
789                (dtCreated, dtNow))
790       
791        expiresNode = timestampNode.getElementsByTagName("Expires")
792        if expiresNode is None:
793            log.warning(\
794                "Verifying message - No Expires element found in Timestamp")
795            return
796
797        try:
798            [expiresDateTime, expiresSecFraction]=expiresNode.nodeValue.split()
799        except ValueError, e:
800            raise ValueError("Parsing timestamp Expires element: %s" % e)
801       
802        dtCreated = datetime.strptime(expiresDateTime, '%Y-%m-%dT%H:%M:%S')
803        dtCreated += timedelta(seconds=int(createdSecFraction))
804        if dtExpiry > dtNow:
805            raise TimestampError(\
806                "Timestamp expiry time %s is after the current time %s" % \
807                (dtCreated, dtNow))
808           
809                   
810    #_________________________________________________________________________
811    def sign(self, soapWriter):
812        '''Sign the message body and binary security token of a SOAP message
813       
814        @type soapWriter: ZSI.writer.SoapWriter
815        @param soapWriter: ZSI object to write SOAP message
816        '''
817       
818        # Namespaces for XPath searches
819        processorNss = \
820        {
821            'ds':     DSIG.BASE, 
822            'wsu':    _WSU.UTILITY, 
823            'wsse':   OASIS.WSSE, 
824            'SOAP-ENV':"http://schemas.xmlsoap.org/soap/envelope/" 
825        }
826
827        # Add X.509 cert as binary security token
828        if self.__reqBinSecTokValType==self.binSecTokValType['X509PKIPathv1']:
829            binSecTokVal = base64.encodestring(self.__signingCertChain.asDER())
830        else:
831            # Assume X.509 / X.509 vers 3
832            binSecTokVal = base64.encodestring(self.__signingCert.asDER())
833
834        soapWriter._header.setNamespaceAttribute('wsse', OASIS.WSSE)
835        soapWriter._header.setNamespaceAttribute('wsse11', OASIS.WSSE11)
836        soapWriter._header.setNamespaceAttribute('wsu', _WSU.UTILITY)
837        soapWriter._header.setNamespaceAttribute('ds', DSIG.BASE)
838       
839        try:
840            refC14nPfxSet = len(self.__refC14nKw['unsuppressedPrefixes']) > 0
841        except KeyError:
842            refC14nPfxSet = False
843
844        try:
845            signedInfoC14nPfxSet = \
846                len(self.__signedInfoC14nKw['unsuppressedPrefixes']) > 0
847        except KeyError:
848            signedInfoC14nPfxSet = False
849               
850        if refC14nPfxSet or refC14nPfxSet:
851           soapWriter._header.setNamespaceAttribute('ec', DSIG.C14N_EXCL)
852       
853        # Check <wsse:security> isn't already present in header
854        wsseNodes = soapWriter.dom.evaluate('.//wsse:security',
855                                            processorNss=processorNss)
856        if len(wsseNodes) > 1:
857            raise SignatureError('wsse:Security element is already present')
858
859        # Add WSSE element
860        wsseElem = soapWriter._header.createAppendElement(OASIS.WSSE, 
861                                                          'Security')
862        wsseElem.setNamespaceAttribute('wsse', OASIS.WSSE)
863       
864        # Recipient MUST parse and check this signature
865        wsseElem._etree.set('SOAP-ENV:mustUnderstand', "1")
866       
867        # Binary Security Token element will contain the X.509 cert
868        # corresponding to the private key used to sing the message
869        binSecTokElem = wsseElem.createAppendElement(OASIS.WSSE, 
870                                                     'BinarySecurityToken')
871       
872        # Value type can be any be any one of those supported via
873        # binSecTokValType
874        binSecTokElem._etree.set('ValueType', self.__reqBinSecTokValType)
875
876        encodingType = \
877"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
878        binSecTokElem._etree.set('EncodingType', encodingType)
879       
880        # Add ID so that the binary token can be included in the signature
881        binSecTokElem._etree.set('wsu:Id', "binaryToken")
882
883        binSecTokElem.createAppendTextNode(binSecTokVal)
884
885
886        # Timestamp
887        if self.addTimestamp:
888            self._addTimeStamp(wsseElem)
889           
890        # Signature Confirmation
891        if self.applySignatureConfirmation: 
892            self._applySignatureConfirmation(wsseElem)
893       
894        # Signature
895        signatureElem = wsseElem.createAppendElement(DSIG.BASE, 'Signature')
896        signatureElem.setNamespaceAttribute('ds', DSIG.BASE)
897       
898        # Signature - Signed Info
899        signedInfoElem = signatureElem.createAppendElement(DSIG.BASE, 
900                                                           'SignedInfo')
901       
902        # Signed Info - Canonicalization method
903        c14nMethodElem = signedInfoElem.createAppendElement(DSIG.BASE,
904                                                    'CanonicalizationMethod')
905       
906        # Set based on 'signedInfoIsExcl' property
907        c14nAlgOpt = (DSIG.C14N, DSIG.C14N_EXCL)
908        signedInfoC14nAlg = c14nAlgOpt[int(self.signedInfoC14nIsExcl)]
909       
910        c14nMethodElem._etree.set('Algorithm', signedInfoC14nAlg)
911       
912        if signedInfoC14nPfxSet:
913            c14nInclNamespacesElem = c14nMethodElem.createAppendElement(\
914                                                    signedInfoC14nAlg,
915                                                    'InclusiveNamespaces')
916            c14nInclNamespacesElem._etree.set('PrefixList', 
917                ' '.join(self.__signedInfoC14nKw['unsuppressedPrefixes']))
918       
919        # Signed Info - Signature method
920        sigMethodElem = signedInfoElem.createAppendElement(DSIG.BASE,
921                                                           'SignatureMethod')
922        sigMethodElem._etree.set('Algorithm', DSIG.SIG_RSA_SHA1)
923       
924        # Signature - Signature value
925        signatureValueElem = signatureElem.createAppendElement(DSIG.BASE, 
926                                                             'SignatureValue')
927       
928        # Key Info
929        KeyInfoElem = signatureElem.createAppendElement(DSIG.BASE, 'KeyInfo')
930        secTokRefElem = KeyInfoElem.createAppendElement(OASIS.WSSE, 
931                                                  'SecurityTokenReference')
932       
933        # Reference back to the binary token included earlier
934        wsseRefElem = secTokRefElem.createAppendElement(OASIS.WSSE, 
935                                                        'Reference')
936        wsseRefElem._etree.set('URI', "#binaryToken")
937       
938        # Add Reference to body so that it can be included in the signature
939        soapWriter.body._etree.set('wsu:Id', "body")
940        soapWriter.body._etree.set('xmlns:wsu', _WSU.UTILITY)
941
942        # Serialize and re-parse prior to reference generation - calculating
943        # canonicalization based on soapWriter.dom.node seems to give an
944        # error: the order of wsu:Id attribute is not correct
945#        try:
946#            docNode = Reader().fromString(str(soapWriter))
947#        except Exception, e:
948#            raise SignatureError("Error parsing SOAP message for signing: %s"%\
949#                                 e)
950#
951#        ctxt = Context(docNode, processorNss=processorNss)
952#        refNodes = xpath.Evaluate('//*[@wsu:Id]',
953#                                  contextNode=docNode,
954#                                  context=ctxt)
955        import pdb;pdb.set_trace()
956
957        refNodes = soapWriter.dom.evaluate('.//*[@wsu:Id]', 
958                                           processorNss=processorNss)
959       
960        # Set based on 'signedInfoIsExcl' property
961        refC14nAlg = c14nAlgOpt[self.refC14nIsExcl]
962       
963        # 1) Reference Generation
964        #
965        # Find references
966        for refNode in refNodes:
967           
968            # Set URI attribute to point to reference to be signed
969            #uri = u"#" + refNode.getAttribute('wsu:Id')
970            uri = u"#" + refNode.attributes[(_WSU.UTILITY, 'Id')].value
971           
972            # Canonicalize reference
973#            refC14n = Canonicalize(refNode, **self.__refC14nKw)
974           
975            refSubsetList = getChildNodes(refNode)
976            refC14n = Canonicalize(docNode, 
977                                   None, 
978                                   subset=refSubsetList,
979                                   **self.__refC14nKw)
980           
981            # Calculate digest for reference and base 64 encode
982            #
983            # Nb. encodestring adds a trailing newline char
984            digestValue = base64.encodestring(sha(refC14n).digest()).strip()
985
986
987            # Add a new reference element to SignedInfo
988            refElem = signedInfoElem.createAppendElement(DSIG.BASE, 
989                                                         'Reference')
990            refElem._etree.set('URI', uri)
991           
992            # Use ds:Transforms or wsse:TransformationParameters?
993            transformsElem = refElem.createAppendElement(DSIG.BASE, 
994                                                        'Transforms')
995            transformElem = transformsElem.createAppendElement(DSIG.BASE, 
996                                                               'Transform')
997
998            # Set Canonicalization algorithm type
999            transformElem._etree.set('Algorithm', refC14nAlg)
1000            if refC14nPfxSet:
1001                # Exclusive C14N requires inclusive namespace elements
1002                inclNamespacesElem = transformElem.createAppendElement(\
1003                                                       refC14nAlg,
1004                                                       'InclusiveNamespaces')
1005                inclNamespacesElem._etree.set('PrefixList',
1006                        ' '.join(self.__refC14nKw['unsuppressedPrefixes']))
1007           
1008            # Digest Method
1009            digestMethodElem = refElem.createAppendElement(DSIG.BASE, 
1010                                                           'DigestMethod')
1011            digestMethodElem._etree.set('Algorithm', DSIG.DIGEST_SHA1)
1012           
1013            # Digest Value
1014            digestValueElem = refElem.createAppendElement(DSIG.BASE, 
1015                                                          'DigestValue')
1016            digestValueElem.createAppendTextNode(digestValue)
1017
1018   
1019        # 2) Signature Generation
1020        #       
1021        # Canonicalize the signedInfo node
1022#        c14nSignedInfo = Canonicalize(signedInfoElem.node,
1023#                                      **self.__signedInfoC14nKw)
1024           
1025#        signedInfoSubsetList = getChildNodes(signedInfoElem.node)
1026#        c14nSignedInfo = Canonicalize(soapWriter._header.node,
1027#                                      None,
1028#                                      subset=signedInfoSubsetList,
1029#                                      **self.__signedInfoC14nKw)
1030
1031        docNode = Reader().fromString(str(soapWriter))
1032        ctxt = Context(docNode, processorNss=processorNss)
1033        signedInfoNode = xpath.Evaluate('//ds:SignedInfo', 
1034                                          contextNode=docNode, 
1035                                          context=ctxt)[0]
1036
1037        signedInfoSubsetList = getChildNodes(signedInfoNode)
1038        c14nSignedInfo = Canonicalize(docNode, 
1039                                      None, 
1040                                      subset=signedInfoSubsetList,
1041                                      **self.__signedInfoC14nKw)
1042
1043        # Calculate digest of SignedInfo
1044        signedInfoDigestValue = sha(c14nSignedInfo).digest()
1045       
1046        # Sign using the private key and base 64 encode the result
1047        signatureValue = self.__signingPriKey.sign(signedInfoDigestValue)
1048        b64EncSignatureValue = base64.encodestring(signatureValue).strip()
1049
1050        # Add to <SignatureValue>
1051        signatureValueElem.createAppendTextNode(b64EncSignatureValue)
1052
1053        log.info("Signature generation complete")
1054
1055
1056    def verify(self, parsedSOAP):
1057        """Verify signature
1058       
1059        @type parsedSOAP: ZSI.parse.ParsedSoap
1060        @param parsedSOAP: object contain parsed SOAP message received from
1061        sender"""
1062
1063        processorNss = \
1064        {
1065            'ds':     DSIG.BASE, 
1066            'wsu':    _WSU.UTILITY, 
1067            'wsse':   OASIS.WSSE, 
1068            'soapenv':"http://schemas.xmlsoap.org/soap/envelope/" 
1069        }
1070        ctxt = Context(parsedSOAP.dom, processorNss=processorNss)
1071       
1072
1073        signatureNodes = xpath.Evaluate('//ds:Signature', 
1074                                        contextNode=parsedSOAP.dom, 
1075                                        context=ctxt)
1076        if len(signatureNodes) > 1:
1077            raise VerifyError, 'Multiple ds:Signature elements found'
1078       
1079        try:
1080            signatureNodes = signatureNodes[0]
1081        except:
1082            # Message wasn't signed
1083            log.warning("Input message wasn't signed!")
1084            return
1085       
1086        # Two stage process: reference validation followed by signature
1087        # validation
1088       
1089        # 1) Reference Validation
1090       
1091        # Check for canonicalization set via ds:CanonicalizationMethod -
1092        # Use this later as a back up in case no Canonicalization was set in
1093        # the transforms elements
1094        c14nMethodNode = xpath.Evaluate('//ds:CanonicalizationMethod', 
1095                                        contextNode=parsedSOAP.dom, 
1096                                        context=ctxt)[0]
1097       
1098        refNodes = xpath.Evaluate('//ds:Reference', 
1099                                  contextNode=parsedSOAP.dom, 
1100                                  context=ctxt)
1101
1102        for refNode in refNodes:
1103            # Get the URI for the reference
1104            refURI = refNode.getAttributeNode('URI').value
1105                         
1106            try:
1107                transformsNode = getElements(refNode, "Transforms")[0]
1108                transforms = getElements(transformsNode, "Transform")
1109   
1110                refAlgorithm = \
1111                            transforms[0].getAttributeNode("Algorithm").value
1112            except Exception, e:
1113                raise VerifyError, \
1114            'failed to get transform algorithm for <ds:Reference URI="%s">'%\
1115                        (refURI, str(e))
1116               
1117            # Add extra keyword for Exclusive canonicalization method
1118            refC14nKw = {}
1119            if refAlgorithm == DSIG.C14N_EXCL:
1120                try:
1121                    # Check for no inclusive namespaces set
1122                    inclusiveNS = getElements(transforms[0], 
1123                                              "InclusiveNamespaces")                   
1124                    if inclusiveNS:
1125                        pfxListAttNode = \
1126                                inclusiveNS[0].getAttributeNode('PrefixList')
1127                           
1128                        refC14nKw['unsuppressedPrefixes'] = \
1129                                                pfxListAttNode.value.split()
1130                    else:
1131                        # Set to empty list to ensure Exclusive C14N is set for
1132                        # Canonicalize call
1133                        refC14nKw['unsuppressedPrefixes'] = []
1134                except Exception, e:
1135                    raise VerifyError(
1136            'failed to handle transform (%s) in <ds:Reference URI="%s">: %s' %\
1137                        (transforms[0], refURI, e))
1138       
1139            # Canonicalize the reference data and calculate the digest
1140            if refURI[0] != "#":
1141                raise VerifyError, \
1142                    "Expecting # identifier for Reference URI \"%s\"" % refURI
1143                   
1144            # XPath reference
1145            uriXPath = '//*[@wsu:Id="%s"]' % refURI[1:]
1146            uriNode = xpath.Evaluate(uriXPath, 
1147                                     contextNode=parsedSOAP.dom, 
1148                                     context=ctxt)[0]
1149
1150#            refC14n = Canonicalize(uriNode, **refC14nKw)
1151            refSubsetList = getChildNodes(uriNode)
1152            refC14n = Canonicalize(parsedSOAP.dom,
1153                                   None, 
1154                                   subset=refSubsetList,
1155                                   **refC14nKw)
1156            digestValue = base64.encodestring(sha(refC14n).digest()).strip()
1157           
1158            # Extract the digest value that was stored           
1159            digestNode = getElements(refNode, "DigestValue")[0]
1160            nodeDigestValue = str(digestNode.childNodes[0].nodeValue).strip()   
1161           
1162            # Reference validates if the two digest values are the same
1163            if digestValue != nodeDigestValue:
1164                raise InvalidSignature, \
1165                        'Digest Values do not match for URI: "%s"' % refURI
1166           
1167            log.info("Verified canonicalization for element %s" % refURI[1:])
1168               
1169        # 2) Signature Validation
1170        signedInfoNode = xpath.Evaluate('//ds:SignedInfo',
1171                                        contextNode=parsedSOAP.dom, 
1172                                        context=ctxt)[0]
1173
1174        # Get algorithm used for canonicalization of the SignedInfo
1175        # element.  Nb. This is NOT necessarily the same as that used to
1176        # canonicalize the reference elements checked above!
1177        signedInfoC14nAlg = c14nMethodNode.getAttributeNode("Algorithm").value
1178        signedInfoC14nKw = {}
1179        if signedInfoC14nAlg == DSIG.C14N_EXCL:
1180            try:
1181                # Check for inclusive namespaces
1182                inclusiveNS = c14nMethodNode.getElementsByTagName(
1183                                                        "InclusiveNamespaces")
1184                if inclusiveNS:                   
1185                    pfxListAttNode = inclusiveNS[0].getAttributeNode(\
1186                                                                 'PrefixList')
1187                    signedInfoC14nKw['unsuppressedPrefixes'] = \
1188                                                pfxListAttNode.value.split()
1189                else:
1190                    # Must default to [] otherwise exclusive C14N is not
1191                    # triggered
1192                    signedInfoC14nKw['unsuppressedPrefixes'] = []
1193            except Exception, e:
1194                raise VerifyError, \
1195            'failed to handle exclusive canonicalisation for SignedInfo: %s'%\
1196                        str(e)
1197
1198        # Canonicalize the SignedInfo node and take digest
1199        #c14nSignedInfo = Canonicalize(signedInfoNode, **signedInfoC14nKw)
1200        signedInfoSubsetList = getChildNodes(signedInfoNode)
1201        c14nSignedInfo = Canonicalize(parsedSOAP.dom, 
1202                                      None, 
1203                                      subset=signedInfoSubsetList,
1204                                      **signedInfoC14nKw)
1205                             
1206        signedInfoDigestValue = sha(c14nSignedInfo).digest()
1207       
1208        # Get the signature value in order to check against the digest just
1209        # calculated
1210        signatureValueNode = xpath.Evaluate('//ds:SignatureValue',
1211                                            contextNode=parsedSOAP.dom, 
1212                                            context=ctxt)[0]
1213
1214        # Remove base 64 encoding
1215        # This line necessary? - only decode call needed??  pyGridWare vers
1216        # seems to preserve whitespace
1217#        b64EncSignatureValue = \
1218#                    str(signatureValueNode.childNodes[0].nodeValue).strip()
1219        b64EncSignatureValue = signatureValueNode.childNodes[0].nodeValue
1220        signatureValue = base64.decodestring(b64EncSignatureValue)
1221
1222        # Cache Signature Value here so that a response can include it
1223        if self.applySignatureConfirmation:
1224            # re-encode string to avoid possible problems with interpretation
1225            # of line breaks
1226            self.b64EncSignatureValue = b64EncSignatureValue
1227        else:
1228            self.b64EncSignatureValue = None
1229         
1230        # Look for X.509 Cert in wsse:BinarySecurityToken node
1231        try:
1232            binSecTokNode = xpath.Evaluate('//wsse:BinarySecurityToken',
1233                                           contextNode=parsedSOAP.dom,
1234                                           context=ctxt)[0]
1235        except:
1236            # Signature may not have included the Binary Security Token in
1237            # which case the verifying cert will need to have been set
1238            # elsewhere
1239            binSecTokNode = None
1240       
1241        if binSecTokNode:
1242            try:
1243                x509CertTxt=str(binSecTokNode.childNodes[0].nodeValue)
1244               
1245                valueType = binSecTokNode.getAttributeNode("ValueType").value
1246                if valueType in (self.__class__.binSecTokValType['X509v3'],
1247                                 self.__class__.binSecTokValType['X509']):
1248                    # Remove base 64 encoding
1249                    derString = base64.decodestring(x509CertTxt)
1250                   
1251                    # Load from DER format into M2Crypto.X509
1252                    m2X509Cert = X509.load_cert_string(derString,
1253                                                       format=X509.FORMAT_DER)
1254                    self.__setVerifyingCert(m2X509Cert)
1255                   
1256                    x509Stack = X509Stack()
1257
1258                elif valueType == \
1259                    self.__class__.binSecTokValType['X509PKIPathv1']:
1260                   
1261                    derString = base64.decodestring(x509CertTxt)
1262                    x509Stack = X509StackParseFromDER(derString)
1263                   
1264                    # TODO: Check ordering - is the last off the stack the
1265                    # one to use to verify the message?
1266                    self.__verifyingCert = x509Stack[-1]
1267                else:
1268                    raise WSSecurityError, "BinarySecurityToken ValueType " +\
1269                        'attribute is not recognised: "%s"' % valueType
1270                               
1271            except Exception, e:
1272                raise VerifyError, "Error extracting BinarySecurityToken " + \
1273                                   "from WSSE header: " + str(e)
1274
1275        if self.__verifyingCert is None:
1276            raise VerifyError, "No certificate set for verification " + \
1277                "of the signature"
1278       
1279        # Extract RSA public key from the cert
1280        rsaPubKey = self.__verifyingCert.pubKey.get_rsa()
1281
1282        # Apply the signature verification
1283        try:
1284            verify = rsaPubKey.verify(signedInfoDigestValue, signatureValue)
1285        except RSA.RSAError, e:
1286            raise VerifyError, "Error in Signature: " + str(e)
1287       
1288        if not verify:
1289            raise InvalidSignature, "Invalid signature"
1290       
1291        # Verify chain of trust
1292        x509Stack.verifyCertChain(x509Cert2Verify=self.__verifyingCert,
1293                                  caX509Stack=self.__caX509Stack)
1294       
1295        self._verifyTimeStamp(parsedSOAP, ctxt) 
1296        log.info("Signature OK")       
Note: See TracBrowser for help on using the repository browser.