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

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

python/ndg.security.server/setup.py:

  • comment out Twisted from install - won't do egg install
  • updated long description

python/ndg.security.server/ndg/security/server/AttAuthority/server-config.tac:

  • added verifyingCertFilePath keyword to SignatureHandler? initialisation
  • added SSL capability

python/conf/attAuthorityProperties.xml,
python/ndg.security.test/ndg/security/test/AttAuthority/siteAAttAuthorityProperties.xml,
python/ndg.security.test/ndg/security/test/AttAuthority/siteBAttAuthorityProperties.xml,
python/ndg.security.server/ndg/security/server/AttAuthority/init.py:
added element names for reading SSL settings from properties file.

python/ndg.security.server/ndg/security/server/SessionMgr/server-config.tac:
added verifyingCertFilePath keyword to SignatureHandler? initialisation

python/conf/sessionMgrProperties.xml,
python/ndg.security.test/ndg/security/test/SessionMgr/sessionMgrProperties.xml,
python/ndg.security.server/ndg/security/server/SessionMgr/init.py:
added clntCertFile properties file element name for setting certificate for
verifying incoming SOAP messages.

python/ndg.security.server/ndg/security/server/SessionMgr/Makefile:
corrected typo.

python/ndg.security.server/ndg/security/server/MyProxy.py:
Put OpenSSLConfig and OpenSSLConfigError classes into their own package
'openssl' so that they can also be used by the Certificate Authority client.

python/www/html/certificateAuthority.wsdl,
python/ndg.security.server/ndg/security/server/ca/CertificateAuthority_services_server.py,
python/ndg.security.common/ndg/security/common/ca/CertificateAuthority_services_types.py,
python/ndg.security.common/ndg/security/common/ca/CertificateAuthority_services.py: updated operations to issueCert, revokeCert and getCRL.

python/ndg.security.test/ndg/security/test/AttAuthority/attAuthorityClientTest.cfg: changed address of service to connect to.

python/ndg.security.test/ndg/security/test/SessionMgr/sessionMgrClientTest.cfg:
alternative username connection settings

python/ndg.security.common/ndg/security/common/AttAuthority/init.py:
fixed typos in error message and comments.

ython/ndg.security.common/ndg/security/common/XMLSec.py: changed call to
getAttributeNodeNS to getAttributeNode for retrieving reference element URI
attribute.

python/ndg.security.common/ndg/security/common/ca/init.py: code for
Certificate Authority client

python/ndg.security.common/ndg/security/common/wsSecurity.py:

  • tidied up imports
  • added properties for setting keywords to reference and SignedInfo? C14N
  • changed sign method so that it is truely configurable allow use of inclusive or exclusive C14N based on the keywords set for reference and SignedInfo? C14N calls.
  • swapped calls to getAttributeNodeNS with getAttributeNode where appropriate.

java/DEWS/AttAuthority/appClientModule/META-INF/ibm-webservicesclient-bnd.xmi,
java/DEWS/AttAuthority/build/classes/META-INF/ibm-webservicesclient-bnd.xmi:
updated to that request generator correctly places X.509 cert in
BinarySecurityToken? element.

java/DEWS/AttAuthority/appClientModule/Main.java,
java/DEWS/AttAuthority/appClientjava/DEWS/AttAuthority/appClientModule/META-INF/ibm-webservicesclient-bnd.xmiModule/Main.java:
include calls to getX509Cert and getAttCert methods.

java/DEWS/SessionMgr/build/classes/META-INF/ibm-webservicesclient-bnd.xmi,
java/DEWS/SessionMgr/appClientModule/META-INF/ibm-webservicesclient-bnd.xmi:
updates for testing Session MAnager client

java/DEWS/SessionMgr/appClientModule/Main.java: switched username setting.

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