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

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

Added http proxy handling for ZSI - see Tests/Echo?/urllib2Client.py

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