source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/XMLSec.py @ 2437

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/XMLSec.py@2437
Revision 2437, 31.7 KB checked in by pjkersha, 13 years ago (diff)

ndg.security.server/ndg/security/server/SessionMgr/server-config.tac:

  • soap_disconnect: added call to SessionMgr?.disconnect, added logic for retrieving ID from cert.

used with WS-Security signature.

  • add code to check for useSignatureHandler config param. If this flag is set, get user ID from

cert in WS-Security header

ndg.security.test/ndg/security/test/SessionMgr/sessionMgrProperties.xml,
ndg.security.server/ndg/security/server/SessionMgr/init.py: added "useSignatureHandler" parameter
to properties file elements.

www/html/sessionMgr.wsdl,
ndg.security.server/ndg/security/server/SessionMgr/SessionMgr_services_server.py,
ndg.security.common/ndg/security/common/SessionMgr/SessionMgr_services.py,
ndg.security.common/ndg/security/common/SessionMgr/SessionMgr_services_types.py: removed userCert
argument. - This is not needed as cert chain can be passed in by setting #X509PKIPathv1 for
BinarySecurityToken?.

ndg.security.client/ndg/security/client/ndgSessionClient.py: started on updates from alpha version -
--req-autho flag is now --req-attr

ndg.security.test/ndg/security/test/AttCert/attCertTest.cfg,
ndg.security.test/ndg/security/test/AttCert/attCertTest.cfg: added more tests for signature
verification tests.

ndg.security.test/ndg/security/test/SessionMgr/SessionMgrClientTest.py: removed userCert arg from
disconnect call. It's passed in the signature in the WS-Security header.

ndg.security.common/ndg/security/common/XMLSec.py: fixed bug in applyEnvelopedSignature - removed
incorrect strip call from digest calc:

calcSignedInfoDigestValue = sha(signedInfoC14n).digest()#.strip()


ndg.security.common/ndg/security/common/SessionMgr/init.py: Session Manager client code -
remove refs to "userCert" for disconnect and connect calls. It's passed in the WS-Security header
instead.

