source: TI12-security/trunk/python/NDG/XMLSecDoc.py @ 1125

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/NDG/XMLSecDoc.py@1125
Revision 1125, 51.2 KB checked in by pjkersha, 14 years ago (diff)

ts/xDomainCredsTransfer.py, NDG/SecurityCGI.py, NDG/Session.py, XMLSecDoc.py: use urlsafe_b64encode/
urlsafe_b64decode to allow safe passing of encoded parameters across URIs. Standard b64encode/b64decode
will include + and / symbols which have special meanings (thanks to Netscape :/ )

NDG/SecurityCGI.py: more work on authorisation request handlers.

NDG/AttAuthority.py: improved no local roles to map to error to print comma separated list of roles.

  • 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
3Wraps pyXMLSec package
4
5Nerc Data Grid Project
6
7P J Kershaw 05/04/05
8
9Copyright (C) 2005 CCLRC & NERC
10
11This software may be distributed under the terms of the Q Public License,
12version 1.0 or later."""
13
14cvsID = '$Id$'
15
16
17import types
18import os
19
20# Fudge for re-directing error output from xmlsec.shutdown()
21import sys
22
23# For asString() - enables XML header to be stripped if required
24import re
25
26# Use to create buffer for string output for asString() method
27from cStringIO import StringIO
28
29# xmlsec requires libxml2
30import libxml2
31
32# XML security module
33import xmlsec       
34
35
36class XMLSecDocError(Exception):
37    """Exception handling for NDG XML Security class."""
38   
39    def __init__(self, msg):
40        self.__msg = msg
41         
42    def __str__(self):
43        return self.__msg
44
45
46class XMLSecDoc(object):
47    """Implements XML Signature and XML Encryption for a Document."""
48
49    #__metaclass__ = XMLSecDocMetaClass
50   
51    def __init__(self,
52                 filePath=None,
53                 signingKeyFilePath=None,
54                 encrPubKeyFilePath=None,
55                 encrPriKeyFilePath=None,
56                 certFilePathList=None):
57
58        """Initialisation -
59           
60        filePath:            file path for document
61        signingKeyFilePath:  file path for private key used in signature
62        encrPriKeyFilePath:     file path for private key used to decrypt
63                             previously encrypted document
64        certFilePathList:    list of public keys used for checking signature
65                             of a document or for encrypting a document"""
66
67        self.__filePath = None
68        self.__signingKeyFilePath = None
69        self.__encrPubKeyFilePath = None
70        self.__encrPriKeyFilePath = None
71        self.__certFilePathList = None
72
73
74        if filePath is not None:
75            if not isinstance(filePath, basestring):
76                raise XMLSecDocError("Input key file path is %s" % \
77                                     type(filePath) + \
78                                     ": string type expected")
79
80            self.__filePath = filePath
81
82
83        # Private key file to be used to sign the document
84        if signingKeyFilePath is not None:
85            self.__setSigningKeyFilePath(signingKeyFilePath)
86
87
88        # Public key file to be used to encrypt document
89        if encrPubKeyFilePath is not None:
90            self.__setEncrPubKeyFilePath(encrPubKeyFilePath)
91
92
93        # Private key file to be used to decrypt document
94        if encrPriKeyFilePath is not None:
95            self.__setEncrPriKeyFilePath(encrPriKeyFilePath)
96
97
98        # This may be either of:
99        # 1) Certificate file to be used for signing document
100        # 2) list of files one of which is the certificate used to sign the
101        # document being checked
102        if certFilePathList is not None:
103            self.__setCertFilePathList(certFilePathList)
104
105
106        # Keep track of libxml2/libxmlsec variables which need explicit
107        # freeing
108        self.__libxml2Doc = None
109        self.__bLibxml2DocFreed = False
110           
111        self.__libxml2Ctxt = None
112        self.__bLibxml2CtxtFreed = False
113
114        self.__dSigCtxt = None
115        self.__bDSigCtxtFreed = False
116
117        self.__encCtxt = None
118        self.__bEncCtxtFreed = False
119
120        self.__keysMngr = None
121        self.__bKeysMngrFreed = False
122
123        self.__keysStore = None
124        self.__bKeysStoreFreed = False
125 
126        self.__initLibs()
127       
128       
129    #_________________________________________________________________________
130    def __initLibs(self):
131
132        # Initialise libxml2 library
133        libxml2.initParser()
134        libxml2.substituteEntitiesDefault(1)
135       
136        # Init xmlsec library
137        if xmlsec.init() < 0:
138            raise XMLSecDocError("xmlsec initialization failed.")
139
140       
141        # Check loaded library version
142        if xmlsec.checkVersion() != 1:
143            raise XMLSecDocError("xmlsec library version is not compatible.")
144
145
146        # Init crypto library
147        if xmlsec.cryptoAppInit(None) < 0:
148            raise XMLSecDocError("Crypto initialization failed.")
149
150       
151        # Init xmlsec-crypto library
152        if xmlsec.cryptoInit() < 0:
153            raise XMLSecDocError("xmlsec-crypto initialization failed.")
154
155
156    #_________________________________________________________________________       
157    def __shutdownLibs(self):
158
159        try:
160            # Shutdown xmlsec-crypto library
161            xmlsec.cryptoShutdown()
162   
163            # Shutdown crypto library
164            xmlsec.cryptoAppShutdown()
165   
166            # Shutdown xmlsec library
167            # Fudge - comment out to avoid error messages but may cause
168            # mem leaks??
169            #
170            # P J Kershaw 24/03/06
171            #xmlsec.shutdown()
172   
173            # Shutdown LibXML2
174            libxml2.cleanupParser()
175           
176        except Exception, e:
177            raise XMLSecDocError("Cleaning up xmlsec: %s" % e)
178
179
180    def __str__(self):
181        """String representation of doc - only applies if doc had been read
182        or parsed"""
183        if self.__libxml2Doc:
184            return self.asString()
185        else:
186            return ""
187   
188   
189    def __del__(self):
190        """Ensure cleanup of libxml2 and xmlsec memory allocated"""
191        self.__cleanup()
192        self.__shutdownLibs()
193       
194
195    #_________________________________________________________________________
196    def __libxml2ParseDoc(self, xmlTxt):
197        """Wrapper for libxml2.parseDoc() - enables inclusion of flag to
198        indicate to __cleanup method if libxml2.freeDoc() needs to be
199        called."""
200       
201        if self.__libxml2Doc is not None and not self.__bLibxml2DocFreed:
202            self.__libxml2Doc.freeDoc()
203
204        # Create new doc and reset flag
205        try:
206            self.__libxml2Doc = libxml2.parseDoc(xmlTxt)
207
208        except Exception, excep:
209            raise XMLSecDocError("Error parsing document: %s" % str(excep))
210       
211        if self.__libxml2Doc is None or \
212           self.__libxml2Doc.getRootElement() is None:
213            raise XMLSecDocError("Error parsing Attribute Certificate")
214           
215        self.__bLibxml2DocFreed = False
216         
217
218    #_________________________________________________________________________
219    def __libxml2ParseFile(self):
220
221        """Wrapper for libxml2.parseFile() - enables inclusion of flag to
222        indicate to __cleanup method if libxml2.freeDoc() needs to be
223        called."""
224       
225        if self.__libxml2Doc is not None and not self.__bLibxml2DocFreed:
226            self.__libxml2Doc.freeDoc()
227
228        # Create new doc and reset flag
229        try:
230            self.__libxml2Doc = libxml2.parseFile(self.__filePath)
231
232        except Exception, excep:
233            raise XMLSecDocError("Error parsing file: %s" % str(excep))
234
235       
236        if self.__libxml2Doc is None or \
237           self.__libxml2Doc.getRootElement() is None:
238            raise XMLSecDocError(\
239                "Error parsing Attribute Certificate \"%s\"" % self.__filePath)
240           
241        self.__bLibxml2DocFreed = False
242   
243       
244    #_________________________________________________________________________
245    def __libxml2XPathNewContext(self):
246
247        """Wrapper for libxml2.parseDocxpathNewContext() - enables inclusion
248        of flag to indicate to __cleanup method if libxml2.xpathFreeContext()
249        needs to be called."""
250
251        if self.__libxml2Doc is None:
252            raise XMLSecDocError(\
253                "self.__libxml2ParseDoc() must be called first")
254       
255        if self.__libxml2Ctxt is not None and not self.__bLibxml2CtxtFreed:
256            self.__libxml2Ctxt.xpathFreeContext()
257
258        # Create new context and reset flag
259        self.__libxml2Ctxt = self.__libxml2Doc.xpathNewContext()
260        if self.__libxml2Ctxt is None:
261            raise XMLSecDocError("Error creating XPath context for \"%s\"" % \
262                                  self.__filePath)
263
264        self.__bLibxml2CtxtFreed = False
265
266
267    #_________________________________________________________________________
268    def __xmlsecDSigCtx(self, keysMngr=None):   
269        """Wrapper for xmlsec.DSigCtx() - enables inclusion
270        of flag to indicate to __cleanup method if xmlsec.DSigCtx.destroy()
271        needs to be called."""
272
273        # Check memory from any previous object has been released
274        if self.__dSigCtxt is not None and not self.__bDSigCtxtFreed:
275            self.__dSigCtxt.destroy()
276       
277        self.__dSigCtxt = xmlsec.DSigCtx(keysMngr)
278        if self.__dSigCtxt is None:
279            raise XMLSecDocError("Error creating signature context")
280
281        self.__bDSigCtxtFreed = False
282
283
284    #_________________________________________________________________________
285    def __xmlsecEncCtx(self, keysMngr=None):   
286        """Wrapper for xmlsec.EncCtx() - enables inclusion
287        of flag to indicate to __cleanup method if xmlsec.EncCtx.destroy()
288        needs to be called."""
289
290        # Check memory from any previous object has been released
291        if self.__encCtxt is not None and not self.__bEncCtxtFreed:
292            self.__encCtxt.destroy()
293       
294
295        self.__encCtxt = xmlsec.EncCtx(keysMngr)
296        if self.__encCtxt is None:
297            raise XMLSecDocError("Error creating encryption context")
298
299        self.__bEncCtxtFreed = False
300
301
302    #_________________________________________________________________________
303    def __xmlsecKeysMngr(self):
304        """Wrapper for xmlsec.KeysMngr() - enables inclusion
305        of flag to indicate to __cleanup method if xmlsec.KeysMngr.destroy()
306        needs to be called."""
307
308        # Check memory from any previous object has been released
309        if self.__keysMngr is not None and not self.__bKeysMngrFreed:
310            self.__keysMngr.destroy()
311   
312        self.__keysMngr = xmlsec.KeysMngr()
313        if self.__keysMngr is None:
314            raise XMLSecDocError("Failed to create keys manager.")
315
316        self.__bKeysMngrFreed = False
317
318
319    #_________________________________________________________________________     
320    def __cleanup(self):
321        """Private method for cleanup associated with libxml2/xmlsec"""
322
323        # libxml2 doc created for parsing an existing Attribut Certificate/
324        # signing a new certificate
325        if self.__libxml2Doc is not None and not self.__bLibxml2DocFreed:
326            self.__libxml2Doc.freeDoc()
327            self.__bLibxml2DocFreed = True
328
329        # libxml2 XPath Context associated with libxml2 doc object
330        if self.__libxml2Ctxt is not None and not self.__bLibxml2CtxtFreed:
331            self.__libxml2Ctxt.xpathFreeContext()
332            self.__bLibxml2CtxtFreed = True
333
334        # xmlsec Digital Signature object
335        if self.__dSigCtxt is not None and not self.__bDSigCtxtFreed:
336            self.__dSigCtxt.destroy()
337            self.__bDSigCtxtFreed = True
338
339        # xmlsec Encryption Context object
340        if self.__encCtxt is not None and not self.__bEncCtxtFreed:
341            self.__encCtxt.destroy()
342            self.__bEncCtxtFreed = True
343       
344        # xmlsec Keys Manager
345        if self.__keysMngr is not None and not self.__bKeysMngrFreed:
346            self.__keysMngr.destroy()
347            self.__bKeysMngrFreed = True
348 
349
350
351
352    def __getFilePath(self):
353        """Get file path for file to be signed/encrypted."""
354        return self.__filePath
355
356
357    def __setFilePath(self, filePath):
358        """Set file path for file to be signed/encrypted."""
359       
360        if filePath is None or not isinstance(filePath, basestring):           
361            raise XMLSecDocError("Document file path must be a valid string")
362       
363        self.__filePath = filePath
364
365
366    def __delFilePath(self):
367        """Prevent file path being deleted."""
368        raise AttributeError("\"filePath\" cannot be deleted")
369 
370 
371    # Publish attribute as read/write
372    filePath = property(fget=__getFilePath,
373                        fset=__setFilePath,
374                        fdel=__delFilePath,
375                        doc="File Path for XML document to apply security to")
376
377
378    #_________________________________________________________________________
379    def __setCertFilePathList(self, filePath):
380        """File path for certificate used to sign document /
381        list of certificates used to check the signature of a document"""
382       
383        if isinstance(filePath, basestring):       
384            self.__certFilePathList = [filePath]
385
386        elif isinstance(filePath, list):
387            self.__certFilePathList = filePath
388                                           
389        elif isinstance(filePath, tuple):
390            self.__certFilePathList = list(filePath)
391
392        else:
393            raise XMLSecDocError(\
394            "Signing Certificate file path must be a valid string or list")
395 
396 
397    # Publish attribute as write only
398    certFilePathList = property(fset=__setCertFilePathList,
399        doc="File Path of certificate used to sign document / " + \
400            "list of certificates used to check the signature of a doc")
401
402
403    #_________________________________________________________________________
404    def __setSigningKeyFilePath(self, filePath):
405        """Set file path for certificate private key used to sign doc."""
406       
407        if filePath is None or not isinstance(filePath, basestring):           
408            raise XMLSecDocError(\
409                "Certificate key file path must be a valid string")
410       
411        self.__signingKeyFilePath = filePath
412
413    # Publish attribute as write only
414    signingKeyFilePath = property(fset=__setSigningKeyFilePath,
415                doc="file path for certificate private key used to sign doc")
416
417
418
419    #_________________________________________________________________________
420    def __setEncrPubKeyFilePath(self, filePath):
421        """Set file path for certificate publiv key used to decrypt doc."""
422       
423        if filePath is None or not isinstance(filePath, basestring):           
424            raise XMLSecDocError(\
425                "Certificate key file path must be a valid string")
426
427        self.__encrPubKeyFilePath = filePath
428
429    # Publish attribute as write only
430    encrPubKeyFilePath = property(fset=__setEncrPubKeyFilePath,
431        doc="file path for certificate publiv key used to decrypt doc")
432
433
434    #_________________________________________________________________________
435    def __setEncrPriKeyFilePath(self, filePath):
436        """Set file path for certificate private key used to decrypt doc."""
437       
438        if filePath is None or not isinstance(filePath, basestring):           
439            raise XMLSecDocError(\
440                "Certificate key file path must be a valid string")
441       
442        self.__encrPriKeyFilePath = filePath
443
444    # Publish attribute as write only
445    encrPriKeyFilePath = property(fset=__setEncrPriKeyFilePath,
446        doc="file path for certificate private key used to decrypt doc")
447
448
449    #_________________________________________________________________________
450    def asString(self, filePath=None, stripXMLhdr=False):
451        """Return certificate file content as a string"""
452       
453        # Check libxml2.xmlDoc object has been instantiated - if not call
454        # read method
455        if self.__libxml2Doc is None:
456            if filePath is None:
457                raise XMLSecDocError(\
458                    "A file must be parsed first for asString()")
459                   
460            self.read(filePath)
461
462        try:
463            # Make a buffer
464            f = StringIO()
465            buf = libxml2.createOutputBuffer(f, 'UTF-8')
466
467            # Write to buffer
468            self.__libxml2Doc.saveFormatFileTo(buf, 'UTF-8', 0)
469
470
471            # Return string content
472            if stripXMLhdr:
473                return re.sub("<\?xml.*>\s", "", f.getvalue())
474            else:
475                return f.getvalue()
476
477        except Exception, e:
478            raise XMLSecDocError("Error outputting document as a string:" % \
479                                 e)
480
481
482    #_________________________________________________________________________
483    def parse(self, xmlTxt):
484
485        """Parse string containing XML into a libxml2 document to allow
486        signature validation"""
487
488        self.__libxml2ParseDoc(xmlTxt)
489
490
491    #_________________________________________________________________________
492    def read(self, filePath=None):
493
494        """Read XML into a libxml2 document to allow signature validation"""
495
496        # Check for file path passed as input argument otherwise use member
497        # variable
498        if filePath is not None:
499            self.__filePath = filePath
500
501        self.__libxml2ParseFile()
502
503
504    #_________________________________________________________________________
505    def write(self, filePath=None, bSign=False, xmlTxt=None, **signKeys):
506
507        """Write XML document applying digital signature
508
509        filePath:               file path for XML output
510                           
511        bSign:                  flag defaults to False to NOT sign the
512                                document.  Explicitly set to True to sign
513                                the new certificate using the private key
514                                and certificate.
515
516        xmlTxt:                 string containing formatted xml text for
517                                signing.  If no text is provided, the method
518                                createXML() is called to return the text.
519                                Derived classes should implement this method.
520
521        Keyword arguments required by XMLSecDoc.sign():
522       
523        signingKeyFilePath:     file path to private key file of Attribute
524                                Authority - may also be set in  __init__
525
526        signingKeyPwd:          password for signing key file.
527
528        certFilePathList:       file paths to certificate files
529                                """
530
531       
532        if filePath is not None:
533            self.setFilePath(filePath)
534
535       
536        # Set up libxml2 object from string containing XML for writing
537        if xmlTxt is None: xmlTxt = self.createXML()
538
539       
540        # Apply digital signature of Attribute Authority
541        if bSign: self.sign(xmlTxt=xmlTxt, **signKeys)
542
543
544        # Ensure libxml2 doc has been created for document to be written
545        #  A valid one should be present if sign() was called above
546        if self.__libxml2Doc is None: self.__libxml2ParseDoc(xmlTxt)
547
548           
549        # Updated content is held in libxml2 doc instance.  Use this to
550        # write new certificate to file
551        self.__libxml2Doc.saveFormatFile(self.__filePath, True)
552
553
554    #_________________________________________________________________________
555    def createXML(self):
556
557        """VIRTUAL method - derived class should implement -
558        Create text for output and return as a string"""
559       
560        raise XMLSecDocError(\
561                    "Virtual function: Derived class should implement.")
562
563        return None
564
565
566    #_________________________________________________________________________
567    def sign(self,
568             xmlTxt=None,
569             signingKeyFilePath=None,
570             signingKeyPwd=None,
571             certFilePathList=None,
572                 inclX509Cert=True,
573             inclX509SubjName=True,
574                 inclX509IssSerial=True,
575             rtnAsString=False):
576        """Sign XML document using an X.509 certificate private key
577
578        xmlTxt:                 string buffer containing xml to be signed. If
579                                not provided, calls XMLSecDoc.createXML().
580                                This is a virtual method so must be defined
581                                in a derived class.
582                           
583        signingKeyFilePath:     file path to private key file used to sign
584                                the document
585
586        signingKeyPwd:          password for signing key file.
587       
588        certFilePathList:       include certificate of signer
589            inclX509Cert:                   include MIME encoded content of X.509
590                                                certificate that will sign the document
591        inclX509SubjName:           include subject name of signing X.509
592                                                certificate.
593            inclX509IssSerial:      include issuer name and serial inumber in
594                                                signature
595                                       
596        rtnAsString:            This method returns None by default.  Set to
597                                True to override and return the signed
598                                result instead as a string."""
599
600        # Create string buffer from virtual function if not passed
601        # as input argument - derived class must implement
602        if xmlTxt is None:
603            xmlTxt = self.createXML()
604
605           
606        # Set private key file
607        if signingKeyFilePath is not None:
608            self.__setSigningKeyFilePath(signingKeyFilePath)           
609
610
611        # Public certificate file
612        if certFilePathList is not None:
613            self.__setCertFilePathList(certFilePathList)
614
615
616        # Check files for read access
617        try:
618            if not os.access(self.__signingKeyFilePath, os.R_OK):
619                raise XMLSecDocError("not found or no read access")
620                                     
621        except Exception, e:
622            raise XMLSecDocError(\
623                "Private key file path is not valid: \"%s\": %s" % \
624                (self.__signingKeyFilePath, str(e)))
625
626       
627        try:
628            if not os.access(self.__certFilePathList[0], os.R_OK):
629                raise XMLSecDocError("not found or no read access")
630           
631        except Exception, e:
632            raise XMLSecDocError(\
633                "Signing certificate file path is not valid: \"%s\": %s" % \
634                (self.__certFilePathList[0], str(e)))
635
636
637        # Create libxml2 doc instance
638        self.__libxml2ParseDoc(xmlTxt)           
639
640
641        # Create signature template for RSA-SHA1 enveloped signature
642        sigNode = xmlsec.TmplSignature(self.__libxml2Doc,
643                                       xmlsec.transformExclC14NId(),
644                                       xmlsec.transformRsaSha1Id(),
645                                       None)
646        if sigNode is None:
647            raise XMLSecDocError("Error creating signature template")
648
649       
650        # Add <dsig:Signature/> node to the doc
651        self.__libxml2Doc.getRootElement().addChild(sigNode)
652
653
654        # Add reference
655        refNode = sigNode.addReference(xmlsec.transformSha1Id(),
656                                       None, None, None)
657        if refNode is None:
658            raise XMLSecDocError(
659                "Error adding reference to signature template")
660
661
662        # Add enveloped transform
663        if refNode.addTransform(xmlsec.transformEnvelopedId()) is None:
664            raise XMLSecDocError(\
665                "Error adding enveloped transform to reference")
666
667 
668        # Add <dsig:KeyInfo/> and <dsig:X509Data/>
669        keyInfoNode = sigNode.ensureKeyInfo(None)
670        if keyInfoNode is None:
671            raise XMLSecDocError("Error adding key info")
672
673
674        x509DataNode = keyInfoNode.addX509Data()
675        if x509DataNode is None:
676            raise XMLSecDocError("Error adding X509Data node")
677
678
679        # Add extra X509Data info
680        if inclX509Cert:
681            if xmlsec.addChild(x509DataNode,
682                                           xmlsec.NodeX509Certificate) is None:
683                raise XMLSecDocError("Error adding %s node" % \
684                                     xmlsec.NodeX509Certificate)
685
686        if inclX509SubjName:
687            if xmlsec.addChild(x509DataNode,
688                                           xmlsec.NodeX509SubjectName) is None:
689                raise XMLSecDocError("Error adding %s node" % \
690                                     xmlsec.NodeX509SubjectName)
691
692        if inclX509IssSerial:
693            if xmlsec.addChild(x509DataNode,
694                                           xmlsec.NodeX509IssuerSerial) is None:
695                raise XMLSecDocError("Error adding %s node" % \
696                                     xmlsec.NodeX509IssuerSerial)
697
698
699        # Create signature context
700        self.__xmlsecDSigCtx()
701
702       
703        # Load private key
704        self.__dSigCtxt.signKey = xmlsec.cryptoAppKeyLoad(
705                                                    self.__signingKeyFilePath,
706                                                    xmlsec.KeyDataFormatPem,
707                                                    signingKeyPwd,
708                                                    None, 
709                                                    None)
710        if self.__dSigCtxt.signKey is None:
711            raise XMLSecDocError(\
712                "Error loading private pem key from \"%s\"" % \
713                self.__signingKeyFilePath)
714
715
716        # Load certificate and add to the key
717        if xmlsec.cryptoAppKeyCertLoad(self.__dSigCtxt.signKey,
718                                       self.__certFilePathList[0],
719                                       xmlsec.KeyDataFormatPem) < 0:
720            raise XMLSecDocError("Error loading pem certificate \"%s\"" % \
721                                  self.__certFilePathList[0])
722
723
724        # Set key name to the file name
725        if self.__dSigCtxt.signKey.setName(self.__signingKeyFilePath) < 0:
726            raise XMLSecDocError(\
727                "Error setting key name for key from \"%s\"" % \
728                  self.__signingKeyFilePath)
729
730
731        # Sign the template
732        if self.__dSigCtxt.sign(sigNode) < 0:
733            raise XMLSecDocError("Signature failed")
734
735           
736        # Return as required
737        if rtnAsString:
738            return self.asString()
739       
740
741    #_________________________________________________________________________
742    def isValidSig(self,
743                   xmlTxt=None,
744                   filePath=None,
745                   certFilePathList=None):
746
747        """
748        Verify XML signature in file.  Returns True if valid otherwise
749        False.
750
751        xmlTxt:                 string buffer containing the text from the XML
752                                file to be checked.  If omitted, the
753                                filePath argument is used instead.
754
755        filePath:               file path to XML file to be checked.  This
756                                argument is used if no xmlTxt was provided.
757                                If filePath itself is omitted the file set
758                                by self.__filePath is read instead.
759
760        certFilePathList:       Certificate used to sign the document.
761                                """
762
763        if certFilePathList is not None:
764            self.__setCertFilePathList(certFilePathList)
765
766       
767        # Check Certificate files for read access
768        if not self.__certFilePathList:               
769            raise XMLSecDocError("No certificate files set for check")
770           
771
772        # Create and initialize keys manager
773        self.__xmlsecKeysMngr()
774
775        if xmlsec.cryptoAppDefaultKeysMngrInit(self.__keysMngr) < 0:
776            raise XMLSecDocError("Failed to initialize keys manager.")
777
778       
779        # Load certificate(s) list should contain certificate used to sign the
780        # document
781        for certFilePath in self.__certFilePathList:
782            if self.__keysMngr.certLoad(certFilePath,
783                                        xmlsec.KeyDataFormatPem,
784                                        xmlsec.KeyDataTypeTrusted) < 0:
785                raise XMLSecDocError(\
786                    "Error loading signing certificate \"%s\"" % certFilePath)
787
788
789        # If 'xmlTxt' was input, update libxml2 doc instance with it's content
790        if xmlTxt is not None: 
791            self.__libxml2ParseDoc(xmlTxt)
792           
793        elif filePath:
794            # Likewise, if a new file was set
795            self.__setFilePath(filePath)               
796            self.__libxml2ParseFile()
797       
798        # libxml2Doc should now be instantiated - from xmlTxt, an input file
799        # or previous call to parse or read document
800        if not self.__libxml2Doc:
801            raise XMLSecDocError("document is invalid")
802
803       
804        # Find start node
805        dSigNode = xmlsec.findNode(self.__libxml2Doc.getRootElement(),
806                                   xmlsec.NodeSignature, xmlsec.DSigNs)
807        if dSigNode is None:
808            raise XMLSecDocError(\
809                "Start node not found in \"%s\"" % self.__filePath)
810
811 
812        # Create signature context
813        self.__xmlsecDSigCtx(self.__keysMngr)
814
815
816        # Verify signature
817        if self.__dSigCtxt.verify(dSigNode) < 0:
818            raise XMLSecDocError("Error verifying signature.")
819
820
821        # Return True if signature is OK, False otherwise
822        return self.__dSigCtxt.status == xmlsec.DSigStatusSucceeded
823
824
825    #_________________________________________________________________________
826    def getKeyInfoData(self):
827        """Return tags associated with KeyInfo tag of digital signature as a
828        dictionary
829
830        Call after isValidSig() or read()"""
831
832        keyInfo = {}
833
834       
835        # Find start node
836        dSigNode = xmlsec.findNode(self.__libxml2Doc.getRootElement(),
837                                   xmlsec.NodeSignature, 
838                                   xmlsec.DSigNs)
839        if dSigNode is None:
840            raise XMLSecDocError(\
841                "Start node not found in \"%s\"" % self.__filePath)
842       
843           
844        keyInfoNode = xmlsec.findNode(dSigNode,
845                                      xmlsec.NodeKeyInfo,
846                                      xmlsec.DSigNs)
847        if keyInfoNode is None:
848            raise XMLSecDocError("KeyInfo node not found in \"%s\"" % \
849                                 self.__filePath)
850
851           
852        keyNameNode = xmlsec.findNode(keyInfoNode,
853                                      xmlsec.NodeKeyName,
854                                      xmlsec.DSigNs)
855        if keyNameNode is not None:
856            keyInfo[keyNameNode.name] = keyNameNode.content
857
858           
859        x509DataNode = xmlsec.findNode(keyInfoNode,
860                                       xmlsec.NodeX509Data,
861                                       xmlsec.DSigNs)
862        if x509DataNode is None:
863            # Return the keyInfo found up to this point
864            return keyInfo
865
866        keyInfo[x509DataNode.name] = {}
867
868       
869        x509CertificateNode = xmlsec.findNode(x509DataNode,
870                                              xmlsec.NodeX509Certificate,
871                                              xmlsec.DSigNs)
872        if x509CertificateNode is not None:
873            keyInfo[x509DataNode.name][x509CertificateNode.name] = \
874                                                x509CertificateNode.content
875
876
877        x509SubjectNameNode = xmlsec.findNode(x509DataNode,
878                                              xmlsec.NodeX509SubjectName,
879                                              xmlsec.DSigNs)
880        if x509SubjectNameNode is not None:
881            keyInfo[x509DataNode.name][x509SubjectNameNode.name] = \
882                                                x509SubjectNameNode.content
883
884
885        x509IssuerSerialNode = xmlsec.findNode(x509DataNode,
886                                               xmlsec.NodeX509IssuerSerial,
887                                               xmlsec.DSigNs)
888        if x509IssuerSerialNode is None:
889            # Return the keyInfo found up to this point
890            return keyInfo
891
892
893        keyInfo[x509DataNode.name][x509IssuerSerialNode.name] = {}
894       
895        x509IssuerNameNode = xmlsec.findNode(x509IssuerSerialNode,
896                                             xmlsec.NodeX509IssuerName,
897                                             xmlsec.DSigNs)
898        if x509IssuerNameNode is not None:           
899            keyInfo[x509DataNode.name][x509IssuerSerialNode.name]\
900                    [x509IssuerNameNode.name] = x509IssuerNameNode.content
901
902
903        x509SerialNumberNode = xmlsec.findNode(x509IssuerSerialNode,
904                                               xmlsec.NodeX509SerialNumber,
905                                               xmlsec.DSigNs)
906        if x509SerialNumberNode is not None:           
907            keyInfo[x509DataNode.name][x509IssuerSerialNode.name]\
908                    [x509SerialNumberNode.name] = x509SerialNumberNode.content
909
910
911        return keyInfo
912   
913
914    #_________________________________________________________________________
915    def encrypt(self,
916                xmlTxt=None, 
917                filePath=None, 
918                encrPubKeyFilePath=None,
919                inclX509SubjName=True,
920                inclX509IssSerial=True,
921                rtnAsString=False):
922        """Encrypt a document using recipient's public key
923
924        Encrypts xml file using a dynamically created template, a session
925        triple DES key and an RSA key from keys manager.
926       
927        xmlTxt:                 string buffer containing the text from the XML
928                                file to be encrypted.  If omitted, the
929                                filePath argument is used instead.
930
931        filePath:               file path to XML file to be encrypted.  This
932                                argument is used if no xmlTxt was provided.
933                                If filePath itself is omitted the file set
934                                by self.__filePath is read instead.
935                               
936        encrPubKeyFilePath:     file path to RSA public key file used to
937                                encrypt the document.
938                               
939        inclX509SubjName:       include subject name of signing X.509
940                                certificate.
941        inclX509IssSerial:      include issuer name and serial number in
942                                signature   
943       
944        rtnAsString:            This method returns None by default.  Set to
945                                True to override and return the encrypted
946                                result instead as a string."""
947       
948        if encrPubKeyFilePath:
949            self.__setEncrPubKeyFilePath(encrPubKeyFilePath)
950           
951           
952        # Create file based Keys manager to load the public key used to
953        self.__xmlsecKeysMngr()
954
955
956        if xmlsec.cryptoAppDefaultKeysMngrInit(self.__keysMngr) < 0:
957            raise XMLSecDocError("Error initializing keys manager.")
958
959
960        # Load public RSA key
961        #
962        # Nb. 7 value corresponds to XMLSec "xmlSecKeyDataFormatCertPem" -
963        # there isn't a variable set up for it in pyXMLSec
964        encrKey = xmlsec.cryptoAppKeyLoad(self.__encrPubKeyFilePath, 
965                                          7,
966                                          None, 
967                                          None, 
968                                          None);
969        if encrKey is None:
970            raise XMLSecDocError(\
971                "Error loading RSA public key from file \"%s\"" % \
972                self.__encrPubKeyFilePath)
973
974
975        # Set key name to the file name, this is just an example!
976        if encrKey.setName(self.__encrPubKeyFilePath) < 0:
977            raise XMLSecDocError(\
978                "Error setting key name for RSA public key from \"%s\"" % \
979                self.__encrPubKeyFilePath)
980
981
982        # Add key to keys manager, from now on keys manager is responsible
983        # for destroying key
984        if xmlsec.cryptoAppDefaultKeysMngrAdoptKey(self.__keysMngr, 
985                                                   encrKey) < 0:
986            raise XMLSecDocError(\
987                "Error adding RSA public key from \"%s\" to keys manager" % \
988                self.__encrPubKeyFilePath)
989
990   
991        # If 'xmlTxt' was input update libxml2 doc instance with it's content
992        if xmlTxt: 
993            self.__libxml2ParseDoc(xmlTxt)
994        else:
995            # Update file path if set - otherwise, existing setting 
996            # self.__filePath will be used.
997            if filePath:
998                self.__setFilePath(filePath)
999               
1000                # Read XML document from file
1001                self.__libxml2ParseFile()
1002
1003   
1004        # Create encryption template to encrypt XML file and replace
1005        # its content with encryption result
1006        encrNode = xmlsec.TmplEncData(self.__libxml2Doc, 
1007                                      xmlsec.transformDes3CbcId(),
1008                                      None, 
1009                                      xmlsec.TypeEncElement, 
1010                                      None, 
1011                                      None)
1012        if encrNode is None:
1013            raise XMLSecDocError("Error creating encryption template")
1014
1015   
1016        # Put encrypted data in the <enc:CipherValue/> node
1017        if encrNode.ensureCipherValue() is None:
1018            raise XMLSecDocError("Error adding CipherValue node")
1019 
1020   
1021        # add <dsig:KeyInfo/>
1022        keyInfoNode = encrNode.ensureKeyInfo(None)
1023        if keyInfoNode is None:
1024            XMLSecDocError("Error adding key info node")
1025
1026   
1027        # Add <enc:EncryptedKey/> to store the encrypted session key
1028        encrKeyNode = keyInfoNode.addEncryptedKey(xmlsec.transformRsaOaepId(), 
1029                                                  None, 
1030                                                  None, 
1031                                                  None)
1032        if encrKeyNode is None:
1033            XMLSecDocError("Error adding encryption key info node")
1034
1035   
1036        # Put encrypted key in the <enc:CipherValue/> node
1037        if encrKeyNode.ensureCipherValue() is None:
1038            XMLSecDocError("Error adding CipherValue node")
1039
1040   
1041        keyInfoNode2 = encrKeyNode.ensureKeyInfo(None)
1042        if keyInfoNode2 is None:
1043            XMLSecDocError("Error adding public key <KeyInfo>")
1044           
1045       
1046        # Add info about public key in <KeyInfo> <X509Data> if any of X.509
1047        # flags are set
1048        if inclX509SubjName or inclX509IssSerial:
1049           
1050            keyInfoNode2 = encrKeyNode.ensureKeyInfo(None)
1051            if keyInfoNode2 is None:
1052                XMLSecDocError("Error adding public key <KeyInfo>")
1053                cleanup(self.__libxml2Doc, encrNode)
1054       
1055       
1056            x509DataNode = keyInfoNode2.addX509Data()
1057            if x509DataNode is None:
1058                raise XMLSecDocError("Error adding <X509Data> node")
1059   
1060   
1061            # Individual X509Data components
1062            if inclX509SubjName:
1063                if xmlsec.addChild(x509DataNode,
1064                                   xmlsec.NodeX509SubjectName) is None:
1065                    raise XMLSecDocError("Error adding <%s> node" % \
1066                                         xmlsec.NodeX509SubjectName)
1067       
1068            if inclX509IssSerial:
1069                if xmlsec.addChild(x509DataNode,
1070                                   xmlsec.NodeX509IssuerSerial) is None:
1071                    raise XMLSecDocError("Error adding <%s> node" % \
1072                                         xmlsec.NodeX509IssuerSerial)
1073   
1074           
1075        # Create encryption context
1076        self.__xmlsecEncCtx(self.__keysMngr)
1077
1078   
1079        # Generate a Triple DES key
1080        self.__encCtxt.encKey = xmlsec.keyGenerate(xmlsec.keyDataDesId(), 
1081                                                   192,
1082                                                   xmlsec.KeyDataTypeSession)
1083        if self.__encCtxt.encKey is None:
1084            raise XMLSecDocError("Error generating session DES key")
1085   
1086   
1087        # Encrypt the data
1088        if self.__encCtxt.xmlEncrypt(encrNode, 
1089                                     self.__libxml2Doc.getRootElement()) < 0:
1090            raise XMLSecDocError("Encryption failed")
1091   
1092
1093        # Success
1094        if rtnAsString:
1095            return self.asString()
1096 
1097 
1098    #_________________________________________________________________________
1099    def decrypt(self, 
1100                xmlTxt=None, 
1101                filePath=None, 
1102                encrPriKeyFilePath=None,
1103                encrPriKeyPwd=None,
1104                rtnAsString=False):
1105        """Decrypt a document using a private key of public/private key pair
1106       
1107        xmlTxt:                 string buffer containing the text from the XML
1108                                file to be decrypted.  If omitted, the
1109                                filePath argument is used instead.
1110
1111        filePath:               file path to XML file to be decrypted.  This
1112                                argument is used if no xmlTxt was provided.
1113                                If filePath itself is omitted the file set
1114                                by self.__filePath is read instead.
1115                               
1116        encrPriKeyFilePath:     file path to private key file used to decrypt
1117
1118        encrPriKeyPwd:          password for private key file.
1119       
1120        rtnAsString:            This method returns None by default.  Set to
1121                                True to override and return the decrypted
1122                                result instead as a string."""
1123       
1124        if encrPriKeyFilePath:
1125            self.__setEncrPriKeyFilePath(encrPriKeyFilePath)
1126           
1127           
1128        # Create Keys manager to hold the private key used to decrypt
1129        self.__xmlsecKeysMngr()
1130
1131        if xmlsec.cryptoAppDefaultKeysMngrInit(self.__keysMngr) < 0:
1132            raise XMLSecDocError("Error initializing keys manager.")
1133
1134       
1135        encrKey = xmlsec.cryptoAppKeyLoad(self.__encrPriKeyFilePath, 
1136                                          xmlsec.KeyDataFormatPem, 
1137                                          encrPriKeyPwd, 
1138                                          None, 
1139                                          None)
1140        if encrKey is None:
1141            raise XMLSecDocError(\
1142                "Error loading private key from file \"%s\"" % \
1143                self.__encrPriKeyFilePath)
1144 
1145       
1146        # Add key to keys manager, from now on keys manager is responsible
1147        # for destroying key
1148        if xmlsec.cryptoAppDefaultKeysMngrAdoptKey(self.__keysMngr, 
1149                                                   encrKey) < 0:
1150            raise XMLSecDocError(\
1151                "Error adding private key from \"%s\" to keys manager" % \
1152                self.__encrPriKeyFilePath)
1153 
1154   
1155        # If 'xmlTxt' was input update libxml2 doc instance with it's content
1156        if xmlTxt: 
1157            self.__libxml2ParseDoc(xmlTxt)
1158        else:
1159            # Update file path if set - otherwise, existing setting 
1160            # self.__filePath will be used.
1161            if filePath:
1162                self.__setFilePath(filePath)
1163           
1164            # Read XML document from file
1165            self.__libxml2ParseFile()
1166             
1167               
1168        # Find start node
1169        encrNode = xmlsec.findNode(self.__libxml2Doc.getRootElement(), 
1170                                   xmlsec.NodeEncryptedData,
1171                                   xmlsec.EncNs)
1172        if encrNode is None:
1173            raise XMLSecDocError("Start node not found")
1174       
1175       
1176        # Create encryption context
1177        self.__xmlsecEncCtx(self.__keysMngr)
1178
1179   
1180        # Decrypt the data
1181        if self.__encCtxt.decrypt(encrNode) < 0 or \
1182           self.__encCtxt.result is None:
1183            raise XMLSecDocError("Decryption failed")
1184   
1185        # Check
1186        if not self.__encCtxt.resultReplaced:
1187            raise XMLSecDocError("Expecting XML data result")
1188   
1189        # Success
1190        if rtnAsString:
1191            return self.asString()
1192
1193
1194    #_________________________________________________________________________
1195    def symEncrypt(self, 
1196                   xmlTxt=None, 
1197                   filePath=None,
1198                   keyBinFilePath=None, 
1199                   rtnAsString=False):
1200        """Encrypt document using symmetric key
1201
1202        xmlTxt:                 string buffer containing the text from the XML
1203                                file to be encrypted.  If omitted, the
1204                                filePath argument is used instead.
1205
1206        filePath:               file path to XML file to be encrypted.  This
1207                                argument is used if no xmlTxt was provided.
1208                                If filePath itself is omitted the file set
1209                                by self.__filePath is read instead.
1210                               
1211        keyBinFilePath:         file path to binary file containing DES key
1212                                used to encrypt.  If none provided,
1213                                dynamically generate one.
1214
1215        rtnAsString:            This method returns None by default.  Set to
1216                                True to override and return the encrypted
1217                                result instead as a string."""
1218
1219   
1220        # If 'xmlTxt' was input update libxml2 doc instance with it's content
1221        if xmlTxt: 
1222            self.__libxml2ParseDoc(xmlTxt)
1223        elif filePath:
1224            # Update file path if set - otherwise, existing setting 
1225            # self.__filePath will be used.           
1226            self.__setFilePath(filePath)
1227           
1228            # Read XML document from file
1229            self.__libxml2ParseFile()
1230
1231       
1232        # Create encryption template to encrypt XML file and replace
1233        # its content with encryption result
1234        encrNode = xmlsec.TmplEncData(self.__libxml2Doc, 
1235                                      xmlsec.transformDes3CbcId(),
1236                                      None, 
1237                                      xmlsec.TypeEncElement, 
1238                                      None, 
1239                                      None)
1240        if encrNode is None:
1241            raise XMLSecDocError("Error creating encryption template")
1242   
1243        # Put encrypted data in the <enc:CipherValue/> node
1244        if encrNode.ensureCipherValue() is None:
1245            raise XMLSecDocError("Error adding CipherValue node")
1246   
1247        # add <dsig:KeyInfo/>
1248        keyInfoNode = encrNode.ensureKeyInfo(None)
1249        if keyInfoNode is None:
1250            XMLSecDocError("Error adding key info node")
1251     
1252     
1253        if keyInfoNode.addKeyName() is None:
1254            XMLSecDocError("Error adding key name")
1255           
1256           
1257        # Create encryption context, no keys manager is needed
1258        self.__xmlsecEncCtx()
1259   
1260        if keyBinFilePath:
1261            # Load DES key from file, assume that there is no password
1262            self.__encCtxt.encKey = xmlsec.keyReadBinaryFile(\
1263                                                   xmlsec.keyDataDesId(), 
1264                                                   keyBinFilePath)               
1265        else:
1266            # Generate a Triple DES key dynamically
1267            self.__encCtxt.encKey = xmlsec.keyGenerate(xmlsec.keyDataDesId(), 
1268                                                   192,
1269                                                   xmlsec.KeyDataTypeSession)
1270
1271        if self.__encCtxt.encKey is None:
1272            raise XMLSecDocError("Error setting DES key")
1273   
1274     
1275        # Set key name
1276        if self.__encCtxt.encKey.setName(os.path.basename(keyBinFilePath))<0:
1277            raise XMLSecDocError("Error setting key name")
1278
1279   
1280        # Encrypt the data
1281        if self.__encCtxt.xmlEncrypt(encrNode, 
1282                                     self.__libxml2Doc.getRootElement()) < 0:
1283            raise XMLSecDocError("Encryption failed")
1284   
1285        # Success
1286        if rtnAsString:
1287            return self.asString()
1288       
1289       
1290    #_________________________________________________________________________
1291    def symDecrypt(self,
1292                   xmlTxt=None, 
1293                   filePath=None,
1294                   keyBinFilePath=None,
1295                   rtnAsString=False):                       
1296        """Decrypt XML encrypted with a symmetric key
1297       
1298        xmlTxt:                 string buffer containing the text from the XML
1299                                file to be checked.  If omitted, the
1300                                filePath argument is used instead.
1301
1302        filePath:               file path to XML file to be checked.  This
1303                                argument is used if no xmlTxt was provided.
1304                                If filePath itself is omitted the file set
1305                                by self.__filePath is read instead.
1306                               
1307        keyBinFilePath:         file path to binary file containing DES key
1308                                used to decrypt
1309
1310        rtnAsString:            This method returns None by default.  Set to
1311                                True to override and return the decrypted
1312                                result instead as a string."""
1313                               
1314        # Load key into keys manager
1315        self.__xmlsecKeysMngr()
1316
1317        if xmlsec.cryptoAppDefaultKeysMngrInit(self.__keysMngr) < 0:
1318            raise XMLSecDocError("Error initializing keys manager.")
1319       
1320        # Load DES key
1321        encrKey = xmlsec.keyReadBinaryFile(xmlsec.keyDataDesId(), 
1322                                           keyBinFilePath)
1323        if encrKey is None:
1324            raise XMLSecDocError(\
1325                "Error loading DES key from binary file \"%s\"" % \
1326                keyBinFilePath)
1327
1328        # Add key to keys manager, from now on keys manager is responsible
1329        # for destroying key
1330        if xmlsec.cryptoAppDefaultKeysMngrAdoptKey(self.__keysMngr, 
1331                                                   encrKey) < 0:
1332            raise XMLSecDocError(\
1333                "Error adding DES key from \"%s\" to keys manager" % \
1334                keyBinFilePath)
1335
1336   
1337        # If 'xmlTxt' was input update libxml2 doc instance with it's content
1338        if xmlTxt: 
1339            self.__libxml2ParseDoc(xmlTxt)
1340        else:
1341            # Update file path if set - otherwise, existing setting 
1342            # self.__filePath will be used.
1343            if filePath:
1344                self.__setFilePath(filePath)
1345           
1346            # Read XML document from file
1347            self.__libxml2ParseFile()
1348             
1349               
1350        # Find start node
1351        encrNode = xmlsec.findNode(self.__libxml2Doc.getRootElement(), 
1352                                   xmlsec.NodeEncryptedData,
1353                                   xmlsec.EncNs)
1354        if encrNode is None:
1355            raise XMLSecDocError("Start node not found in \"%s\"" % \
1356                                                            self.__filePath)
1357               
1358        # Create encryption context
1359        self.__xmlsecEncCtx(self.__keysMngr)
1360
1361   
1362        # Decrypt the data
1363        if self.__encCtxt.decrypt(encrNode) < 0 or \
1364           self.__encCtxt.result is None:
1365            raise XMLSecDocError("Decryption failed")
1366   
1367        # Check
1368        if not self.__encCtxt.resultReplaced:
1369            raise XMLSecDocError("Expecting XML data result")
1370   
1371        # Success
1372        if rtnAsString:
1373            return self.asString()
1374
1375
1376    def generateSymKey(self, keyLength=192, outFilePath=None):
1377        """Generate a symmetric key for encrypting a document
1378       
1379        keyLength:    length of key in characters - must 192 or greater
1380        outFilePath:  if a file path is provided, write the key to this file
1381        """
1382       
1383        if keyLength < 192:
1384            keyLength = 192
1385           
1386        try:
1387            key = base64.urlsafe_b64encode(os.urandom(keyLength))[0:192]
1388        except Exception, e:
1389            raise XMLSecDocError("Error creating symmetric key")
1390       
1391        if outFilePath:
1392            try:
1393                open(outFilePath, "w").write(key)
1394               
1395            except IOError, e:
1396                raise XMLSecDocError(\
1397                    "Error writing key to file \"%s\": %s" % \
1398                                     (e.filename, e.strerror))
1399            except Exception, e:
1400                raise XMLSecDocError("Error writing key to file \"%s\"" %\
1401                                     outFilePath)
1402                                 
1403        return key
1404
Note: See TracBrowser for help on using the repository browser.