source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/AttCert.py @ 4840

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

Fix problem with search and replace licence not adding a new line.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1"""NDG Attribute Certificate (Authorisation -or Access- Token)
2
3NERC Data Grid Project
4
5"""
6__author__ = "P J Kershaw"
7__date__ = "05/04/05"
8__copyright__ = "(C) 2009 Science and Technology Facilities Council"
9__license__ = "BSD - see LICENSE file in top-level directory"
10__contact__ = "Philip.Kershaw@stfc.ac.uk"
11__revision__ = '$Id$'
12
13import types
14import os
15import re
16import copy
17
18# XML Parsing
19
20# For parsing of properties file
21try: # python 2.5
22    from xml.etree import cElementTree as ElementTree
23except ImportError:
24    # if you've installed it yourself it comes this way
25    import cElementTree as ElementTree
26
27# Time module for use with validity times
28from time import strftime, strptime
29from datetime import datetime, timedelta
30
31# XML signature module based on M2Crypto, ZSI Canonicalization and DOM
32from XMLSec import XMLSecDoc, InvalidSignature, getParentNode
33
34from X509 import X500DN
35from X509 import X500DNError
36
37
38#_____________________________________________________________________________
39class AttCertError(Exception): 
40    """Exception handling for NDG Attribute Certificate class."""
41
42#_____________________________________________________________________________
43class AttCertReadOnlyDict(dict):
44    def __init__(self, inputDict):
45        super(AttCertReadOnlyDict, self).__init__(inputDict)
46       
47    def __setitem__(self, key, item):
48        raise KeyError, "Items are read-only in this dictionary"
49       
50#_____________________________________________________________________________
51class _MetaAttCert(type):
52    """Enable AttCert to have read only class variables e.g.
53   
54    print AttCert.mappedProvenance is allowed but,
55   
56    AttCert.mappedProvenance = None
57   
58    ... raises - AttributeError: can't set attribute"""
59   
60    #_________________________________________________________________________   
61    def __getVersion(cls):
62        '''Version of THIS format for the certificate'''
63        return '1.0'
64
65    version = property(fget=__getVersion, 
66                       doc="Version of the certificate format")
67   
68    #_________________________________________________________________________   
69    def __getMappedProvenance(cls):
70        '''Text for mapped provenance setting of certificate'''
71        return 'mapped'
72
73    mappedProvenance = property(fget=__getMappedProvenance,
74        doc="Text constant indicating cert has mapped roles from another")
75
76    #_________________________________________________________________________   
77    def __getOriginalProvenance(cls):
78        '''Text for original provenance setting of certificate'''
79        return 'original'
80   
81    origProvenance = property(fget=__getOriginalProvenance,
82        doc="Text constant indicating cert has original and not mapped roles")
83   
84   
85#_____________________________________________________________________________
86class AttCert(dict, XMLSecDoc):
87    """NDG Attribute Certificate (Authorisation or Access Token).
88   
89    @type __validProvenanceSettings: tuple
90    @cvar __validProvenanceSettings: string constants for allowable certificate provenance settings
91   
92    @type namespace: string
93    @cvar namespace: namespace for Attribute Certificate"""
94   
95    __metaclass__ = _MetaAttCert
96
97    # Provenance of certificate may be original or mapped from another
98    # certificate
99    __validProvenanceSettings = ('original', 'mapped')
100    namespace = "urn:ndg:security:attributeCertificate"
101
102    #_________________________________________________________________________   
103    def __init__(self, provenance='original', lifetime=28800, **xmlSecDocKw):
104        """Initialisation - Attribute Certificate file path may be specified.
105        Also, holder and issuer details and signing authority key and
106        certificate.
107       
108        @type lifetime: int
109        @param lifetime: set the lifetime for the certificate in seconds.
110        Defaults to 8 hours.
111       
112        @type **xmlSecDocKw: dict
113        @param **xmlSecDocKw: see XMLSec.XMLSec class for an explanation.
114        Keywords include, filePath for the cert. for reading/writing and
115        cert./private key settings for digital signature and verification."""
116
117        # Base class initialisation
118        dict.__init__(self)
119        XMLSecDoc.__init__(self, **xmlSecDocKw)
120
121        #: Data dictionary version of xml
122        #:
123        #: Nb. RoleSet is an empty list - it will be filled role dictionary
124        #: items [{'role': {'name': '<Name>'}}, ... ]
125        self.__dat = {
126           
127            "version":            AttCert.version,
128            "holder":             '',
129            "issuer":             '',
130            "issuerName":         '',
131            "issuerSerialNumber": 0,
132            "userId":             '',
133            "validity":           {"notBefore": '', "notAfter": ''},
134            "attributes":         {"roleSet": []},
135            "provenance":         ''
136        }
137
138        #: Holder X500DN object - instantiated in read method
139        self.__issuerDN = None
140        #: issuer X500DN object - instantiated in read method
141        self.__holderDN = None
142
143        self.__setProvenance(provenance)
144       
145        #: Certificate life time interval in seconds
146        self.__lifetime = lifetime
147       
148        #: Certificate not before time as datetime type
149        self.__dtNotBefore = None
150       
151        #: Certificate not after time as a datetime type
152        self.__dtNotAfter = None
153
154
155    #_________________________________________________________________________   
156    def __repr__(self):
157        """Override default behaviour to return internal dictionary content"""
158        return str(self.__dat)
159
160
161    #_________________________________________________________________________   
162    def __str__(self):
163        """Override XMLSec.XMLSecDoc equivalent"""
164        return self.toString()
165   
166   
167    #_________________________________________________________________________
168    def toString(self, **kw):
169        """Return certificate file content as a string
170       
171        @param **kw: keywords to XMLSec.XMLSecDoc.toString()
172        @rtype: string
173        @return: content of document"""
174
175        # If doc hasn't been parsed by parent (ie. not signed) return elements
176        # set so far using createXML method
177        return super(AttCert, self).toString(**kw) or self.createXML()
178
179               
180    #_________________________________________________________________________   
181    def __delitem__(self, key):
182        "Attribute Certificate keys cannot be removed"
183       
184        raise AttCertError, 'Keys cannot be deleted from ' + \
185                           self.__class__.__name__
186
187
188    #_________________________________________________________________________   
189    def __getitem__(self, key):
190        """Get an item from the __dat, __dat['validity'] or
191        __dat['attributes'] dictionaries.  This class behaves as data
192        dictionary of Attribute Certificate properties
193
194        @param key: name of key - key can be specified belonging to validity
195        or the attributes sub dictionaries
196        @param item: value to set dictionary item to
197        """
198       
199        # Check input key
200        if key in self.__dat:
201
202            # key recognised
203            item = self.__dat[key]               
204
205        elif key in self.__dat['validity']:
206
207            # Allow indexing via validity keys - a shorthand way of
208            # referencing for convenience
209            item = self.__dat['validity'][key]
210
211        elif key in self.__dat['attributes']:
212
213            # Allow indexing via attributes keys - a shorthand way of
214            # referencing for convenience
215            item = self.__dat['attributes'][key]
216
217        else:
218            # key not recognised as a short or long name version
219            raise KeyError, 'Key "%s" not recognised for %s' % \
220                               (key, self.__class__.__name__)
221
222        if isinstance(item, dict):
223            return AttCertReadOnlyDict(item)
224        else:
225            return item
226
227
228    #_________________________________________________________________________   
229    def __setitem__(self, key, item):       
230        """Set an item from the __dat dictionary.  This class behaves as data
231        dictionary of Attribute Certificate properties
232
233        @type key: string
234        @param key: name of key - key can be specified belonging to validity
235        or the attributes sub dictionaries
236       
237        @type item: string / int
238        @param item: value to set dictionary item to
239        """
240
241        # Check input key
242        if key in self.__dat:
243
244            # key recognised - check if setting provenance
245            if key == "provenance":
246                self.__setProvenance(item)
247               
248            elif key == "version":
249                self.__setVersion(item)
250                 
251            elif key == "holder":
252                self.__setHolder(item)
253               
254            elif key == "issuer":
255                self.__setIssuer(item)
256           
257            elif key == "issuerName":
258                self.__setIssuerName(item)
259           
260            elif key == "issuerSerialNumber":
261                self.__setIssuerSerialNumber(item)
262         
263            elif key == "userId":
264                self.__setUserId(item)
265                   
266            elif key == "validity":
267                raise KeyError, "'%s': use setValidityTime method " % \
268                    key + "to set notBefore/notAfter times"
269                           
270            elif key == "attributes":
271                raise KeyError, "'%s': use addRoles method to " % \
272                    key + "set list of role attributes"           
273            else:   
274                raise KeyError, "Key '%s' not recognised for %s'" % \
275                               (key, self.__class__.__name__)
276
277        elif key in self.__dat['attributes'] or \
278             key in self.__dat['attributes']['roleSet']:
279
280            # To complex to allow direct setting here
281            raise KeyError, "'%s': use addRoles method to " % key + \
282                            "set list of roles"           
283
284        elif key in self.__dat['validity']:
285            # Prevent setting of notBefore/notAfter - restrict to method
286            # setValidityTime
287            raise KeyError, "'%s': use setValidityTime method " % key + \
288                            "to set notBefore/notAfter times"           
289        else:
290            # key not recognised as a short or long name version
291            raise KeyError, "Key '%s' not recognised for %s'" % \
292                               (key, self.__class__.__name__)
293       
294
295    #_________________________________________________________________________   
296    def __eq__(self, attCert):
297        """Return true if all elements are the same"""       
298        try:
299            return min([self.__dat[key] == attCert[key] \
300                       for key in self.__dat.keys()])
301        except:
302            return False
303       
304
305    #_________________________________________________________________________   
306    def __nonzero__(self):
307        """Ensure if <attCertInstance> test yields True"""
308        return True
309   
310   
311    #_________________________________________________________________________   
312    def clear(self):
313        raise AttCertError, "Data cannot be cleared from " + \
314                           self.__class__.__name__
315
316   
317    #_________________________________________________________________________   
318    def copy(self):
319        return copy.copy(self)
320
321   
322    #_________________________________________________________________________   
323    def keys(self):
324        return self.__dat.keys()
325
326    #_________________________________________________________________________   
327    def items(self):
328        return self.__dat.items()
329
330    #_________________________________________________________________________   
331    def values(self):
332        return self.__dat.values()
333
334    #_________________________________________________________________________   
335    def has_key(self, key):
336        return self.__dat.has_key(key)
337
338    # 'in' operator
339    #_________________________________________________________________________   
340    def __contains__(self, key):
341        return key in self.__dat
342
343
344    #
345    # Get/Set methods
346    #
347    # Nb. it's also possible to access the data dictionary parameters via
348    # __setitem__ and __getitem__ standard dictionary methods
349    #
350    #_________________________________________________________________________   
351    def __setVersion(self, version):
352        """Set the version number to be written to file."""       
353        self.__dat['version'] = version
354   
355    #_________________________________________________________________________   
356    def __getVersion(self):
357        """Get version number as set in file."""
358        return self.__dat['version']
359
360    version = property(fget=__getVersion,
361                       fset=__setVersion, 
362                       doc="Attribute Certificate version")
363   
364    #_________________________________________________________________________   
365    def __setHolder(self, holder):
366        """Set holder's Distinguished Name string."""
367        if not isinstance(holder, basestring):
368            raise AttributeError, "holder DN must be a string"
369
370        self.__dat['holder'] = holder
371   
372    #_________________________________________________________________________   
373    def __getHolder(self):
374        """Get holder's Distinguished Name string."""
375        return self.__dat['holder']
376
377    holder = property(fget=__getHolder,
378                      fset=__setHolder, 
379                      doc="Attribute Certificate holder DN")
380
381    #_________________________________________________________________________   
382    def __getHolderDN(self):
383         """Get the holder's Distinguished Name as an X500DN instance"""
384         return self.__holderDN
385     
386    holderDN = property(fget=__getHolderDN,
387                        doc="Attribute Certificate holder DN as X500DN type")
388   
389    #_________________________________________________________________________   
390    def __setIssuer(self, issuer):
391        """Set issuer's Distinguished Name."""
392        if not isinstance(issuer, basestring):
393            raise AttributeError, "issuer DN must be a string"
394       
395        self.__dat['issuer'] = issuer
396   
397    #_________________________________________________________________________   
398    def __getIssuer(self):
399        """Get the issuer's Distinguished Name string"""
400        return self.__dat['issuer']
401
402    issuer = property(fget=__getIssuer, 
403                      fset=__setIssuer,
404                      doc="Certificate Issuer DN")
405
406    #_________________________________________________________________________   
407    def __getIssuerDN(self):
408         """Get the issuer's Distinguished Name as an X500DN instance"""
409         return self.__issuerDN
410     
411    issuerDN = property(fget=__getIssuerDN,
412                        doc="Attribute Certificate issuer DN as X500DN type")
413       
414    #_________________________________________________________________________   
415    def __setIssuerName(self, issuerName):
416        """Set the name of the issuer"""
417        if not isinstance(issuerName, basestring):
418            raise AttributeError, "issuerName must be a string"
419       
420        self.__dat['issuerName'] = issuerName
421   
422    #_________________________________________________________________________   
423    def __getIssuerName(self):
424        """@rtype: string
425        @return: the name of the issuer"""
426        return self.__dat['issuerName']
427
428    issuerName = property(fget=__getIssuerName, 
429                          fset=__setIssuerName,
430                          doc="Certificate Issuer name")
431   
432    #_________________________________________________________________________   
433    def __setIssuerSerialNumber(self, serialNumber):
434        """@param serialNumber: the issuer serial number"""
435        if not isinstance(serialNumber, int):
436            raise AttributeError, "issuerSerialNumber must be an integer"
437
438        self.__dat['issuerSerialNumber'] = serialNumber
439   
440    #_________________________________________________________________________   
441    def __getIssuerSerialNumber(self):
442        """@rtype: string
443        @return: the issuer serial number"""
444        return self.__dat['issuerSerialNumber']
445   
446    issuerSerialNumber = property(fget=__getIssuerSerialNumber, 
447                                  fset=__setIssuerSerialNumber,
448                                  doc="Certificate Issuer Serial Number")
449 
450       
451    #_________________________________________________________________________   
452    def __setUserId(self, userId):
453        """Set the name of the userId
454        @type userId: string
455        @param userId: user identifier"""
456        if not isinstance(userId, basestring):
457            raise AttributeError, "userId must be a string"
458       
459        self.__dat['userId'] = userId
460   
461    #_________________________________________________________________________   
462    def __getUserId(self):
463        """@rtype: string
464        @return: the user idenitifier"""
465        return self.__dat['userId']
466
467    userId = property(fget=__getUserId, 
468                      fset=__setUserId,
469                      doc="Certificate user identifier")
470   
471
472    # Nb. no setValidityNotBefore/setValidityNotAfter methods - use
473    # setValidityTime instead.
474   
475    #_________________________________________________________________________   
476    def getValidityNotBefore(self, asDatetime=False):
477        """Get the validity Not Before date/time string
478
479        @param asDatetime: boolean to True to return as a datetime type
480        Nb. time may not have been set - if so it will be set to None
481       
482        @rtype: string/datetime
483        @return: the not before time"""
484        if asDatetime is True:
485            return self.__dtNotBefore
486        else:
487            return self.__dat['validity']['notBefore']
488
489    validityNotBefore = property(fget=getValidityNotBefore, 
490                                  doc="Validity not before time as a string")
491
492
493    #_________________________________________________________________________   
494    def getValidityNotAfter(self, asDatetime=False):
495        """Get the validity Not After date/time string
496
497        @param asDatetime: boolean set to True to return as a datetime type
498        Nb. time may not have been set - if so it will be set to None
499       
500        @rtype: string/datetime
501        @return: the not after time"""
502        if asDatetime is True:
503            return self.__dtNotAfter
504        else:
505            return self.__dat['validity']['notAfter']
506
507    validityNotAfter = property(fget=getValidityNotAfter, 
508                                doc="Validity not after time as a string")
509
510   
511    #_________________________________________________________________________   
512    def __getRoleSet(self):
513        """@rtype: list of dict type
514        @return: the roleSet as a list of role dictionaries."""
515        return self.__dat['attributes']['roleSet']
516
517    roleSet = property(fget=__getRoleSet, 
518                       doc="Role set dictionary")
519
520    #_________________________________________________________________________   
521    def __getRoles(self):
522        """Return roles as a list
523       
524        @rtype: list
525        @return: list of roles contained in the certificate"""
526       
527        try:
528            return [i.values()[0].values()[0] \
529                    for i in self.__dat['attributes']['roleSet']]
530        except:
531            return []
532       
533    roles = property(fget=__getRoles, 
534                     doc="List of roles in Attribute Certificate")
535
536       
537    #_________________________________________________________________________   
538    def __setProvenance(self, provenance):
539        """Set the provenance for the certificate: 'original' or 'mapped'.
540       
541        @param provenance: string provenance setting"""
542
543        if not self.isValidProvenance(provenance):
544            raise AttCertError, "Provenance must be set to \"" + \
545                   "\" or \"".join(AttCert.__validProvenanceSettings) + "\""
546       
547        self.__dat['provenance'] = provenance
548
549   
550    #_________________________________________________________________________   
551    def __getProvenance(self):
552        """Get the provenance for the certificate.
553       
554        @rtype: string
555        @return: provenance of certificate mapped or original"""
556        return self.__dat['provenance']
557
558    provenance = property(fget=__getProvenance,
559                          fset=__setProvenance, 
560                          doc="Provenance of the cert. - original or mapped")
561   
562
563    #_________________________________________________________________________   
564    def isValidProvenance(self, provenance=None):
565        """Check provenance is set correctly - to 'original'/'mapped'.
566
567        If no provenance argument is provided, test against the setting in
568        the current instance.
569       
570        @param provenance: by default the current provenance setting is
571        checked i.e. self.__dat['provenance'].  Set this keyword to override
572        and check against an alternate provenance setting.
573       
574        @rtype: bool
575        @return: True if certificate has a valid provenance setting
576        """
577       
578        if not provenance:
579            provenance = self.__dat['provenance']
580
581        return provenance in AttCert.__validProvenanceSettings
582       
583
584    #_________________________________________________________________________   
585    def isOriginal(self):
586        """Check for original provenance.
587        @rtype: bool
588        @return: True if certificate has original roles"""
589        return self.__dat['provenance'] == self.__class__.origProvenance
590
591
592    #_________________________________________________________________________   
593    def isMapped(self):
594        """Check for mapped provenance.
595        @rtype: bool
596        @return: True if certificate contain roles mapped from another"""
597        return self.__dat['provenance'] == self.__class__.mappedProvenance
598
599
600    #_________________________________________________________________________   
601    def addRoles(self, roleName):
602        """Add new roles to the roleSet in attributes.
603       
604        @param roleName: role name or list of role names to add to certificate
605        """
606
607        if isinstance(roleName, basestring):
608            roleName = [roleName]
609           
610        self.__dat['attributes']['roleSet'].extend(\
611                                [{'role': {'name': i}} for i in roleName])
612
613
614    #_________________________________________________________________________   
615    def parse(self, xmlTxt, rtnRootElem=False):
616        """Parse an Attribute Certificate content contained in string input
617
618        @param xmlTxt:     Attribute Certificate XML content as string
619        @param rtnRootElem: boolean set to True to return the ElementTree
620        root element
621       
622        @rtype: ElementTree root element
623        @return: root element if rtnRootElem keyword is set to True"""
624       
625        rootElem = ElementTree.XML(xmlTxt)
626
627        # Call generic ElementTree parser
628        self.__parse(rootElem)
629
630
631        # Call base class parser method to initialise DOM objects for
632        # signature validation
633        try:
634            XMLSecDoc.parse(self, xmlTxt)
635
636        except Exception, e:
637            raise AttCertError, "Attribute Certificate: %s" % e
638
639        if rtnRootElem:
640            return rootElem
641
642       
643    #_________________________________________________________________________   
644    def read(self, filePath=None, **xmlSecDocKw):
645        """Read an Attribute Certificate from file
646
647        @param filePath:   file to be read, if omitted XMLSecDoc.__filePath
648        member variable is used instead"""
649
650        if filePath:
651            self.filePath = filePath
652
653        try:   
654            tree = ElementTree.parse(self.filePath)
655            rootElem = tree.getroot()
656        except Exception, e:
657            raise AttCertError, "Attribute Certificate: %s" % e
658       
659        # Call generic ElementTree parser
660        self.__parse(rootElem)
661
662        # Call base class read method to initialise libxml2 objects for
663        # signature validation
664        try:
665            XMLSecDoc.read(self, **xmlSecDocKw)
666
667        except Exception, e:
668            raise AttCertError, "Attribute Certificate: %s" % e
669
670       
671    #_________________________________________________________________________   
672    def __parse(self, rootElem):
673        """Private XML parsing method accepts a ElementTree.Element type
674        as input
675
676        @param rootElem: root element of doc - ElementTree.Element type
677        """
678       
679        # Extract from acInfo tag
680        acInfoElem = rootElem.find("acInfo")
681       
682        if not acInfoElem:
683            raise AttCertError, "<acInfo> tag not found in \"%s\"" % \
684                               self.filePath
685
686
687        # Copy all acInfo tags into dictionary
688        for elem in acInfoElem:
689            if elem.tag not in self.__dat:
690                raise AttCertError, '%s: "<%s>" not recognised.' % \
691                                    (self.filePath, elem.tag)
692
693            # Make sure not to copy validity and attributes tags - handle
694            # these separately below
695            if not elem.getchildren():
696                self.__dat[elem.tag] = elem.text
697
698        # Convert issuer and holder into X500DN instances
699        try:
700            self.__issuerDN = X500DN(dn=self.__dat['issuer'])
701
702        except X500DNError, x500dnErr:
703            raise AttCertError, "Issuer DN: %s" % x500dnErr
704
705
706        try:
707            self.__holderDN = X500DN(dn=self.__dat['holder'])
708
709        except X500DNError, x500dnErr:
710            raise AttCertError, "Holder DN: %s" % x500dnErr
711       
712                                 
713        # Extract validity and attributes subsets
714        self.__dat['validity']['notBefore'] = \
715                                rootElem.findtext("acInfo/validity/notBefore")
716       
717        if self.__dat['validity']['notBefore'] is None:
718            raise AttCertError, "<notBefore> tag not found in \"%s\"" % \
719                                                               self.filePath
720        elif self.__dat['validity']['notBefore'] == '':
721           
722            # Allow empty string setting but re-initialise corresponding
723            # datetime value
724            self.__dtNotBefore = None
725        else:
726            # Update datetime object equivalent
727            self.__dtNotBefore = self.timeStr2datetime(\
728                                        self.__dat['validity']['notBefore'])
729
730       
731        self.__dat['validity']['notAfter'] = \
732                                rootElem.findtext("acInfo/validity/notAfter")
733       
734        if self.__dat['validity']['notAfter'] is None:
735            raise AttCertError, '<notAfter> tag not found in "%s"' % \
736                               self.filePath
737        elif self.__dat['validity']['notBefore'] == '':
738           
739            # Allow empty string setting but re-initialise corresponding
740            # datetime value
741            self.__dtNotAfter = None
742        else:
743            # Update datetime object equivalent
744            self.__dtNotAfter = self.timeStr2datetime(\
745                                        self.__dat['validity']['notAfter'])
746
747        # set up role list
748        roleElem = acInfoElem.findall("attributes/roleSet/role/name")
749        if roleElem is None:
750            raise AttCertError, "<role> tag not found in \"%s\"" % \
751                               self.filePath
752       
753        self.__dat['attributes']['roleSet'] = \
754                                [{'role': {'name': i.text}} for i in roleElem]
755                   
756       
757        if not self.isValidVersion():           
758            raise AttCertError, 'Attribute Certificate version is ' + \
759                               self.__dat['version'] + ' but version ' + \
760                               AttCert.version + ' expected'
761
762
763    #_________________________________________________________________________   
764    def createXML(self):
765        """Create XML for Attribute Token from current data settings and
766        return as a string.  The XML created is MINUS the digital signature.
767        To obtain the signed version, run the applyEnvelopedSignature method
768        (inherited from XMLSecDoc) and pass the attCert object reference into
769        str()
770
771        @rtype: string
772        @return: formatted XML for certificate as a string"""
773
774        # Nb.
775        # * this method is used by AttCert.read()
776        # * Signing by Attribute Authority is separate - see AttCert.sign()
777       
778
779        # Check for valid provenance
780        if not self.isValidProvenance():
781            raise AttCertError, "Provenance must be set to \"" + \
782                   "\" or \"".join(AttCert.__validProvenanceSettings) + "\""
783
784       
785        # Create string of all XML content       
786        xmlTxt = '<attributeCertificate targetNamespace="%s">' % \
787                                                self.__class__.namespace + \
788"""
789    <acInfo>
790        <version>""" + self.__dat['version'] + """</version>
791        <holder>""" + self.__dat['holder'] + """</holder>
792        <issuer>""" + self.__dat['issuer'] + """</issuer>
793        <issuerName>""" + self.__dat['issuerName'] + """</issuerName>
794        <issuerSerialNumber>""" + str(self.__dat['issuerSerialNumber']) +\
795            """</issuerSerialNumber>
796        <userId>""" + self.__dat['userId'] + """</userId>
797        <validity>
798            <notBefore>""" + self.__dat['validity']['notBefore'] + \
799            """</notBefore>
800            <notAfter>""" + self.__dat['validity']['notAfter'] + \
801            """</notAfter>
802        </validity>
803        <attributes>
804            <roleSet>
805            """ + "".join([\
806"""    <role>
807                    <name>""" + i['role']['name'] + """</name>
808                </role>
809            """ for i in self.__dat['attributes']['roleSet']]) + \
810            """</roleSet>
811        </attributes>
812        <provenance>""" + self.__dat['provenance'] + """</provenance>
813    </acInfo>
814</attributeCertificate>"""
815
816        # Return XML file content as a string
817        return xmlTxt
818
819
820    def applyEnvelopedSignature(self, **xmlSecDocKw):
821        '''Override super class version to ensure settings have been parsed
822        into a DOM object ready for signature
823       
824        @param **xmlSecDocKw: keywords applying to
825        XMLSecDoc.applyEnvelopedSignature()
826        '''       
827        self.parse(self.createXML())
828        super(AttCert, self).applyEnvelopedSignature(**xmlSecDocKw)
829
830       
831    #_________________________________________________________________________   
832    def setValidityTime(self,
833                        dtNotBefore=None, 
834                        dtNotAfter=None, 
835                        lifetime=None,
836                        notBeforeOffset=None):
837        """Set the notBefore and notAfter times which determine the window for
838        which the Attribute Certificate is valid.  These times are set as
839        datetime types and also the correct string format settings are made
840        ready for output.
841
842        Nb. use UTC time.  lifetime and notBeforeOffset are in seconds
843       
844        @param dtNotBefore: not before time as datetime type.  If omitted,
845        it defaults to the current time
846       
847        @param dtNotAfter: not after time as datetime type.  Defaults to
848        self.__dtNotBefore + self.__lifetime.  If dtNotAfter is set it will
849        reset self.__lifetime to self.__dtNotAfter - self.dtNotBefore
850       
851        @param lifetime: lifetime for certificate in seconds i.e. not after
852        time - not before time.  If dtNotAfter is set then this keyword will
853        be ignored.
854       
855        @param notBeforeOffset: skew the not before time by some offset.  This
856        is useful in cases where system clocks are not correctly synchronized
857        between different hosts.  Set a negative value to skew the offset
858        backward in time.
859        """
860
861        if dtNotBefore is not None:
862            if not isinstance(dtNotBefore, datetime):
863                raise AttCertError, \
864                                "Input not before time must be datetime type"
865           
866            self.__dtNotBefore = dtNotBefore
867           
868        else:
869            # Use current UTC +/- offset
870            self.__dtNotBefore = datetime.utcnow()
871           
872        if notBeforeOffset is not None:
873            self.__dtNotBefore += timedelta(seconds=notBeforeOffset)
874           
875
876        if dtNotAfter is not None:
877            if not isinstance(dtNotAfter, datetime):
878                raise AttCertError, \
879                                "Input not after time must be datetime type"
880
881            # Use input Not After time to calculate a new lifetime setting
882            dtDeltaLifeTime = dtNotAfter - self.__dtNotBefore
883            if dtDeltaLifeTime < timedelta(0):
884                raise AttCertError, "Input Not After time is invalid %s" % \
885                                   str(dtNotAfter)
886
887            self.__lifetime = dtDeltaLifeTime.days*86400 + \
888                              dtDeltaLifeTime.seconds
889
890            self.__dtNotAfter = dtNotAfter
891           
892        else:
893            # Check for input certificate life time interval
894            if lifetime is not None:
895                self.__lifetime = lifetime
896               
897            try:
898                # Make a time delta object from the lifetime expressed in
899                # seconds
900                dtDeltaLifeTime = timedelta(seconds=self.__lifetime)
901            except Exception, e:
902                raise AttCertError("Invalid Certificate lifetime set %.3f" %
903                                   self.__lifetime)
904           
905            # Add certificate lifetime to calculate not after time
906            self.__dtNotAfter = self.__dtNotBefore + dtDeltaLifeTime
907
908       
909        self.__dat['validity']['notBefore'] = \
910                                    self.datetime2timeStr(self.__dtNotBefore)
911       
912        self.__dat['validity']['notAfter'] = \
913                                    self.datetime2timeStr(self.__dtNotAfter)
914
915
916    #_________________________________________________________________________   
917    def datetime2timeStr(self, dtVal):
918        """Convert a datetime object to a notBefore/notAfter time string
919       
920        @param dtVal: input datetime
921       
922        @rtype: string
923        @return: datetime converted into correct string format for AttCert"""
924
925        if not isinstance(dtVal, datetime):
926            raise AttCertError, \
927                        "Invalid datetime object for conversion to string"
928       
929        # Convert from 1-12 to 0-11 month format used in XML file
930        #lDateTime = list(dtVal.utctimetuple()[0:6])
931
932        #lDateTime[1] -= 1
933
934        # Format as a single string with no commas or brackets
935        #return ''.join(re.findall('[0-9 ]', str(lDateTime)))
936
937        # Use 1-12 format
938        # P J Kershaw 09/06/05
939        return dtVal.strftime("%Y %m %d %H %M %S")
940
941
942    #_________________________________________________________________________   
943    def timeStr2datetime(self, sTime):
944        """Convert a notBefore/notAfter time string to a datetime object
945       
946        @param sTime: time in string format as used by AttCert
947        @rtype: datetime
948        @return: datetime type equivalent of string input"""
949
950        try:
951            lTime = strptime(sTime, "%Y %m %d %H %M %S")
952            return datetime(*lTime[0:6])
953       
954        except Exception, e:
955            raise AttCertError, \
956                "Error converting time string into datetime object: %s" % e
957       
958
959    #_________________________________________________________________________   
960    def isValidTime(self, dtNow=None, raiseExcep=False):
961        """Check Attribute Certificate for expiry.  Set raiseExcep to True
962        to raise an exception with a message indicating the nature of the
963        time error
964       
965        @param dtNow: the time to test against in datetime format.  This time
966        must be within the range of the not before and not after times in
967        order for the certificate to be valid.  Defaults to the current
968        system time
969       
970        @param raiseExcep: boolean set to True to raise an exception if the
971        time is invalid.  Defaults to False in which case no exception is
972        raised if the time is invalid, instead False is returned
973       
974        @rtype: bool
975        @return: boolean True if time is valid, False if invalid.  Also see
976        raiseExcep keyword above."""
977
978        if not isinstance(self.__dtNotBefore, datetime):
979            raise AttCertError, "Not Before datetime is not set"
980
981        if not isinstance(self.__dtNotAfter, datetime):
982            raise AttCertError, "Not After datetime is not set"
983       
984        if dtNow is None:
985            dtNow = datetime.utcnow()
986       
987        # Testing only
988        #
989        # P J Kershaw 02/03/06
990        #notBefore = self.__dtNotBefore
991        #notAfter = self.__dtNotAfter
992        #print "Valid Time? = %d" % (dtNow > notBefore and dtNow < notAfter)
993        if raiseExcep:
994            if dtNow < self.__dtNotBefore:
995                raise AttCertError, "Current time %s " % \
996                           dtNow.strftime("%d/%m/%Y %H:%M:%S") + \
997                           "is before Attribute Certificate's " + \
998                           "not before time of %s" % \
999                           self.__dtNotBefore.strftime("%d/%m/%Y %H:%M:%S")
1000           
1001            if dtNow > self.__dtNotAfter:
1002                raise AttCertError, "Current time %s " % \
1003                           dtNow.strftime("%d/%m/%Y %H:%M:%S") + \
1004                           "is after Attribute Certificate's " + \
1005                           "expiry time of %s" % \
1006                           self.__dtNotBefore.strftime("%d/%m/%Y %H:%M:%S")               
1007           
1008            return True       
1009        else:
1010            return dtNow > self.__dtNotBefore and dtNow < self.__dtNotAfter
1011       
1012       
1013    def isValidVersion(self):
1014        """Check Attribute Certificate XML file version
1015       
1016        @rtype: bool
1017        @return: boolean True if certificate version matches the expected one,
1018        False otherwise.
1019        """
1020        return self.__dat['version'] == AttCert.version
1021
1022
1023    def isValid(self,
1024                raiseExcep=False,
1025                chkTime=True,
1026                chkVersion=True,
1027                chkProvenance=True,
1028                chkSig=True,
1029                **xmlSecDocKw):
1030        """Check Attribute Certificate is valid:
1031
1032        - Time validity is OK
1033        - XML file version is OK
1034        - valid provenance setting
1035        - Signature is valid.
1036
1037        @param chkTime: set to True to do time validity check (default is
1038        True)
1039
1040        @param chkVersion: set to True to Attribute Certificate file
1041        version (default is True)
1042
1043        @param chkProvenanceset to True to check provenance value is valid
1044        (default is True)
1045
1046        @param chkSig: set to True to check digital signature - for
1047        this certFilePathList must contain the root certificate of the X.509
1048        certificate used to sign the AttCert.  Alternatively, certFilePathList
1049        can be set via __init__ (default chkSig value is True)
1050                               
1051        @param raiseExcep: set to true to raise an exception if invalid
1052        instead of returning False.  Default is to set this flag to False.
1053
1054        @param **xmlSecDocKw: Also accepts keyword arguments corresponding to
1055        XMLSecDoc.verifyEnvelopedSignature().
1056       
1057        @rtype: bool
1058        @return: boolean True if certificate is valid, False otherwise.  Also
1059        see explanation for raiseExcep keyword.                         
1060        """
1061
1062        # Carry out checks in turn - Specific exception error messages are
1063        # raised if flag is set
1064        if chkTime and not self.isValidTime(raiseExcep=raiseExcep):
1065            return False
1066
1067        if chkVersion and not self.isValidVersion():
1068            if raiseExcep:
1069                raise AttCertError('Attribute Certificate version is %s '
1070                                   'but version %s expected' %
1071                                   (self.__dat['version'], AttCert.version))
1072            return False
1073
1074        if chkProvenance and not self.isValidProvenance():
1075            if raiseExcep:
1076                raise AttCertError(
1077                    "Attribute Certificate Provenance must be set to \""
1078                    "\" or \"".join(AttCert.__validProvenanceSettings) + "\"")
1079            return False
1080
1081        # Handle exception from XMLSecDoc.isValidSig() regardless of
1082        # raiseExcep flag setting
1083        if chkSig:
1084            try:
1085                self.verifyEnvelopedSignature(**xmlSecDocKw)
1086       
1087            except InvalidSignature, e:
1088                 if raiseExcep:
1089                     raise AttCertError(e)
1090                 else:
1091                     return False
1092               
1093        # All tests passed
1094        return True
1095
1096    @classmethod
1097    def Read(cls, filePath):
1098        """Create a new attribute certificate read in from a file"""
1099        attCert = cls(filePath=filePath)
1100        attCert.read()
1101       
1102        return attCert
1103
1104    @classmethod
1105    def Parse(cls, attCertTxt):
1106        """Create a new attribute certificate from string of file content"""
1107       
1108        attCert = cls()
1109        attCert.parse(attCertTxt)
1110       
1111        return attCert
1112       
1113# Alternative AttCert constructors
1114def AttCertRead(filePath):
1115    """Create a new attribute certificate read in from a file"""
1116   
1117    attCert = AttCert(filePath=filePath)
1118    attCert.read()
1119   
1120    return attCert
1121
1122def AttCertParse(attCertTxt):
1123    """Create a new attribute certificate from string of file content"""
1124   
1125    attCert = AttCert()
1126    attCert.parse(attCertTxt)
1127   
1128    return attCert
Note: See TracBrowser for help on using the repository browser.