source: TI12-security/trunk/NDGSecurity/python/Tests/xmlsec/WS-Security/wsSecurity.py @ 7080

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/NDGSecurity/python/Tests/xmlsec/WS-Security/wsSecurity.py@7080
Revision 7080, 31.1 KB checked in by pjkersha, 9 years ago (diff)
  • Property svn:executable set to *
  • Property svn:keywords set to Id
Line 
1#!/bin/env python
2
3"""WS-Security test class includes digital signature handler
4
5NERC Data Grid Project
6
7P J Kershaw 01/09/06
8
9Copyright (C) 2009 Science and Technology Facilities Council
10
11"""
12
13__revision__ = '$Id$'
14
15import re
16
17# Digest and signature/verify
18from sha import sha
19from M2Crypto import X509, BIO, RSA
20import base64
21
22# For shared key encryption
23from Crypto.Cipher import AES, DES3
24import os
25
26import ZSI
27from ZSI.wstools.Namespaces import DSIG, ENCRYPTION, OASIS, WSU, WSA200403, \
28                                   SOAP, SCHEMA # last included for xsi
29                                   
30from ZSI.TC import ElementDeclaration,TypeDefinition
31from ZSI.generate.pyclass import pyclass_type
32
33from ZSI.wstools.Utility import DOMException, SplitQName
34from ZSI.wstools.Utility import NamespaceError, MessageInterface, ElementProxy
35
36# XML Parsing
37from cStringIO import StringIO
38from Ft.Xml.Domlette import NonvalidatingReaderBase, NonvalidatingReader
39from Ft.Xml import XPath
40
41# Canonicalization
42from ZSI.wstools.c14n import Canonicalize
43from xml.dom import Node
44from xml.xpath.Context import Context
45from xml import xpath
46
47# Include for re-parsing doc ready for canonicalization in sign method - see
48# associated note
49from xml.dom.ext.reader.PyExpat import Reader
50
51
52class _ENCRYPTION(ENCRYPTION):
53    '''Derived from ENCRYPTION class to add in extra 'tripledes-cbc' - is this
54    any different to 'des-cbc'?  ENCRYPTION class implies that it is the same
55    because it's assigned to 'BLOCK_3DES' ??'''
56    BLOCK_TRIPLEDES = "http://www.w3.org/2001/04/xmlenc#tripledes-cbc"
57
58
59def getElements(node, nameList):
60    '''DOM Helper function for getting child elements from a given node'''
61    # Avoid sub-string matches
62    nameList = isinstance(nameList, basestring) and [nameList] or nameList
63    return [n for n in node.childNodes if str(n.localName) in nameList]
64
65
66class VerifyError(Exception):
67    """Raised from SignatureHandler.verify if signature is invalid"""
68
69class SignatureError(Exception):
70    """Flag if an error occurs during signature generation"""
71       
72class SignatureHandler(object):
73   
74    def __init__(self,
75                 certFilePath=None, 
76                 priKeyFilePath=None, 
77                 priKeyPwd=None):
78       
79        self.__certFilePath = certFilePath
80        self.__priKeyFilePath = priKeyFilePath
81        self.__priKeyPwd = priKeyPwd
82
83
84    def sign(self, soapWriter):
85        '''Sign the message body and binary security token of a SOAP message
86        '''
87        # Add X.509 cert as binary security token
88        x509Cert = X509.load_cert(self.__certFilePath)
89       
90        x509CertPat = re.compile(\
91            '-----BEGIN CERTIFICATE-----\n?(.*?)\n?-----END CERTIFICATE-----',
92            re.S)
93        x509CertStr = x509CertPat.findall(x509Cert.as_pem())[0]
94
95        soapWriter._header.setNamespaceAttribute('wsse', OASIS.WSSE)
96        soapWriter._header.setNamespaceAttribute('wsu', WSU.UTILITY)
97        soapWriter._header.setNamespaceAttribute('ds', DSIG.BASE)
98       
99        # TODO: Put in a check to make sure <wsse:security> isn't already
100        # present in header
101        wsseElem = soapWriter._header.createAppendElement(OASIS.WSSE, 
102                                                         'Security')
103        wsseElem.setNamespaceAttribute('wsse', OASIS.WSSE)
104        wsseElem.node.setAttribute('SOAP-ENV:mustUnderstand', "1")
105       
106        binSecTokElem = wsseElem.createAppendElement(OASIS.WSSE, 
107                                                     'BinarySecurityToken')
108        binSecTokElem.node.setAttribute('ValueType', "wsse:X509v3")
109        binSecTokElem.node.setAttribute('EncodingType', "wsse:Base64Binary")
110       
111        # Add ID so that the binary token can be included in the signature
112        binSecTokElem.node.setAttribute('wsu:Id', "binaryToken")
113
114        binSecTokElem.createAppendTextNode(x509CertStr)
115
116       
117        # Signature
118        signatureElem = wsseElem.createAppendElement(DSIG.BASE, 'Signature')
119        signatureElem.setNamespaceAttribute('ds', DSIG.BASE)
120       
121        # Signature - Signed Info
122        signedInfoElem = signatureElem.createAppendElement(DSIG.BASE, 
123                                                           'SignedInfo')
124       
125        # Signed Info - Canonicalization method
126        c14nMethodElem = signedInfoElem.createAppendElement(DSIG.BASE,
127                                                    'CanonicalizationMethod')
128        c14nMethodElem.node.setAttribute('Algorithm', DSIG.C14N)
129       
130        # Signed Info - Signature method
131        sigMethodElem = signedInfoElem.createAppendElement(DSIG.BASE,
132                                                    'SignatureMethod')
133        sigMethodElem.node.setAttribute('Algorithm', DSIG.DIGEST_SHA1)
134       
135        # Signature - Signature value
136        signatureValueElem = signatureElem.createAppendElement(DSIG.BASE, 
137                                                             'SignatureValue')
138       
139        # Key Info
140        KeyInfoElem = signatureElem.createAppendElement(DSIG.BASE, 'KeyInfo')
141        secTokRefElem = KeyInfoElem.createAppendElement(OASIS.WSSE, 
142                                                  'SecurityTokenReference')
143       
144        # Reference back to the binary token included earlier
145        wsseRefElem = secTokRefElem.createAppendElement(OASIS.WSSE, 
146                                                        'Reference')
147        wsseRefElem.node.setAttribute('URI', "#binaryToken")
148       
149        # Add Reference to body so that it can be included in the signature
150        soapWriter.body.node.setAttribute('wsu:Id', "body")
151        soapWriter.body.node.setAttribute('xmlns:wsu', WSU.UTILITY)
152
153        # Serialize and re-parse prior to reference generation - calculating
154        # canonicalization based on soapWriter.dom.node seems to give an
155        # error: the order of wsu:Id attribute is not correct
156        docNode = Reader().fromString(str(soapWriter))
157       
158        # Namespaces for XPath searches
159        processorNss = \
160        {
161            'ds':     DSIG.BASE, 
162            'wsu':    WSU.UTILITY, 
163            'wsse':   OASIS.WSSE, 
164            'soapenv':"http://schemas.xmlsoap.org/soap/envelope/" 
165        }
166        ctxt = Context(docNode, processorNss=processorNss)
167        idNodes = xpath.Evaluate('//*[@wsu:Id]', 
168                                 contextNode=docNode, 
169                                 context=ctxt)
170               
171        # 1) Reference Generation
172        #
173        # Find references
174        for idNode in idNodes:
175           
176            # Set URI attribute to point to reference to be signed
177            #uri = u"#" + idNode.getAttribute('wsu:Id')
178            uri = u"#" + idNode.attributes[(WSU.UTILITY, 'Id')].value
179           
180            # Canonicalize reference
181            c14nRef = Canonicalize(idNode)
182           
183            # Calculate digest for reference and base 64 encode
184            #
185            # Nb. encodestring adds a trailing newline char
186            digestValue = base64.encodestring(sha(c14nRef).digest()).strip()
187
188
189            # Add a new reference element to SignedInfo
190            refElem = signedInfoElem.createAppendElement(DSIG.BASE, 
191                                                         'Reference')
192            refElem.node.setAttribute('URI', uri)
193           
194            # Use ds:Transforms or wsse:TransformationParameters?
195            tranformsElem = refElem.createAppendElement(DSIG.BASE, 
196                                                        'Transforms')
197            tranformElem = tranformsElem.createAppendElement(DSIG.BASE, 
198                                                             'Transform')
199            tranformElem.node.setAttribute('Algorithm', DSIG.C14N)
200           
201            # Digest Method
202            digestMethodElem = refElem.createAppendElement(DSIG.BASE, 
203                                                           'DigestMethod')
204            digestMethodElem.node.setAttribute('Algorithm', DSIG.DIGEST_SHA1)
205           
206            # Digest Value
207            digestValueElem = refElem.createAppendElement(DSIG.BASE, 
208                                                          'DigestValue')
209            digestValueElem.createAppendTextNode(digestValue)
210
211   
212        # 2) Signature Generation
213        #
214
215        # Test against signature generated by pyXMLSec version
216        #xmlTxt = open('./wsseSign-xmlsec-res.xml').read()
217        #dom = NonvalidatingReader.parseStream(StringIO(xmlTxt))
218       
219        # Canonicalize the signedInfo node
220        #
221        # Nb. When extracted the code adds the namespace attribute to the
222        # signedInfo!  This has important consequences for validation -
223        #
224        # 1) Do you strip the namespace attribute before taking the digest to
225        # ensure the text is exactly the same as what is displayed in the
226        # message?
227        #
228        # 2) Leave it in and assume the validation algorithm will expect to
229        # add in the namespace attribute?!
230        #
231        # http://www.w3.org/TR/xml-c14n#NoNSPrefixRewriting implies you need
232        # to include namespace declarations for namespaces referenced in a doc
233        # subset - yes to 2)
234        c14nSignedInfo = signedInfoElem.canonicalize()
235
236        # Calculate digest of SignedInfo
237        signedInfoDigestValue = sha(c14nSignedInfo).digest().strip()
238       
239        # Read Private key to sign with   
240        priKeyFile = BIO.File(open(self.__priKeyFilePath))                                           
241        priKey = RSA.load_key_bio(priKeyFile, 
242                                  callback=lambda *ar, **kw: self.__priKeyPwd)
243       
244        # Sign using the private key and base 64 encode the result
245        signatureValue = priKey.sign(signedInfoDigestValue)
246        b64EncSignatureValue = base64.encodestring(signatureValue).strip()
247
248        # Add to <SignatureValue>
249        signatureValueElem.createAppendTextNode(b64EncSignatureValue)
250       
251        # Extract RSA public key from the cert
252        rsaPubKey = x509Cert.get_pubkey().get_rsa()
253       
254        # Check the signature
255#        verify = bool(rsaPubKey.verify(signedInfoDigestValue, signatureValue))
256#       
257#        open('soap.xml', 'w').write(str(soapWriter))
258#        import pdb;pdb.set_trace()
259        print "Signature Generated"
260
261
262    def verify(self, parsedSOAP):
263        """Verify signature"""
264       
265        processorNss = \
266        {
267            'ds':     DSIG.BASE, 
268            'wsu':    WSU.UTILITY, 
269            'wsse':   OASIS.WSSE, 
270            'soapenv':"http://schemas.xmlsoap.org/soap/envelope/" 
271        }
272        ctxt = Context(parsedSOAP.dom, processorNss=processorNss)
273       
274
275        signatureNodes = xpath.Evaluate('//ds:Signature', 
276                                        contextNode=parsedSOAP.dom, 
277                                        context=ctxt)
278        if len(signatureNodes) > 1:
279            raise VerifyError, 'Multiple ds:Signature elements found'
280       
281        try:
282            signatureNodes = signatureNodes[0]
283        except:
284            # Message wasn't signed
285            return
286       
287        # Two stage process: reference validation followed by signature
288        # validation
289       
290        # 1) Reference Validation       
291        refNodes = xpath.Evaluate('//ds:Reference', 
292                                  contextNode=parsedSOAP.dom, 
293                                  context=ctxt)
294           
295        for refNode in refNodes:
296            # Get the URI for the reference
297            refURI = refNode.getAttributeNodeNS(None, 'URI').value
298                           
299            transformsNode = getElements(refNode, "Transforms")[0]
300            transforms = getElements(transformsNode, "Transform")
301   
302            algorithm = transforms[0].getAttributeNodeNS(None, 
303                                                         "Algorithm").value
304           
305            # Add extra keyword for Exclusive canonicalization method
306            kw = {}
307            if algorithm == DSIG.C14N_EXCL:
308                try:
309                    inclusiveNS = transforms[0].getElement(DSIG.C14N_EXCL, 
310                                                       "InclusiveNamespaces")
311                    kw['unsuppressedPrefixes'] = \
312                    inclusiveNS.getAttributeValue(None, "PrefixList").split()
313                except:
314                    raise VerifyError, \
315                'failed to handle transform (%s) in <ds:Reference URI="%s">'%\
316                        (transforms[0], uri)
317       
318            # Canonicalize the reference data and calculate the digest
319            if refURI[0] != "#":
320                raise VerifyError, \
321                    "Expecting # identifier for Reference URI \"%s\"" % refURI
322                   
323            # XPath reference
324            uriXPath = '//*[@wsu:Id="%s"]' % refURI[1:]
325            uriNode = xpath.Evaluate(uriXPath, 
326                                     contextNode=parsedSOAP.dom, 
327                                     context=ctxt)[0]
328
329            c14nRef = Canonicalize(uriNode, **kw)
330            digestValue = base64.encodestring(sha(c14nRef).digest()).strip()
331           
332            # Extract the digest value that was stored           
333            digestNode = getElements(refNode, "DigestValue")[0]
334            nodeDigestValue = str(digestNode.childNodes[0].nodeValue).strip()   
335           
336            # Reference validates if the two digest values are the same
337            if digestValue != nodeDigestValue:
338                raise VerifyError, 'Digest Values do not match for URI: "%s"' %\
339                                                                        refURI
340               
341        # 2) Signature Validation
342        signedInfoNode = xpath.Evaluate('//ds:SignedInfo',
343                                        contextNode=parsedSOAP.dom, 
344                                        context=ctxt)[0]
345
346        # Get the canonicalization method - change later to check this and
347        # make sure it's an algorithm supported by this code
348        c14nMethodNode = getElements(signedInfoNode, 
349                                     "CanonicalizationMethod")[0]
350                                             
351        algorithm = c14nMethodNode.getAttributeNodeNS(None, 'Algorithm').value
352        if algorithm != DSIG.C14N:
353            raise VerifyError, \
354                "Only \"%s\" canonicalization algorithm supported" % DSIG.C14N
355               
356        # Canonicalize the SignedInfo node and take digest
357        c14nSignedInfo = Canonicalize(signedInfoNode)       
358        signedInfoDigestValue = sha(c14nSignedInfo).digest()
359       
360        # Get the signature value in order to check against the digest just
361        # calculated
362        signatureValueNode = xpath.Evaluate('//ds:SignatureValue',
363                                            contextNode=parsedSOAP.dom, 
364                                            context=ctxt)[0]
365
366        # Remove base 64 encoding
367        b64EncSignatureValue = \
368                    str(signatureValueNode.childNodes[0].nodeValue).strip()
369                   
370        signatureValue = base64.decodestring(b64EncSignatureValue)
371
372
373        # Read X.509 Cert from wsse:BinarySecurityToken node
374        # - leave out for now and read direct from hard coded pem file
375        x509Cert = X509.load_cert(self.__certFilePath)
376       
377        # Extract RSA public key from the cert
378        rsaPubKey = x509Cert.get_pubkey().get_rsa()
379       
380        # Apply the signature verification
381        try:
382            verify = bool(rsaPubKey.verify(signedInfoDigestValue, 
383                                           signatureValue))
384        except RSA.RSAError:
385            raise VerifyError, "Invalid Signature"
386       
387        print "Signature OK"
388
389
390class EncryptionError(Exception):
391    """Flags an error in the encryption process"""
392
393class DecryptionError(Exception):
394    """Raised from EncryptionHandler.decrypt if an error occurs with the
395    decryption process"""
396
397
398class EncryptionHandler(object):
399    """Encrypt/Decrypt SOAP messages using WS-Security""" 
400   
401    # Map namespace URIs to Crypto algorithm module and mode
402    cryptoAlg = \
403    {
404         _ENCRYPTION.WRAP_AES256:      {'module':       AES, 
405                                        'mode':         AES.MODE_ECB,
406                                        'blockSize':    16},
407         
408         # CBC (Cipher Block Chaining) modes
409         _ENCRYPTION.BLOCK_AES256:     {'module':       AES, 
410                                        'mode':         AES.MODE_CBC,
411                                        'blockSize':    16},
412                                       
413         _ENCRYPTION.BLOCK_TRIPLEDES:  {'module':       DES3, 
414                                        'mode':         DES3.MODE_CBC,
415                                        'blockSize':    8}   
416    }
417
418     
419    def __init__(self,
420                 certFilePath=None, 
421                 priKeyFilePath=None, 
422                 priKeyPwd=None,
423                 chkSecurityTokRef=False,
424                 encrNS=_ENCRYPTION.BLOCK_AES256):
425       
426        self.__certFilePath = certFilePath
427        self.__priKeyFilePath = priKeyFilePath
428        self.__priKeyPwd = priKeyPwd
429       
430        self.__chkSecurityTokRef = chkSecurityTokRef
431       
432        # Algorithm for shared key encryption
433        try:
434            self.__encrAlg = self.cryptoAlg[encrNS]
435           
436        except KeyError:
437            raise EncryptionError, \
438        'Input encryption algorithm namespace "%s" is not supported' % encrNS
439
440        self.__encrNS = encrNS
441       
442       
443    def encrypt(self, soapWriter):
444        """Encrypt an outbound SOAP message
445       
446        Use Key Wrapping - message is encrypted using a shared key which
447        itself is encrypted with the public key provided by the X.509 cert.
448        certFilePath"""
449       
450        # Use X.509 Cert to encrypt
451        x509Cert = X509.load_cert(self.__certFilePath)
452       
453        soapWriter.dom.setNamespaceAttribute('wsse', OASIS.WSSE)
454        soapWriter.dom.setNamespaceAttribute('xenc', _ENCRYPTION.BASE)
455        soapWriter.dom.setNamespaceAttribute('ds', DSIG.BASE)
456       
457        # TODO: Put in a check to make sure <wsse:security> isn't already
458        # present in header
459        wsseElem = soapWriter._header.createAppendElement(OASIS.WSSE, 
460                                                         'Security')
461        wsseElem.node.setAttribute('SOAP-ENV:mustUnderstand', "1")
462       
463        encrKeyElem = wsseElem.createAppendElement(_ENCRYPTION.BASE, 
464                                                   'EncryptedKey')
465       
466        # Encryption method used to encrypt the shared key
467        keyEncrMethodElem = encrKeyElem.createAppendElement(_ENCRYPTION.BASE, 
468                                                        'EncryptionMethod')
469       
470        keyEncrMethodElem.node.setAttribute('Algorithm', 
471                                            _ENCRYPTION.KT_RSA_1_5)
472
473
474        # Key Info
475        KeyInfoElem = encrKeyElem.createAppendElement(DSIG.BASE, 'KeyInfo')
476       
477        secTokRefElem = KeyInfoElem.createAppendElement(OASIS.WSSE, 
478                                                  'SecurityTokenReference')
479       
480        x509IssSerialElem = secTokRefElem.createAppendElement(DSIG.BASE, 
481                                                          'X509IssuerSerial')
482
483       
484        x509IssNameElem = x509IssSerialElem.createAppendElement(DSIG.BASE, 
485                                                          'X509IssuerName')
486        x509IssNameElem.createAppendTextNode(x509Cert.get_issuer().as_text())
487
488       
489        x509IssSerialNumElem = x509IssSerialElem.createAppendElement(
490                                                  DSIG.BASE, 
491                                                  'X509IssuerSerialNumber')
492       
493        x509IssSerialNumElem.createAppendTextNode(
494                                          str(x509Cert.get_serial_number()))
495
496        # References to what has been encrypted
497        encrKeyCiphDataElem = encrKeyElem.createAppendElement(
498                                                          _ENCRYPTION.BASE,
499                                                          'CipherData')
500       
501        encrKeyCiphValElem = encrKeyCiphDataElem.createAppendElement(
502                                                          _ENCRYPTION.BASE,
503                                                          'CipherValue')
504
505        # References to what has been encrypted
506        refListElem = encrKeyElem.createAppendElement(_ENCRYPTION.BASE,
507                                                      'ReferenceList')
508       
509        dataRefElem = refListElem.createAppendElement(_ENCRYPTION.BASE,
510                                                      'DataReference')
511        dataRefElem.node.setAttribute('URI', "#encrypted")
512
513                     
514        # Add Encrypted data to SOAP body
515        encrDataElem = soapWriter.body.createAppendElement(_ENCRYPTION.BASE, 
516                                                           'EncryptedData')
517        encrDataElem.node.setAttribute('Id', 'encrypted')
518        encrDataElem.node.setAttribute('Type', _ENCRYPTION.BASE) 
519             
520        # Encryption method used to encrypt the target data
521        dataEncrMethodElem = encrDataElem.createAppendElement(
522                                                      _ENCRYPTION.BASE, 
523                                                      'EncryptionMethod')
524       
525        dataEncrMethodElem.node.setAttribute('Algorithm', self.__encrNS)
526       
527        # Cipher data
528        ciphDataElem = encrDataElem.createAppendElement(_ENCRYPTION.BASE,
529                                                        'CipherData')
530       
531        ciphValueElem = ciphDataElem.createAppendElement(_ENCRYPTION.BASE,
532                                                         'CipherValue')
533
534
535        # Get elements from SOAP body for encryption
536        dataElem = soapWriter.body.node.childNodes[0]
537        data = dataElem.toxml()
538     
539        # Pad data to nearest multiple of encryption algorithm's block size   
540        modData = len(data) % self.__encrAlg['blockSize']
541        nPad = modData and self.__encrAlg['blockSize'] - modData or 0
542       
543        # PAd with random junk but ...
544        data += os.urandom(nPad-1)
545       
546        # Last byte should be number of padding bytes
547        # (http://www.w3.org/TR/xmlenc-core/#sec-Alg-Block)
548        data += chr(nPad)       
549       
550        # Generate shared key and input vector - for testing use hard-coded
551        # values to allow later comparison             
552        sharedKey = os.urandom(self.__encrAlg['blockSize'])
553        iv = os.urandom(self.__encrAlg['blockSize'])
554       
555        alg = self.__encrAlg['module'].new(sharedKey,
556                                           self.__encrAlg['mode'],
557                                           iv)
558 
559        # Encrypt required elements - prepend input vector
560        encryptedData = alg.encrypt(iv + data)
561        dataCiphValue = base64.encodestring(encryptedData).strip()
562
563        ciphValueElem.createAppendTextNode(dataCiphValue)
564       
565       
566        # ! Delete unencrypted message body elements !
567        soapWriter.body.node.removeChild(dataElem)
568
569       
570        # Use X.509 cert public key to encrypt the shared key - Extract key
571        # from the cert
572        rsaPubKey = x509Cert.get_pubkey().get_rsa()
573       
574        # Encrypt the shared key
575        encryptedSharedKey = rsaPubKey.public_encrypt(sharedKey, 
576                                                      RSA.pkcs1_padding)
577       
578        encrKeyCiphVal = base64.encodestring(encryptedSharedKey).strip()
579       
580        # Add the encrypted shared key to the EncryptedKey section in the SOAP
581        # header
582        encrKeyCiphValElem.createAppendTextNode(encrKeyCiphVal)
583
584        print soapWriter.dom.node.toprettyxml()
585        import pdb;pdb.set_trace()
586       
587       
588    def decrypt(self, parsedSOAP):
589        """Decrypt an inbound SOAP message"""
590       
591        processorNss = \
592        {
593            'xenc':   _ENCRYPTION.BASE,
594            'ds':     DSIG.BASE, 
595            'wsu':    WSU.UTILITY, 
596            'wsse':   OASIS.WSSE, 
597            'soapenv':"http://schemas.xmlsoap.org/soap/envelope/" 
598        }
599        ctxt = Context(parsedSOAP.dom, processorNss=processorNss)
600       
601        refListNodes = xpath.Evaluate('//xenc:ReferenceList', 
602                                      contextNode=parsedSOAP.dom, 
603                                      context=ctxt)
604        if len(refListNodes) > 1:
605            raise DecryptionError, 'Expecting a single ReferenceList element'
606       
607        try:
608            refListNode = refListNodes[0]
609        except:
610            # Message wasn't encrypted - is this OK or is a check needed for
611            # encryption info in SOAP body - enveloped form?
612            return
613
614
615        # Check for wrapped key encryption
616        encrKeyNodes = xpath.Evaluate('//xenc:EncryptedKey', 
617                                      contextNode=parsedSOAP.dom, 
618                                      context=ctxt)
619        if len(encrKeyNodes) > 1:
620            raise DecryptionError, 'This implementation can only handle ' + \
621                                   'single EncryptedKey element'
622       
623        try:
624            encrKeyNode = encrKeyNodes[0]
625        except:
626            # Shared key encryption used - leave out for the moment
627            raise DecryptionError, 'This implementation can only handle ' + \
628                                   'wrapped key encryption'
629
630       
631        # Check encryption method
632        keyEncrMethodNode = getElements(encrKeyNode, 'EncryptionMethod')[0]     
633        keyAlgorithm = keyEncrMethodNode.getAttributeNodeNS(None, 
634                                                            "Algorithm").value
635        if keyAlgorithm != _ENCRYPTION.KT_RSA_1_5:
636            raise DecryptionError, \
637            'Encryption algorithm for wrapped key is "%s", expecting "%s"' % \
638                (keyAlgorithm, _ENCRYPTION.KT_RSA_1_5)
639
640                                                           
641        if self.__chkSecurityTokRef and self.__certFilePath:
642             
643            # Check input cert. against SecurityTokenReference
644            securityTokRefXPath = '/ds:KeyInfo/wsse:SecurityTokenReference'
645            securityTokRefNode = xpath.Evaluate(securityTokRefXPath, 
646                                                contextNode=encrKeyNode, 
647                                                context=ctxt)
648            # TODO: Look for ds:X509* elements to check against X.509 cert
649            # input
650
651
652        # Look for cipher data for wrapped key
653        keyCiphDataNode = getElements(encrKeyNode, 'CipherData')[0]
654        keyCiphValNode = getElements(keyCiphDataNode, 'CipherValue')[0]
655
656        keyCiphVal = str(keyCiphValNode.childNodes[0].nodeValue)
657        encryptedKey = base64.decodestring(keyCiphVal)
658
659        # Read RSA Private key in order to decrypt wrapped key 
660        priKeyFile = BIO.File(open(self.__priKeyFilePath))                                           
661        priKey = RSA.load_key_bio(priKeyFile, 
662                                  callback=lambda *ar, **kw: self.__priKeyPwd)
663       
664        sharedKey = priKey.private_decrypt(encryptedKey, RSA.pkcs1_padding)
665       
666
667        # Check list of data elements that have been encrypted
668        for dataRefNode in refListNode.childNodes:
669
670            # Get the URI for the reference
671            dataRefURI = dataRefNode.getAttributeNodeNS(None, 'URI').value                           
672            if dataRefURI[0] != "#":
673                raise VerifyError, \
674                    "Expecting # identifier for DataReference URI \"%s\"" % \
675                    dataRefURI
676
677            # XPath reference - need to check for wsu namespace qualified?
678            #encrNodeXPath = '//*[@wsu:Id="%s"]' % dataRefURI[1:]
679            encrNodeXPath = '//*[@Id="%s"]' % dataRefURI[1:]
680            encrNode = xpath.Evaluate(encrNodeXPath, 
681                                      contextNode=parsedSOAP.dom, 
682                                      context=ctxt)[0]
683               
684            dataEncrMethodNode = getElements(encrNode, 'EncryptionMethod')[0]     
685            dataAlgorithm = dataEncrMethodNode.getAttributeNodeNS(None, 
686                                                            "Algorithm").value
687            try:       
688                # Match algorithm name to Crypto module
689                CryptoAlg = self.cryptoAlg[dataAlgorithm]
690               
691            except KeyError:
692                raise DecryptionError, \
693'Encryption algorithm for data is "%s", supported algorithms are:\n "%s"' % \
694                    (keyAlgorithm, "\n".join(self.cryptoAlg.keys()))
695
696            # Get Data
697            dataCiphDataNode = getElements(encrNode, 'CipherData')[0]
698            dataCiphValNode = getElements(dataCiphDataNode, 'CipherValue')[0]
699       
700            dataCiphVal = str(dataCiphValNode.childNodes[0].nodeValue)
701            encryptedData = base64.decodestring(dataCiphVal)
702           
703            alg = CryptoAlg['module'].new(sharedKey, CryptoAlg['mode'])
704            decryptedData = alg.decrypt(encryptedData)
705           
706            # Strip prefix - assume is block size
707            decryptedData = decryptedData[CryptoAlg['blockSize']:]
708           
709            # Strip any padding suffix - Last byte should be number of padding
710            # bytes
711            # (http://www.w3.org/TR/xmlenc-core/#sec-Alg-Block)
712            lastChar = decryptedData[-1]
713            nPad = ord(lastChar)
714           
715            # Sanity check - there may be no padding at all - the last byte
716            # being the end of the encrypted XML?
717            #
718            # TODO: are there better sanity checks than this?!
719            if nPad < CryptoAlg['blockSize'] and nPad > 0 and \
720               lastChar != '\n' and lastChar != '>':
721               
722                # Follow http://www.w3.org/TR/xmlenc-core/#sec-Alg-Block -
723                # last byte gives number of padding bytes
724                decryptedData = decryptedData[:-nPad]
725
726
727            # Parse the encrypted data - inherit from Reader as a fudge to
728            # enable relevant namespaces to be added prior to parse
729            processorNss.update({'xsi': SCHEMA.XSI3})
730            class _Reader(Reader):
731                def initState(self, ownerDoc=None):
732                    Reader.initState(self, ownerDoc=ownerDoc)
733                    self._namespaces.update(processorNss)
734                   
735            rdr = _Reader()
736            dataNode = rdr.fromString(decryptedData, ownerDoc=parsedSOAP.dom)
737           
738            # Add decrypted element to parent and remove encrypted one
739            parentNode = encrNode._get_parentNode()
740            parentNode.appendChild(dataNode)
741            parentNode.removeChild(encrNode)
742           
743            from xml.dom.ext import ReleaseNode
744            ReleaseNode(encrNode)
745           
746            # Ensure body_root attribute is up to date in case it was
747            # previously encrypted
748            parsedSOAP.body_root = parsedSOAP.body.childNodes[0]
749            print decryptedData
750            import pdb;pdb.set_trace()
751           
752
753
754       
755if __name__ == "__main__":
756    import sys
757    txt = None
758   
759    e = EncryptionHandler(certFilePath='../../Junk-cert.pem',
760                          priKeyFilePath='../../Junk-key.pem',
761                          priKeyPwd=open('../../tmp2').read().strip())
762   
763    encryptedData = e.encrypt(None)
764    print e.decrypt(None, encryptedData)
765   
Note: See TracBrowser for help on using the repository browser.