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

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

Updates to licence info and README for unit tests.

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