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

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

ndg.security.server/ndg/security/server/conf/sessionMgrProperties.xml:

  • don't comment out hostname instead include by default

ndg.security.server/ndg/security/server/SessionMgr/init.py:

  • fixed comment typo

ndg.security.server/ndg/security/server/MyProxy.py:

to prevent setting of OpenSSL config file without the required file name and
directory path.

ndg.security.test/ndg/security/test/AttCert/attCertTest.cfg,
ndg.security.test/ndg/security/test/AttCert/AttCertTest.py:

  • fixed unit tests for AC signature verification. certFilePathList can now

be set to include CA certs. to verify the X.509 cert. used in the signature

ndg.security.test/ndg/security/test/SessionMgr/SessionMgrClientTest.py:

  • fix: extAttCertList is no longer returned in getAttCert calls to SM client.

ndg.security.test/ndg/security/test/SessionMgr/sessionMgrClientTest.cfg:

  • tests with services on glue

ndg.security.common/ndg/security/common/XMLSec.py:

  • fixed verifyEnvelopedSignature so that it is now possible to verify the

X.509 cert. in the signature against it's issuing CA cert.

ndg.security.common/ndg/security/common/SessionMgr/init.py:

  • modified getAttCert call so that extAttCertList is no longer passed back in

the returned tuple but is instead included as an attribute of the
AttributeRequestDenied? exception type.

  • updated pydoc for getAttCert method

ndg.security.common/ndg/security/common/AttAuthority/init.py:

  • typo fix - doesn't affect execution

ndg.security.common/ndg/security/common/CredWallet.py:

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