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

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

Added test example for a document literal style WSDL + using WS-Security test code to sign and verify messages. Example
is based on the existing NDG SimpleCA WS.

  • 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        print "Signature OK"
390
391
392class EncryptionError(Exception):
393    """Flags an error in the encryption process"""
394
395class DecryptionError(Exception):
396    """Raised from EncryptionHandler.decrypt if an error occurs with the
397    decryption process"""
398
399
400class EncryptionHandler(object):
401    """Encrypt/Decrypt SOAP messages using WS-Security""" 
402   
403    # Map namespace URIs to Crypto algorithm module and mode
404    cryptoAlg = \
405    {
406         _ENCRYPTION.WRAP_AES256:      {'module':       AES, 
407                                        'mode':         AES.MODE_ECB,
408                                        'blockSize':    16},
409         
410         # CBC (Cipher Block Chaining) modes
411         _ENCRYPTION.BLOCK_AES256:     {'module':       AES, 
412                                        'mode':         AES.MODE_CBC,
413                                        'blockSize':    16},
414                                       
415         _ENCRYPTION.BLOCK_TRIPLEDES:  {'module':       DES3, 
416                                        'mode':         DES3.MODE_CBC,
417                                        'blockSize':    8}   
418    }
419
420     
421    def __init__(self,
422                 certFilePath=None, 
423                 priKeyFilePath=None, 
424                 priKeyPwd=None,
425                 chkSecurityTokRef=False,
426                 encrNS=_ENCRYPTION.BLOCK_AES256):
427       
428        self.__certFilePath = certFilePath
429        self.__priKeyFilePath = priKeyFilePath
430        self.__priKeyPwd = priKeyPwd
431       
432        self.__chkSecurityTokRef = chkSecurityTokRef
433       
434        # Algorithm for shared key encryption
435        try:
436            self.__encrAlg = self.cryptoAlg[encrNS]
437           
438        except KeyError:
439            raise EncryptionError, \
440        'Input encryption algorithm namespace "%s" is not supported' % encrNS
441
442        self.__encrNS = encrNS
443       
444       
445    def encrypt(self, soapWriter):
446        """Encrypt an outbound SOAP message
447       
448        Use Key Wrapping - message is encrypted using a shared key which
449        itself is encrypted with the public key provided by the X.509 cert.
450        certFilePath"""
451       
452        # Use X.509 Cert to encrypt
453        x509Cert = X509.load_cert(self.__certFilePath)
454       
455        soapWriter.dom.setNamespaceAttribute('wsse', OASIS.WSSE)
456        soapWriter.dom.setNamespaceAttribute('xenc', _ENCRYPTION.BASE)
457        soapWriter.dom.setNamespaceAttribute('ds', DSIG.BASE)
458       
459        # TODO: Put in a check to make sure <wsse:security> isn't already
460        # present in header
461        wsseElem = soapWriter._header.createAppendElement(OASIS.WSSE, 
462                                                         'Security')
463        wsseElem.node.setAttribute('SOAP-ENV:mustUnderstand', "1")
464       
465        encrKeyElem = wsseElem.createAppendElement(_ENCRYPTION.BASE, 
466                                                   'EncryptedKey')
467       
468        # Encryption method used to encrypt the shared key
469        keyEncrMethodElem = encrKeyElem.createAppendElement(_ENCRYPTION.BASE, 
470                                                        'EncryptionMethod')
471       
472        keyEncrMethodElem.node.setAttribute('Algorithm', 
473                                            _ENCRYPTION.KT_RSA_1_5)
474
475
476        # Key Info
477        KeyInfoElem = encrKeyElem.createAppendElement(DSIG.BASE, 'KeyInfo')
478       
479        secTokRefElem = KeyInfoElem.createAppendElement(OASIS.WSSE, 
480                                                  'SecurityTokenReference')
481       
482        x509IssSerialElem = secTokRefElem.createAppendElement(DSIG.BASE, 
483                                                          'X509IssuerSerial')
484
485       
486        x509IssNameElem = x509IssSerialElem.createAppendElement(DSIG.BASE, 
487                                                          'X509IssuerName')
488        x509IssNameElem.createAppendTextNode(x509Cert.get_issuer().as_text())
489
490       
491        x509IssSerialNumElem = x509IssSerialElem.createAppendElement(
492                                                  DSIG.BASE, 
493                                                  'X509IssuerSerialNumber')
494       
495        x509IssSerialNumElem.createAppendTextNode(
496                                          str(x509Cert.get_serial_number()))
497
498        # References to what has been encrypted
499        encrKeyCiphDataElem = encrKeyElem.createAppendElement(
500                                                          _ENCRYPTION.BASE,
501                                                          'CipherData')
502       
503        encrKeyCiphValElem = encrKeyCiphDataElem.createAppendElement(
504                                                          _ENCRYPTION.BASE,
505                                                          'CipherValue')
506
507        # References to what has been encrypted
508        refListElem = encrKeyElem.createAppendElement(_ENCRYPTION.BASE,
509                                                      'ReferenceList')
510       
511        dataRefElem = refListElem.createAppendElement(_ENCRYPTION.BASE,
512                                                      'DataReference')
513        dataRefElem.node.setAttribute('URI', "#encrypted")
514
515                     
516        # Add Encrypted data to SOAP body
517        encrDataElem = soapWriter.body.createAppendElement(_ENCRYPTION.BASE, 
518                                                           'EncryptedData')
519        encrDataElem.node.setAttribute('Id', 'encrypted')
520        encrDataElem.node.setAttribute('Type', _ENCRYPTION.BASE) 
521             
522        # Encryption method used to encrypt the target data
523        dataEncrMethodElem = encrDataElem.createAppendElement(
524                                                      _ENCRYPTION.BASE, 
525                                                      'EncryptionMethod')
526       
527        dataEncrMethodElem.node.setAttribute('Algorithm', self.__encrNS)
528       
529        # Cipher data
530        ciphDataElem = encrDataElem.createAppendElement(_ENCRYPTION.BASE,
531                                                        'CipherData')
532       
533        ciphValueElem = ciphDataElem.createAppendElement(_ENCRYPTION.BASE,
534                                                         'CipherValue')
535
536
537        # Get elements from SOAP body for encryption
538        dataElem = soapWriter.body.node.childNodes[0]
539        data = dataElem.toxml()
540     
541        # Pad data to nearest multiple of encryption algorithm's block size   
542        modData = len(data) % self.__encrAlg['blockSize']
543        nPad = modData and self.__encrAlg['blockSize'] - modData or 0
544       
545        # PAd with random junk but ...
546        data += os.urandom(nPad-1)
547       
548        # Last byte should be number of padding bytes
549        # (http://www.w3.org/TR/xmlenc-core/#sec-Alg-Block)
550        data += chr(nPad)       
551       
552        # Generate shared key and input vector - for testing use hard-coded
553        # values to allow later comparison             
554        sharedKey = os.urandom(self.__encrAlg['blockSize'])
555        iv = os.urandom(self.__encrAlg['blockSize'])
556       
557        alg = self.__encrAlg['module'].new(sharedKey,
558                                           self.__encrAlg['mode'],
559                                           iv)
560 
561        # Encrypt required elements - prepend input vector
562        encryptedData = alg.encrypt(iv + data)
563        dataCiphValue = base64.encodestring(encryptedData).strip()
564
565        ciphValueElem.createAppendTextNode(dataCiphValue)
566       
567       
568        # ! Delete unencrypted message body elements !
569        soapWriter.body.node.removeChild(dataElem)
570
571       
572        # Use X.509 cert public key to encrypt the shared key - Extract key
573        # from the cert
574        rsaPubKey = x509Cert.get_pubkey().get_rsa()
575       
576        # Encrypt the shared key
577        encryptedSharedKey = rsaPubKey.public_encrypt(sharedKey, 
578                                                      RSA.pkcs1_padding)
579       
580        encrKeyCiphVal = base64.encodestring(encryptedSharedKey).strip()
581       
582        # Add the encrypted shared key to the EncryptedKey section in the SOAP
583        # header
584        encrKeyCiphValElem.createAppendTextNode(encrKeyCiphVal)
585
586        print soapWriter.dom.node.toprettyxml()
587        import pdb;pdb.set_trace()
588       
589       
590    def decrypt(self, parsedSOAP):
591        """Decrypt an inbound SOAP message"""
592       
593        processorNss = \
594        {
595            'xenc':   _ENCRYPTION.BASE,
596            'ds':     DSIG.BASE, 
597            'wsu':    WSU.UTILITY, 
598            'wsse':   OASIS.WSSE, 
599            'soapenv':"http://schemas.xmlsoap.org/soap/envelope/" 
600        }
601        ctxt = Context(parsedSOAP.dom, processorNss=processorNss)
602       
603        refListNodes = xpath.Evaluate('//xenc:ReferenceList', 
604                                      contextNode=parsedSOAP.dom, 
605                                      context=ctxt)
606        if len(refListNodes) > 1:
607            raise DecryptionError, 'Expecting a single ReferenceList element'
608       
609        try:
610            refListNode = refListNodes[0]
611        except:
612            # Message wasn't encrypted - is this OK or is a check needed for
613            # encryption info in SOAP body - enveloped form?
614            return
615
616
617        # Check for wrapped key encryption
618        encrKeyNodes = xpath.Evaluate('//xenc:EncryptedKey', 
619                                      contextNode=parsedSOAP.dom, 
620                                      context=ctxt)
621        if len(encrKeyNodes) > 1:
622            raise DecryptionError, 'This implementation can only handle ' + \
623                                   'single EncryptedKey element'
624       
625        try:
626            encrKeyNode = encrKeyNodes[0]
627        except:
628            # Shared key encryption used - leave out for the moment
629            raise DecryptionError, 'This implementation can only handle ' + \
630                                   'wrapped key encryption'
631
632       
633        # Check encryption method
634        keyEncrMethodNode = getElements(encrKeyNode, 'EncryptionMethod')[0]     
635        keyAlgorithm = keyEncrMethodNode.getAttributeNodeNS(None, 
636                                                            "Algorithm").value
637        if keyAlgorithm != _ENCRYPTION.KT_RSA_1_5:
638            raise DecryptionError, \
639            'Encryption algorithm for wrapped key is "%s", expecting "%s"' % \
640                (keyAlgorithm, _ENCRYPTION.KT_RSA_1_5)
641
642                                                           
643        if self.__chkSecurityTokRef and self.__certFilePath:
644             
645            # Check input cert. against SecurityTokenReference
646            securityTokRefXPath = '/ds:KeyInfo/wsse:SecurityTokenReference'
647            securityTokRefNode = xpath.Evaluate(securityTokRefXPath, 
648                                                contextNode=encrKeyNode, 
649                                                context=ctxt)
650            # TODO: Look for ds:X509* elements to check against X.509 cert
651            # input
652
653
654        # Look for cipher data for wrapped key
655        keyCiphDataNode = getElements(encrKeyNode, 'CipherData')[0]
656        keyCiphValNode = getElements(keyCiphDataNode, 'CipherValue')[0]
657
658        keyCiphVal = str(keyCiphValNode.childNodes[0].nodeValue)
659        encryptedKey = base64.decodestring(keyCiphVal)
660
661        # Read RSA Private key in order to decrypt wrapped key 
662        priKeyFile = BIO.File(open(self.__priKeyFilePath))                                           
663        priKey = RSA.load_key_bio(priKeyFile, 
664                                  callback=lambda *ar, **kw: self.__priKeyPwd)
665       
666        sharedKey = priKey.private_decrypt(encryptedKey, RSA.pkcs1_padding)
667       
668
669        # Check list of data elements that have been encrypted
670        for dataRefNode in refListNode.childNodes:
671
672            # Get the URI for the reference
673            dataRefURI = dataRefNode.getAttributeNodeNS(None, 'URI').value                           
674            if dataRefURI[0] != "#":
675                raise VerifyError, \
676                    "Expecting # identifier for DataReference URI \"%s\"" % \
677                    dataRefURI
678
679            # XPath reference - need to check for wsu namespace qualified?
680            #encrNodeXPath = '//*[@wsu:Id="%s"]' % dataRefURI[1:]
681            encrNodeXPath = '//*[@Id="%s"]' % dataRefURI[1:]
682            encrNode = xpath.Evaluate(encrNodeXPath, 
683                                      contextNode=parsedSOAP.dom, 
684                                      context=ctxt)[0]
685               
686            dataEncrMethodNode = getElements(encrNode, 'EncryptionMethod')[0]     
687            dataAlgorithm = dataEncrMethodNode.getAttributeNodeNS(None, 
688                                                            "Algorithm").value
689            try:       
690                # Match algorithm name to Crypto module
691                CryptoAlg = self.cryptoAlg[dataAlgorithm]
692               
693            except KeyError:
694                raise DecryptionError, \
695'Encryption algorithm for data is "%s", supported algorithms are:\n "%s"' % \
696                    (keyAlgorithm, "\n".join(self.cryptoAlg.keys()))
697
698            # Get Data
699            dataCiphDataNode = getElements(encrNode, 'CipherData')[0]
700            dataCiphValNode = getElements(dataCiphDataNode, 'CipherValue')[0]
701       
702            dataCiphVal = str(dataCiphValNode.childNodes[0].nodeValue)
703            encryptedData = base64.decodestring(dataCiphVal)
704           
705            alg = CryptoAlg['module'].new(sharedKey, CryptoAlg['mode'])
706            decryptedData = alg.decrypt(encryptedData)
707           
708            # Strip prefix - assume is block size
709            decryptedData = decryptedData[CryptoAlg['blockSize']:]
710           
711            # Strip any padding suffix - Last byte should be number of padding
712            # bytes
713            # (http://www.w3.org/TR/xmlenc-core/#sec-Alg-Block)
714            lastChar = decryptedData[-1]
715            nPad = ord(lastChar)
716           
717            # Sanity check - there may be no padding at all - the last byte
718            # being the end of the encrypted XML?
719            #
720            # TODO: are there better sanity checks than this?!
721            if nPad < CryptoAlg['blockSize'] and nPad > 0 and \
722               lastChar != '\n' and lastChar != '>':
723               
724                # Follow http://www.w3.org/TR/xmlenc-core/#sec-Alg-Block -
725                # last byte gives number of padding bytes
726                decryptedData = decryptedData[:-nPad]
727
728
729            # Parse the encrypted data - inherit from Reader as a fudge to
730            # enable relevant namespaces to be added prior to parse
731            processorNss.update({'xsi': SCHEMA.XSI3})
732            class _Reader(Reader):
733                def initState(self, ownerDoc=None):
734                    Reader.initState(self, ownerDoc=ownerDoc)
735                    self._namespaces.update(processorNss)
736                   
737            rdr = _Reader()
738            dataNode = rdr.fromString(decryptedData, ownerDoc=parsedSOAP.dom)
739           
740            # Add decrypted element to parent and remove encrypted one
741            parentNode = encrNode._get_parentNode()
742            parentNode.appendChild(dataNode)
743            parentNode.removeChild(encrNode)
744           
745            from xml.dom.ext import ReleaseNode
746            ReleaseNode(encrNode)
747           
748            # Ensure body_root attribute is up to date in case it was
749            # previously encrypted
750            parsedSOAP.body_root = parsedSOAP.body.childNodes[0]
751            print decryptedData
752            import pdb;pdb.set_trace()
753           
754
755
756       
757if __name__ == "__main__":
758    import sys
759    txt = None
760   
761    e = EncryptionHandler(certFilePath='../../Junk-cert.pem',
762                          priKeyFilePath='../../Junk-key.pem',
763                          priKeyPwd=open('../../tmp2').read().strip())
764   
765    encryptedData = e.encrypt(None)
766    print e.decrypt(None, encryptedData)
767   
Note: See TracBrowser for help on using the repository browser.