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

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

ndg.security.server/ndg/security/server/AttAuthority/server-config.tac:
fix to caCertFilePathList input to SignatureHandler?. Correctly initialise
if not set.

ndg.security.server/ndg/security/server/AttAuthority/init.py:
Corrected error message text for where a user is not registered or no
mapping is available: ref. userId rather than AC holder DN to allow for the
case in DEWS where a userId distinct from a Proxy cert. DN is used.

ndg.security.test/ndg/security/test/AttAuthority/AttAuthorityClientTest.py:
added test8GetMappedAttCertStressTest test for WebSphere? integration tests.
It makes multiple calls with different ACs input to check for errors in
signature or verification.

ndg.security.test/ndg/security/test/AttAuthority/attAuthorityClientTest.cfg:
added additional config params for the above.

ndg.security.test/ndg/security/test/MyProxy/myProxyProperties.xml and
ndg.security.test/ndg/security/test/MyProxy/myProxyClientTest.cfg:
switched cert ID of test machine.

ndg.security.common/ndg/security/common/X509.py:

  • new X509Cert methods asDER and asPEM to convert to these formats.

toString now calls to asPEM

  • new class X509Stack to wrap M2Crypto.X509.X509_Stack. This includes an

extra method, verifyCertChain, to verify a chain of trust in the certs
contained in the stack.

  • standalone function, X509StackParseFromDER, wraps

M2Crypto.X509.new_stack_from_der

  • fix to X500DN class to enable correct parsing of proxy certificate DNs.

These have multiple CN entries. These are represented by changing the CN
dict entry to a tuple when initialised.

ndg.security.common/ndg/security/common/wsSecurity.py: changes to enable
handling of certificate chains in WSSE BinarySecurityToken? elements. This
will enable use of proxy certificates with signatures as their chain of
trust is proxy cert -> user cert -> CA cert rather than just cert -> CA cert.

types.

BinarySecurityToken? ValueType? to use

  • SignatureHandler?.init includes new signingCertChain keyword.
  • signingCertChain attribute of class enables setting of an X509Stack object

to assign to BinarySecurityToken?.

then Base 64 encode rather than converting into PEM and then having to
strip BEGIN CERT / END CERT delimiters.

to enable check of Canonicalization - REMOVE in future check in.

BinarySecurityToken? ValueTypes? - 'X509PKIPathv1', 'X509' and 'X509v3'

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