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

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

Added a Policy Information Point to encapsulate subject attribute retrieval.

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