ndg.security.common/ndg/security/common/wsSecurity.py: comment - query whitespace strip in
extraction of calculated signature value from message "b64EncSignatureValue".

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1"""NDG XML Security - Encryption and Digital Signature
2
3NERC Data Grid Project
4
5@author P J Kershaw 05/04/05
6
7P J Kershaw 03/01/07: Re-write to use M2Crypto, ZSI and DOM instead of
8pyXMLSec
9
10@copyright (C) 2007 CCLRC & NERC
11
12@license This software may be distributed under the terms of the Q Public
13License, version 1.0 or later."""
14
15__revision__ = '$Id$'
16
17
18import types
19import os
20
21# Fudge for re-directing error output from xmlsec.shutdown()
22import sys
23
24# For removal of BEGIN and END CERTIFICATE markers from X.509 certs
25import re
26
27# Include for re-parsing doc ready for canonicalization in sign method - see
28# associated note
29from xml.dom.ext.reader.PyExpat import Reader
30from Ft.Xml.Domlette import NonvalidatingReader
31
32# Use to find parent node when parsing docs
33from xml.dom.Element import Element
34
35getParentNode = lambda docNode: [elem for elem in docNode.childNodes \
36                                 if isinstance(elem, Element)][0]
37
38# Digest and signature/verify
39from sha import sha
40from M2Crypto import X509, BIO, RSA
41import base64
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
49from ZSI.wstools.Namespaces import DSIG, XMLNS
50
51   
52def getElements(node, nameList):
53    '''DOM Helper function for getting child elements from a given node'''
54    # Avoid sub-string matches
55    nameList = isinstance(nameList, basestring) and [nameList] or nameList
56    return [n for n in node.childNodes if str(n.localName) in nameList]
57
58
59class XMLSecDocError(Exception):
60    """Exception handling for NDG XML Security class."""
61
62class SignError(Exception): 
63    """Raised form sign method if an error occurs generating the signature"""
64     
65class VerifyError(Exception):
66    """Raised from verify method if an error occurs"""
67   
68class InvalidSignature(Exception):
69    """Raised from verify method for an invalid signature"""
70
71
72#_____________________________________________________________________________
73class XMLSecDoc(object):
74    """Implements XML Signature and XML Encryption for a Document.
75   
76    @type __beginCert: string
77    @param __beginCert: delimiter for beginning of base64 encoded portion of
78    a PEM encoded X.509 certificate
79    @type __endCert: string
80    @cvar: __endCert: equivalent end delimiter
81   
82    @type __x509CertPat: regular expression pattern object
83    @cvar __x509CertPat: regular expression for extracting the base64 encoded
84    portion of a PEM encoded X.509 certificate"""
85   
86    __beginCert = '-----BEGIN CERTIFICATE-----\n'
87    __endCert = '\n-----END CERTIFICATE-----'
88    __x509CertPat = re.compile(__beginCert + \
89                               '?(.*?)\n?-----END CERTIFICATE-----',
90                               re.S)
91
92    def __init__(self,
93                 filePath=None,
94                 signingKeyFilePath=None,
95                 signingKeyPwd=None,
96                 certFilePathList=None,
97                 encrCertFilePath=None,
98                 encrPriKeyFilePath=None):
99
100        """Initialisation -
101           
102        @param filePath:            file path for document
103        @param signingKeyFilePath:  file path for private key used in
104        signature
105        @param certFilePathList:    list of certificates used in verification
106        of a signed document
107        @param encrCertFilePath:    file path for X.509 cert used to encrypt
108        the document - see note for __setCertFilePathList() method
109        @param encrPriKeyFilePath:  file path for private key used to decrypt
110        previously encrypted document"""
111
112        self.__filePath = None
113        self.__signingKeyFilePath = None
114        self.__certFilePathList = None
115        self.__encrCertFilePath = None
116        self.__encrPriKeyFilePath = None
117        self.__docNode = None
118
119
120        if filePath is not None:
121            self.__setFilePath(filePath)
122
123        # Private key file to be used to sign the document
124        if signingKeyFilePath is not None:
125            self.__setSigningKeyFilePath(signingKeyFilePath)
126
127        # Password proetcting Private key used to sign the document - password
128        # may be None
129        self.__setSigningKeyPwd(signingKeyPwd)
130
131        # Public key file to be used to encrypt document
132        if encrCertFilePath is not None:
133            self.__setEncrCertFilePath(encrCertFilePath)
134
135        # Private key file to be used to decrypt document
136        if encrPriKeyFilePath is not None:
137            self.__setEncrPriKeyFilePath(encrPriKeyFilePath)
138
139        # This may be either of:
140        # 1) Certificate file to be used for signing document
141        # 2) list of certificates used to verify a signed document
142        if certFilePathList is not None:
143            self.__setCertFilePathList(certFilePathList)
144       
145       
146    def __str__(self):
147        """String representation of doc - only applies if doc had been read
148        or parsed"""
149        return self.toString()
150       
151
152    def __getFilePath(self):
153        """Get file path for file to be signed/encrypted."""
154        return self.__filePath
155
156
157    def __setFilePath(self, filePath):
158        """Set file path for file to be signed/verified/encrypted/decrypted
159       
160        @param filePath: file path of XML doc"""
161       
162        if filePath is None or not isinstance(filePath, basestring):           
163            raise XMLSecDocError, "Document file path must be a valid string"
164       
165        self.__filePath = filePath
166
167
168    def __delFilePath(self):
169        """Prevent file path being deleted."""
170        raise AttributeError, "\"filePath\" cannot be deleted"
171 
172 
173    # Publish attribute as read/write
174    filePath = property(fget=__getFilePath,
175                        fset=__setFilePath,
176                        fdel=__delFilePath,
177                        doc="File Path for XML document to apply security to")
178   
179   
180    def __getDocNode(self):
181        """Get file path for file to be signed/encrypted."""
182        return self.__docNode
183
184
185    def __delDocNode(self):
186        """Prevent file path being deleted."""
187        raise AttributeError, "\"docNode\" cannot be deleted"
188 
189 
190    # Publish attribute as read/write
191    docNode = property(fget=__getDocNode,
192                       fdel=__delDocNode,
193                       doc="DOM document node for XML")
194
195    #_________________________________________________________________________
196    def __setCertFilePathList(self, filePath):
197        """File path for certificate used to sign document /
198        list of certificates used to check the signature of a document
199       
200        @param filePath: file path or list of file paths to files used to
201        verify a signature.  The first element should be the cert
202        corresponding to the proviate key used to make the signature. 
203        Successive certs in the list correspond to the chain of trust e.g.
204        if a proxy cert/private key was used the list would be
205       
206        proxy cert.,
207        user cert which issued the proxy cert,
208        CA cert that issued the user cert
209        """
210       
211        if isinstance(filePath, basestring):       
212            self.__certFilePathList = [filePath]
213
214        elif isinstance(filePath, list):
215            self.__certFilePathList = filePath
216                                           
217        elif isinstance(filePath, tuple):
218            self.__certFilePathList = list(filePath)
219
220        else:
221            raise XMLSecDocError, \
222            "Signing Certificate file path must be a valid string or list"
223 
224 
225    # Publish attribute as write only
226    certFilePathList = property(fset=__setCertFilePathList,
227        doc="File Path of certificate used to sign document / " + \
228            "list of certificates used to check the signature of a doc")
229
230
231    #_________________________________________________________________________
232    def __setSigningKeyFilePath(self, filePath):
233        """Set file path for certificate private key used to sign doc."""
234       
235        if filePath is None or not isinstance(filePath, basestring):           
236            raise XMLSecDocError, \
237                "Certificate key file path must be a valid string"
238       
239        self.__signingKeyFilePath = filePath
240
241    # Publish attribute as write only
242    signingKeyFilePath = property(fset=__setSigningKeyFilePath,
243                          doc="path for private key file used to sign doc")
244
245
246    #_________________________________________________________________________
247    def __setSigningKeyPwd(self, pwd):
248        """Set password to read private key from file
249       
250        @param pwd: password protecting private key file - set to None if no
251        password is set"""
252       
253        if pwd is not None and not isinstance(pwd, basestring):           
254            raise XMLSecDocError, \
255            "Private key password must be set to None or to a valid string"
256       
257        self.__signingKeyPwd = pwd
258
259    # Publish attribute as write only
260    signingKeyPwd = property(fset=__setSigningKeyPwd,
261                doc="Password protecting private key file used to sign doc")
262
263
264
265    #_________________________________________________________________________
266    def __setEncrCertFilePath(self, filePath):
267        """Set file path for X.509 certificate file containing public
268        key used to decrypt doc.
269       
270        @param filePath: path to X.509 Certificate file"""
271       
272        if filePath is None or not isinstance(filePath, basestring):           
273            raise XMLSecDocError, \
274                "Certificate key file path must be a valid string"
275
276        self.__encrCertFilePath = filePath
277
278    # Publish attribute as write only
279    encrCertFilePath = property(fset=__setEncrCertFilePath,
280        doc="file path for certificate publiv key used to decrypt doc")
281
282
283    #_________________________________________________________________________
284    def __setEncrPriKeyFilePath(self, filePath):
285        """Set file path for private key used to decrypt doc.
286       
287        @param filePath: path to private key file"""
288       
289        if filePath is None or not isinstance(filePath, basestring):           
290            raise XMLSecDocError, \
291                "Certificate key file path must be a valid string"
292       
293        self.__encrPriKeyFilePath = filePath
294
295    # Publish attribute as write only
296    encrPriKeyFilePath = property(fset=__setEncrPriKeyFilePath,
297        doc="file path for certificate private key used to decrypt doc")
298
299
300    #_________________________________________________________________________
301    def toString(self, inclXMLhdr=True):
302        """Return certificate file content as a string
303       
304        @param inclXMLhdr: boolean - set to true to include XML header
305        @return content of document as a string or None if the document has
306        not been parsed."""
307
308        if not self.__docNode:
309            return None
310       
311        if inclXMLhdr:
312            return '<?xml version="1.0" encoding="utf-8"?>\n' + \
313                    Canonicalize(self.__docNode)
314        else:
315            return Canonicalize(self.__docNode)
316
317
318    #_________________________________________________________________________
319    def parse(self, xmlTxt):
320        """Parse string containing XML into a DOM to allow signature or
321        signature validation
322       
323        @param xmlTxt: text to be parsed"""
324       
325        self.__docNode = Reader().fromString(xmlTxt)
326
327
328    #_________________________________________________________________________
329    def read(self, stream=None):
330        """Read XML into a libxml2 document to allow signature validation
331       
332        @param stream: read from a file stream object instead of
333        self.__filePath"""
334       
335        if stream is None:
336            stream = open(self.__filePath)
337               
338        self.__docNode = Reader().fromStream(stream)
339
340
341    #_________________________________________________________________________
342    def write(self):
343        """Write XML document"""
344        open(self.__filePath, 'w').write(self.toString() + os.linesep)
345
346
347    #_________________________________________________________________________
348    def applyEnvelopedSignature(self,
349                        xmlTxt=None,
350                        inclX509Cert=True,
351                        refC14nKw={'unsuppressedPrefixes': ['xmlns']},
352                        signedInfoC14nKw={'unsuppressedPrefixes': ['ds']}):
353       
354        """Make enveloped signature of XML document
355
356        @param xmlTxt: string buffer containing xml to be signed. If not
357        provided, calls XMLSecDoc.createXML().  This is a virtual method so
358        must be defined in a derived class.
359                           
360        @param inclX509Cert: include MIME encoded content of X.509
361        certificate.  This can be used by the  recipient of the XML in order
362        to verify the message
363       
364        @param refC14nKw: Keywords for canonicalization of the reference
365        - for enveloped type signature this is the parent element of the XML
366        document.  If the key 'unsuppressedPrefixes' is set to a list of
367        element prefix strings, exclusive canonicalization will be applied. 
368        To use inclusive canonicalization set 'unsuppressedPrefixes' to None
369         or set refC14nKw to None.
370         
371        @param signedInfoC14nKw: keywords for canonicalization of the
372        SignedInfo section of the signature.  See explanation for refC14nKw
373        keyword for options."""
374
375        if xmlTxt:
376            self.parse(xmlTxt)
377
378        if self.__docNode is None:
379            raise XMLSecDocError, \
380                            "XML to be signed has not been read in or parsed."
381
382        try:
383            parentNode = getParentNode(self.__docNode)
384        except Exception, e:
385            raise VerifyError, "Locating parent node: " + str(e)
386
387        parentNode.setAttributeNS(XMLNS.BASE, 'xmlns:%s' % 'ds', DSIG.BASE)
388        parentNode.setAttributeNS(XMLNS.BASE,'xmlns:%s' % 'ec',DSIG.C14N_EXCL)
389       
390        # Namespaces for XPath searches
391        processorNss = \
392        {
393            'ds':    DSIG.BASE,
394            'ec':    DSIG.C14N_EXCL
395        }
396        ctx = Context(self.__docNode, processorNss=processorNss)
397       
398        # 1) Reference Generation
399       
400        # Canonicalize reference - for enveloped signature this is the parent
401        # element
402        refC14n = Canonicalize(parentNode, **refC14nKw)
403       
404        # Calculate digest for reference and base 64 encode
405        #
406        # Nb. encodestring adds a trailing newline char
407        refDigestValue = base64.encodestring(sha(refC14n).digest()).strip()
408
409
410        # Add Signature elements
411        signatureNode = self.__docNode.createElementNS(DSIG.BASE,
412                                                       'ds:Signature')
413        parentNode.appendChild(signatureNode)
414
415       
416        # Signature - Signed Info
417        signedInfoNode = self.__docNode.createElementNS(DSIG.BASE, 
418                                                        'ds:SignedInfo')
419        signatureNode.appendChild(signedInfoNode)
420       
421        # Signed Info - Canonicalization method
422        c14nMethodNode = self.__docNode.createElementNS(DSIG.BASE,
423                                                'ds:CanonicalizationMethod')       
424        c14nMethodNode.setAttribute('Algorithm', DSIG.C14N_EXCL)
425        signedInfoNode.appendChild(c14nMethodNode)
426
427        c14nInclNamespacesNode = self.__docNode.createElementNS(\
428                                                     DSIG.C14N_EXCL,
429                                                     'ec:InclusiveNamespaces')       
430        c14nInclNamespacesNode.setAttribute('PrefixList', 
431                        ' '.join(signedInfoC14nKw['unsuppressedPrefixes']))       
432        c14nMethodNode.appendChild(c14nInclNamespacesNode)
433       
434       
435        # Signed Info - Signature method
436        sigMethodNode = self.__docNode.createElementNS(DSIG.BASE,
437                                                       'ds:SignatureMethod')
438        sigMethodNode.setAttribute('Algorithm', DSIG.SIG_RSA_SHA1)
439        signedInfoNode.appendChild(sigMethodNode)
440       
441        # Signature - Signature value
442        signatureValueNode = self.__docNode.createElementNS(DSIG.BASE, 
443                                                        'ds:SignatureValue')
444        signatureNode.appendChild(signatureValueNode)
445       
446       
447        # Key Info
448        keyInfoNode = self.__docNode.createElementNS(DSIG.BASE, 'ds:KeyInfo')
449        signatureNode.appendChild(keyInfoNode)
450
451
452        # Add a new reference element to SignedInfo - URI is set to null
453        # indicating enveloped signature used
454        refNode = self.__docNode.createElementNS(DSIG.BASE, 'ds:Reference')
455        refNode.setAttribute('URI', "")
456        signedInfoNode.appendChild(refNode)
457       
458       
459        # Add Transforms
460        transformsNode = self.__docNode.createElementNS(DSIG.BASE, 
461                                                        'ds:Transforms')
462        refNode.appendChild(transformsNode)
463
464        # Individual transforms - enveloped digital signature
465        transformNode = self.__docNode.createElementNS(DSIG.BASE, 
466                                                       'ds:Transform')
467        transformNode.setAttribute('Algorithm', DSIG.ENVELOPED)
468        transformsNode.appendChild(transformNode)
469       
470        # ... - exclusive canonicalization
471        transformNode = self.__docNode.createElementNS(DSIG.BASE, 
472                                                       'ds:Transform')
473        transformNode.setAttribute('Algorithm', DSIG.C14N_EXCL)
474        transformsNode.appendChild(transformNode)
475       
476        inclNamespacesNode = self.__docNode.createElementNS(\
477                                                   DSIG.C14N_EXCL,
478                                                   'ec:InclusiveNamespaces')
479        inclNamespacesNode.setAttribute('PrefixList',
480                                ' '.join(refC14nKw['unsuppressedPrefixes']))
481        transformNode.appendChild(inclNamespacesNode)
482       
483       
484        # Digest Method
485        digestMethodNode = self.__docNode.createElementNS(DSIG.BASE, 
486                                                          'ds:DigestMethod')
487        digestMethodNode.setAttribute('Algorithm', DSIG.DIGEST_SHA1)
488        refNode.appendChild(digestMethodNode)
489       
490        # Digest Value
491        digestValueNode = self.__docNode.createElementNS(DSIG.BASE, 
492                                                         'ds:DigestValue')
493        refNode.appendChild(digestValueNode)
494       
495        digestValueTxtNode = self.__docNode.createTextNode(refDigestValue)
496        digestValueNode.appendChild(digestValueTxtNode)
497
498
499        # 2) Signature Generation
500        signedInfoC14n = Canonicalize(signedInfoNode, **signedInfoC14nKw)
501
502        # Calculate digest of SignedInfo
503        calcSignedInfoDigestValue = sha(signedInfoC14n).digest()
504       
505        # Read Private key to sign with   
506        priKeyFile = BIO.File(open(self.__signingKeyFilePath))
507        priKeyPwdCallback = lambda *ar, **kw: self.__signingKeyPwd
508        priKey = RSA.load_key_bio(priKeyFile, callback=priKeyPwdCallback)
509       
510        # Sign using the private key and base 64 encode the result
511        signatureValue = priKey.sign(calcSignedInfoDigestValue)
512        b64EncSignatureValue = base64.encodestring(signatureValue).strip()
513
514        # Add to <ds:SignatureValue>
515        signatureValueTxtNode = \
516                        self.__docNode.createTextNode(b64EncSignatureValue)
517        signatureValueNode.appendChild(signatureValueTxtNode)
518       
519       
520        if inclX509Cert:
521            if not len(self.__certFilePathList):
522                raise XMLSecDocError, \
523                    "No X.509 Certificate set for inclusion in signature"
524                   
525            # Add X.509 cert data
526            x509Cert = X509.load_cert(self.__certFilePathList[0])           
527            x509DataNode = self.__docNode.createElementNS(DSIG.BASE, 
528                                                          'ds:X509Data')
529            keyInfoNode.appendChild(x509DataNode)
530           
531       
532            x509CertNode = self.__docNode.createElementNS(DSIG.BASE, 
533                                                      'ds:X509Certificate')
534            x509DataNode.appendChild(x509CertNode)
535
536            x509CertPat = re.compile(\
537            '-----BEGIN CERTIFICATE-----\n?(.*?)\n?-----END CERTIFICATE-----',
538            re.S)
539            x509CertStr = x509CertPat.findall(x509Cert.as_pem())[0]
540               
541            x509CertTxtNode = self.__docNode.createTextNode(x509CertStr)
542            x509CertNode.appendChild(x509CertTxtNode)
543
544
545    #_________________________________________________________________________
546    def verifyEnvelopedSignature(self, xmlTxt=None):
547        """Verify enveloped signature of XML document.  Raises
548        InvalidSignature exception if the signature is invalid
549
550        @param xmlTxt: string buffer containing the text from the XML file to
551        be checked.  If omitted, the filePath argument is used instead.
552
553        @param filePath: file path to XML file to be checked.  This
554        argument is used if no xmlTxt was provided.  If filePath itself is
555        omitted the file set by self.__filePath is read instead.
556
557        @param certFilePathList: Certificate used to sign the document."""
558
559       
560        if xmlTxt:
561            self.parse(xmlTxt)
562                               
563        if self.__docNode is None:
564            raise XMLSecDocError, \
565                            "verify signature: no document has been parsed"
566
567        try:
568            parentNode = getParentNode(self.__docNode)
569        except Exception, e:
570            raise VerifyError, "Locating parent node: " + str(e)
571       
572        processorNss = \
573        {
574            'ds':    DSIG.BASE,
575            'ec':    DSIG.C14N_EXCL
576        }
577        ctx = Context(self.__docNode, processorNss=processorNss)
578       
579
580        signatureNodes = xpath.Evaluate('//ds:Signature', 
581                                        contextNode=self.__docNode, 
582                                        context=ctx)
583        if len(signatureNodes) > 1:
584            raise VerifyError, 'Multiple ds:Signature elements found'
585       
586        try:
587            signatureNode = signatureNodes[0]
588        except:
589            # Message wasn't signed
590            return
591       
592        # Extract all information required from the Signature node first and
593        # then remove it so that the reference digest may be calculated.  This
594        # is necessary as for enveloped signature the digest was calculated
595        # prior to the addition of the Signature node
596       
597        # Check for canonicalization set via ds:CanonicalizationMethod -
598        # Use this later as a back up in case no Canonicalization was set in
599        # the transforms elements
600        try:
601            c14nMethodNode = xpath.Evaluate('//ds:CanonicalizationMethod', 
602                                            contextNode=self.__docNode, 
603                                            context=ctx)[0]
604        except Exception, e:
605            raise VerifyError, "CanonicalizationMethod element not found: " +\
606                                str(e)
607       
608        refNodes = xpath.Evaluate('//ds:Reference', 
609                                  contextNode=self.__docNode, 
610                                  context=ctx)
611        if len(refNodes) != 1:
612            raise VerifyError, \
613                    "Expecting one reference element for enveloped signature"
614       
615        refNode = refNodes[0]
616       
617        # Check for reference URI set
618        refURIattrNode = refNode.getAttributeNode('URI')
619        if refURIattrNode and refURIattrNode.value:
620            raise VerifyError, "Reference URI value is expected to be " + \
621                               "null for enveloped type signature"
622       
623       
624        # Get transforms that were applied
625        try:
626            tfmsNode = getElements(refNode, "Transforms")[0]
627            tfmNodes = getElements(tfmsNode, "Transform")
628
629        except Exception, e:
630            raise VerifyError,'failed to get transform algorithm: %s' % str(e)
631           
632           
633        # Check for enveloped style signature and also check for list of
634        # namespaces to be excluded if Exclusive canonicalization method was
635        # specified
636        refC14nKw = {}
637        envelopedAlgorithmSet = False
638       
639        try:
640            for tfmNode in tfmNodes:
641                refAlgorithm = tfmNode.getAttributeNode('Algorithm').value
642               
643                if refAlgorithm == DSIG.C14N_EXCL:
644                        inclusiveNSnode = getElements(tfmNode, 
645                                                  "InclusiveNamespaces")[0]
646                       
647                        pfxListAttNode = inclusiveNSnode.getAttributeNode(\
648                                                               'PrefixList')
649                        refC14nKw['unsuppressedPrefixes'] = \
650                                                pfxListAttNode.value.split()
651                        break
652                elif refAlgorithm == DSIG.ENVELOPED:
653                    envelopedAlgorithmSet = True
654        except Exception, e:
655            raise VerifyError, 'Failed to parse tranform node: %s' % str(e)
656       
657       
658        if not envelopedAlgorithmSet:
659            raise VerifyError, \
660            "Expecting enveloped type signature to be specified in transform"
661       
662       
663        # Extract the digest value for the reference
664        try:           
665            refDigestNode = getElements(refNode, "DigestValue")[0]
666            refDigestValue=str(refDigestNode.childNodes[0].nodeValue).strip()
667           
668        except Exception, e:
669            raise VerifyError, "Error reading reference digest value"
670       
671
672        try:
673            signedInfoNode = xpath.Evaluate('//ds:SignedInfo',
674                                            contextNode=self.__docNode, 
675                                            context=ctx)[0]
676        except Exception, e:
677            raise VerifyError, "Error reading SignedInfo element: " + str(e)
678
679
680        # Get algorithm used for canonicalization of the SignedInfo
681        # element.  Nb. This is NOT necessarily the same as that used to
682        # canonicalize the reference elements checked above!
683        try:
684            signedInfoC14nAlg = c14nMethodNode.getAttributeNode(\
685                                                            "Algorithm").value
686            signedInfoC14nKw = {}
687            if signedInfoC14nAlg == DSIG.C14N_EXCL:
688                inclusiveNS = getElements(c14nMethodNode,
689                                          "InclusiveNamespaces")
690               
691                pfxListAttNode = inclusiveNS[0].getAttributeNode('PrefixList')
692                signedInfoC14nKw['unsuppressedPrefixes'] = \
693                                                pfxListAttNode.value.split()
694        except Exception, e:
695            raise VerifyError, 'failed to handle exclusive ' + \
696                                'canonicalisation for SignedInfo: %s' % str(e)
697       
698        # Get the signature value in order to check against the digest just
699        # calculated
700        try:
701            signatureValueNode = xpath.Evaluate('//ds:SignatureValue',
702                                                contextNode=self.__docNode, 
703                                                context=ctx)[0]   
704            # Remove base 64 encoding
705            b64EncSignatureValue = \
706                    str(signatureValueNode.childNodes[0].nodeValue).strip()
707        except Exception, e:
708            raise VerifyError, "Error reading signatureValue: " + str(e)
709                   
710        signatureValue = base64.decodestring(b64EncSignatureValue)
711
712        # Canonicalize the SignedInfo node and take digest
713        signedInfoC14n = Canonicalize(signedInfoNode, **signedInfoC14nKw)       
714        calcSignedInfoDigestValue = sha(signedInfoC14n).digest()
715
716        try:
717            # Try extracting X.509 Cert from ds:X509Certificate node in
718            # KeyInfo
719            x509CertNode = xpath.Evaluate('//ds:X509Certificate',
720                                          contextNode=self.__docNode,
721                                          context=ctx)[0]
722            b64EncX509Cert = self.__class__.__beginCert + \
723                         str(x509CertNode.childNodes[0]._get_nodeValue()) + \
724                         self.__class__.__endCert
725
726            x509Cert = X509.load_cert_string(b64EncX509Cert)
727           
728        except Exception, e:
729            raise VerifyError, \
730                'Unable to read certificate from "ds:X509Certificate" element'
731
732
733        # Temporarily remove the Signature node in order to correctly
734        # canonicalize the reference
735        signatureNode = parentNode.removeChild(signatureNode)
736       
737       
738        # Two stage process: reference validation followed by signature
739        # validation
740       
741        # 1) Reference Validation
742        #
743        # With the Signature node now removed, the parent node can now be
744        # canonicalized and the digest calculated
745        refC14n = Canonicalize(parentNode, **refC14nKw)
746        calcDigestValue = base64.encodestring(sha(refC14n).digest()).strip()
747       
748        # Restore signature node
749        parentNode.appendChild(signatureNode)
750       
751       
752        # Reference validates if the newly calculated digest value and the
753        # digest value extracted from the Reference section of the SignedInfo
754        # are the same
755        if calcDigestValue != refDigestValue:
756            raise InvalidSignature, \
757                            'Digest Values do not match for reference data'
758
759
760        # 2) Signature Validation
761        #       
762        # Extract RSA public key from the cert
763        rsaPubKey = x509Cert.get_pubkey().get_rsa()
764
765
766        # Compare digest value of the SignedInfo element calculated earlier
767        # against the signatureValue read from the SignedInfo
768        try:
769            verify = rsaPubKey.verify(calcSignedInfoDigestValue, 
770                                      signatureValue)
771        except RSA.RSAError, e:
772            raise VerifyError, "Error in Signature: " + str(e)
773       
774        if not verify:
775            raise InvalidSignature, "Invalid signature"
776
777
778    #_________________________________________________________________________
779    def encrypt(self,
780                xmlTxt=None, 
781                filePath=None, 
782                inclX509SubjName=True,
783                inclX509IssSerial=True):
784        """Encrypt a document using recipient's public key
785
786        Encrypts xml file using a dynamically created template, a session
787        triple DES key and an RSA key from keys manager.
788       
789        @param xmlTxt: string buffer containing the text from the XML file to
790        be encrypted.  If omitted, the filePath argument is used instead.
791
792        @param filePath: file path to XML file to be encrypted.  This
793        argument is used if no xmlTxt was provided.  If filePath itself is
794        omitted the file set by self.__filePath is read instead.
795                               
796        @param inclX509SubjName: include subject name of signing X.509
797        certificate.
798       
799        @param inclX509IssSerial: include issuer name and serial number in
800        signature"""
801       
802        raise NotImplementedError, \
803                        "Encryption algorithm not implemented in this version"
804 
805 
806    #_________________________________________________________________________
807    def decrypt(self, 
808                xmlTxt=None, 
809                filePath=None):
810        """Decrypt a document using a private key of public/private key pair
811       
812        @param xmlTxt: string buffer containing the text from the XML file to
813         be decrypted.  If omitted, the filePath argument is used instead.
814
815        @param filePath: file path to XML file to be decrypted.  This
816        argument is used if no xmlTxt was provided.  If filePath itself is
817        omitted the file set by self.__filePath is read instead."""
818       
819        raise NotImplementedError, \
820                        "Encryption algorithm not implemented in this version"
Note: See TracBrowser for help on using the repository browser.