source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/X509.py @ 2956

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

ndg.security.common/ndg/security/common/X509.py: fix to X509Stack.verifyCertChain - only reject if stack is empty AND x509Cert2Verify is not set.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1"""X.509 certificate handling class encapsulates M2Crypto.X509
2
3NERC Data Grid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "05/04/05"
7__copyright__ = "(C) 2007 STFC & NERC"
8__license__ = \
9"""This software may be distributed under the terms of the Q Public
10License, version 1.0 or later."""
11__contact__ = "P.J.Kershaw@rl.ac.uk"
12__revision__ = '$Id$'
13
14
15import types
16import re
17
18# Handle not before and not after strings
19from time import strftime
20from time import strptime
21from datetime import datetime
22
23import M2Crypto
24
25
26class X509CertError(Exception):
27    """Exception handling for NDG X.509 Certificate handling class."""
28
29class X509CertInvalidNotBeforeTime(X509CertError):
30    """Call from X509Cert.isValidTime if certificates not before time is
31    BEFORE the current system time"""
32   
33class X509CertExpired(X509CertError):
34    """Call from X509Cert.isValidTime if certificate has expired"""
35
36   
37class X509Cert(object):
38    "NDG X509 Certificate Handling"
39
40    def __init__(self, filePath=None, m2CryptoX509=None):
41
42        # Set certificate file path
43        if filePath is not None:
44            if not isinstance(filePath, basestring):
45                raise X509CertError, \
46                        "Certificate File Path input must be a valid string"
47           
48        self.__filePath = filePath           
49        self.__dn = None
50        self.__dtNotBefore = None
51        self.__dtNotAfter = None
52       
53        if m2CryptoX509:
54            self.__setM2CryptoX509(m2CryptoX509)
55       
56
57    def read(self, filePath=None):
58        """Read a certificate from file"""
59       
60        # Check for optional input certificate file path
61        if filePath is not None:
62            if not isinstance(filePath, basestring):
63                raise X509CertError, \
64                    "Certificate File Path input must be a valid string"
65           
66            self.__filePath = filePath
67       
68        try:
69            self.__m2CryptoX509 = M2Crypto.X509.load_cert(self.__filePath)
70        except Exception, e:
71            raise X509CertError, "Error loading certificate \"%s\": %s" % \
72                                (self.__filePath, str(e))
73
74        # Update DN and validity times from M2Crypto X509 object just
75        # created
76        self.__setM2CryptoX509()
77
78
79    def parse(self, certTxt):
80        """Read a certificate input as a string"""
81
82        try:
83            # Create M2Crypto memory buffer and pass to load certificate
84            # method
85            #
86            # Nb. input converted to standard string - buffer method won't
87            # accept unicode type strings
88            certBIO = M2Crypto.BIO.MemoryBuffer(str(certTxt))
89            self.__m2CryptoX509 = M2Crypto.X509.load_cert_bio(certBIO)
90           
91        except Exception, e:
92            raise X509CertError, "Error loading certificate: %s" % str(e)
93
94        # Update DN and validity times from M2Crypto X509 object just
95        # created
96        self.__setM2CryptoX509()
97
98
99       
100       
101    def __setM2CryptoX509(self, m2CryptoX509=None):
102        """Private method allows class members to be updated from the
103        current M2Crypto object.  __m2CryptoX509 must have been set."""
104       
105        if m2CryptoX509 is not None:
106            if not isinstance(m2CryptoX509, M2Crypto.X509.X509):
107                raise TypeError, \
108                    "Incorrect type for input M2Crypto.X509.X509 object"
109                   
110            self.__m2CryptoX509 = m2CryptoX509
111           
112           
113        # Get distinguished name
114        m2CryptoX509Name = self.__m2CryptoX509.get_subject()
115
116        # Instantiate X500 Distinguished name
117        self.__dn = X500DN(m2CryptoX509Name=m2CryptoX509Name)
118
119
120        # Get not before and not after validity times
121        #
122        # Only option for M2Crypto seems to be to return the times as
123        # formatted strings and then parse them in order to create a datetime
124        # type
125       
126        try:
127            m2CryptoNotBefore = self.__m2CryptoX509.get_not_before()
128            self.__dtNotBefore=self.__m2CryptoUTC2datetime(m2CryptoNotBefore)
129                                       
130        except Exception, e:
131            raise X509CertError, "Not Before time: " + str(e)
132
133       
134        try:
135            m2CryptoNotAfter = self.__m2CryptoX509.get_not_after()
136            self.__dtNotAfter = self.__m2CryptoUTC2datetime(m2CryptoNotAfter)
137                                   
138        except Exception, e:
139            raise X509CertError, "Not After time: " + str(e)
140
141
142    #_________________________________________________________________________
143    def __getM2CryptoX509(self, m2CryptoX509=None):
144        "Return M2Crypto X.509 cert object"
145        return self.__m2CryptoX509
146   
147   
148    m2CryptoX509 = property(fset=__setM2CryptoX509,
149                            fget=__getM2CryptoX509,
150                            doc="M2Crypto.X509.X509 type")
151
152       
153    def toString(self, **kw):
154        """Return certificate file content as a PEM format
155        string"""
156        return self.asPEM(**kw)
157       
158    def asPEM(self, filePath=None):
159        """Return certificate file content as a PEM format
160        string"""
161       
162        # Check M2Crypto.X509 object has been instantiated - if not call
163        # read method
164        if self.__m2CryptoX509 is None:
165            self.read(filePath)
166           
167        return self.__m2CryptoX509.as_pem()
168
169       
170    def asDER(self):
171        """Return certificate file content in DER format"""
172       
173        # Check M2Crypto.X509 object has been instantiated
174        assert(self.__m2CryptoX509)
175        return self.__m2CryptoX509.as_der()
176
177   
178    #_________________________________________________________________________
179    # Make some attributes accessible as read-only
180    def __getDN(self):
181        """Get X500 Distinguished Name."""
182        return self.__dn
183
184    dn = property(fget=__getDN, doc="X.509 Distinguished Name")
185
186
187    def __getVersion(self):
188        """Get X.509 Certificate version"""
189        if self.__m2CryptoX509 is None:
190            return None
191       
192        return self.__m2CryptoX509.get_version()
193
194    version = property(fget=__getVersion, doc="X.509 Certificate version")
195       
196       
197    def __getSerialNumber(self):
198        """Get Serial Number"""
199        if self.__m2CryptoX509 is None:
200            return None
201       
202        return self.__m2CryptoX509.get_serial_number()
203   
204    serialNumber = property(fget=__getSerialNumber, 
205                            doc="X.509 Certificate Serial Number")
206       
207
208    def __getNotBefore(self):
209        """Get not before validity time as datetime type"""
210        if self.__m2CryptoX509 is None:
211            return None
212       
213        return self.__dtNotBefore
214
215    notBefore = property(fget=__getNotBefore, 
216                         doc="Not before validity time as datetime type")
217       
218       
219    def __getNotAfter(self):
220        """Get not after validity time as datetime type"""
221        if self.__m2CryptoX509 is None:
222            return None
223       
224        return self.__dtNotAfter
225
226    notAfter = property(fget=__getNotAfter, 
227                         doc="Not after validity time as datetime type")
228       
229       
230    def __getPubKey(self):
231        """Get public key
232       
233        @return: RSA public key for certificate
234        @rtype: M2Crypto.RSA.RSA_pub"""
235        if self.__m2CryptoX509 is None:
236            return None
237       
238        return self.__m2CryptoX509.get_pubkey()
239
240    pubKey = property(fget=__getPubKey,  doc="Public Key")
241       
242       
243    def __getIssuer(self):
244        """Get Certificate issuer"""
245        if self.__m2CryptoX509 is None:
246            return None
247       
248        # Return as X500DN type
249        return X500DN(m2CryptoX509Name=self.__m2CryptoX509.get_issuer())
250
251    issuer = property(fget=__getIssuer,  doc="Certificate Issuer")
252       
253   
254    def __getSubject(self):
255        """Get Certificate subject"""
256        if self.__m2CryptoX509 is None:
257            return None
258
259        # Return as X500DN type
260        return X500DN(m2CryptoX509Name=self.__m2CryptoX509.get_subject())
261   
262    subject = property(fget=__getSubject,  doc="Certificate subject")
263
264
265    def isValidTime(self, raiseExcep=False):
266        """Check Certificate for expiry
267
268        raiseExcep: set True to raise an exception if certificate is invalid"""
269
270        if not isinstance(self.__dtNotBefore, datetime):
271            raise X509CertError("Not Before datetime is not set")
272
273        if not isinstance(self.__dtNotAfter, datetime):
274            raise X509CertError("Not After datetime is not set")
275       
276        dtNow = datetime.utcnow()
277
278        if raiseExcep:
279            if dtNow < self.__dtNotBefore:
280                raise X509CertInvalidNotBeforeTime, \
281                    "Current time is before the certificate's Not Before Time"
282           
283            elif dtNow > self.__dtNotAfter:
284                raise X509CertExpired, \
285                    "Certificate has expired: the time now is %s " % dtNow + \
286                    "and the certificate expiry is %s" % self.__dtNotAfter
287        else:
288            return dtNow > self.__dtNotBefore and dtNow < self.__dtNotAfter
289
290
291
292
293    def __m2CryptoUTC2datetime(self, m2CryptoUTC):
294        """Convert M2Crypto UTC time string as returned by get_not_before/
295        get_not_after methods into datetime type"""
296       
297        datetimeRE = "([a-zA-Z]{3} {1,2}\d{1,2} \d{2}:\d{2}:\d{2} \d{4}).*"
298        sM2CryptoUTC = None
299       
300        try:
301            # Convert into string
302            sM2CryptoUTC = str(m2CryptoUTC)
303           
304            # Check for expected format - string may have trailing GMT - ignore
305            sTime = re.findall(datetimeRE, sM2CryptoUTC)[0]
306
307            # Convert into a tuple
308            lTime = strptime(sTime, "%b %d %H:%M:%S %Y")[0:6]
309
310            return datetime(lTime[0], lTime[1], lTime[2],
311                            lTime[3], lTime[4], lTime[5])
312                                   
313        except Exception, e:
314            msg = "Error parsing M2Crypto UTC"
315            if sM2CryptoUTC is not None:
316                msg += ": " + sM2CryptoUTC
317               
318            raise X509CertError(msg)
319       
320    def verify(self, pubKey, **kw):
321        """Verify a certificate against the public key of the
322        issuer
323       
324        @param pubKey: public key of cert that issued self
325        @type pubKey: M2Crypto.RSA.RSA_pub
326        @param **kw: keywords to pass to M2Crypto.X509.X509 -
327        'pkey'
328        @type: dict
329        @return: True if verifies OK, False otherwise
330        @rtype: bool
331        """
332        return bool(self.__m2CryptoX509.verify(pubKey, **kw))
333
334#_____________________________________________________________________________
335# Alternative AttCert constructors
336#
337def X509CertRead(filePath):
338    """Create a new X509 certificate read in from a file"""
339
340    x509Cert = X509Cert(filePath=filePath)
341    x509Cert.read()
342   
343    return x509Cert
344
345
346#_____________________________________________________________________________
347def X509CertParse(x509CertTxt):
348    """Create a new X509 certificate from string of file content"""
349
350    x509Cert = X509Cert()
351    x509Cert.parse(x509CertTxt)
352   
353    return x509Cert
354
355
356#_____________________________________________________________________________
357class X509StackError(Exception):
358    """Error from X509Stack type"""
359
360#_____________________________________________________________________________
361class CertIssuerNotFound(X509StackError):
362    """Raise from verifyCertChain if no certificate can be found to verify the
363    input"""
364
365class SelfSignedCert(X509StackError):
366    """Raise from verifyCertChain if cert. is self-signed and
367    rejectSelfSignedCert=True"""
368       
369#_____________________________________________________________________________
370class X509Stack(object):
371    """Wrapper for M2Crypto X509_Stack"""
372   
373    def __init__(self, m2X509Stack=None):
374        """Initialise from an M2Crypto stack object
375       
376        @param m2X509Stack: M2Crypto X.509 stack object
377        @type m2X509Stack: M2Crypto.X509.X509_Stack"""
378       
379        self.__m2X509Stack = m2X509Stack or M2Crypto.X509.X509_Stack()
380       
381    def __len__(self):
382        """@return: length of stack
383        @rtype: int"""
384        return self.__m2X509Stack.__len__()
385
386    def __getitem__(self, idx):
387        """Index stack as an array
388        @param idx: stack index
389        @type idx: int
390        @return: X.509 cert object
391        @rtype: ndg.security.common.X509.X509Cert"""
392       
393        return X509Cert(m2CryptoX509=self.__m2X509Stack.__getitem__(idx))
394   
395    def __iter__(self):
396        """@return: stack iterator
397        @rtype: listiterator"""
398        return iter([X509Cert(m2CryptoX509=i) for i in self.__m2X509Stack])
399
400    def push(self, x509Cert):
401        """Push an X509 certificate onto the stack.
402       
403        @param x509Cert: X509 object.
404        @type x509Cert: M2Crypto.X509.X509,
405        ndg.security.common.X509.X509Cert or basestring
406        @return: The number of X509 objects currently on the stack.
407        @rtype: int"""
408        if isinstance(x509Cert, M2Crypto.X509.X509):
409            return self.__m2X509Stack.push(x509Cert)
410       
411        elif isinstance(x509Cert, X509Cert):
412            return self.__m2X509Stack.push(x509Cert.m2CryptoX509)
413       
414        elif isinstance(x509Cert, basestring):
415            return self.__m2X509Stack.push(\
416                                       X509CertParse(x509Cert).m2CryptoX509)           
417        else:
418            raise X509StackError, "Expecting M2Crypto.X509.X509, " + \
419                "ndg.security.common.X509.X509Cert or string type"
420               
421    def pop(self):
422        """Pop a certificate from the stack.
423       
424        @return: X509 object that was popped, or None if there is nothing
425        to pop.
426        @rtype: ndg.security.common.X509.X509Cert
427        """
428        return X509Cert(m2CryptoX509=self.__m2X509Stack.pop())
429
430
431    def asDER(self):
432        """Return the stack as a DER encoded string
433        @return: DER string
434        @rtype: string"""
435        return self.__m2X509Stack.as_der()
436
437
438    def verifyCertChain(self, 
439                        x509Cert2Verify=None, 
440                        caX509Stack=[],
441                        rejectSelfSignedCert=True):
442        """Treat stack as a list of certificates in a chain of
443        trust.  Validate the signatures through to a single root issuer. 
444
445        @param x509Cert2Verify: X.509 certificate to be verified default is
446        last in the stack
447        @type x509Cert2Verify: X509Cert
448       
449        @param caX509Stack: X.509 stack containing CA certificates that are
450        trusted.
451        @type caX509Stack: X509Stack
452       
453        @param rejectSelfSignedCert: Set to True (default) to raise an
454        SelfSignedCert exception if a certificate in self's stack is
455        self-signed. 
456        @type rejectSelfSignedCert: bool"""
457       
458       
459        if x509Cert2Verify is None:
460            if not len(self):
461                raise X509StackError, \
462                "Empty stack and no x509Cert2Verify set: no cert.s to verify"
463
464            x509Cert2Verify = self[-1]
465
466        # Exit loop if all certs have been validated or if find a self
467        # signed cert.
468        nValidated = 0 
469        while nValidated < len(self):               
470            issuerX509Cert = None
471            issuerDN = x509Cert2Verify.issuer
472           
473            # Search for issuing certificate in stack
474            for x509Cert in self:
475                if x509Cert.dn == issuerDN:
476                    # Match found - the cert.'s issuer has been found in the
477                    # stack
478                    issuerX509Cert = x509Cert
479                    break
480                   
481            if issuerX509Cert:
482                # An issuing cert. has been found - use it to check the
483                # signature of the cert. to be verified
484                if not x509Cert2Verify.verify(issuerX509Cert.pubKey):
485                    X509CertError, 'Signature is invalid for cert. "%s"' % \
486                                    x509Cert2Verify.dn
487               
488                # In the next iteration the issuer cert. will be checked:
489                # 1) search for a cert. in the stack that issued it
490                # 2) If found use the issuing cert. to verify
491                x509Cert2Verify = issuerX509Cert
492                nValidated += 1
493            else:
494                # All certs in the stack have been searched
495                break
496
497
498        if issuerX509Cert:           
499            # Check for self-signed certificate
500            if nValidated == 1 and rejectSelfSignedCert and \
501               issuerX509Cert.dn == issuerX509Cert.issuer:
502
503                # If only one iteration occured then it must be a self
504                # signed certificate
505                raise SelfSignedCert, "Certificate is self signed"
506           
507            if not caX509Stack:
508                caX509Stack = [issuerX509Cert]
509                         
510        elif not caX509Stack:
511            raise CertIssuerNotFound, \
512                    'No issuer cert. found for cert. "%s"'%x509Cert2Verify.dn
513           
514        for caCert in caX509Stack:
515            issuerDN = x509Cert2Verify.issuer
516            if caCert.dn == issuerDN:
517                issuerX509Cert = caCert
518                break
519       
520        if issuerX509Cert:   
521            if not x509Cert2Verify.verify(issuerX509Cert.pubKey):
522                X509CertError, 'Signature is invalid for cert. "%s"' % \
523                                x509Cert2Verify.dn
524           
525            # Chain is validated through to CA cert
526            return
527        else:
528            raise CertIssuerNotFound, 'No issuer cert. found for cert. "%s"'%\
529                                x509Cert2Verify.dn
530       
531        # If this point is reached then an issuing cert is missing from the
532        # chain       
533        raise X509CertError, 'Can\'t find issuer cert "%s" for cert "%s"' % \
534                          (x509Cert2Verify.issuer, x509Cert2Verify.dn) 
535
536
537#_____________________________________________________________________________
538def X509StackParseFromDER(derString):
539    """Make a new stack from a DER string
540   
541    @param derString: DER formatted X.509 stack data
542    @type derString: string
543    @return: new stack object
544    @rtype: X509Stack""" 
545    return X509Stack(m2X509Stack=M2Crypto.X509.new_stack_from_der(derString))
546
547
548#_____________________________________________________________________________
549class X500DNError(Exception):
550    """Exception handling for NDG X.500 DN class."""
551
552
553#_____________________________________________________________________________
554# For use with parseSeparator method:
555import re
556
557
558class X500DN(dict):
559    "NDG X500 Distinguished name"
560   
561    # Class attribute - look-up mapping short name attributes to their long
562    # name equivalents
563    # * private *
564    __shortNameLUT = {  'commonName':               'CN',
565                        'organisationalUnitName':   'OU',
566                        'organisation':             'O',
567                        'countryName':              'C',
568                        'emailAddress':             'EMAILADDRESS',
569                        'localityName':             'L',
570                        'stateOrProvinceName':      'ST',
571                        'streetAddress':            'STREET',
572                        'domainComponent':          'DC',
573                        'userid':                   'UID'}
574
575   
576    def __init__(self,
577                 dn=None,
578                 m2CryptoX509Name=None,
579                 separator=None):
580
581        """Create a new X500 Distinguished Name
582
583        m2CryptoX509Name:   initialise using using an M2Crypto.X509.X509_Name
584        dn:                 initialise using a distinguished name string
585        separator:          separator used to delimit dn fields - usually
586                            '/' or ','.  If dn is input and separator is
587                            omitted the separator character will be
588                            automatically parsed from the dn string.
589                            """
590        # Private key data
591        self.__dat = {  'CN':           '',
592                        'OU':           '',
593                        'O':            '',
594                        'C':            '',
595                        'EMAILADDRESS': '',
596                        'L':            '',
597                        'ST':           '',
598                        'STREET':       '',
599                        'DC':           '',
600                        'UID':          ''}
601   
602        dict.__init__(self)
603   
604        self.__separator = None
605       
606        # Check for separator from input
607        if separator is not None:
608            if not isinstance(separator, basestring):
609                raise X500DNError("dn Separator must be a valid string")
610
611            # Check for single character but allow trailing space chars
612            if len(separator.lstrip()) is not 1:
613                raise X500DNError("dn separator must be a single character")
614
615            self.__separator = separator
616
617           
618        if m2CryptoX509Name is not None:
619            # the argument is an x509 dn in m2crypto format
620            #
621            # Hack required here because M2Crypto doesn't
622            # correctly separate emailAddress fields e.g.
623            #
624            # C=SG, ST=Singapore, O=BMTAP Pte Ltd,
625            # OU=Environmental Development,
626            # CN=www.bmtap.com.sg/emailAddress=sjamsul.lakau@bmtasia.com.sg
627            #                    ^
628            # - The slash is left in place
629            #
630            # TODO: re-check this for future M2Crypto releases
631            dnTxt = ', '.join(m2CryptoX509Name.as_text().split('/'))           
632            # End hack
633           
634            self.deserialise(dnTxt)
635           
636        elif dn is not None:
637            # Separator can be parsed from the input DN string - only attempt
638            # if no explict separator was input
639            if self.__separator is None:
640                self.__separator = self.parseSeparator(dn)
641               
642            # Split Distinguished name string into constituent fields
643            self.deserialise(dn)
644
645
646    def __repr__(self):
647        """Override default behaviour to return internal dictionary content"""
648        return self.serialise()
649
650
651    def __str__(self):
652        """Behaviour for print and string statements - convert DN into
653        serialised format."""
654        return self.serialise()
655
656       
657    def __eq__(self, x500dn):
658        """Return true if the all the fields of the two DNs are equal"""
659       
660        if not isinstance(x500dn, X500DN):
661            return False
662
663        return self.__dat.items() == x500dn.items()
664
665       
666    def __ne__(self, x500dn):
667        """Return true if the all the fields of the two DNs are equal"""
668       
669        if not isinstance(x500dn, X500DN):
670            return False
671
672        return self.__dat.items() != x500dn.items()
673
674   
675    def __delitem__(self, key):
676        """Prevent keys from being deleted."""
677        raise X500DNError('Keys cannot be deleted from the X500DN')
678
679
680    def __getitem__(self, key):
681
682        # Check input key
683        if self.__dat.has_key(key):
684
685            # key recognised
686            return self.__dat[key]
687       
688        elif X500DN.__shortNameLUT.has_key(key):
689
690            # key not recognised - but a long name version of the key may
691            # have been passed
692            shortName = X500DN.__shortNameLUT[key]
693            return self.__dat[shortName]
694
695        else:
696            # key not recognised as a short or long name version
697            raise X500DNError('Key "' + key + '" not recognised for X500DN')
698
699
700    def __setitem__(self, key, item):
701       
702        # Check input key
703        if self.__dat.has_key(key):
704
705            # key recognised
706            self.__dat[key] = item
707           
708        elif X500DN.__shortNameLUT.has_key(key):
709               
710            # key not recognised - but a long name version of the key may
711            # have been passed
712            shortName = X500DN.__shortNameLUT[key]
713            self.__dat[shortName] = item
714           
715        else:
716            # key not recognised as a short or long name version
717            raise X500DNError('Key "' + key + '" not recognised for X500DN')
718
719
720    def clear(self):
721        raise X500DNError("Data cannot be cleared from " + self.__class__.__name__)
722
723   
724    def copy(self):
725
726        import copy
727        return copy.copy(self)
728
729   
730    def keys(self):
731        return self.__dat.keys()
732
733
734    def items(self):
735        return self.__dat.items()
736
737
738    def values(self):
739        return self.__dat.values()
740
741
742    def has_key(self, key):
743        return self.__dat.has_key(key)
744
745    # 'in' operator
746    def __contains__(self, key):
747        return key in self.__tags
748
749
750    def get(self, kw):
751        return self.__dat.get(kw)
752
753   
754    def serialise(self, separator=None):
755        """Combine fields in Distinguished Name into a single string."""
756       
757        if separator:
758            if not isinstance(separator, basestring):
759                raise X500DNError("Separator must be a valid string")
760               
761            self.__separator = separator
762           
763        else:
764            # Default to / if no separator is set
765            separator = '/'
766
767
768        # If using '/' then prepend DN with an initial '/' char
769        if separator == '/':
770            sDN = separator
771        else:
772            sDN = ''
773     
774        dnList = []
775        for (key, val) in self.__dat.items():
776            if val:
777                if isinstance(val, tuple):
778                    dnList += [separator.join(["%s=%s" % (key, valSub) \
779                                            for valSub in val])]
780                else:
781                    dnList += ["%s=%s" % (key, val)]
782               
783        sDN += separator.join(dnList)
784                               
785        return sDN
786
787    serialize = serialise
788   
789    def deserialise(self, dn, separator=None):
790        """Break up a DN string into it's constituent fields and use to
791        update the object's dictionary"""
792       
793        if separator:
794            if not isinstance(separator, basestring):
795                raise X500DNError("Separator must be a valid string")
796
797            self.__separator = separator
798
799
800        # If no separator has been set, parse if from the DN string           
801        if self.__separator is None:
802            self.__separator = self.parseSeparator(dn)
803
804        try:
805            dnFields = dn.split(self.__separator)
806            if len(dnFields) < 2:
807                raise X500DNError("Error parsing DN string: \"%s\"" % dn)
808
809           
810            # Split fields into key/value and also filter null fields if
811            # found e.g. a leading '/' in the DN would yield a null field
812            # when split
813           
814            items = [field.split('=') for field in dnFields if field]
815
816            # Reset existing dictionary values
817            self.__dat.fromkeys(self.__dat, '')
818           
819            # Strip leading and trailing space chars and convert into a
820            # dictionary
821            parsedDN = {}
822            for (key, val) in items:
823                key = key.strip()
824                if key in parsedDN:
825                    if isinstance(parsedDN[key], tuple):
826                        parsedDN[key] = tuple(list(parsedDN[key]) + [val])                   
827                    else:
828                        parsedDN[key] = (parsedDN[key], val)
829                else:
830                    parsedDN[key] = val
831               
832            # Copy matching DN fields
833            for key, val in parsedDN.items():
834                if key not in self.__dat and key not in self.__shortNameLUT:
835                    raise X500DNError, \
836                        "Invalid field \"%s\" in input DN string" % key
837
838                self.__dat[key] = val
839
840               
841        except Exception, excep:
842            raise X500DNError("Error de-serialising DN \"%s\": %s" % \
843                              (dn, str(excep)))
844
845    deserialize = deserialise
846   
847    def parseSeparator(self, dn):
848        """Attempt to parse the separator character from a given input
849        DN string.  If not found, return None
850
851        DNs don't use standard separators e.g.
852
853        /C=UK/O=eScience/OU=CLRC/L=DL/CN=AN Other
854        CN=SUM Oneelse,L=Didcot, O=RAL,OU=SSTD
855
856        This function isolates and identifies the character.  - In the above,
857        '/' and ',' respectively"""
858
859
860        # Make a regular expression containing all the possible field
861        # identifiers with equal sign appended and 'or'ed together.  \W should
862        # match the separator which preceeds the field name. \s* allows any
863        # whitespace between field name and field separator to be taken into
864        # account.
865        #
866        # The resulting match should be a list.  The first character in each
867        # element in the list should be the field separator and should be the
868        # same
869        regExpr = '|'.join(['\W\s*'+i+'=' for i in self.__dat.keys()])
870        match = re.findall(regExpr, dn)
871           
872        # In the first example above, the resulting match is:
873        # ['/C=', '/O=', '/OU=', '/L=']
874        # In each element the first character is the separator
875        sepList = [i[0:1] for i in match]
876
877        # All separators should be the same character - return None if they
878        # don't match
879        if not [i for i in sepList if i != sepList[0]]:
880            return sepList[0]
881        else:
882            return None
Note: See TracBrowser for help on using the repository browser.