source: TI12-security/trunk/python/Tests/SimpleCA/wsSecurity.py @ 1618

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

Experimented with namespaces for SignatureHandler? to try to get working with WebSphere?.

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