source: TI12-security/trunk/python/MyProxyClient/myproxy.py @ 4624

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

#941: refactored to use ini file instead of XML for config.

Line 
1"""MyProxy Client interface
2
3Developed for the NERC DataGrid Project: http://ndg.nerc.ac.uk/
4
5Major re-write of an original class.   This updated version implements methods
6with SSL calls with M2Crypto rather use calls to myproxy client executables as
7in the original.  This version is adapted and extended from an original
8program myproxy_logon by Tom Uram <turam@mcs.anl.gov>
9"""
10__author__ = "P J Kershaw"
11__date__ = "02/06/05"
12__copyright__ = "(C) 2008 STFC & NERC"
13__license__ = \
14"""This software may be distributed under the terms of the Q Public
15License, version 1.0 or later.
16
17For myproxy_logon see Access Grid Toolkit Public License (AGTPL):
18
19http://www-unix.mcs.anl.gov/fl/research/accessgrid/about/license.html
20
21This product includes software developed by and/or derived from the Access
22Grid Project (http://www.accessgrid.org) to which the U.S. Government retains
23certain rights."""
24__contact__ = "Philip.Kershaw@stfc.ac.uk"
25__revision__ = '$Id$'
26import logging
27log = logging.getLogger(__name__)
28import sys, os
29import socket
30from M2Crypto import X509, RSA, EVP, m2, BIO, SSL
31
32import base64
33
34# For parsing of properties file
35try: # python 2.5
36    from xml.etree import cElementTree as ElementTree
37except ImportError:
38    # if you've installed it yourself it comes this way
39    import cElementTree as ElementTree
40
41from openssl import OpenSSLConfig, OpenSSLConfigError
42
43from ConfigParser import SafeConfigParser
44
45class CaseSensitiveConfigParser(SafeConfigParser):
46    '''Subclass the SafeConfigParser - to preserve the original string case of
47    config section names
48    '''   
49    def optionxform(self, optionstr):
50        '''Extend SafeConfigParser.optionxform to preserve case of option names
51        '''
52        return optionstr
53
54
55class _HostCheck(SSL.Checker.Checker):
56    """Override SSL.Checker.Checker to allow additional check of MyProxy
57    server identity.  If hostname doesn't match, allow match of host's 
58    Distinguished Name against MYPROXY_SERVER_DN setting"""
59
60    def __init__(self, 
61                 myProxyServerDN=os.environ.get('MYPROXY_SERVER_DN'),
62                 cnHostPfx='host/',
63                 **kw):
64        """Override parent class __init__ to enable setting of myProxyServerDN
65        setting
66       
67        @type myProxyServerDN: string
68        @param myProxyServerDN: Set the expected Distinguished Name of the
69        MyProxy server to avoid errors matching hostnames.  This is useful
70        where the hostname is not fully qualified
71       
72        @type cnHostPfx: string
73        @param cnHostPfx: globus host certificates are
74        generated by default with a 'host/' prefix to the host name.  Set
75        this keyword to '' or None to override and omit the prefix"""
76       
77        SSL.Checker.Checker.__init__(self, **kw)
78       
79        self.myProxyServerDN = myProxyServerDN
80        self.cnHostPfx = cnHostPfx
81       
82       
83    def __call__(self, peerCert, host=None):
84        """Carry out checks on server ID
85        @param peerCert: MyProxy server host certificate as M2Crypto.X509.X509
86        instance
87        @param host: name of host to check
88        """
89       
90        # Globus host certificate has a "host/" prefix - see explanation in
91        # __init__.__doc__
92        cnHostPfx = isinstance(self.cnHostPfx, basestring) \
93                    and self.cnHostPfx or ''
94        host = None or cnHostPfx + self.host
95       
96        try:
97            SSL.Checker.Checker.__call__(self, peerCert, host=host)
98           
99        except SSL.Checker.WrongHost, e:
100            # Try match against DN set from MYPROXY_SERVER_DN / config
101            # file setting
102            peerCertDN = '/'+peerCert.get_subject().as_text().replace(', ','/')
103           
104            # If they match drop the exception and return all OK instead
105            if peerCertDN != self.myProxyServerDN:
106                raise
107           
108        return True
109   
110   
111class MyProxyClientError(Exception):
112    """Base exception class for MyProxyClient exceptions"""
113
114class MyProxyClientConfigError(MyProxyClientError):
115    """Error with configuration"""
116     
117class MyProxyClientGetError(MyProxyClientError):
118    """Exceptions arising from get request to server"""
119   
120class MyProxyClientRetrieveError(MyProxyClientError):
121    """Error recovering a response from MyProxy"""
122           
123       
124class MyProxyClient(object):
125    """MyProxy client interface
126   
127    Based on protocol definitions in:
128   
129    http://grid.ncsa.uiuc.edu/myproxy/protocol/
130   
131    @type getCmd: string
132    @cvar getCmd: get command string
133   
134    @type infoCmd: string
135    @cvar infoCmd: info command string
136   
137    @type destroyCmd: string
138    @cvar destroyCmd: destroy command string
139   
140    @type changePassphraseCmd: string
141    @cvar changePassphraseCmd: command string to change cred pass-phrase
142   
143    @type storeCmd: string
144    @cvar storeCmd: store command string
145   
146    @type _hostCertSubDirPath: string
147    @cvar _hostCertSubDirPath: sub-directory path host certificate (as tuple)
148   
149    @type _hostKeySubDirPath: string
150    @cvar _hostKeySubDirPath: sub-directory path to host key (as tuple)
151   
152    @type propertyDefaults: tuple
153    @cvar propertyDefaults: sets permissable element names for MyProxy config
154    file
155    """
156     
157    getCmd="""VERSION=MYPROXYv2
158COMMAND=0
159USERNAME=%s
160PASSPHRASE=%s
161LIFETIME=%d"""
162 
163    infoCmd="""VERSION=MYPROXYv2
164COMMAND=2
165USERNAME=%s
166PASSPHRASE=PASSPHRASE
167LIFETIME=0"""
168 
169    destroyCmd="""VERSION=MYPROXYv2
170COMMAND=3
171USERNAME=%s
172PASSPHRASE=PASSPHRASE
173LIFETIME=0"""
174
175    changePassphraseCmd="""VERSION=MYPROXYv2
176 COMMAND=4
177 USERNAME=%s
178 PASSPHRASE=%s
179 NEW_PHRASE=%s
180 LIFETIME=0"""
181   
182    storeCmd="""VERSION=MYPROXYv2
183COMMAND=5
184USERNAME=%s
185PASSPHRASE=
186LIFETIME=%d"""
187
188    _hostCertSubDirPath = ('etc', 'hostcert.pem')
189    _hostKeySubDirPath = ('etc', 'hostkey.pem')
190   
191    # valid configuration property keywords
192    propertyDefaults = {
193       'hostname':              'localhost',
194       'port':                  7512,
195       'serverDN':              '',
196       'serverCNPrefix':        'host/',
197       'openSSLConfFilePath':   '',
198       'proxyCertMaxLifetime':  43200,
199       'proxyCertLifetime':     43200,
200       'caCertFilePathList':    []
201    }
202
203    # Work out default location of proxy file if it exists.  This is set if a
204    # call has been made previously to logon / get-delegation
205    defProxyFile = sys.platform == 'win32' and 'proxy' or \
206    sys.platform in ('linux2', 'darwin') and '/tmp/x509up_u%s'%(os.getuid()) \
207    or None
208
209    @classmethod
210    def writeProxyFile(cls,proxyCert,proxyPriKey,userX509Cert,filePath=None):
211        """Write out proxy cert to file in the same way as myproxy-logon -
212        proxy cert, private key, user cert.  Nb. output from logon can be
213        passed direct into this method
214       
215        @type proxyCert: string
216        @param proxyCert: proxy certificate
217        @type proxyPriKey: string
218        @param proxyPriKey: private key for proxy
219        @type userX509Cert: string
220        @param userX509Cert: user certificate which issued the proxy
221        @type filePath: string
222        @param filePath: set to override the default filePath"""
223       
224        if filePath is None:
225            filePath = MyProxyClient.defProxyFile
226           
227        if filePath is None:
228            MyProxyClientConfigError("Error setting proxy file path - invalid "
229                                     "platform?")
230       
231        outStr = proxyCert + proxyPriKey + userX509Cert       
232        open(MyProxyClient.defProxyFile, 'w').write(outStr)
233        try:
234            # Make sure permissions are set correctly
235            os.chmod(MyProxyClient.defProxyFile, 0600)
236        except Exception, e:
237            # Don't leave the file lying around if couldn't change it's
238            # permissions
239            os.unlink(MyProxyClient.defProxyFile)
240           
241            log.error('Unable to set 0600 permissions for proxy file "%s": %s'% 
242                      (MyProxyClient.defProxyFile, e))
243            raise
244
245    @classmethod
246    def readProxyFile(cls, filePath=None):
247        """Read proxy cert file following the format used by myproxy-logon -
248        proxy, cert, private key, user cert.
249       
250        @rtype: tuple
251        @return: tuple containing proxy cert, private key, user cert"""
252        if filePath is None:
253            filePath = MyProxyClient.defProxyFile
254           
255        if filePath is None:
256            MyProxyClientConfigError("Error setting proxy file path - invalid "
257                                     "platform?")
258               
259        proxy = open(MyProxyClient.defProxyFile).read()
260       
261        # Split certs and key into separate tuple items
262        return tuple(['-----BEGIN'+i for i in proxy.split('-----BEGIN')[1:]])
263       
264       
265    def __init__(self, cfgFilePath=None, **prop):
266        """Make any initial settings for client connections to MyProxy
267       
268        Settings are held in a dictionary which can be set from **prop,
269        a call to setProperties() or by passing settings in an XML file
270        given by cfgFilePath
271       
272        @param cfgFilePath:   set properties via a configuration file
273        @param **prop:         set properties via keywords - see
274        propertyDefaults class variable for a list of these
275        """
276       
277        # Configuration file used to get default subject when generating a
278        # new proxy certificate request
279        self._openSSLConfig = OpenSSLConfig()
280       
281        # Default settings.  Nb. '_' - override property methods in order to
282        # set defaults
283        for opt, val in MyProxyClient.propertyDefaults.items():
284            setattr(self, '_'+opt, val)
285       
286        # Server host name - take from environment variable if available
287        if 'MYPROXY_SERVER' in os.environ:
288            self.hostname = os.environ['MYPROXY_SERVER']
289           
290        # ... and port number
291        if 'MYPROXY_SERVER_PORT' in os.environ:
292            self.port = int(os.environ['MYPROXY_SERVER_PORT'])
293
294        # keyword settings
295        for opt, val in prop.items():
296            setattr(self, opt, val)
297       
298        # If properties file is set any parameters settings in file will
299        # override those set by input keyword
300        if cfgFilePath is not None:
301            self.parseConfig(cfg=cfgFilePath)
302
303
304    def parseConfig(self, cfg, section='DEFAULT'):
305        '''Extract parameters from _cfg config object'''
306       
307        if isinstance(cfg, basestring):
308            cfgFilePath = os.path.expandvars(cfg)
309            self._cfg = CaseSensitiveConfigParser()
310            self._cfg.read(cfgFilePath)
311        else:
312            cfgFilePath = None
313            self._cfg = cfg
314       
315        for key, val in self._cfg.items(section):
316            setattr(self, key, val)
317       
318    # Get/Set Property methods
319    def _getHostname(self):
320        return self._hostname
321   
322    def _setHostname(self, val):
323        if not isinstance(val, basestring):
324            raise AttributeError("Expecting string type for hostname "
325                                 "attribute")
326        self._hostname = val
327       
328    hostname = property(fget=_getHostname,
329                        fset=_setHostname,
330                        doc="hostname of MyProxy server")
331   
332    def _getPort(self):
333        return self._port
334   
335    def _setPort(self, val):
336        if isinstance(val, basestring):
337            self._port = int(val)
338        elif isinstance(val, int):
339            self._port = val
340        else:
341            raise AttributeError("Expecting int type for port attribute")
342   
343    port = property(fget=_getPort,
344                    fset=_setPort,
345                    doc="Port number for MyProxy server")
346   
347    def _getServerDN(self):
348        return self._serverDN
349   
350    def _setServerDN(self, val):
351        if not isinstance(val, basestring):
352            raise AttributeError("Expecting string type for serverDN "
353                                 "attribute")
354        self._serverDN = val
355   
356    serverDN = property(fget=_getServerDN,
357                        fset=_setServerDN,
358                        doc="Distinguished Name for MyProxy Server "
359                            "Certificate")
360   
361    def _getServerCNPrefix(self):
362        return self._serverCNPrefix
363   
364    def _setServerCNPrefix(self, val):
365        if not isinstance(val, basestring):
366            raise AttributeError("Expecting string type for serverCNPrefix "
367                                 "attribute")
368        self._serverCNPrefix = val
369   
370    serverCNPrefix = property(fget=_getServerCNPrefix,
371                              fset=_setServerCNPrefix,
372                              doc="Prefix if any for Server Certificate DN "
373                                  "Common Name e.g. 'host/'")
374   
375    def _getOpenSSLConfFilePath(self):
376        return self._openSSLConfFilePath
377   
378    def _setOpenSSLConfFilePath(self, val):
379        if not isinstance(val, basestring):
380            raise AttributeError("Expecting string type for "
381                                 "openSSLConfFilePath attribute")
382        self._openSSLConfFilePath = os.path.expandvars(val)
383        self._openSSLConfig.filePath = self._openSSLConfFilePath
384        self._openSSLConfig.read() 
385   
386    openSSLConfFilePath = property(fget=_getOpenSSLConfFilePath,
387                                   fset=_setOpenSSLConfFilePath,
388                                   doc="file path for OpenSSL config file")
389   
390    def _getProxyCertMaxLifetime(self):
391        return self._proxyCertMaxLifetime
392   
393    def _setProxyCertMaxLifetime(self, val):
394        if isinstance(val, basestring):
395            self._proxyCertMaxLifetime = int(val)
396           
397        elif isinstance(val, int):
398            self._proxyCertMaxLifetime = val
399        else:
400            raise AttributeError("Expecting int type for proxyCertMaxLifetime "
401                                 "attribute")
402   
403    proxyCertMaxLifetime = property(fget=_getProxyCertMaxLifetime,
404                                    fset=_setProxyCertMaxLifetime,
405                                    doc="Default max. lifetime allowed for "
406                                        "Proxy Certificate retrieved - used "
407                                        "by store method")
408   
409    def _getProxyCertLifetime(self):
410        return self._proxyCertLifetime
411   
412    def _setProxyCertLifetime(self, val):
413        if isinstance(val, basestring):
414            self._proxyCertLifetime = int(val)
415        elif isinstance(val, int):
416            self._proxyCertLifetime = val
417        else:
418            raise AttributeError("Expecting int type for proxyCertLifetime "
419                                 "attribute")
420   
421    proxyCertLifetime = property(fget=_getProxyCertLifetime,
422                                 fset=_setProxyCertLifetime,
423                                 doc="Default proxy cert. lifetime used in "
424                                     "logon request")
425   
426    def _getCACertFilePath(self):
427        return self._caCertFilePath
428   
429    def _setCACertFilePath(self, val):
430        '''@type val: basestring
431        @param val: file path for CA certificate to be used to verify
432        MyProxy server certificate'''
433       
434        if not isinstance(val, basestring):
435            raise AttributeError("Expecting string type for caCertFilePath "
436                                 "attribute")
437           
438        self._caCertFilePath = os.path.expandvars(val)
439       
440    caCertFilePath = property(fget=_getCACertFilePath,
441                              fset=_setCACertFilePath,
442                              doc="CA certificate file path - MyProxy server "
443                                  "certificate must validate against it and/"
444                                  "or any present in caCertDir")
445
446    def _getCACertDir(self):
447        return self._caCertDir
448
449    def _setCACertDir(self, val):
450        '''Specify a directory containing PEM encoded CA certs. used for
451        validation of MyProxy server certificate.
452       
453        @type val: basestring
454        @param val: directory path'''
455       
456        if not isinstance(val, basestring):
457            raise AttributeError("Expecting string type for caCertDir "
458                                 "attribute")
459           
460        self._caCertDir = os.path.expandvars(val)
461       
462    caCertFileDir = property(fget=_getCACertDir,
463                             fset=_setCACertDir,
464                             doc="directory containing PEM encoded CA "
465                                 "certs. used along with caCertFilePath "
466                                 "setting to validate MyProxy server "
467                                 "certificate")
468
469
470    def _getOpenSSLConfig(self):
471        "Get OpenSSLConfig object property method"
472        return self._openSSLConfig
473   
474    openSSLConfig = property(fget=_getOpenSSLConfig,
475                             doc="OpenSSLConfig object")
476
477         
478    def _initConnection(self, 
479                        ownerCertFile=None, 
480                        ownerKeyFile=None,
481                        ownerPassphrase=None):
482        """Initialise connection setting up SSL context and client and
483        server side identity checks
484       
485        @type ownerCertFile: basestring
486        @param ownerCertFile: client certificate and owner of credential
487        to be acted on.  Can be a proxy cert + proxy's signing cert.  Cert
488        and private key are not necessary for getDelegation / logon calls
489        @type ownerKeyFile: basestring
490        @param ownerKeyFile: client private key file
491        @type ownerPassphrase: basestring
492        @param ownerPassphrase: pass-phrase protecting private key if set -
493        not needed in the case of a proxy private key
494        """
495
496        # Must be version 3 for MyProxy
497        context = SSL.Context(protocol='sslv3')
498
499        if self.caCertFilePath or self.caCertDir:
500            context.load_verify_locations(cafile=self.caCertFilePath,
501                                          capath=self.caCertDir)
502                           
503            # Stop if peer's certificate can't be verified
504            context.set_allow_unknown_ca(False)
505        else:
506            context.set_allow_unknown_ca(True)
507           
508        if ownerCertFile:
509            try:
510                context.load_cert_chain(ownerCertFile,
511                                    keyfile=ownerKeyFile,
512                                    callback=lambda *ar, **kw: ownerPassphrase)
513            except Exception, e:
514                raise MyProxyClientError("Error loading CA cert., cert. and "
515                                         "key for SSL connection: %s" % e) 
516           
517            # Verify peer's certificate
518            context.set_verify(SSL.verify_peer, 1) 
519       
520           
521        # Disable for compatibility with myproxy server (er, globus)
522        # globus doesn't handle this case, apparently, and instead
523        # chokes in proxy delegation code
524        context.set_options(m2.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS)
525       
526        # connect to myproxy server
527        conn = SSL.Connection(context, sock=socket.socket())
528       
529        # Check server host identity - if host doesn't match use explicit
530        # 'serverDN'
531        # host/<hostname> one
532        hostCheck = _HostCheck(host=self.hostname,
533                               myProxyServerDN=self.serverDN,
534                               cnHostPfx=self.serverCNPrefix)
535        conn.set_post_connection_check_callback(hostCheck)
536       
537        return conn
538   
539           
540    def _createCertReq(self, CN, nBitsForKey=1024, messageDigest="md5"):
541        """
542        Create a certificate request.
543       
544        @type CN: basestring
545        @param CN: Common Name for certificate - effectively the same as the
546        username for the MyProxy credential
547        @type nBitsForKey: int
548        @param nBitsForKey: number of bits for private key generation -
549        default is 1024
550        @type messageDigest: basestring
551        @param messageDigest: message digest type - default is MD5
552        @rtype: tuple
553        @return certificate request PEM text and private key PEM text
554        """
555       
556        # Check all required certifcate request DN parameters are set               
557        # Create certificate request
558        req = X509.Request()
559   
560        # Generate keys
561        key = RSA.gen_key(nBitsForKey, m2.RSA_F4)
562   
563        # Create public key object
564        pubKey = EVP.PKey()
565        pubKey.assign_rsa(key)
566       
567        # Add the public key to the request
568        req.set_version(0)
569        req.set_pubkey(pubKey)
570       
571        defaultReqDN = self._openSSLConfig.reqDN
572             
573        # Set DN
574        x509Name = X509.X509_Name()
575        x509Name.CN = CN
576       
577        if defaultReqDN:
578            x509Name.OU = defaultReqDN['OU']
579            x509Name.O = defaultReqDN['O']
580                       
581        req.set_subject_name(x509Name)
582       
583        req.sign(pubKey, messageDigest)
584
585        return (req.as_der(), key.as_pem(cipher=None))
586   
587   
588    def _deserializeResponse(self, msg, *fieldNames):
589        """
590        Deserialize a MyProxy server response
591       
592        @param msg: string response message from MyProxy server
593        @*fieldNames: the content of additional fields can be returned by
594        specifying the field name or names as additional arguments e.g. info
595        method passes 'CRED_START_TIME', 'CRED_END_TIME' and 'CRED_OWNER'
596        field names.  The content of fields is returned as an extra element
597        in the tuple response.  This element is itself a dictionary indexed
598        by field name.
599        @return tuple of integer response and errorTxt string (if any)
600        """
601       
602        lines = msg.split('\n')
603       
604        # get response value
605        responselines = filter(lambda x: x.startswith('RESPONSE'), lines)
606        responseline = responselines[0]
607        respCode = int(responseline.split('=')[1])
608       
609        # get error text
610        errorTxt = ""
611        errorlines = filter(lambda x: x.startswith('ERROR'), lines)
612        for e in errorlines:
613            etext = e.split('=', 1)[1]
614            errorTxt += os.linesep + etext
615       
616        if fieldNames:
617            fields = {}
618                       
619            for fieldName in fieldNames:
620                fieldlines = filter(lambda x: x.startswith(fieldName), lines)
621                try:
622                    # Nb. '1' arg to split ensures owner DN is not split up.
623                    field = fieldlines[0].split('=', 1)[1]
624                    fields[fieldName]=field.isdigit() and int(field) or field
625
626                except IndexError:
627                    # Ignore fields that aren't found
628                    pass
629               
630            return respCode, errorTxt, fields
631        else:
632            return respCode, errorTxt
633   
634 
635    def _deserializeCerts(self, inputDat):
636        """Unpack certificates returned from a get delegation call to the
637        server
638       
639        @param inputDat: string containing the proxy cert and private key
640        and signing cert all in DER format
641       
642        @return list containing the equivalent to the input in PEM format"""
643        pemCerts = []       
644        dat = inputDat
645       
646        while dat:   
647            # find start of cert, get length       
648            ind = dat.find('\x30\x82')
649            if ind < 0:
650                break
651               
652            len = 256*ord(dat[ind+2]) + ord(dat[ind+3])
653   
654            # extract der-format cert, and convert to pem
655            derCert = dat[ind:ind+len+4]
656           
657            x509 = X509.load_cert_der_string(derCert)
658            pemCert = x509.as_pem()
659           
660            pemCerts.append(pemCert)
661   
662            # trim cert from data
663            dat = dat[ind + len + 4:]
664           
665        return pemCerts
666
667
668    def info(self,
669             username, 
670             ownerCertFile=None,
671             ownerKeyFile=None,
672             ownerPassphrase=None):
673        """return True/False whether credentials exist on the server for a
674        given username
675       
676        @raise MyProxyClientGetError:
677        @raise MyProxyClientRetrieveError:
678       
679        @type username: string
680        @param username: username selected for credential
681        @type ownerCertFile: string
682        @param ownerCertFile: certificate used for client authentication with
683        the MyProxy server SSL connection.  This ID will be set as the owner
684        of the stored credentials.  Only the owner can later remove
685        credentials with myproxy-destroy or the destroy method.  If not set,
686        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
687        @type ownerKeyFile: string
688        @param ownerKeyFile: corresponding private key file.  See explanation
689        for ownerCertFile
690        @type ownerPassphrase: string
691        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
692        private key is not password protected.
693        """
694        globusLoc = os.environ.get('GLOBUS_LOCATION')
695        if not ownerCertFile:
696            if globusLoc:
697                ownerCertFile = os.path.join(globusLoc, 
698                                            *MyProxyClient._hostCertSubDirPath)
699                ownerKeyFile = os.path.join(globusLoc, 
700                                            *MyProxyClient._hostKeySubDirPath)
701            else:
702                raise MyProxyClientError(
703            "No client authentication cert. and private key file were given")
704
705        # Set-up SSL connection
706        conn = self._initConnection(ownerCertFile=ownerCertFile,
707                                    ownerKeyFile=ownerKeyFile,
708                                    ownerPassphrase=ownerPassphrase)
709       
710        conn.connect((self.hostname, self.port))
711       
712        # send globus compatibility stuff
713        conn.write('0')
714   
715        # send info command - ensure conversion from unicode before writing
716        cmd = MyProxyClient.infoCmd % username
717        conn.write(str(cmd))
718   
719        # process server response
720        dat = conn.recv(8192)
721         
722        # Pass in the names of fields to return in the dictionary 'field'
723        respCode, errorTxt, field = self._deserializeResponse(dat, 
724                                                             'CRED_START_TIME', 
725                                                             'CRED_END_TIME', 
726                                                             'CRED_OWNER')
727
728        return not bool(respCode), errorTxt, field
729
730
731    def changePassphrase(self,
732                         username, 
733                         passphrase,
734                         newPassphrase,
735                         ownerCertFile=None,
736                         ownerKeyFile=None,
737                         ownerPassphrase=None):
738        """change pass-phrase protecting the credentials for a given username
739       
740        @raise MyProxyClientGetError:
741        @raise MyProxyClientRetrieveError:
742       
743        @param username: username of credential
744        @param passphrase: existing pass-phrase for credential
745        @param newPassphrase: new pass-phrase to replace the existing one.
746        @param ownerCertFile: certificate used for client authentication with
747        the MyProxy server SSL connection.  This ID will be set as the owner
748        of the stored credentials.  Only the owner can later remove
749        credentials with myproxy-destroy or the destroy method.  If not set,
750        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
751        @param ownerKeyFile: corresponding private key file.  See explanation
752        for ownerCertFile
753        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
754        private key is not password protected. 
755        @return none
756        """
757        globusLoc = os.environ.get('GLOBUS_LOCATION')
758        if not ownerCertFile or not ownerKeyFile:
759            if globusLoc:
760                ownerCertFile = os.path.join(globusLoc, 
761                                         *MyProxyClient._hostCertSubDirPath)
762                ownerKeyFile = os.path.join(globusLoc, 
763                                         *MyProxyClient._hostKeySubDirPath)
764            else:
765                raise MyProxyClientError(
766            "No client authentication cert. and private key file were given")
767       
768        # Set-up SSL connection
769        conn = self._initConnection(ownerCertFile=ownerCertFile,
770                                    ownerKeyFile=ownerKeyFile,
771                                    ownerPassphrase=ownerPassphrase)
772
773        conn.connect((self.hostname, self.port))
774       
775        # send globus compatibility stuff
776        conn.write('0')
777   
778        # send command - ensure conversion from unicode before writing
779        cmd = MyProxyClient.changePassphraseCmd % (username, 
780                                                   passphrase,
781                                                   newPassphrase)
782        conn.write(str(cmd))
783   
784        # process server response
785        dat = conn.recv(8192)
786           
787        respCode, errorTxt = self._deserializeResponse(dat)
788        if respCode:
789            raise MyProxyClientGetError(errorTxt)
790
791
792    def destroy(self,
793                username, 
794                ownerCertFile=None,
795                ownerKeyFile=None,
796                ownerPassphrase=None):
797        """destroy credentials from the server for a given username
798       
799        @raise MyProxyClientGetError:
800        @raise MyProxyClientRetrieveError:
801       
802        @param username: username selected for credential
803        @param ownerCertFile: certificate used for client authentication with
804        the MyProxy server SSL connection.  This ID will be set as the owner
805        of the stored credentials.  Only the owner can later remove
806        credentials with myproxy-destroy or the destroy method.  If not set,
807        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
808        @param ownerKeyFile: corresponding private key file.  See explanation
809        for ownerCertFile
810        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
811        private key is not password protected. 
812        @return none
813        """
814        globusLoc = os.environ.get('GLOBUS_LOCATION')
815        if not ownerCertFile or not ownerKeyFile:
816            if globusLoc:
817                ownerCertFile = os.path.join(globusLoc, 
818                                         *MyProxyClient._hostCertSubDirPath)
819                ownerKeyFile = os.path.join(globusLoc, 
820                                         *MyProxyClient._hostKeySubDirPath)
821            else:
822                raise MyProxyClientError(
823            "No client authentication cert. and private key file were given")
824       
825        # Set-up SSL connection
826        conn = self._initConnection(ownerCertFile=ownerCertFile,
827                                    ownerKeyFile=ownerKeyFile,
828                                    ownerPassphrase=ownerPassphrase)
829
830        conn.connect((self.hostname, self.port))
831       
832        # send globus compatibility stuff
833        conn.write('0')
834   
835        # send destroy command - ensure conversion from unicode before writing
836        cmd = MyProxyClient.destroyCmd % username
837        conn.write(str(cmd))
838   
839        # process server response
840        dat = conn.recv(8192)
841           
842        respCode, errorTxt = self._deserializeResponse(dat)
843        if respCode:
844            raise MyProxyClientGetError(errorTxt)
845
846
847    def store(self,
848              username,
849              passphrase, 
850              certFile,
851              keyFile,
852              ownerCertFile=None,
853              ownerKeyFile=None,
854              ownerPassphrase=None,
855              lifetime=None,
856              force=True):
857        """Upload credentials to the server
858       
859        @raise MyProxyClientGetError:
860        @raise MyProxyClientRetrieveError:
861       
862        @type username: string
863        @param username: username selected for new credential
864        @type passphrase: string
865        @param passphrase: pass-phrase for new credential.  This is the pass
866        phrase which protects keyfile.
867        @type certFile: string
868        @param certFile: user's X.509 certificate in PEM format
869        @type keyFile: string
870        @param keyFile: equivalent private key file in PEM format
871        @type ownerCertFile: string
872        @param ownerCertFile: certificate used for client authentication with
873        the MyProxy server SSL connection.  This ID will be set as the owner
874        of the stored credentials.  Only the owner can later remove
875        credentials with myproxy-destroy or the destroy method.  If not set,
876        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem or if this
877        is not set, certFile
878        @type ownerKeyFile: string
879        @param ownerKeyFile: corresponding private key file.  See explanation
880        for ownerCertFile
881        @type ownerPassphrase: string
882        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
883        private key is not password protected.  Nb. keyFile is expected to
884        be passphrase protected as this will be the passphrase used for
885        logon / getDelegation.
886        @type Force: bool
887        @param force: set to True to overwrite any existing creds with the
888        same username.  If, force=False a check is made with a call to info.
889        If creds already, exist exit without proceeding
890        """
891       
892        lifetime = lifetime or self.proxyCertMaxLifetime
893
894        # Inputs must be string type otherwise server will reject the request
895        if isinstance(username, unicode):
896            username = str(username)
897           
898        if isinstance(passphrase, unicode):
899            passphrase = str(passphrase)
900       
901        globusLoc = os.environ.get('GLOBUS_LOCATION')
902        if not ownerCertFile or not ownerKeyFile:
903            if globusLoc:
904                ownerCertFile = os.path.join(globusLoc, 
905                                         *MyProxyClient._hostCertSubDirPath)
906                ownerKeyFile = os.path.join(globusLoc, 
907                                         *MyProxyClient._hostKeySubDirPath)
908            else:
909                # Default so that the owner is the same as the ID of the
910                # credentials to be uploaded.
911                ownerCertFile = certFile
912                ownerKeyFile = keyFile
913                ownerPassphrase = passphrase
914               
915        if not force:
916            # Check credentials don't already exist
917            if self.info(username,
918                         ownerCertFile=ownerCertFile,
919                         ownerKeyFile=ownerKeyFile,
920                         ownerPassphrase=ownerPassphrase)[0]:
921                raise MyProxyClientError(
922                        "Credentials already exist for user: %s" % username)
923
924        # Set up SSL connection
925        conn = self._initConnection(ownerCertFile=ownerCertFile,
926                                    ownerKeyFile=ownerKeyFile,
927                                    ownerPassphrase=ownerPassphrase)
928       
929        conn.connect((self.hostname, self.port))
930       
931        # send globus compatibility stuff
932        conn.write('0')
933   
934        # send store command - ensure conversion from unicode before writing
935        cmd = MyProxyClient.storeCmd % (username, lifetime)
936        conn.write(str(cmd))
937   
938        # process server response
939        dat = conn.recv(8192)
940           
941        respCode, errorTxt = self._deserializeResponse(dat)
942        if respCode:
943            raise MyProxyClientGetError(errorTxt)
944       
945        # Send certificate and private key
946        certTxt = X509.load_cert(certFile).as_pem()
947        keyTxt = open(keyFile).read()
948       
949        conn.send(certTxt + keyTxt)
950   
951   
952        # process server response
953        resp = conn.recv(8192)
954        respCode, errorTxt = self._deserializeResponse(resp)
955        if respCode:
956            raise MyProxyClientRetrieveError(errorTxt)
957       
958         
959    def logon(self, username, passphrase, lifetime=None):
960        """Retrieve a proxy credential from a MyProxy server
961       
962        Exceptions:  MyProxyClientGetError, MyProxyClientRetrieveError
963       
964        @type username: basestring
965        @param username: username of credential
966       
967        @type passphrase: basestring
968        @param passphrase: pass-phrase for private key of credential held on
969        server
970       
971        @type lifetime: int
972        @param lifetime: lifetime for generated certificate
973       
974        @rtype: tuple
975        @return credentials as strings in PEM format: the
976        user certificate, it's private key and the issuing certificate.  The
977        issuing certificate is only set if the user certificate is a proxy
978        """
979       
980        lifetime = lifetime or self.proxyCertLifetime
981
982        # Generate certificate request here - any errors will be thrown
983        # prior to making the connection and so not upsetting the server
984        #
985        # - The client will generate a public/private key pair and send a
986        #   NULL-terminated PKCS#10 certificate request to the server.
987        certReq, priKey = self._createCertReq(username)
988
989        # Set-up SSL connection
990        conn = self._initConnection()
991        conn.connect((self.hostname, self.port))
992       
993        # send globus compatibility stuff
994        conn.write('0')
995   
996        # send get command - ensure conversion from unicode before writing
997        cmd = MyProxyClient.getCmd % (username, passphrase, lifetime)
998        conn.write(str(cmd))
999   
1000        # process server response
1001        dat = conn.recv(8192)
1002        respCode, errorTxt = self._deserializeResponse(dat)
1003        if respCode:
1004            raise MyProxyClientGetError(errorTxt)
1005       
1006        # Send certificate request
1007        conn.send(certReq)
1008   
1009        # process certificates
1010        # - 1st byte , number of certs
1011        dat = conn.recv(1)
1012        nCerts = ord(dat[0])
1013       
1014        # - n certs
1015        dat = conn.recv(8192)
1016   
1017        # process server response
1018        resp = conn.recv(8192)
1019        respCode, errorTxt = self._deserializeResponse(resp)
1020        if respCode:
1021            raise MyProxyClientRetrieveError(errorTxt)
1022   
1023        # deserialize certs from received cert data
1024        pemCerts = self._deserializeCerts(dat)
1025        if len(pemCerts) != nCerts:
1026            MyProxyClientRetrieveError("%d certs expected, %d received" % 
1027                                                    (nCerts, len(pemCerts)))
1028   
1029        # Return certs and private key
1030        # - proxy cert
1031        # - private key
1032        # - rest of cert chain
1033        creds = [pemCerts[0], priKey]
1034        creds.extend(pemCerts[1:])
1035       
1036        return tuple(creds)
1037       
1038
1039    def getDelegation(self, *arg, **kw):
1040        """Retrieve proxy cert for user - same as logon"""
1041        return self.logon(*arg, **kw)
Note: See TracBrowser for help on using the repository browser.