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

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