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

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

#941: MyProxyClient egg

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