source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/xmlsec/etree.py @ 4061

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/xmlsec/etree.py@4061
Revision 4061, 31.1 KB checked in by pjkersha, 11 years ago (diff)

More fixes to XMLSec functionality.

Line 
1"""XML Security ElementTree implementation
2
3NERC Data Grid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "23/07/08"
7__copyright__ = "(C) 2008 STFC & NERC"
8__contact__ = "P.J.Kershaw@rl.ac.uk"
9__license__ = \
10"""This software may be distributed under the terms of the Q Public
11License, version 1.0 or later."""
12__contact__ = "P.J.Kershaw@rl.ac.uk"
13__revision__ = "$Id$"
14
15import types
16import os
17
18# Digest and signature/verify
19from sha import sha
20from M2Crypto import X509, BIO, RSA
21from ndg.security.common.X509 import X509CertRead, X509Stack
22import base64
23
24from ZSI.wstools.Namespaces import DSIG, XMLNS
25
26# Check ElementTree Canonicalization keywords to check if Exclusive
27# Canonicalisation is set and whether an inclusive namespaces are set
28isExclC14n = lambda c14nKw: bool(c14nKw.get('exclusive'))
29inclNSsSet = lambda c14nKw: bool(c14nKw.get('inclusive_namespaces'))
30
31class XMLSecDocError(Exception):
32    """Exception handling for NDG XML Security class."""
33
34class SignError(XMLSecDocError): 
35    """Raised from signature method if an error occurs generating the signature
36    """
37     
38class VerifyError(XMLSecDocError):
39    """Raised from verify method if an error occurs"""
40   
41class InvalidSignature(XMLSecDocError):
42    """Raised from verify method for an invalid signature"""
43
44class NoSignatureFound(XMLSecDocError): 
45    """Incoming message to be verified was not signed"""
46
47
48#_____________________________________________________________________________
49class XMLSecDoc(object):
50    """Implements XML Signature and XML Encryption for a Document.
51    """
52   
53    def __init__(self,
54                 filePath=None,
55                 signingKeyFilePath=None,
56                 signingKeyPwd=None,
57                 certFilePathList=None,
58                 encrCertFilePath=None,
59                 encrPriKeyFilePath=None):
60
61        """Initialisation -
62           
63        @param filePath:            file path for document
64        @param signingKeyFilePath:  file path for private key used in
65        signature
66        @param certFilePathList:    list of certificates used in verification
67        of a signed document
68        @param encrCertFilePath:    file path for X.509 cert used to encrypt
69        the document - see note for _setCertFilePathList() method
70        @param encrPriKeyFilePath:  file path for private key used to decrypt
71        previously encrypted document"""
72
73        self._filePath = None
74        self._signingKeyFilePath = None
75        self._certFilePathList = None
76        self._encrCertFilePath = None
77        self._encrPriKeyFilePath = None
78        self._rootTree = None
79        self._rootElem = None
80
81        if filePath is not None:
82            self._setFilePath(filePath)
83
84        # Private key file to be used to sign the document
85        if signingKeyFilePath is not None:
86            self._setSigningKeyFilePath(signingKeyFilePath)
87
88        # Password protecting Private key used to sign the document - password
89        # may be None
90        self._setSigningKeyPwd(signingKeyPwd)
91
92        # Public key file to be used to encrypt document
93        if encrCertFilePath is not None:
94            self._setEncrCertFilePath(encrCertFilePath)
95
96        # Private key file to be used to decrypt document
97        if encrPriKeyFilePath is not None:
98            self._setEncrPriKeyFilePath(encrPriKeyFilePath)
99
100        # This may be either of:
101        # 1) Certificate file to be used for signing document
102        # 2) list of certificates used to verify a signed document
103        if certFilePathList is not None:
104            self._setCertFilePathList(certFilePathList)
105       
106       
107    def __str__(self):
108        """String representation of doc - only applies if doc had been read
109        or parsed"""
110        return self.toString()
111       
112
113    def _getFilePath(self):
114        """Get file path for file to be signed/encrypted."""
115        return self._filePath
116
117
118    def _setFilePath(self, filePath):
119        """Set file path for file to be signed/verified/encrypted/decrypted
120       
121        @param filePath: file path of XML doc"""
122       
123        if filePath is None or not isinstance(filePath, basestring):           
124            raise XMLSecDocError, "Document file path must be a valid string"
125       
126        self._filePath = filePath
127
128
129    def _delFilePath(self):
130        """Prevent file path being deleted."""
131        raise AttributeError, "\"filePath\" cannot be deleted"
132 
133 
134    # Publish attribute as read/write
135    filePath = property(fget=_getFilePath,
136                        fset=_setFilePath,
137                        fdel=_delFilePath,
138                        doc="File Path for XML document to apply security to")
139   
140   
141    def _getRootElem(self):
142        """Get file path for file to be signed/encrypted."""
143        return self._rootElem
144
145
146    def _delRootElem(self):
147        """Prevent file path being deleted."""
148        raise AttributeError("\"rootElem\" cannot be deleted")
149 
150 
151    # Publish attribute as read/write
152    rootElem = property(fget=_getRootElem,
153                        fdel=_delRootElem,
154                        doc="Root ElementTree.Element for XML")
155
156    def _setCertFilePathList(self, filePath):
157        """File path for certificate used to sign document /
158        list of certificates used to check the signature of a document
159       
160        @param filePath: file path or list of file paths to files used to
161        verify a signature.  The first element should be the cert
162        corresponding to the proviate key used to make the signature. 
163        Successive certs in the list correspond to the chain of trust e.g.
164        if a proxy cert/private key was used the list would be
165       
166        proxy cert.,
167        user cert which issued the proxy cert,
168        CA cert that issued the user cert
169        """
170       
171        if isinstance(filePath, basestring):       
172            self._certFilePathList = [filePath]
173
174        elif isinstance(filePath, list):
175            self._certFilePathList = filePath
176                                           
177        elif isinstance(filePath, tuple):
178            self._certFilePathList = list(filePath)
179
180        else:
181            raise XMLSecDocError, \
182            "Signing Certificate file path must be a valid string or list"
183 
184 
185    # Publish attribute as write only
186    certFilePathList = property(fset=_setCertFilePathList,
187        doc="File Path of certificate used to sign document / " + \
188            "list of certificates used to check the signature of a doc")
189
190
191    def _setSigningKeyFilePath(self, filePath):
192        """Set file path for certificate private key used to sign doc."""
193       
194        if filePath is None or not isinstance(filePath, basestring):           
195            raise XMLSecDocError(
196                "Certificate key file path must be a valid string")
197       
198        self._signingKeyFilePath = filePath
199
200    # Publish attribute as write only
201    signingKeyFilePath = property(fset=_setSigningKeyFilePath,
202                          doc="path for private key file used to sign doc")
203
204
205    def _setSigningKeyPwd(self, pwd):
206        """Set password to read private key from file
207       
208        @param pwd: password protecting private key file - set to None if no
209        password is set"""
210       
211        if pwd is not None and not isinstance(pwd, basestring):           
212            raise XMLSecDocError(
213            "Private key password must be set to None or to a valid string")
214       
215        self._signingKeyPwd = pwd
216
217    # Publish attribute as write only
218    signingKeyPwd = property(fset=_setSigningKeyPwd,
219                doc="Password protecting private key file used to sign doc")
220
221
222    def _setEncrCertFilePath(self, filePath):
223        """Set file path for X.509 certificate file containing public
224        key used to decrypt doc.
225       
226        @param filePath: path to X.509 Certificate file"""
227       
228        if filePath is None or not isinstance(filePath, basestring):           
229            raise XMLSecDocError, \
230                "Certificate key file path must be a valid string"
231
232        self._encrCertFilePath = filePath
233
234    # Publish attribute as write only
235    encrCertFilePath = property(fset=_setEncrCertFilePath,
236        doc="file path for certificate public key used to decrypt doc")
237
238
239    def _setEncrPriKeyFilePath(self, filePath):
240        """Set file path for private key used to decrypt doc.
241       
242        @param filePath: path to private key file"""
243       
244        if filePath is None or not isinstance(filePath, basestring):           
245            raise XMLSecDocError, \
246                "Certificate key file path must be a valid string"
247       
248        self._encrPriKeyFilePath = filePath
249
250    # Publish attribute as write only
251    encrPriKeyFilePath = property(fset=_setEncrPriKeyFilePath,
252        doc="file path for certificate private key used to decrypt doc")
253       
254       
255    def toString(self, inclXMLhdr=True):
256        """Return certificate file content as a string
257       
258        @param inclXMLhdr: boolean - set to true to include XML header
259        @return content of document as a string or None if the document has
260        not been parsed."""
261
262        if not self._rootElem:
263            return None
264       
265        if inclXMLhdr:
266            return '<?xml version="1.0" encoding="utf-8"?>\n' + \
267                   self.canonicalize()
268        else:
269            return self.canonicalize()
270
271
272    def parse(self, xmlTxt):
273        """Parse string containing XML into a DOM to allow signature or
274        signature validation
275       
276        @param xmlTxt: text to be parsed"""
277        fInput = StringIO()
278        fInput.write(xmlTxt)
279        fInput.seek(0)
280       
281        self._rootETree = ElementC14N.parse(fInput)
282        self._rootElem = self._rootETree.getroot()
283       
284
285    fromString = parse
286
287    def read(self, stream=None):
288        """Read XML into an ElementTree document to allow signature validation
289       
290        @param stream: read from a file stream object instead of
291        self._filePath"""
292       
293        if stream is None:
294            try:
295                stream = open(self._filePath, 'w')
296            except IOError, e:
297                raise XMLSecDocError('Reading file: %s' % e)
298       
299        self._rootETree = ElementC14N.parse(stream)
300        self._rootElem = self._rootETree.getroot()
301
302
303    def write(self):
304        """Write XML document"""
305        try:
306            f = open(self._filePath, 'w')
307        except IOError, e:
308            raise XMLSecDocError('Writing file: %s' % e)
309       
310        # Check that namespace scope has been added - this will be the case
311        # for a parsed message but not true for a document created in memory.
312        # In the latter case a call to build the scope is required
313        if hasattr(self._rootETree, '_scope'):
314            ElementC14N.write(self._rootETree, f, **kw)
315        else:
316            ElementC14N.write(ElementC14N.build_scoped_tree(self._rootElem),
317                              f,
318                              **kw)
319
320
321    def canonicalize(self, **kw):
322        '''ElementTree based Canonicalization - See ElementC14N for keyword
323        info'''
324        f = StringIO()
325
326        # Check that namespace scope has been added - this will be the case
327        # for a parsed message but not true for a document created in memory.
328        # In the latter case a call to build the scope is required
329        if hasattr(self._rootETree, '_scope'):
330            ElementC14N.write(self._rootETree, f, **kw)
331        else:
332            ElementC14N.write(ElementC14N.build_scoped_tree(self._rootElem),
333                              f,
334                              **kw)
335           
336        c14n = f.getvalue()
337
338        return c14n
339       
340       
341    def applyEnvelopedSignature(self,
342                                xmlTxt=None,
343                                inclX509Cert=True,
344                                refC14nKw={},
345                                signedInfoC14nKw={}):       
346        """Make enveloped signature of XML document
347
348        @type xmlTxt: string
349        @param xmlTxt: string buffer containing xml to be signed. If not
350        provided, calls XMLSecDoc.createXML().  This is a virtual method so
351        must be defined in a derived class.
352         
353        @type inclX509Cert: bool                 
354        @param inclX509Cert: include MIME encoded content of X.509
355        certificate.  This can be used by the  recipient of the XML in order
356        to verify the message
357       
358        @type refC14nKw: dict
359        @param refC14nKw: Keywords for canonicalization of the reference
360        - for enveloped type signature this is the parent element of the XML
361        document. 
362
363        @type signedInfoC14nKw: dict
364        @param signedInfoC14nKw: keywords for canonicalization of the
365        SignedInfo section of the signature. 
366        """
367
368        if xmlTxt:
369            self.parse(xmlTxt)
370
371        if self._rootElem is None:
372            raise XMLSecDocError("XML to be signed has not been read in or "
373                                 "parsed.")
374
375        # Exclusive or inclusive Canonicalization applied to Reference and
376        # SignedInfo sections?
377       
378        signedInfoC14nIsExcl = isExclC14n(signedInfoC14nKw)
379        signedInfoC14nHasInclNSs = inclNSsSet(signedInfoC14nKw)
380       
381        refC14nIsExcl = isExclC14n(refC14nKw)
382        refC14nHasInclNSs = inclNSsSet(refC14nKw)
383       
384#        parentNode.setAttributeNS(XMLNS.BASE, 'xmlns:%s' % 'ds', DSIG.BASE)
385#        parentNode.setAttributeNS(XMLNS.BASE,'xmlns:%s' % 'ec',DSIG.C14N_EXCL)
386        self._rootElem.set('xmlns:%s' % 'ds', DSIG.BASE)
387        if refC14nIsExcl:
388            self._rootElem.set('xmlns:%s' % 'ec', DSIG.C14N_EXCL)
389        else:
390            self._rootElem.set('xmlns:%s' % 'ec', DSIG.C14N)
391           
392        # Namespaces for XPath searches
393        processorNss = {'ds': DSIG.BASE}
394        if refC14nIsExcl:
395            processorNss['ec'] = DSIG.C14N_EXCL
396
397
398        # 1) Reference Generation
399       
400        # Canonicalize reference - for enveloped signature this is the parent
401        # element
402        refC14n = self.canonicalize(**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        signatureElem = ElementTree.SubElement(self._rootElem,
412                                            "{%s}%s" % (DSIG.BASE,'Signature'))
413
414       
415        # Signature - Signed Info
416        signedInfoElem = ElementTree.SubElement(self._rootElem,
417                                        "{%s}%s" % (DSIG.BASE,'SignedInfo'))
418
419       
420        # Signed Info - Canonicalization method
421        c14nMethodElem = ElementTree.SubElement(signedInfoElem,
422                                                "{%s}%s" % (DSIG.BASE,
423                                                'CanonicalizationMethod'))
424               
425        if signedInfoC14nIsExcl:
426            c14nMethodElem.set('Algorithm', DSIG.C14N_EXCL)
427            if signedInfoC14nHasInclNSs:
428                c14nInclNamespacesElem = ElementTree.SubElement(c14nMethodElem,
429                                                "{%s}%s" % (DSIG.C14N_EXCL,
430                                                'InclusiveNamespaces')) 
431                 
432                c14nInclNamespacesElem.setAttribute('PrefixList', 
433                            ' '.join(signedInfoC14nKw['inclusive_namespaces']))       
434        else:
435            c14nMethodElem.set('Algorithm', DSIG.C14N)
436                   
437       
438        # Signed Info - Signature method
439        sigMethodElem = ElementTree.SubElement(signedInfoElem,
440                                               "{%s}%s" % (DSIG.BASE,
441                                               'SignatureMethod'))
442        sigMethodElem.set('Algorithm', DSIG.SIG_RSA_SHA1)
443       
444        # Signature - Signature value
445        signatureValueElem = ElementTree.SubElement(signatureElem,
446                                                    "{%s}%s" % (DSIG.BASE, 
447                                                    'SignatureValue'))
448       
449        # Key Info
450        keyInfoElem = ElementTree.SubElement(signatureElem,
451                                            "{%s}%s" % (DSIG.BASE, 'KeyInfo'))
452
453        # Add a new reference element to SignedInfo - URI is set to null
454        # indicating enveloped signature used
455        refElem = ElementTree.SubElement(signedInfoElem,
456                                         "{%s}%s" % (DSIG.BASE, 'Reference'))
457        refElem.set('URI', "")
458       
459       
460        # Add Transforms
461        transformsElem = ElementTree.SubElement(refElem,
462                                                "{%s}%s" % (DSIG.BASE, 
463                                                'Transforms'))
464
465
466        # Individual transforms - enveloped digital signature
467        transformElem = ElementTree.SubElement(transformsElem,
468                                               "{%s}%s" % (DSIG.BASE, 
469                                               'Transform'))
470        transformElem.set('Algorithm', DSIG.ENVELOPED)
471       
472        # ... - canonicalization algorithm
473        transformElem = ElementTree.SubElement(transformsElem,
474                                               "{%s}%s" % (DSIG.BASE, 
475                                               'Transform'))
476        if refC14nIsExcl:
477            transformElem.set('Algorithm', DSIG.C14N_EXCL)
478            if refC14nHasInclNSs:
479                inclNamespacesElem = ElementTree.SubElement(transformElem,
480                                                    "{%s}%s" % (DSIG.C14N_EXCL,
481                                                    'InclusiveNamespaces'))
482                inclNamespacesElem.setAttribute('PrefixList',
483                                ' '.join(refC14nKw['inclusive_namespaces']))
484        else:
485            transformElem.set('Algorithm', DSIG.C14N)
486       
487        # Digest Method
488        digestMethodElem = ElementTree.SubElement(refElem,
489                                                  "{%s}%s" % (DSIG.BASE, 
490                                                              'DigestMethod'))
491        digestMethodElem.set('Algorithm', DSIG.DIGEST_SHA1)
492       
493        # Digest Value
494        digestValueElem = ElementTree.SubElement(refElem,
495                                                 "{%s}%s" % (DSIG.BASE, 
496                                                             'DigestValue'))
497
498        digestValueElem.text = refDigestValue
499
500
501        # 2) Signature Generation
502        signedInfoC14n = self.canonicalize(subset=signedInfoElem, 
503                                           **signedInfoC14nKw)
504
505        # Calculate digest of SignedInfo
506        calcSignedInfoDigestValue = sha(signedInfoC14n).digest()
507       
508        # Read Private key to sign with   
509        priKeyFile = BIO.File(open(self._signingKeyFilePath))
510        priKeyPwdCallback = lambda *ar, **kw: self._signingKeyPwd
511        priKey = RSA.load_key_bio(priKeyFile, callback=priKeyPwdCallback)
512       
513        # Sign using the private key and base 64 encode the result
514        signatureValue = priKey.sign(calcSignedInfoDigestValue)
515        b64EncSignatureValue = base64.encodestring(signatureValue).strip()
516
517        # Add to <ds:SignatureValue>
518        signatureValueElem.text = b64EncSignatureValue
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            x509DataElem = ElementTree.SubElement(keyInfoElem,
528                                                  "{%s}%s" % (DSIG.BASE, 
529                                                              'X509Data'))
530       
531            x509CertElem = ElementTree.SubElement(x509DataElem,
532                                                  "{%s}%s" % (DSIG.BASE, 
533                                                  'X509Certificate'))
534            x509CertElem.text = base64.encodestring(x509Cert.as_der())
535
536
537    def verifyEnvelopedSignature(self, xmlTxt=None, raiseExcep=True):
538        """Verify enveloped signature of XML document.  Raises
539        InvalidSignature exception if the signature is invalid
540
541        @type xmlTxt: string
542        @param xmlTxt: text from the XML file to be checked.  If omitted, the
543        the existing parse document is used instead.
544       
545        @type raiseExcep: bool
546        @param raiseExcep: set to True to raise a NoSignatureFound exception if
547        no signature element is found, False to return to caller logging a
548        message"""
549       
550        if xmlTxt:
551            self.parse(xmlTxt)
552                               
553        if self._rootElem is None:
554            raise XMLSecDocError("Verify signature: no document has been "
555                                 "parsed")
556
557        processorNss = \
558        {
559            'ds':    DSIG.BASE,
560            'ec':    DSIG.C14N_EXCL
561        }
562        ctx = Context(self._rootElem, processorNss=processorNss)
563       
564
565        signatureElems = self._rootElem.findall('.//ds:Signature', 
566                                                namespaces=processorNss)
567        if len(signatureElems) > 1:
568            raise VerifyError('Multiple ds:Signature elements found')
569       
570        try:
571            signatureElem = signatureElems[0]
572        except:
573            # Message wasn't signed
574            msg = "No <ds:Signature> elements found - message not signed?"
575            if raiseExcep:
576                raise NoSignatureFound(msg)
577            else:
578                log.warning(msg)
579                return
580       
581        # Extract all information required from the Signature node first and
582        # then remove it so that the reference digest may be calculated.  This
583        # is necessary as for enveloped signature the digest was calculated
584        # prior to the addition of the Signature node
585       
586        # Check for canonicalization set via ds:CanonicalizationMethod -
587        # Use this later as a back up in case no Canonicalization was set in
588        # the transforms elements
589
590        c14nMethodElem = self._rootElem.find('.//ds:CanonicalizationMethod', 
591                                             namespaces=processorNss)
592        if c14nMethodElem is None:
593            raise VerifyError("CanonicalizationMethod element not found: %s"%e)
594       
595        refElems = self._rootElem.findall('.//ds:Reference', 
596                                          namespaces=processorNss)
597        if len(refElems) != 1:
598            raise VerifyError("Expecting one reference element for enveloped "
599                              "signature")
600       
601        refElem = refElems[0]
602       
603        # Check for reference URI set to ""
604        # TODO: Nb. also allows for no ref URI set at all.  IS this OK with
605        # XMLSec Spec.
606        refURI = refElem.get('URI')
607        if refURI:
608            raise VerifyError("Reference URI value is expected to be null for "
609                              "enveloped type signature")
610       
611       
612        # Get transforms that were applied
613        try:
614            transformsElem = refElem.find("Transforms",
615                                          namespaces=processorNss)
616            transformElems = transformsElem.findall("Transform")
617
618        except Exception, e:
619            raise VerifyError,'failed to get transform algorithm: %s' % str(e)
620           
621           
622        # Check for enveloped style signature and also check for list of
623        # namespaces to be excluded if Exclusive canonicalization method was
624        # specified
625        refC14nKw = {}
626        envelopedAlgorithmSet = False
627       
628        for transformElem in transformElems:
629            refAlgorithm = transformElem.get('Algorithm')
630           
631            if refAlgorithm == DSIG.C14N_EXCL:
632                refC14nKw['exclusive'] = True
633                inclusiveNsElem = transformElem.find("InclusiveNamespaces",
634                                                     namespaces=processorNss)
635                if inclusiveNsElem is not None:
636                    pfxListAttrVal = inclusiveNsElem.get('PrefixList')
637                    refC14nKw['inclusive_namespaces'] = pfxListAttrVal.split()
638
639            elif refAlgorithm == DSIG.ENVELOPED:
640                envelopedAlgorithmSet = True
641       
642       
643        if not envelopedAlgorithmSet:
644            raise VerifyError("Expecting enveloped type signature to be "
645                              "specified in transform")
646       
647       
648        # Extract the digest value for the reference
649         
650        refDigestElem = refElem.find("DigestValue")
651        if refDigestElem is None:
652            raise VerifyError("Error reading reference digest value")
653
654        refDigestValue = str(refDigestElem.text).strip()
655
656        signedInfoElems = self._rootElem.findall('.//ds:SignedInfo',
657                                                 namespaces=processorNss)
658        nSignedInfoElems = len(signedInfoElems)
659        if nSignedInfoElems > 1:
660            raise VerifyError("Multiple <ds:SignedInfo/> elements found")
661        elif nSignedInfoElems == 0:
662            raise VerifyError("No <ds:SignedInfo/> element found")
663
664        # Get algorithm used for canonicalization of the SignedInfo
665        # element.  Nb. This is NOT necessarily the same as that used to
666        # canonicalize the reference elements checked above!
667        signedInfoC14nAlg = c14nMethodElem.get("Algorithm")
668        if signedInfoC14nAlg is None:
669            raise VerifyError("No SignedInfo Algorithm attribute found")
670       
671        signedInfoC14nKw = {}
672        if signedInfoC14nAlg == DSIG.C14N_EXCL:
673            signedInfoC14nKw['exclusive'] = True
674            inclusiveNsElem = c14nMethodElem.find("InclusiveNamespaces",
675                                                  namespaces=processorNss)
676            if inclusiveNsElem is not None:
677                pfxListAttrVal = inclusiveNsElem.get('PrefixList')
678                signedInfoC14nKw['inclusive_namespaces']=pfxListAttrVal.split()
679
680       
681        # Get the signature value in order to check against the digest just
682        # calculated
683        signatureValueElem = self._rootElem.find('.//ds:SignatureValue',
684                                                 namespaces=processorNss)
685        if signatureValueElem is None:
686            raise VerifyError("Error reading signatureValue: %s" % e)
687       
688        # Remove base 64 encoding
689        b64EncSignatureValue = str(signatureValueElem.text).strip()                               
690        signatureValue = base64.decodestring(b64EncSignatureValue)
691
692        # Canonicalize the SignedInfo node and take digest
693        signedInfoC14n = self.canonicalize(subset=signedInfoElem, 
694                                           **signedInfoC14nKw)       
695        calcSignedInfoDigestValue = sha(signedInfoC14n).digest()
696
697        # Try extracting X.509 Cert from ds:X509Certificate node in KeyInfo
698        x509CertElem = self._rootElem.find('.//ds:X509Certificate',
699                                           namespaces=processorNss)
700        if x509CertElem is None:
701            log.info("No <ds:X509Certificate/> element found for signature "
702                     "verification: loading cert from 1st file in "
703                     "_certFilePathList")
704            m2X509Cert = X509.load_cert(self._certFilePathList[0])
705        else:
706            # Get certificate from <ds:X509Certificate/>
707            # - Remove base 64 encoding
708            derString = base64.decodestring(x509CertElem.text)
709           
710            # Load from DER format into M2Crypto.X509
711            m2X509Cert=X509.load_cert_string(derString, format=X509.FORMAT_DER)
712
713
714        # Temporarily remove the Signature node in order to correctly
715        # canonicalize the reference
716        self._rootElem.remove(signatureElem)
717       
718       
719        # Two stage process: reference validation followed by signature
720        # validation
721       
722        # 1) Reference Validation
723        #
724        # With the Signature node now removed, the parent node can now be
725        # canonicalized and the digest calculated
726        refC14n = Canonicalize(**refC14nKw)
727        calcRefDigestValue = base64.encodestring(sha(refC14n).digest()).strip()
728       
729        # Restore signature node
730        self._rootElem.append(signatureElem)
731       
732       
733        # Reference validates if the newly calculated digest value and the
734        # digest value extracted from the Reference section of the SignedInfo
735        # are the same
736        if calcRefDigestValue != refDigestValue:
737            raise InvalidSignature('Digest Values do not match for reference '
738                                   'data')
739
740        # 2) Signature Validation
741        #       
742        # Extract RSA public key from the cert
743        rsaPubKey = x509Cert.get_pubkey().get_rsa()
744
745        # Compare digest value of the SignedInfo element calculated earlier
746        # against the signatureValue read from the SignedInfo
747        try:
748            verify = rsaPubKey.verify(calcSignedInfoDigestValue,signatureValue)
749        except RSA.RSAError, e:
750            raise VerifyError("Error in Signature: " + str(e))
751       
752        if not verify:
753            raise InvalidSignature("Invalid signature")
754       
755        # Verify chain of trust if list cert list is present
756        if self._certFilePathList:
757            # Make a stack object for CA certs
758            caX509Stack = X509Stack()
759            for cert in self._certFilePathList:
760                caX509Stack.push(X509CertRead(cert))
761             
762            # Make a stack object for certs to be verified   
763            x509Stack = X509Stack()
764            x509Stack.push(x509Cert)
765            x509Stack.verifyCertChain(caX509Stack=caX509Stack)
766
767
768    def encrypt(self,
769                xmlTxt=None, 
770                filePath=None, 
771                inclX509SubjName=True,
772                inclX509IssSerial=True):
773        """Encrypt a document using recipient's public key
774
775        Encrypts xml file using a dynamically created template, a session
776        triple DES key and an RSA key from keys manager.
777       
778        @param xmlTxt: string buffer containing the text from the XML file to
779        be encrypted.  If omitted, the filePath argument is used instead.
780
781        @param filePath: file path to XML file to be encrypted.  This
782        argument is used if no xmlTxt was provided.  If filePath itself is
783        omitted the file set by self._filePath is read instead.
784                               
785        @param inclX509SubjName: include subject name of signing X.509
786        certificate.
787       
788        @param inclX509IssSerial: include issuer name and serial number in
789        signature"""
790       
791        raise NotImplementedError, \
792                        "Encryption algorithm not implemented in this version"
793 
794    def decrypt(self, 
795                xmlTxt=None, 
796                filePath=None):
797        """Decrypt a document using a private key of public/private key pair
798       
799        @param xmlTxt: string buffer containing the text from the XML file to
800         be decrypted.  If omitted, the filePath argument is used instead.
801
802        @param filePath: file path to XML file to be decrypted.  This
803        argument is used if no xmlTxt was provided.  If filePath itself is
804        omitted the file set by self._filePath is read instead."""
805       
806        raise NotImplementedError, \
807                        "Encryption algorithm not implemented in this version"
Note: See TracBrowser for help on using the repository browser.