source: TI12-security/trunk/python/MyProxyClient/myproxy/utils/openssl.py @ 5048

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/MyProxyClient/myproxy/utils/openssl.py@5048
Revision 5048, 8.3 KB checked in by pjkersha, 11 years ago (diff)

Updates to licence info and README for unit tests.

  • Property svn:keywords set to Id
RevLine 
[2140]1"""OpenSSL utilities module - contains OpenSSLConfig class for
2parsing OpenSSL configuration files
3
4NERC Data Grid Project
5"""
[2909]6__author__ = "P J Kershaw"
7__date__ = "08/02/07"
[4770]8__copyright__ = "(C) 2009 Science and Technology Facilities Council"
[5048]9__license__ = """BSD - See LICENSE file in top-level directory"""
[4404]10__contact__ = "Philip.Kershaw@stfc.ac.uk"
[4646]11__revision__ = '$Id:openssl.py 4643 2008-12-15 14:53:53Z pjkersha $'
[2140]12
[4647]13import re
14import os
[2150]15from ConfigParser import SafeConfigParser
[2827]16from M2Crypto.X509 import X509_Name
[2140]17
[4647]18
[2140]19class OpenSSLConfigError(Exception):
20    """Exceptions related to OpenSSLConfig class"""   
21
22
[2150]23class OpenSSLConfig(SafeConfigParser, object):
[2140]24    """Wrapper to OpenSSL Configuration file to allow extraction of
25    required distinguished name used for making certificate requests
26   
[2150]27    @type _certReqDNParamName: tuple
[2145]28    @cvar _certReqDNParamName: permissable keys for Distinguished Name
[4642]29    (not including CN which gets set separately).  This is used in _setReqDN
[2150]30    to check input
[2140]31   
[2150]32    @type _caDirPat: string
33    @cvar _caDirPat: sub-directory path to CA config directory
[4642]34    @type _gridCASubDir: string
35    @cvar _gridCASubDir: sub-directory of globus user for CA settings"""
[2150]36   
[4642]37    _certReqDNParamName = X509_Name.nid.keys()   
38    _caDirPat = re.compile('\$dir')   
39    _gridCASubDir = os.path.join(".globus", "simpleCA")
[2145]40
41   
[2735]42    def __init__(self, filePath=None, caDir=None):
[2140]43        """Initial OpenSSL configuration optionally setting a file path to
44        read from
45       
[2731]46        @type filePath: string       
[2927]47        @param filePath: path to OpenSSL configuration file
[2140]48       
[2827]49        @type caDir: string
[2927]50        @param caDir: directory for SimpleCA.  This is substituted for $dir
[2827]51        in OpenSSL config file where present.  caDir can be left out in
52        which case the substitution is not done"""
53       
[2150]54        SafeConfigParser.__init__(self)
55       
[4642]56        self._reqDN = None
57        self._setFilePath(filePath)
[2140]58
[2150]59        # Set-up CA directory
[2735]60        self.setCADir(caDir)
[2150]61
[2140]62           
[4642]63    def _setFilePath(self, filePath):
[2140]64        """Set property method
[2731]65        @type filePath: string
[2140]66        @param filePath: path for OpenSSL configuration file"""
67        if filePath is not None:
68            if not isinstance(filePath, basestring):
[4642]69                raise OpenSSLConfigError("Input OpenSSL config file path must "
70                                         "be a string")
[2140]71
72            try:
[2145]73                if not os.access(filePath, os.R_OK):
[4642]74                    raise OpenSSLConfigError("Not found or no read access")
[2140]75                                         
76            except Exception, e:
[4642]77                raise OpenSSLConfigError("OpenSSL config file path is not "
78                                         'valid: "%s": %s' % (filePath, e))
[2145]79                   
[4642]80        self._filePath = filePath
[2145]81                   
[2140]82
[4642]83    def _getFilePath(self):
[2140]84        """Get property method
[2731]85        @rtype: string
86        @return: file path for OpenSSL configuration file"""
[2140]87        return self.__filePath
88
[4642]89    filePath = property(fget=_getFilePath,
90                        fset=_setFilePath,
[2140]91                        doc="file path for configuration file")
[2145]92
[2150]93           
[2735]94    def setCADir(self, caDir):
[2150]95        """Set property method
[2731]96        @type caDir: string
[2150]97        @param caDir: path for OpenSSL configuration file"""
[2735]98        if caDir is None:
99            # Try to set default from 'HOME' env variable
100            homeDir = os.environ.get('HOME')
101            if homeDir:
[4654]102                self._caDir = os.path.join(homeDir, self._gridCASubDir)
[2735]103            else:
[4642]104                self._caDir = None
[2735]105        else:
[2150]106            if not isinstance(caDir, basestring):
[4642]107                raise OpenSSLConfigError("Input OpenSSL CA directory path "
108                                         "must be a string")
[2145]109
[2150]110            try:
111                if not os.access(caDir, os.R_OK):
[4642]112                    raise OpenSSLConfigError("Not found or no read access")
[2150]113                                         
114            except Exception, e:
[4642]115                raise OpenSSLConfigError("OpenSSL CA directory path is not "
116                                         'valid: "%s": %s' % (filePath, e))
[2150]117                   
[4642]118        self._caDir = caDir
[2150]119                   
120
[4642]121    def _getCADir(self):
[2150]122        """Get property method
[2731]123        @rtype caDir: string
[2150]124        @return caDir: directory path for CA configuration files"""
[4642]125        return self._caDir
[2150]126
[4642]127    caDir = property(fget=_getCADir,
[2735]128                     fset=setCADir,
[2150]129                     doc="directory path for CA configuration files")
130
131
[4642]132    def _getReqDN(self):
[2145]133        """Get property method
134        @rtype reqDN: dict
135        @return reqDN: Distinguished Name for certificate request"""
[4642]136        return self._reqDN
[2145]137
[2150]138
[4642]139    def _setReqDN(self, reqDN):
[2145]140        """Set property method
141        @type reqDN: dict
142        @param reqDN: Distinguished Name for certificate request"""
143        if not isinstance(reqDN, dict):
[4642]144            raise AttributeError("Distinguished Name must be dict type")
[2140]145       
[2145]146        invalidKw = [k for k in dict \
[4642]147                     if k not in OpenSSLConfig._certReqDNParamName]
[2145]148        if invalidKw:
[4642]149            raise AttributeError("Invalid certificate request keyword(s): "
150                                 "%s.  Valid keywords are: %s" % 
151                                 (', '.join(invalidKw), 
152                                 ', '.join(OpenSSLConfig._certReqDNParamName)))
[2140]153
[4642]154        self._reqDN = reqDN
[2140]155
156
[4642]157    reqDN = property(fget=_getReqDN,
158                     fset=_setReqDN,
[2145]159                     doc="Distinguished Name for certificate request")
160   
161    def read(self):
[2150]162        """Override base class version to avoid parsing error with the first
163        'RANDFILE = ...' part of the openssl file.  Also, reformat _sections
164        to allow for the style of SSL config files where section headings can
165        have spaces either side of the brackets e.g.
166        [ sectionName ]
167       
168        and comments can occur on the same line as an option e.g.
169        option = blah # This is option blah
170       
171        Reformat _sections to """
[2145]172        try:
[4647]173            file = open(self._filePath)
[2150]174            fileTxt = file.read()
[2145]175        except Exception, e:
[4642]176            raise OpenSSLConfigError('Reading OpenSSL config file "%s": %s' % 
177                                                    (self._filePath, e))
[2150]178
179        idx = re.search('\[\s*\w*\s*\]', fileTxt).span()[0]
180        file.seek(idx)
181        SafeConfigParser.readfp(self, file)
182       
[4647]183        # Filter section names and remove comments from options
[2150]184        for section, val in self._sections.items():
185            newSection = section
186            self._sections[newSection.strip()] = \
187                                    dict([(opt, self._filtOptVal(optVal))
188                                          for opt, optVal in val.items()])
189            del self._sections[section]
190       
191        self._setReqDN()
192
193   
194    def _filtOptVal(self, optVal):
195        """For option value, filter out comments and substitute $dir with
[2731]196        the CA directory location
197       
198        @type optVal: string
199        @param optVal: option value"""
[2735]200        filtVal = optVal.split('#')[0].strip()
[4642]201        if self._caDir:
[2735]202            # Replace $dir with CA directory path
[4642]203            return OpenSSLConfig._caDirPat.sub(self._caDir, filtVal)
[2735]204        else:
205            # Leave $dir in place as no CA directory has been set
206            return filtVal
207       
[2150]208
209    def readfp(self, fp):
210        """Set to not implemented as using a file object could be problematic
211        given read() has to seek ahead to the first actual section to avoid
212        parsing errors"""
[4642]213        raise NotImplementedError("Use read method instead")
[2145]214
215
[2150]216    def _setReqDN(self):
217        """Set Required DN parameters from the configuration file returning
[2731]218        them in a dictionary"""
[2140]219       
220        # Nb. Match over line boundaries
221        try:
[4642]222            self._reqDN = {
[2150]223                'O': self.get('req_distinguished_name', 
224                              '0.organizationName_default'),
225                'OU': self.get('req_distinguished_name', 
226                               '0.organizationalUnitName_default')
227            }
[2140]228        except Exception, e:
[4642]229            raise OpenSSLConfigError('Setting content of Distinguished Name '
230                                     'from file "%s": %s' %(self._filePath,e))
Note: See TracBrowser for help on using the repository browser.