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

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

NDG Security New Release

rc2 fixes bug in ndg.security.common.AttCert? due to change in M2Crypto 0.20 M2Crypto.X509.X509.get_serial_number() now returns a long type intead of int.

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