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

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

Updated for epydoc: added double underscore vars module info

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