source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/wsSecurity.py @ 2418

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/wsSecurity.py@2418
Revision 2418, 54.7 KB checked in by pjkersha, 13 years ago (diff)

ndg.security.server/ndg/security/server/Log.py: remove ref to 'Logger'

ndg.security.server/ndg/security/server/SessionMgr/server-config.tac:
added M2Crypto SSL support

ndg.security.server/ndg/security/server/SessionMgr/start-container.sh:
copy from Attribute Authority version.

ndg.security.test/ndg/security/test/SessionMgr/SessionMgrClientTest.py:
fix to test5ProxyCertDisconnect call.

ndg.security.test/ndg/security/test/SessionMgr/sessionMgrClientTest.cfg:
set clntprikeypwd to null so that it is not prompted for from terminal.

ndg.security.common/ndg/security/common/SessionMgr/init.py: fix to
disconnect SOAP client call so that userCert omit alone is allowed.

ndg.security.common/ndg/security/common/wsSecurity.py: delete debug call.

  • Property svn:executable set to *
Line 
1#!/bin/env python
2
3"""WS-Security test class includes digital signature handler
4
5NERC Data Grid Project
6
7@author P J Kershaw 01/09/06
8
9@copyright (C) 2006 CCLRC & NERC
10
11@license This software may be distributed under the terms of the Q Public
12License, version 1.0 or later.
13"""
14
15__revision__ = '$Id:$'
16
17import re
18
19# Digest and signature/verify
20from sha import sha
21from M2Crypto import X509, BIO, RSA
22import base64
23
24# For shared key encryption
25from Crypto.Cipher import AES, DES3
26import os
27
28import ZSI
29from ZSI.wstools.Namespaces import DSIG, ENCRYPTION, OASIS, WSU, WSA200403, \
30                                   SOAP, SCHEMA # last included for xsi
31                                   
32from ZSI.TC import ElementDeclaration,TypeDefinition
33from ZSI.generate.pyclass import pyclass_type
34
35from ZSI.wstools.Utility import DOMException
36from ZSI.wstools.Utility import NamespaceError, MessageInterface, ElementProxy
37
38# Canonicalization
39from ZSI.wstools.c14n import Canonicalize
40
41from xml.dom import Node
42from xml.xpath.Context import Context
43from xml import xpath
44
45# Include for re-parsing doc ready for canonicalization in sign method - see
46# associated note
47from xml.dom.ext.reader.PyExpat import Reader
48
49
50from ndg.security.common.X509 import X509Cert, X509CertParse, X509CertRead
51
52
53class _ENCRYPTION(ENCRYPTION):
54    '''Derived from ENCRYPTION class to add in extra 'tripledes-cbc' - is this
55    any different to 'des-cbc'?  ENCRYPTION class implies that it is the same
56    because it's assigned to 'BLOCK_3DES' ??'''
57    BLOCK_TRIPLEDES = "http://www.w3.org/2001/04/xmlenc#tripledes-cbc"
58
59class _WSU(WSU):
60    '''Try different utility namespace for use with WebSphere'''
61    #UTILITY = "http://schemas.xmlsoap.org/ws/2003/06/utility"
62    UTILITY = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
63   
64def getElements(node, nameList):
65    '''DOM Helper function for getting child elements from a given node'''
66    # Avoid sub-string matches
67    nameList = isinstance(nameList, basestring) and [nameList] or nameList
68    return [n for n in node.childNodes if str(n.localName) in nameList]
69
70
71class WSSecurityError(Exception):
72    """For WS-Security generic exceptions not covered by other exception
73    classes in this module"""
74
75class InvalidCertChain(Exception):   
76    """Raised from SignatureHandler.verify if the certificate submitted to
77    verify a signature is not from a known CA"""
78   
79class VerifyError(Exception):
80    """Raised from SignatureHandler.verify if an error occurs in the signature
81    verification"""
82   
83class InvalidSignature(Exception):
84    """Raised from verify method for an invalid signature"""
85
86class SignatureError(Exception):
87    """Flag if an error occurs during signature generation"""
88       
89class SignatureHandler(object):
90    """Class to handle signature and verification of signature with
91    WS-Security
92   
93    @type __beginCert: string
94    @param __beginCert: delimiter for beginning of base64 encoded portion of
95    a PEM encoded X.509 certificate
96    @type __endCert: string
97    @cvar: __endCert: equivalent end delimiter
98   
99    @type __x509CertPat: regular expression pattern object
100    @cvar __x509CertPat: regular expression for extracting the base64 encoded
101    portion of a PEM encoded X.509 certificate"""
102   
103    __beginCert = '-----BEGIN CERTIFICATE-----\n'
104    __endCert = '\n-----END CERTIFICATE-----'
105    __x509CertPat = re.compile(__beginCert + \
106                               '?(.*?)\n?-----END CERTIFICATE-----',
107                               re.S)
108   
109   
110    #_________________________________________________________________________
111    def __init__(self,
112                 verifyingCert=None,
113                 verifyingCertFilePath=None,
114                 signingCert=None,
115                 signingCertFilePath=None, 
116                 signingPriKey=None,
117                 signingPriKeyFilePath=None, 
118                 signingPriKeyPwd=None,
119                 caCertDirPath=None,
120                 caCertFilePathList=[],
121                 refC14nKw={'unsuppressedPrefixes': ['xmlns', 
122                                                     'xsi', 
123                                                     'xsd', 
124                                                     'SOAP-ENV', 
125                                                     'wsu', 
126                                                     'wsse', 
127                                                     'ns1']},
128                # Added 'ec' to list P J Kershaw 01/02/07
129                signedInfoC14nKw = {'unsuppressedPrefixes': ['xsi', 
130                                                             'xsd', 
131                                                             'SOAP-ENV', 
132                                                             'ds', 
133                                                             'wsse', 
134                                                             'ec']}):
135
136        # Set keywords for canonicalization of SignedInfo and reference
137        # elements
138        self.__setRefC14nKw(refC14nKw)
139        self.__setSignedInfoC14nKw(signedInfoC14nKw)
140           
141
142        self.__setVerifyingCert(verifyingCert)
143        self.__setVerifyingCertFilePath(verifyingCertFilePath)
144       
145        self.__setSigningCert(signingCert)
146        self.__setSigningCertFilePath(signingCertFilePath)
147
148        # MUST be set before __setSigningPriKeyFilePath / __setSigningPriKey
149        # are called
150        self.__setSigningPriKeyPwd(signingPriKeyPwd)
151       
152        if signingPriKey is not None:
153            # Don't allow None for private key setting
154            self.__setSigningPriKey(signingPriKey)
155           
156        self.__setSigningPriKeyFilePath(signingPriKeyFilePath)
157       
158        # CA certificate(s) for verification of X.509 certificate used with
159        # signature.
160        if caCertDirPath:
161            self.caCertDirPath = caCertDirPath
162           
163        elif caCertFilePathList:
164            self.caCertFilePathList = caCertFilePathList
165       
166
167    #_________________________________________________________________________
168    def __checkC14nKw(self, Kw):
169        """Check keywords for canonicalization in signing process - generic
170        method for setting keywords for reference element and SignedInfo
171        element c14n"""
172       
173        # Check for dict/None - Set to None in order to use inclusive
174        # canonicalization
175        if Kw is not None and not isinstance(Kw, dict):
176            # Otherwise keywords must be a dictionary
177            raise AttributeError, \
178                "Expecting dictionary type for reference c14n keywords"
179               
180        elif Kw.get('unsuppressedPrefixes') and \
181             not isinstance(Kw['unsuppressedPrefixes'], list) and \
182             not isinstance(Kw['unsuppressedPrefixes'], tuple):
183            raise AttributeError, \
184                'Expecting list or tuple of prefix names for "%s" keyword' % \
185                'unsuppressedPrefixes'
186       
187               
188    #_________________________________________________________________________
189    def __setRefC14nKw(self, Kw):
190        """Set keywords for canonicalization of reference elements in the
191        signing process"""
192        self.__checkC14nKw(Kw)                   
193        self.__refC14nKw = Kw
194       
195    refC14nKw = property(fset=__setRefC14nKw,
196                         doc="Keywords for c14n of reference elements")
197       
198               
199    #_________________________________________________________________________
200    def __setSignedInfoC14nKw(self, Kw):
201        """Set keywords for canonicalization of SignedInfo element in the
202        signing process"""
203        self.__checkC14nKw(Kw)                   
204        self.__signedInfoC14nKw = Kw
205       
206    signedInfoC14nKw = property(fset=__setSignedInfoC14nKw,
207                                doc="Keywords for c14n of SignedInfo element")
208
209
210    #_________________________________________________________________________
211    def __refC14nIsExcl(self):
212        return isinstance(self.__refC14nKw, dict) and \
213               self.__refC14nKw.get('unsuppressedPrefixes') and \
214               len(self.__refC14nKw['unsuppressedPrefixes']) > 0
215               
216    refC14nIsExcl = property(fget=__refC14nIsExcl,
217    doc="Return True/False c14n for reference elements set to exclusive type")
218     
219
220    #_________________________________________________________________________
221    def __signedInfoC14nIsExcl(self):
222        return isinstance(self.__signedInfoC14nKw, dict) and \
223               self.__signedInfoC14nKw.get('unsuppressedPrefixes') and \
224               len(self.__signedInfoC14nKw['unsuppressedPrefixes']) > 0
225               
226    signedInfoC14nIsExcl = property(fget=__signedInfoC14nIsExcl,
227    doc="Return True/False c14n for SignedInfo element set to exclusive type")
228   
229   
230    #_________________________________________________________________________
231    def __setCert(self, cert):
232        """filter and convert input cert to signing verifying cert set
233        property methods.  For signingCert, set to None if it is not to be
234        included in the SOAP header.  For verifyingCert, set to None if this
235        cert can be expected to be retrieved from the SOAP header of the
236        message to be verified
237       
238        @type: ndg.security.common.X509.X509Cert / M2Crypto.X509.X509 /
239        string or None
240        @param cert: X.509 certificate. 
241       
242        @rtype ndg.security.common.X509.X509Cert
243        @return X.509 certificate object"""
244       
245        if cert is None or isinstance(cert, X509Cert):
246            # ndg.security.common.X509.X509Cert type / None
247            return cert
248           
249        elif isinstance(cert, X509.X509):
250            # M2Crypto.X509.X509 type
251            return X509Cert(m2CryptoX509=cert)
252           
253        elif isinstance(cert, basestring):
254            return X509CertParse(cert)
255       
256        else:
257            raise AttributeError, "X.509 Cert. must be type: " + \
258                "ndg.security.common.X509.X509Cert, M2Crypto.X509.X509 or " +\
259                "a base64 encoded string"
260
261   
262    #_________________________________________________________________________
263    def __getVerifyingCert(self):
264        '''Return X.509 cert object corresponding to cert used to verify the
265        signature in the last call to verify
266       
267         * Cert will correspond to one used in the LATEST call to verify, on
268         the next call it will be replaced
269         * if verify hasn't been called, the cert will be None
270       
271        @rtype: M2Crypto.X509.X509
272        @return: certificate object
273        '''
274        return self.__verifyingCert
275
276
277    #_________________________________________________________________________
278    def __setVerifyingCert(self, verifyingCert):
279        "Set property method for X.509 cert. used to verify a signature"
280        self.__verifyingCert = self.__setCert(verifyingCert)
281   
282        # Reset file path as it may no longer apply
283        self.__verifyingCertFilePath = None
284       
285    verifyingCert = property(fset=__setVerifyingCert,
286                             fget=__getVerifyingCert,
287                             doc="Set X.509 Cert. for verifying signature")
288
289
290    #_________________________________________________________________________
291    def __setVerifyingCertFilePath(self, verifyingCertFilePath):
292        "Set method for Service X.509 cert. file path property"
293       
294        if isinstance(verifyingCertFilePath, basestring):
295            self.__verifyingCert = X509CertRead(verifyingCertFilePath)
296           
297        elif verifyingCertFilePath is not None:
298            raise AttributeError, \
299            "Verifying X.509 Cert. file path must be None or a valid string"
300       
301        self.__verifyingCertFilePath = verifyingCertFilePath
302       
303    verifyingCertFilePath = property(fset=__setVerifyingCertFilePath,
304                    doc="file path of X.509 Cert. for verifying signature")
305
306
307    #_________________________________________________________________________
308    def __setSigningCert(self, signingCert):
309        "Set property method for X.509 cert. to be included with signature"
310        self.__signingCert = self.__setCert(signingCert)
311   
312        # Reset file path as it may no longer apply
313        self.__signingCertFilePath = None
314       
315    signingCert = property(fset=__setSigningCert,
316                             doc="Set X.509 Cert. to include signature")
317
318 
319    #_________________________________________________________________________
320    def __setSigningCertFilePath(self, signingCertFilePath):
321        "Set signature X.509 cert property method"
322       
323        if isinstance(signingCertFilePath, basestring):
324            self.__signingCert = X509CertRead(signingCertFilePath)
325           
326        elif signingCertFilePath is not None:
327            raise AttributeError, \
328                "Signature X.509 cert. file path must be a valid string"
329       
330        self.__signingCertFilePath = signingCertFilePath
331       
332       
333    signingCertFilePath = property(fset=__setSigningCertFilePath,
334                   doc="File path X.509 cert. to include with signed message")
335
336 
337    #_________________________________________________________________________
338    def __setSigningPriKeyPwd(self, signingPriKeyPwd):
339        "Set method for private key file password used to sign message"
340        if signingPriKeyPwd is not None and \
341           not isinstance(signingPriKeyPwd, basestring):
342            raise AttributeError, \
343                "Signing private key password must be None or a valid string"
344       
345        self.__signingPriKeyPwd = signingPriKeyPwd
346       
347    signingPriKeyPwd = property(fset=__setSigningPriKeyPwd,
348             doc="Password protecting private key file used to sign message")
349
350 
351    #_________________________________________________________________________
352    def __setSigningPriKey(self, signingPriKey):
353        """Set method for client private key
354       
355        Nb. if input is a string, signingPriKeyPwd will need to be set if
356        the key is password protected.
357       
358        @type signingPriKey: M2Crypto.RSA.RSA / string
359        @param signingPriKey: private key used to sign message"""
360       
361        if isinstance(signingPriKey, basestring):
362            pwdCallback = lambda *ar, **kw: self.__signingPriKeyPwd
363            self.__signingPriKey = RSA.load_key_string(signingPriKey,
364                                                       callback=pwdCallback)
365
366        elif isinstance(signingPriKey, RSA.RSA):
367            self.__signingPriKey = signingPriKey
368                   
369        else:
370            raise AttributeError, "Signing private key must be a valid " + \
371                                  "M2Crypto.RSA.RSA type or a string"
372               
373    signingPriKey = property(fset=__setSigningPriKey,
374                             doc="Private key used to sign outbound message")
375
376 
377    #_________________________________________________________________________
378    def __setSigningPriKeyFilePath(self, signingPriKeyFilePath):
379        """Set method for client private key file path
380       
381        signingPriKeyPwd MUST be set prior to a call to this method"""
382        if isinstance(signingPriKeyFilePath, basestring):                           
383            try:
384                # Read Private key to sign with   
385                priKeyFile = BIO.File(open(signingPriKeyFilePath)) 
386                pwdCallback = lambda *ar, **kw: self.__signingPriKeyPwd                                           
387                self.__signingPriKey = RSA.load_key_bio(priKeyFile, 
388                                                        callback=pwdCallback)           
389            except Exception, e:
390                raise AttributeError, \
391                                "Setting private key for signature: %s" % e
392       
393        elif signingPriKeyFilePath is not None:
394            raise AttributeError, \
395                        "Private key file path must be a valid string or None"
396       
397        self.__signingPriKeyFilePath = signingPriKeyFilePath
398       
399    signingPriKeyFilePath = property(fset=__setSigningPriKeyFilePath,
400                      doc="File path for private key used to sign message")
401
402    def __caCertIsSet(self):
403        '''Check for CA certificate set (X.509 Stack has been created)'''
404        return hasattr(self, '_caX509Stack')
405   
406    caCertIsSet = property(fget=__caCertIsSet,
407           doc='Check for CA certificate set (X.509 Stack has been created)')
408   
409    #_________________________________________________________________________
410    def __appendCAX509Stack(self, caCertList):
411        '''Store CA certificates in an X.509 Stack'''
412       
413        if not self.caCertIsSet:
414            self._caX509Stack = X509.X509_Stack()
415           
416        for cert in caCertList:
417            self._caX509Stack.push(cert)
418
419
420    #_________________________________________________________________________
421    def __setCAX509StackFromDir(self, caCertDir):
422        '''Read CA certificates from directory and add them to the X.509
423        stack'''
424       
425        # Mimic OpenSSL -CApath option which expects directory of CA files
426        # of form <Hash cert subject name>.0
427        reg = re.compile('\d+\.0')
428        try:
429            caCertList = [X509.load_cert(caFile) \
430                          for caFile in os.listdir(caCertDir) \
431                          if reg.match(caFile)]
432        except Exception, e:
433            raise WSSecurityError, \
434                'Loading CA certificate "%s" from CA directory: %s' % \
435                                                        (caFile, str(e))
436                   
437        # Add to stack
438        self.__appendCAX509Stack(caCertList)
439       
440    caCertDirPath = property(fset=__setCAX509StackFromDir,
441                      doc="Dir. containing CA cert.s used for verification")
442
443
444    #_________________________________________________________________________
445    def __setCAX509StackFromCertFileList(self, caCertFilePathList):
446        '''Read CA certificates from file and add them to the X.509
447        stack
448       
449        @type caCertFilePathList: string
450        @param caCertFilePathList: list of file paths for CA certificates to
451        be used to verify certificate used to sign message'''
452       
453        if not isinstance(caCertFilePathList, list) and \
454           not isinstance(caCertFilePathList, tuple):
455            raise WSSecurityError, \
456                        'Expecting a list or tuple for "caCertFilePathList"'
457
458        # Mimic OpenSSL -CApath option which expects directory of CA files
459        # of form <Hash cert subject name>.0
460        try:
461            caCertList = [X509.load_cert(caFile) \
462                          for caFile in caCertFilePathList]
463        except Exception, e:
464            raise WSSecurityError, \
465                    'Loading CA certificate "%s" from file list: %s' % \
466                                                        (caFile, str(e))
467                   
468        # Add to stack
469        self.__appendCAX509Stack(caCertList)
470       
471    caCertFilePathList = property(fset=__setCAX509StackFromCertFileList,
472                      doc="List of CA cert. files used for verification")
473
474       
475    #_________________________________________________________________________
476    def verifyCertChain(self, certIn=None, raiseExcep=True):
477        """Check a certificate has been issued by one of the known CA's
478        specified in X.509 stack
479       
480        @type: ndg.security.common.X509.X509Cert / M2Crypto.X509.X509 /
481        string or None
482        @keyword certIn: X.509 certificate. 
483       
484        @type raiseExcep: bool
485        @keyword raiseExcep: set to True (default) to raise an exception if
486        the input certificate is invalid
487       
488        @rtype bool
489        @return True if certificate was issued by a known CA"""
490       
491        if certIn:
492            cert2Verify = self.__setCert(certIn)
493        else:
494            cert2Verify = self.__verifyingCert
495           
496        for cert in self._caX509Stack:
497            try:
498                assert cert2Verify.m2CryptoX509.verify(cert.get_pubkey())
499                return True
500            except:
501                pass
502           
503        # No CA certs in the stack matched
504        if raiseExcep:
505            raise InvalidCertChain, \
506        'Input certificate "%s" was not issued by a known CA' % cert2Verify.dn
507        else:
508            return False
509               
510       
511    #_________________________________________________________________________
512    def sign(self, soapWriter):
513        '''Sign the message body and binary security token of a SOAP message
514       
515        @type soapWriter: ZSI.writer.SoapWriter
516        @param soapWriter: ZSI object to write SOAP message
517        '''
518       
519        # Namespaces for XPath searches
520        processorNss = \
521        {
522            'ds':     DSIG.BASE, 
523            'wsu':    _WSU.UTILITY, 
524            'wsse':   OASIS.WSSE, 
525            'soapenv':"http://schemas.xmlsoap.org/soap/envelope/" 
526        }
527
528        # Add X.509 cert as binary security token stripping BEGIN CERT and
529        # END CERT delimiters
530        x509CertStr = self.__class__.__x509CertPat.findall(\
531                                           self.__signingCert.toString())[0]
532
533        soapWriter._header.setNamespaceAttribute('wsse', OASIS.WSSE)
534        soapWriter._header.setNamespaceAttribute('wsu', _WSU.UTILITY)
535        soapWriter._header.setNamespaceAttribute('ds', DSIG.BASE)
536       
537        if self.refC14nIsExcl or self.signedInfoC14nIsExcl:
538            soapWriter._header.setNamespaceAttribute('ec', DSIG.C14N_EXCL)
539       
540        # Check <wsse:security> isn't already present in header
541        ctxt = Context(soapWriter.dom.node, processorNss=processorNss)
542        wsseNodes = xpath.Evaluate('//wsse:security', 
543                                   contextNode=soapWriter.dom.node, 
544                                   context=ctxt)
545        if len(wsseNodes) > 1:
546            raise SignatureError, 'wsse:Security element is already present'
547
548        # Add WSSE element
549        wsseElem = soapWriter._header.createAppendElement(OASIS.WSSE, 
550                                                         'Security')
551        wsseElem.setNamespaceAttribute('wsse', OASIS.WSSE)
552       
553        # Recipient MUST parse and check this signature
554        wsseElem.node.setAttribute('SOAP-ENV:mustUnderstand', "1")
555       
556        # Binary Security Token element will contain the X.509 cert
557        # corresponding to the private key used to sing the message
558        binSecTokElem = wsseElem.createAppendElement(OASIS.WSSE, 
559                                                     'BinarySecurityToken')
560       
561        # Value and encoding types to suite WebSphere WSSE implementation
562        valueType = \
563"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509"
564        binSecTokElem.node.setAttribute('ValueType', valueType)
565
566        encodingType = \
567"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
568        binSecTokElem.node.setAttribute('EncodingType', encodingType)
569       
570        # Add ID so that the binary token can be included in the signature
571        binSecTokElem.node.setAttribute('wsu:Id', "binaryToken")
572
573        binSecTokElem.createAppendTextNode(x509CertStr)
574
575       
576        # Signature
577        signatureElem = wsseElem.createAppendElement(DSIG.BASE, 'Signature')
578        signatureElem.setNamespaceAttribute('ds', DSIG.BASE)
579       
580        # Signature - Signed Info
581        signedInfoElem = signatureElem.createAppendElement(DSIG.BASE, 
582                                                           'SignedInfo')
583       
584        # Signed Info - Canonicalization method
585        c14nMethodElem = signedInfoElem.createAppendElement(DSIG.BASE,
586                                                    'CanonicalizationMethod')
587       
588        # Set based on 'signedInfoIsExcl' property
589        c14nAlgOpt = (DSIG.C14N, DSIG.C14N_EXCL)
590        signedInfoC14nAlg = c14nAlgOpt[self.signedInfoC14nIsExcl]
591       
592        c14nMethodElem.node.setAttribute('Algorithm', signedInfoC14nAlg)
593       
594        if self.signedInfoC14nIsExcl:
595            c14nInclNamespacesElem = c14nMethodElem.createAppendElement(\
596                                                    signedInfoC14nAlg,
597                                                    'InclusiveNamespaces')
598            c14nInclNamespacesElem.node.setAttribute('PrefixList', 
599                            ' '.join(self.__signedInfoC14nKw['unsuppressedPrefixes']))
600       
601        # Signed Info - Signature method
602        sigMethodElem = signedInfoElem.createAppendElement(DSIG.BASE,
603                                                           'SignatureMethod')
604        sigMethodElem.node.setAttribute('Algorithm', DSIG.SIG_RSA_SHA1)
605       
606        # Signature - Signature value
607        signatureValueElem = signatureElem.createAppendElement(DSIG.BASE, 
608                                                             'SignatureValue')
609       
610        # Key Info
611        KeyInfoElem = signatureElem.createAppendElement(DSIG.BASE, 'KeyInfo')
612        secTokRefElem = KeyInfoElem.createAppendElement(OASIS.WSSE, 
613                                                  'SecurityTokenReference')
614       
615        # Reference back to the binary token included earlier
616        wsseRefElem = secTokRefElem.createAppendElement(OASIS.WSSE, 
617                                                        'Reference')
618        wsseRefElem.node.setAttribute('URI', "#binaryToken")
619       
620        # Add Reference to body so that it can be included in the signature
621        soapWriter.body.node.setAttribute('wsu:Id', "body")
622        soapWriter.body.node.setAttribute('xmlns:wsu', _WSU.UTILITY)
623
624        # Serialize and re-parse prior to reference generation - calculating
625        # canonicalization based on soapWriter.dom.node seems to give an
626        # error: the order of wsu:Id attribute is not correct
627        docNode = Reader().fromString(str(soapWriter))
628        ctxt = Context(docNode, processorNss=processorNss)
629        refNodes = xpath.Evaluate('//*[@wsu:Id]', 
630                                  contextNode=docNode, 
631                                  context=ctxt)
632       
633        # Set based on 'signedInfoIsExcl' property
634        refC14nAlg = c14nAlgOpt[self.refC14nIsExcl]
635       
636        # 1) Reference Generation
637        #
638        # Find references
639        for refNode in refNodes:
640           
641            # Set URI attribute to point to reference to be signed
642            #uri = u"#" + refNode.getAttribute('wsu:Id')
643            uri = u"#" + refNode.attributes[(_WSU.UTILITY, 'Id')].value
644           
645            # Canonicalize reference
646            refC14n = Canonicalize(refNode, **self.__refC14nKw)
647           
648            # Calculate digest for reference and base 64 encode
649            #
650            # Nb. encodestring adds a trailing newline char
651            digestValue = base64.encodestring(sha(refC14n).digest()).strip()
652
653
654            # Add a new reference element to SignedInfo
655            refElem = signedInfoElem.createAppendElement(DSIG.BASE, 
656                                                         'Reference')
657            refElem.node.setAttribute('URI', uri)
658           
659            # Use ds:Transforms or wsse:TransformationParameters?
660            transformsElem = refElem.createAppendElement(DSIG.BASE, 
661                                                        'Transforms')
662            transformElem = transformsElem.createAppendElement(DSIG.BASE, 
663                                                               'Transform')
664
665            # Set Canonicalization algorithm type
666            transformElem.node.setAttribute('Algorithm', refC14nAlg)
667            if self.refC14nIsExcl:
668                # Exclusive C14N requires inclusive namespace elements
669                inclNamespacesElem = transformElem.createAppendElement(\
670                                                                                   refC14nAlg,
671                                                       'InclusiveNamespaces')
672                inclNamespacesElem.node.setAttribute('PrefixList',
673                                        ' '.join(self.__refC14nKw['unsuppressedPrefixes']))
674           
675            # Digest Method
676            digestMethodElem = refElem.createAppendElement(DSIG.BASE, 
677                                                           'DigestMethod')
678            digestMethodElem.node.setAttribute('Algorithm', DSIG.DIGEST_SHA1)
679           
680            # Digest Value
681            digestValueElem = refElem.createAppendElement(DSIG.BASE, 
682                                                          'DigestValue')
683            digestValueElem.createAppendTextNode(digestValue)
684
685   
686        # 2) Signature Generation
687        #       
688        # Canonicalize the signedInfo node
689        c14nSignedInfo = Canonicalize(signedInfoElem.node, 
690                                      **self.__signedInfoC14nKw)
691
692        # Calculate digest of SignedInfo
693        #
694        # TODO: check status for strip call - almost certainly wrong and not
695        # needed
696        signedInfoDigestValue = sha(c14nSignedInfo).digest()#.strip()
697       
698        # Sign using the private key and base 64 encode the result
699        signatureValue = self.__signingPriKey.sign(signedInfoDigestValue)
700        b64EncSignatureValue = base64.encodestring(signatureValue).strip()
701
702        # Add to <SignatureValue>
703        signatureValueElem.createAppendTextNode(b64EncSignatureValue)
704
705
706    def verify(self, parsedSOAP):
707        """Verify signature
708       
709        @type parsedSOAP: ZSI.parse.ParsedSoap
710        @param parsedSOAP: object contain parsed SOAP message received from
711        sender"""
712
713        processorNss = \
714        {
715            'ds':     DSIG.BASE, 
716            'wsu':    _WSU.UTILITY, 
717            'wsse':   OASIS.WSSE, 
718            'soapenv':"http://schemas.xmlsoap.org/soap/envelope/" 
719        }
720        ctxt = Context(parsedSOAP.dom, processorNss=processorNss)
721       
722
723        signatureNodes = xpath.Evaluate('//ds:Signature', 
724                                        contextNode=parsedSOAP.dom, 
725                                        context=ctxt)
726        if len(signatureNodes) > 1:
727            raise VerifyError, 'Multiple ds:Signature elements found'
728       
729        try:
730            signatureNodes = signatureNodes[0]
731        except:
732            # Message wasn't signed
733            return
734       
735        # Two stage process: reference validation followed by signature
736        # validation
737       
738        # 1) Reference Validation
739       
740        # Check for canonicalization set via ds:CanonicalizationMethod -
741        # Use this later as a back up in case no Canonicalization was set in
742        # the transforms elements
743        c14nMethodNode = xpath.Evaluate('//ds:CanonicalizationMethod', 
744                                        contextNode=parsedSOAP.dom, 
745                                        context=ctxt)[0]
746       
747        refNodes = xpath.Evaluate('//ds:Reference', 
748                                  contextNode=parsedSOAP.dom, 
749                                  context=ctxt)
750
751        for refNode in refNodes:
752            # Get the URI for the reference
753            refURI = refNode.getAttributeNode('URI').value
754                         
755            try:
756                transformsNode = getElements(refNode, "Transforms")[0]
757                transforms = getElements(transformsNode, "Transform")
758   
759                refAlgorithm = \
760                            transforms[0].getAttributeNode("Algorithm").value
761            except Exception, e:
762                raise VerifyError, \
763            'failed to get transform algorithm for <ds:Reference URI="%s">'%\
764                        (refURI, str(e))
765               
766            # Add extra keyword for Exclusive canonicalization method
767            refC14nKw = {}
768            if refAlgorithm == DSIG.C14N_EXCL:
769                try:
770                    inclusiveNS = getElements(transforms[0], 
771                                              "InclusiveNamespaces")
772                   
773                    # Allow for no inclusive namespaces set - this is
774                    # not expected and is likely to cause problems with the
775                    # canonicalization later.
776                    if inclusiveNS:
777                        pfxListAttNode = \
778                                inclusiveNS[0].getAttributeNode('PrefixList')
779                           
780                        refC14nKw['unsuppressedPrefixes'] = \
781                                                pfxListAttNode.value.split()
782                except:
783                    raise VerifyError, \
784                'failed to handle transform (%s) in <ds:Reference URI="%s">'%\
785                        (transforms[0], refURI)
786       
787            # Canonicalize the reference data and calculate the digest
788            if refURI[0] != "#":
789                raise VerifyError, \
790                    "Expecting # identifier for Reference URI \"%s\"" % refURI
791                   
792            # XPath reference
793            uriXPath = '//*[@wsu:Id="%s"]' % refURI[1:]
794            uriNode = xpath.Evaluate(uriXPath, 
795                                     contextNode=parsedSOAP.dom, 
796                                     context=ctxt)[0]
797
798            refC14n = Canonicalize(uriNode, **refC14nKw)
799            digestValue = base64.encodestring(sha(refC14n).digest()).strip()
800           
801            # Extract the digest value that was stored           
802            digestNode = getElements(refNode, "DigestValue")[0]
803            nodeDigestValue = str(digestNode.childNodes[0].nodeValue).strip()   
804           
805            # Reference validates if the two digest values are the same
806            if digestValue != nodeDigestValue:
807                raise InvalidSignature, \
808                        'Digest Values do not match for URI: "%s"' % refURI
809               
810        # 2) Signature Validation
811        signedInfoNode = xpath.Evaluate('//ds:SignedInfo',
812                                        contextNode=parsedSOAP.dom, 
813                                        context=ctxt)[0]
814
815        # Get algorithm used for canonicalization of the SignedInfo
816        # element.  Nb. This is NOT necessarily the same as that used to
817        # canonicalize the reference elements checked above!
818        signedInfoC14nAlg = c14nMethodNode.getAttributeNode("Algorithm").value
819        signedInfoC14nKw = {}
820        if signedInfoC14nAlg == DSIG.C14N_EXCL:
821            try:
822                # Allow for no inclusive namespaces set - this is
823                # not expected and is likely to cause problems with the
824                # canonicalization later.
825                if inclusiveNS:
826                    inclusiveNS = getElements(c14nMethodNode,
827                                              "InclusiveNamespaces")
828                   
829                    pfxListAttNode = inclusiveNS[0].getAttributeNode(\
830                                                                 'PrefixList')
831                    signedInfoC14nKw['unsuppressedPrefixes'] = \
832                                                pfxListAttNode.value.split()
833                                                     
834            except Exception, e:
835                raise VerifyError, \
836            'failed to handle exclusive canonicalisation for SignedInfo: %s'%\
837                        str(e)
838
839        # Canonicalize the SignedInfo node and take digest
840        c14nSignedInfo = Canonicalize(signedInfoNode, **signedInfoC14nKw) 
841               
842        # TODO: strip() call? - almost certainly wrong - leave out!       
843        signedInfoDigestValue = sha(c14nSignedInfo).digest()#.strip()
844       
845        # Get the signature value in order to check against the digest just
846        # calculated
847        signatureValueNode = xpath.Evaluate('//ds:SignatureValue',
848                                            contextNode=parsedSOAP.dom, 
849                                            context=ctxt)[0]
850
851        # Remove base 64 encoding
852        b64EncSignatureValue = \
853                    str(signatureValueNode.childNodes[0].nodeValue).strip()
854       
855        signatureValue = base64.decodestring(b64EncSignatureValue)
856
857
858        # Look for X.509 Cert in wsse:BinarySecurityToken node
859        try:
860            binSecTokNode = xpath.Evaluate('//wsse:BinarySecurityToken',
861                                           contextNode=parsedSOAP.dom,
862                                           context=ctxt)[0]
863        except:
864            # Signature may not have included the Binary Security Token in
865            # which case the verifying cert will need to have been set
866            # elsewhere
867            binSecTokNode = None
868            pass 
869       
870        #import pdb;pdb.set_trace()       
871        if binSecTokNode:
872            try:
873                x509CertTxt=str(binSecTokNode.childNodes[0]._get_nodeValue())
874               
875                # Convert parsed cert text into PEM form that can be read
876                # by X.509 string parser
877                #
878                # Check for line breaks at 64th byte as expected for PEM
879                # encoding
880                if x509CertTxt[64] != "\n":
881                    # Check for other whitespace and reomve
882                    # Expecting cert split into lines of length 64 bytes
883                    x509CertTxt = re.sub('\s', '', x509CertTxt)
884                   
885                    # Split into lines of length 64
886                    x509CertSpl = re.split('(.{64})', x509CertTxt)
887                    x509CertTxt = '\n'.join([i for i in x509CertSpl if i])
888                   
889                b64EncX509Cert = self.__class__.__beginCert + x509CertTxt + \
890                         self.__class__.__endCert
891                             
892                self.__setVerifyingCert(b64EncX509Cert)
893            except Exception, e:
894                raise VerifyError, "Error extracting BinarySecurityToken " + \
895                                   "from WSSE header: " + str(e)
896
897        if self.__verifyingCert is None:
898            raise VerifyError, "No certificate set for verification " + \
899                "of the signature"
900       
901        # Extract RSA public key from the cert
902        rsaPubKey = self.__verifyingCert.m2CryptoX509.get_pubkey().get_rsa()
903
904        # Apply the signature verification
905        try:
906            verify = rsaPubKey.verify(signedInfoDigestValue, signatureValue)
907        except RSA.RSAError, e:
908            raise VerifyError, "Error in Signature: " + str(e)
909       
910        if not verify:
911            raise InvalidSignature, "Invalid signature"
912       
913        # Verify certificate was issued by a known CA
914        if self.caCertIsSet:
915            self.verifyCertChain()
916           
917        #print "Signature OK"
918
919
920class EncryptionError(Exception):
921    """Flags an error in the encryption process"""
922
923class DecryptionError(Exception):
924    """Raised from EncryptionHandler.decrypt if an error occurs with the
925    decryption process"""
926
927
928class EncryptionHandler(object):
929    """Encrypt/Decrypt SOAP messages using WS-Security""" 
930   
931    # Map namespace URIs to Crypto algorithm module and mode
932    cryptoAlg = \
933    {
934         _ENCRYPTION.WRAP_AES256:      {'module':       AES, 
935                                        'mode':         AES.MODE_ECB,
936                                        'blockSize':    16},
937         
938         # CBC (Cipher Block Chaining) modes
939         _ENCRYPTION.BLOCK_AES256:     {'module':       AES, 
940                                        'mode':         AES.MODE_CBC,
941                                        'blockSize':    16},
942                                       
943         _ENCRYPTION.BLOCK_TRIPLEDES:  {'module':       DES3, 
944                                        'mode':         DES3.MODE_CBC,
945                                        'blockSize':    8}   
946    }
947
948     
949    def __init__(self,
950                 signingCertFilePath=None, 
951                 signingPriKeyFilePath=None, 
952                 signingPriKeyPwd=None,
953                 chkSecurityTokRef=False,
954                 encrNS=_ENCRYPTION.BLOCK_AES256):
955       
956        self.__signingCertFilePath = signingCertFilePath
957        self.__signingPriKeyFilePath = signingPriKeyFilePath
958        self.__signingPriKeyPwd = signingPriKeyPwd
959       
960        self.__chkSecurityTokRef = chkSecurityTokRef
961       
962        # Algorithm for shared key encryption
963        try:
964            self.__encrAlg = self.cryptoAlg[encrNS]
965           
966        except KeyError:
967            raise EncryptionError, \
968        'Input encryption algorithm namespace "%s" is not supported' % encrNS
969
970        self.__encrNS = encrNS
971       
972       
973    def encrypt(self, soapWriter):
974        """Encrypt an outbound SOAP message
975       
976        Use Key Wrapping - message is encrypted using a shared key which
977        itself is encrypted with the public key provided by the X.509 cert.
978        signingCertFilePath"""
979       
980        # Use X.509 Cert to encrypt
981        x509Cert = X509.load_cert(self.__signingCertFilePath)
982       
983        soapWriter.dom.setNamespaceAttribute('wsse', OASIS.WSSE)
984        soapWriter.dom.setNamespaceAttribute('xenc', _ENCRYPTION.BASE)
985        soapWriter.dom.setNamespaceAttribute('ds', DSIG.BASE)
986       
987        # TODO: Put in a check to make sure <wsse:security> isn't already
988        # present in header
989        wsseElem = soapWriter._header.createAppendElement(OASIS.WSSE, 
990                                                         'Security')
991        wsseElem.node.setAttribute('SOAP-ENV:mustUnderstand', "1")
992       
993        encrKeyElem = wsseElem.createAppendElement(_ENCRYPTION.BASE, 
994                                                   'EncryptedKey')
995       
996        # Encryption method used to encrypt the shared key
997        keyEncrMethodElem = encrKeyElem.createAppendElement(_ENCRYPTION.BASE, 
998                                                        'EncryptionMethod')
999       
1000        keyEncrMethodElem.node.setAttribute('Algorithm', 
1001                                            _ENCRYPTION.KT_RSA_1_5)
1002
1003
1004        # Key Info
1005        KeyInfoElem = encrKeyElem.createAppendElement(DSIG.BASE, 'KeyInfo')
1006       
1007        secTokRefElem = KeyInfoElem.createAppendElement(OASIS.WSSE, 
1008                                                  'SecurityTokenReference')
1009       
1010        x509IssSerialElem = secTokRefElem.createAppendElement(DSIG.BASE, 
1011                                                          'X509IssuerSerial')
1012
1013       
1014        x509IssNameElem = x509IssSerialElem.createAppendElement(DSIG.BASE, 
1015                                                          'X509IssuerName')
1016        x509IssNameElem.createAppendTextNode(x509Cert.get_issuer().as_text())
1017
1018       
1019        x509IssSerialNumElem = x509IssSerialElem.createAppendElement(
1020                                                  DSIG.BASE, 
1021                                                  'X509IssuerSerialNumber')
1022       
1023        x509IssSerialNumElem.createAppendTextNode(
1024                                          str(x509Cert.get_serial_number()))
1025
1026        # References to what has been encrypted
1027        encrKeyCiphDataElem = encrKeyElem.createAppendElement(
1028                                                          _ENCRYPTION.BASE,
1029                                                          'CipherData')
1030       
1031        encrKeyCiphValElem = encrKeyCiphDataElem.createAppendElement(
1032                                                          _ENCRYPTION.BASE,
1033                                                          'CipherValue')
1034
1035        # References to what has been encrypted
1036        refListElem = encrKeyElem.createAppendElement(_ENCRYPTION.BASE,
1037                                                      'ReferenceList')
1038       
1039        dataRefElem = refListElem.createAppendElement(_ENCRYPTION.BASE,
1040                                                      'DataReference')
1041        dataRefElem.node.setAttribute('URI', "#encrypted")
1042
1043                     
1044        # Add Encrypted data to SOAP body
1045        encrDataElem = soapWriter.body.createAppendElement(_ENCRYPTION.BASE, 
1046                                                           'EncryptedData')
1047        encrDataElem.node.setAttribute('Id', 'encrypted')
1048        encrDataElem.node.setAttribute('Type', _ENCRYPTION.BASE) 
1049             
1050        # Encryption method used to encrypt the target data
1051        dataEncrMethodElem = encrDataElem.createAppendElement(
1052                                                      _ENCRYPTION.BASE, 
1053                                                      'EncryptionMethod')
1054       
1055        dataEncrMethodElem.node.setAttribute('Algorithm', self.__encrNS)
1056       
1057        # Cipher data
1058        ciphDataElem = encrDataElem.createAppendElement(_ENCRYPTION.BASE,
1059                                                        'CipherData')
1060       
1061        ciphValueElem = ciphDataElem.createAppendElement(_ENCRYPTION.BASE,
1062                                                         'CipherValue')
1063
1064
1065        # Get elements from SOAP body for encryption
1066        dataElem = soapWriter.body.node.childNodes[0]
1067        data = dataElem.toxml()
1068     
1069        # Pad data to nearest multiple of encryption algorithm's block size   
1070        modData = len(data) % self.__encrAlg['blockSize']
1071        nPad = modData and self.__encrAlg['blockSize'] - modData or 0
1072       
1073        # PAd with random junk but ...
1074        data += os.urandom(nPad-1)
1075       
1076        # Last byte should be number of padding bytes
1077        # (http://www.w3.org/TR/xmlenc-core/#sec-Alg-Block)
1078        data += chr(nPad)       
1079       
1080        # Generate shared key and input vector - for testing use hard-coded
1081        # values to allow later comparison             
1082        sharedKey = os.urandom(self.__encrAlg['blockSize'])
1083        iv = os.urandom(self.__encrAlg['blockSize'])
1084       
1085        alg = self.__encrAlg['module'].new(sharedKey,
1086                                           self.__encrAlg['mode'],
1087                                           iv)
1088 
1089        # Encrypt required elements - prepend input vector
1090        encryptedData = alg.encrypt(iv + data)
1091        dataCiphValue = base64.encodestring(encryptedData).strip()
1092
1093        ciphValueElem.createAppendTextNode(dataCiphValue)
1094       
1095       
1096        # ! Delete unencrypted message body elements !
1097        soapWriter.body.node.removeChild(dataElem)
1098
1099       
1100        # Use X.509 cert public key to encrypt the shared key - Extract key
1101        # from the cert
1102        rsaPubKey = x509Cert.get_pubkey().get_rsa()
1103       
1104        # Encrypt the shared key
1105        encryptedSharedKey = rsaPubKey.public_encrypt(sharedKey, 
1106                                                      RSA.pkcs1_padding)
1107       
1108        encrKeyCiphVal = base64.encodestring(encryptedSharedKey).strip()
1109       
1110        # Add the encrypted shared key to the EncryptedKey section in the SOAP
1111        # header
1112        encrKeyCiphValElem.createAppendTextNode(encrKeyCiphVal)
1113
1114#        print soapWriter.dom.node.toprettyxml()
1115#        import pdb;pdb.set_trace()
1116       
1117       
1118    def decrypt(self, parsedSOAP):
1119        """Decrypt an inbound SOAP message"""
1120       
1121        processorNss = \
1122        {
1123            'xenc':   _ENCRYPTION.BASE,
1124            'ds':     DSIG.BASE, 
1125            'wsu':    _WSU.UTILITY, 
1126            'wsse':   OASIS.WSSE, 
1127            'soapenv':"http://schemas.xmlsoap.org/soap/envelope/" 
1128        }
1129        ctxt = Context(parsedSOAP.dom, processorNss=processorNss)
1130       
1131        refListNodes = xpath.Evaluate('//xenc:ReferenceList', 
1132                                      contextNode=parsedSOAP.dom, 
1133                                      context=ctxt)
1134        if len(refListNodes) > 1:
1135            raise DecryptionError, 'Expecting a single ReferenceList element'
1136       
1137        try:
1138            refListNode = refListNodes[0]
1139        except:
1140            # Message wasn't encrypted - is this OK or is a check needed for
1141            # encryption info in SOAP body - enveloped form?
1142            return
1143
1144
1145        # Check for wrapped key encryption
1146        encrKeyNodes = xpath.Evaluate('//xenc:EncryptedKey', 
1147                                      contextNode=parsedSOAP.dom, 
1148                                      context=ctxt)
1149        if len(encrKeyNodes) > 1:
1150            raise DecryptionError, 'This implementation can only handle ' + \
1151                                   'single EncryptedKey element'
1152       
1153        try:
1154            encrKeyNode = encrKeyNodes[0]
1155        except:
1156            # Shared key encryption used - leave out for the moment
1157            raise DecryptionError, 'This implementation can only handle ' + \
1158                                   'wrapped key encryption'
1159
1160       
1161        # Check encryption method
1162        keyEncrMethodNode = getElements(encrKeyNode, 'EncryptionMethod')[0]     
1163        keyAlgorithm = keyEncrMethodNode.getAttributeNode("Algorithm").value
1164        if keyAlgorithm != _ENCRYPTION.KT_RSA_1_5:
1165            raise DecryptionError, \
1166            'Encryption algorithm for wrapped key is "%s", expecting "%s"' % \
1167                (keyAlgorithm, _ENCRYPTION.KT_RSA_1_5)
1168
1169                                                           
1170        if self.__chkSecurityTokRef and self.__signingCertFilePath:
1171             
1172            # Check input cert. against SecurityTokenReference
1173            securityTokRefXPath = '/ds:KeyInfo/wsse:SecurityTokenReference'
1174            securityTokRefNode = xpath.Evaluate(securityTokRefXPath, 
1175                                                contextNode=encrKeyNode, 
1176                                                context=ctxt)
1177            # TODO: Look for ds:X509* elements to check against X.509 cert
1178            # input
1179
1180
1181        # Look for cipher data for wrapped key
1182        keyCiphDataNode = getElements(encrKeyNode, 'CipherData')[0]
1183        keyCiphValNode = getElements(keyCiphDataNode, 'CipherValue')[0]
1184
1185        keyCiphVal = str(keyCiphValNode.childNodes[0].nodeValue)
1186        encryptedKey = base64.decodestring(keyCiphVal)
1187
1188        # Read RSA Private key in order to decrypt wrapped key 
1189        priKeyFile = BIO.File(open(self.__signingPriKeyFilePath))         
1190        pwdCallback = lambda *ar, **kw: self.__signingPriKeyPwd                                       
1191        priKey = RSA.load_key_bio(priKeyFile, callback=pwdCallback)
1192       
1193        sharedKey = priKey.private_decrypt(encryptedKey, RSA.pkcs1_padding)
1194       
1195
1196        # Check list of data elements that have been encrypted
1197        for dataRefNode in refListNode.childNodes:
1198
1199            # Get the URI for the reference
1200            dataRefURI = dataRefNode.getAttributeNode('URI').value                           
1201            if dataRefURI[0] != "#":
1202                raise VerifyError, \
1203                    "Expecting # identifier for DataReference URI \"%s\"" % \
1204                    dataRefURI
1205
1206            # XPath reference - need to check for wsu namespace qualified?
1207            #encrNodeXPath = '//*[@wsu:Id="%s"]' % dataRefURI[1:]
1208            encrNodeXPath = '//*[@Id="%s"]' % dataRefURI[1:]
1209            encrNode = xpath.Evaluate(encrNodeXPath, 
1210                                      contextNode=parsedSOAP.dom, 
1211                                      context=ctxt)[0]
1212               
1213            dataEncrMethodNode = getElements(encrNode, 'EncryptionMethod')[0]     
1214            dataAlgorithm = \
1215                        dataEncrMethodNode.getAttributeNode("Algorithm").value
1216            try:       
1217                # Match algorithm name to Crypto module
1218                CryptoAlg = self.cryptoAlg[dataAlgorithm]
1219               
1220            except KeyError:
1221                raise DecryptionError, \
1222'Encryption algorithm for data is "%s", supported algorithms are:\n "%s"' % \
1223                    (keyAlgorithm, "\n".join(self.cryptoAlg.keys()))
1224
1225            # Get Data
1226            dataCiphDataNode = getElements(encrNode, 'CipherData')[0]
1227            dataCiphValNode = getElements(dataCiphDataNode, 'CipherValue')[0]
1228       
1229            dataCiphVal = str(dataCiphValNode.childNodes[0].nodeValue)
1230            encryptedData = base64.decodestring(dataCiphVal)
1231           
1232            alg = CryptoAlg['module'].new(sharedKey, CryptoAlg['mode'])
1233            decryptedData = alg.decrypt(encryptedData)
1234           
1235            # Strip prefix - assume is block size
1236            decryptedData = decryptedData[CryptoAlg['blockSize']:]
1237           
1238            # Strip any padding suffix - Last byte should be number of padding
1239            # bytes
1240            # (http://www.w3.org/TR/xmlenc-core/#sec-Alg-Block)
1241            lastChar = decryptedData[-1]
1242            nPad = ord(lastChar)
1243           
1244            # Sanity check - there may be no padding at all - the last byte
1245            # being the end of the encrypted XML?
1246            #
1247            # TODO: are there better sanity checks than this?!
1248            if nPad < CryptoAlg['blockSize'] and nPad > 0 and \
1249               lastChar != '\n' and lastChar != '>':
1250               
1251                # Follow http://www.w3.org/TR/xmlenc-core/#sec-Alg-Block -
1252                # last byte gives number of padding bytes
1253                decryptedData = decryptedData[:-nPad]
1254
1255
1256            # Parse the encrypted data - inherit from Reader as a fudge to
1257            # enable relevant namespaces to be added prior to parse
1258            processorNss.update({'xsi': SCHEMA.XSI3, 'ns1': 'urn:ZSI:examples'})
1259            class _Reader(Reader):
1260                def initState(self, ownerDoc=None):
1261                    Reader.initState(self, ownerDoc=ownerDoc)
1262                    self._namespaces.update(processorNss)
1263                   
1264            rdr = _Reader()
1265            dataNode = rdr.fromString(decryptedData, ownerDoc=parsedSOAP.dom)
1266           
1267            # Add decrypted element to parent and remove encrypted one
1268            parentNode = encrNode._get_parentNode()
1269            parentNode.appendChild(dataNode)
1270            parentNode.removeChild(encrNode)
1271           
1272            from xml.dom.ext import ReleaseNode
1273            ReleaseNode(encrNode)
1274           
1275            # Ensure body_root attribute is up to date in case it was
1276            # previously encrypted
1277            parsedSOAP.body_root = parsedSOAP.body.childNodes[0]
1278            #print decryptedData
1279            #import pdb;pdb.set_trace()
1280
1281
1282#_____________________________________________________________________________
1283from zope.interface import classProvides, implements, Interface
1284import twisted.web.http
1285from twisted.python import log, failure
1286
1287from ZSI.twisted.WSresource import DefaultHandlerChain, \
1288    DefaultCallbackHandler, CallbackChainInterface, HandlerChainInterface, \
1289    DataHandler
1290   
1291from ZSI import _get_element_nsuri_name, EvaluateException, ParseException
1292   
1293   
1294class WSSecurityHandlerChainFactory:
1295    protocol = DefaultHandlerChain
1296   
1297    @classmethod
1298    def newInstance(cls):
1299        return cls.protocol(DefaultCallbackHandler, 
1300                            DataHandler,
1301                            WSSecurityHandler)
1302   
1303
1304class WSSecurityHandler:
1305    classProvides(HandlerChainInterface)
1306
1307    signatureHandler = None
1308   
1309    @classmethod
1310    def processRequest(cls, ps, **kw):
1311        """invokes callback that should return a (request,response) tuple.
1312        representing the SOAP request and response respectively.
1313        ps -- ParsedSoap instance representing HTTP Body.
1314        request -- twisted.web.server.Request
1315        """
1316        if cls.signatureHandler:
1317            cls.signatureHandler.verify(ps)
1318           
1319        return ps
1320   
1321    @classmethod
1322    def processResponse(cls, sw, **kw):
1323       
1324        if cls.signatureHandler:
1325            cls.signatureHandler.sign(sw)
1326           
1327        return sw
Note: See TracBrowser for help on using the repository browser.