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

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

First working version of an OpenID Provider as opposed to a Relying Party as avail. with AuthKit?. The code is taken from the HTTPServer example in the Python OpenID package and refactored into WSGI middleware.

  • ndg.security.server.wsgi.openid_provider - WSGI middleware package
  • Tests/openid-provider/op: pylons project test harness for the above

TODO: integrate into AuthKit? and Beaker Session Middleware as required.

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