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

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

Adding information about requested attributes for the decide page interface of the OpenID Provider.

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    PRIKEY_NBITS = 2048
190    MESSAGE_DIGEST_TYPE = "md5"
191    SERVER_RESP_BLK_SIZE = 8192
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    def _createKeys(self, nBitsForKey=PRIKEY_NBITS):
508        """Generate keys and return as PEM encoded string
509        @type nBitsForKey: int
510        @param nBitsForKey: number of bits for private key generation -
511        default is 2048
512        @rtype: string
513        @return: public/private key pair
514        """
515        keys = RSA.gen_key(nBitsForKey, m2.RSA_F4)
516       
517        return keys
518           
519    def _createCertReq(self, CN, keys, messageDigest=MESSAGE_DIGEST_TYPE):
520        """Create a certificate request.
521       
522        @type CN: basestring
523        @param CN: Common Name for certificate - effectively the same as the
524        username for the MyProxy credential
525        @type keys: string/None
526        @param keys: public/private key pair
527        @type messageDigest: basestring
528        @param messageDigest: message digest type - default is MD5
529        @rtype: tuple
530        @return certificate request PEM text and private key PEM text
531        """
532       
533        # Check all required certifcate request DN parameters are set               
534        # Create certificate request
535        req = X509.Request()
536       
537        # Create public key object
538        pubKey = EVP.PKey()
539        pubKey.assign_rsa(keys)
540       
541        # Add the public key to the request
542        req.set_version(0)
543        req.set_pubkey(pubKey)
544       
545        defaultReqDN = self._openSSLConfig.reqDN
546             
547        # Set DN
548        x509Name = X509.X509_Name()
549        x509Name.CN = CN
550       
551        if defaultReqDN:
552            x509Name.OU = defaultReqDN['OU']
553            x509Name.O = defaultReqDN['O']
554                       
555        req.set_subject_name(x509Name)
556       
557        req.sign(pubKey, messageDigest)
558
559        return req.as_der()
560   
561   
562    def _deserializeResponse(self, msg, *fieldNames):
563        """
564        Deserialize a MyProxy server response
565       
566        @param msg: string response message from MyProxy server
567        @*fieldNames: the content of additional fields can be returned by
568        specifying the field name or names as additional arguments e.g. info
569        method passes 'CRED_START_TIME', 'CRED_END_TIME' and 'CRED_OWNER'
570        field names.  The content of fields is returned as an extra element
571        in the tuple response.  This element is itself a dictionary indexed
572        by field name.
573        @return tuple of integer response and errorTxt string (if any)
574        """
575       
576        lines = msg.split('\n')
577       
578        # get response value
579        responselines = filter(lambda x: x.startswith('RESPONSE'), lines)
580        responseline = responselines[0]
581        respCode = int(responseline.split('=')[1])
582       
583        # get error text
584        errorTxt = ""
585        errorlines = filter(lambda x: x.startswith('ERROR'), lines)
586        for e in errorlines:
587            etext = e.split('=', 1)[1]
588            errorTxt += os.linesep + etext
589       
590        if fieldNames:
591            fields = {}
592                       
593            for fieldName in fieldNames:
594                fieldlines = filter(lambda x: x.startswith(fieldName), lines)
595                try:
596                    # Nb. '1' arg to split ensures owner DN is not split up.
597                    field = fieldlines[0].split('=', 1)[1]
598                    fields[fieldName]=field.isdigit() and int(field) or field
599
600                except IndexError:
601                    # Ignore fields that aren't found
602                    pass
603               
604            return respCode, errorTxt, fields
605        else:
606            return respCode, errorTxt
607   
608 
609    def _deserializeCerts(self, inputDat):
610        """Unpack certificates returned from a get delegation call to the
611        server
612       
613        @param inputDat: string containing the proxy cert and private key
614        and signing cert all in DER format
615       
616        @return list containing the equivalent to the input in PEM format"""
617        pemCerts = []       
618        dat = inputDat
619       
620        while dat:   
621            # find start of cert, get length       
622            ind = dat.find('\x30\x82')
623            if ind < 0:
624                break
625               
626            len = 256*ord(dat[ind+2]) + ord(dat[ind+3])
627   
628            # extract der-format cert, and convert to pem
629            derCert = dat[ind:ind+len+4]
630           
631            x509 = X509.load_cert_der_string(derCert)
632            pemCert = x509.as_pem()
633           
634            pemCerts.append(pemCert)
635   
636            # trim cert from data
637            dat = dat[ind + len + 4:]
638           
639        return pemCerts
640   
641   
642    @classmethod
643    def writeProxyFile(cls,proxyCert,proxyPriKey,userX509Cert,filePath=None):
644        """Write out proxy cert to file in the same way as myproxy-logon -
645        proxy cert, private key, user cert.  Nb. output from logon can be
646        passed direct into this method
647       
648        @type proxyCert: string
649        @param proxyCert: proxy certificate
650        @type proxyPriKey: string
651        @param proxyPriKey: private key for proxy
652        @type userX509Cert: string
653        @param userX509Cert: user certificate which issued the proxy
654        @type filePath: string
655        @param filePath: set to override the default filePath"""
656       
657        if filePath is None:
658            filePath = MyProxyClient.defProxyFile
659           
660        if filePath is None:
661            MyProxyClientConfigError("Error setting proxy file path - invalid "
662                                     "platform?")
663       
664        outStr = proxyCert + proxyPriKey + userX509Cert       
665        open(MyProxyClient.defProxyFile, 'w').write(outStr)
666        try:
667            # Make sure permissions are set correctly
668            os.chmod(MyProxyClient.defProxyFile, 0600)
669        except Exception, e:
670            # Don't leave the file lying around if couldn't change it's
671            # permissions
672            os.unlink(MyProxyClient.defProxyFile)
673           
674            log.error('Unable to set 0600 permissions for proxy file "%s": %s'% 
675                      (MyProxyClient.defProxyFile, e))
676            raise
677
678    @classmethod
679    def readProxyFile(cls, filePath=None):
680        """Read proxy cert file following the format used by myproxy-logon -
681        proxy, cert, private key, user cert.
682       
683        @rtype: tuple
684        @return: tuple containing proxy cert, private key, user cert"""
685        if filePath is None:
686            filePath = MyProxyClient.defProxyFile
687           
688        if filePath is None:
689            MyProxyClientConfigError("Error setting proxy file path - invalid "
690                                     "platform?")
691               
692        proxy = open(MyProxyClient.defProxyFile).read()
693       
694        # Split certs and key into separate tuple items
695        return tuple(['-----BEGIN'+i for i in proxy.split('-----BEGIN')[1:]])
696   
697
698    def info(self,
699             username, 
700             ownerCertFile=None,
701             ownerKeyFile=None,
702             ownerPassphrase=None):
703        """return True/False whether credentials exist on the server for a
704        given username
705       
706        @raise MyProxyClientGetError:
707        @raise MyProxyClientRetrieveError:
708       
709        @type username: string
710        @param username: username selected for credential
711        @type ownerCertFile: string
712        @param ownerCertFile: certificate used for client authentication with
713        the MyProxy server SSL connection.  This ID will be set as the owner
714        of the stored credentials.  Only the owner can later remove
715        credentials with myproxy-destroy or the destroy method.  If not set,
716        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
717        @type ownerKeyFile: string
718        @param ownerKeyFile: corresponding private key file.  See explanation
719        for ownerCertFile
720        @type ownerPassphrase: string
721        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
722        private key is not password protected.
723        """
724        globusLoc = os.environ.get('GLOBUS_LOCATION')
725        if not ownerCertFile:
726            if globusLoc:
727                ownerCertFile = os.path.join(globusLoc, 
728                                            *MyProxyClient._hostCertSubDirPath)
729                ownerKeyFile = os.path.join(globusLoc, 
730                                            *MyProxyClient._hostKeySubDirPath)
731            else:
732                raise MyProxyClientError(
733            "No client authentication cert. and private key file were given")
734
735        # Set-up SSL connection
736        conn = self._initConnection(ownerCertFile=ownerCertFile,
737                                    ownerKeyFile=ownerKeyFile,
738                                    ownerPassphrase=ownerPassphrase)
739       
740        conn.connect((self.hostname, self.port))
741       
742        # send globus compatibility stuff
743        conn.write('0')
744   
745        # send info command - ensure conversion from unicode before writing
746        cmd = MyProxyClient.infoCmd % username
747        conn.write(str(cmd))
748   
749        # process server response
750        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
751         
752        # Pass in the names of fields to return in the dictionary 'field'
753        respCode, errorTxt, field = self._deserializeResponse(dat, 
754                                                             'CRED_START_TIME', 
755                                                             'CRED_END_TIME', 
756                                                             'CRED_OWNER')
757
758        return not bool(respCode), errorTxt, field
759
760
761    def changePassphrase(self,
762                         username, 
763                         passphrase,
764                         newPassphrase,
765                         ownerCertFile=None,
766                         ownerKeyFile=None,
767                         ownerPassphrase=None):
768        """change pass-phrase protecting the credentials for a given username
769       
770        @raise MyProxyClientGetError:
771        @raise MyProxyClientRetrieveError:
772       
773        @param username: username of credential
774        @param passphrase: existing pass-phrase for credential
775        @param newPassphrase: new pass-phrase to replace the existing one.
776        @param ownerCertFile: certificate used for client authentication with
777        the MyProxy server SSL connection.  This ID will be set as the owner
778        of the stored credentials.  Only the owner can later remove
779        credentials with myproxy-destroy or the destroy method.  If not set,
780        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
781        @param ownerKeyFile: corresponding private key file.  See explanation
782        for ownerCertFile
783        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
784        private key is not password protected. 
785        @return none
786        """
787        globusLoc = os.environ.get('GLOBUS_LOCATION')
788        if not ownerCertFile or not ownerKeyFile:
789            if globusLoc:
790                ownerCertFile = os.path.join(globusLoc, 
791                                         *MyProxyClient._hostCertSubDirPath)
792                ownerKeyFile = os.path.join(globusLoc, 
793                                         *MyProxyClient._hostKeySubDirPath)
794            else:
795                raise MyProxyClientError(
796            "No client authentication cert. and private key file were given")
797       
798        # Set-up SSL connection
799        conn = self._initConnection(ownerCertFile=ownerCertFile,
800                                    ownerKeyFile=ownerKeyFile,
801                                    ownerPassphrase=ownerPassphrase)
802
803        conn.connect((self.hostname, self.port))
804       
805        # send globus compatibility stuff
806        conn.write('0')
807   
808        # send command - ensure conversion from unicode before writing
809        cmd = MyProxyClient.changePassphraseCmd % (username, 
810                                                   passphrase,
811                                                   newPassphrase)
812        conn.write(str(cmd))
813   
814        # process server response
815        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
816           
817        respCode, errorTxt = self._deserializeResponse(dat)
818        if respCode:
819            raise MyProxyClientGetError(errorTxt)
820
821
822    def destroy(self,
823                username, 
824                ownerCertFile=None,
825                ownerKeyFile=None,
826                ownerPassphrase=None):
827        """destroy credentials from the server for a given username
828       
829        @raise MyProxyClientGetError:
830        @raise MyProxyClientRetrieveError:
831       
832        @param username: username selected for credential
833        @param ownerCertFile: certificate used for client authentication with
834        the MyProxy server SSL connection.  This ID will be set as the owner
835        of the stored credentials.  Only the owner can later remove
836        credentials with myproxy-destroy or the destroy method.  If not set,
837        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
838        @param ownerKeyFile: corresponding private key file.  See explanation
839        for ownerCertFile
840        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
841        private key is not password protected. 
842        @return none
843        """
844        globusLoc = os.environ.get('GLOBUS_LOCATION')
845        if not ownerCertFile or not ownerKeyFile:
846            if globusLoc:
847                ownerCertFile = os.path.join(globusLoc, 
848                                         *MyProxyClient._hostCertSubDirPath)
849                ownerKeyFile = os.path.join(globusLoc, 
850                                         *MyProxyClient._hostKeySubDirPath)
851            else:
852                raise MyProxyClientError(
853            "No client authentication cert. and private key file were given")
854       
855        # Set-up SSL connection
856        conn = self._initConnection(ownerCertFile=ownerCertFile,
857                                    ownerKeyFile=ownerKeyFile,
858                                    ownerPassphrase=ownerPassphrase)
859
860        conn.connect((self.hostname, self.port))
861       
862        # send globus compatibility stuff
863        conn.write('0')
864   
865        # send destroy command - ensure conversion from unicode before writing
866        cmd = MyProxyClient.destroyCmd % username
867        conn.write(str(cmd))
868   
869        # process server response
870        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
871           
872        respCode, errorTxt = self._deserializeResponse(dat)
873        if respCode:
874            raise MyProxyClientGetError(errorTxt)
875
876
877    def store(self,
878              username,
879              passphrase, 
880              certFile,
881              keyFile,
882              ownerCertFile=None,
883              ownerKeyFile=None,
884              ownerPassphrase=None,
885              lifetime=None,
886              force=True):
887        """Upload credentials to the server
888       
889        @raise MyProxyClientGetError:
890        @raise MyProxyClientRetrieveError:
891       
892        @type username: string
893        @param username: username selected for new credential
894        @type passphrase: string
895        @param passphrase: pass-phrase for new credential.  This is the pass
896        phrase which protects keyfile.
897        @type certFile: string
898        @param certFile: user's X.509 certificate in PEM format
899        @type keyFile: string
900        @param keyFile: equivalent private key file in PEM format
901        @type ownerCertFile: string
902        @param ownerCertFile: certificate used for client authentication with
903        the MyProxy server SSL connection.  This ID will be set as the owner
904        of the stored credentials.  Only the owner can later remove
905        credentials with myproxy-destroy or the destroy method.  If not set,
906        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem or if this
907        is not set, certFile
908        @type ownerKeyFile: string
909        @param ownerKeyFile: corresponding private key file.  See explanation
910        for ownerCertFile
911        @type ownerPassphrase: string
912        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
913        private key is not password protected.  Nb. keyFile is expected to
914        be passphrase protected as this will be the passphrase used for
915        logon / getDelegation.
916        @type Force: bool
917        @param force: set to True to overwrite any existing creds with the
918        same username.  If, force=False a check is made with a call to info.
919        If creds already, exist exit without proceeding
920        """
921       
922        lifetime = lifetime or self.proxyCertMaxLifetime
923
924        # Inputs must be string type otherwise server will reject the request
925        if isinstance(username, unicode):
926            username = str(username)
927           
928        if isinstance(passphrase, unicode):
929            passphrase = str(passphrase)
930       
931        globusLoc = os.environ.get('GLOBUS_LOCATION')
932        if not ownerCertFile or not ownerKeyFile:
933            if globusLoc:
934                ownerCertFile = os.path.join(globusLoc, 
935                                         *MyProxyClient._hostCertSubDirPath)
936                ownerKeyFile = os.path.join(globusLoc, 
937                                         *MyProxyClient._hostKeySubDirPath)
938            else:
939                # Default so that the owner is the same as the ID of the
940                # credentials to be uploaded.
941                ownerCertFile = certFile
942                ownerKeyFile = keyFile
943                ownerPassphrase = passphrase
944               
945        if not force:
946            # Check credentials don't already exist
947            if self.info(username,
948                         ownerCertFile=ownerCertFile,
949                         ownerKeyFile=ownerKeyFile,
950                         ownerPassphrase=ownerPassphrase)[0]:
951                raise MyProxyClientError(
952                        "Credentials already exist for user: %s" % username)
953
954        # Set up SSL connection
955        conn = self._initConnection(ownerCertFile=ownerCertFile,
956                                    ownerKeyFile=ownerKeyFile,
957                                    ownerPassphrase=ownerPassphrase)
958       
959        conn.connect((self.hostname, self.port))
960       
961        # send globus compatibility stuff
962        conn.write('0')
963   
964        # send store command - ensure conversion from unicode before writing
965        cmd = MyProxyClient.storeCmd % (username, lifetime)
966        conn.write(str(cmd))
967   
968        # process server response
969        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
970           
971        respCode, errorTxt = self._deserializeResponse(dat)
972        if respCode:
973            raise MyProxyClientGetError(errorTxt)
974       
975        # Send certificate and private key
976        certTxt = X509.load_cert(certFile).as_pem()
977        keyTxt = open(keyFile).read()
978       
979        conn.send(certTxt + keyTxt)
980   
981   
982        # process server response
983        resp = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
984        respCode, errorTxt = self._deserializeResponse(resp)
985        if respCode:
986            raise MyProxyClientRetrieveError(errorTxt)
987       
988         
989    def logon(self, username, passphrase, lifetime=None, keys=None, 
990              certReq=None, nBitsForKey=PRIKEY_NBITS):
991        """Retrieve a proxy credential from a MyProxy server
992       
993        Exceptions:  MyProxyClientGetError, MyProxyClientRetrieveError
994       
995        @type username: basestring
996        @param username: username of credential
997       
998        @type passphrase: basestring
999        @param passphrase: pass-phrase for private key of credential held on
1000        server
1001       
1002        @type lifetime: int
1003        @param lifetime: lifetime for generated certificate
1004       
1005        @rtype: tuple
1006        @return credentials as strings in PEM format: the
1007        user certificate, it's private key and the issuing certificate.  The
1008        issuing certificate is only set if the user certificate is a proxy
1009        """
1010       
1011        lifetime = lifetime or self.proxyCertLifetime
1012
1013        # Generate certificate request here - any errors will be thrown
1014        # prior to making the connection and so not upsetting the server
1015        #
1016        # - The client will generate a public/private key pair and send a
1017        #   NULL-terminated PKCS#10 certificate request to the server.
1018        if keys is None:
1019            if certReq is not None:
1020                raise MyProxyClientConfigError("'certReq' key must not be set "
1021                                               "without the 'keys' keyword")
1022            keys = self._createKeys(nBitsForKey=nBitsForKey)
1023           
1024        if certReq is None:
1025            certReq = self._createCertReq(username, keys)
1026       
1027        # Set-up SSL connection
1028        conn = self._initConnection()
1029        conn.connect((self.hostname, self.port))
1030       
1031        # send globus compatibility stuff
1032        conn.write('0')
1033   
1034        # send get command - ensure conversion from unicode before writing
1035        cmd = MyProxyClient.getCmd % (username, passphrase, lifetime)
1036        conn.write(str(cmd))
1037   
1038        # process server response
1039        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1040        respCode, errorTxt = self._deserializeResponse(dat)
1041        if respCode:
1042            raise MyProxyClientGetError(errorTxt)
1043       
1044        # Send certificate request
1045        conn.send(certReq)
1046   
1047        # process certificates
1048        # - 1st byte , number of certs
1049        dat = conn.recv(1)
1050        nCerts = ord(dat[0])
1051       
1052        # - n certs
1053        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1054   
1055        # process server response
1056        resp = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1057        respCode, errorTxt = self._deserializeResponse(resp)
1058        if respCode:
1059            raise MyProxyClientRetrieveError(errorTxt)
1060   
1061        # deserialize certs from received cert data
1062        pemCerts = self._deserializeCerts(dat)
1063        if len(pemCerts) != nCerts:
1064            MyProxyClientRetrieveError("%d certs expected, %d received" % 
1065                                                    (nCerts, len(pemCerts)))
1066   
1067        if key is not None:
1068            # Return certs and private key
1069            # - proxy or dynamically issued certificate (MyProxy CA mode)
1070            # - private key
1071            # - rest of cert chain if proxy cert issued
1072            pemKey = key.as_pem(cipher=None)
1073            creds = [pemCerts[0], pemKey]
1074            creds.extend(pemCerts[1:])
1075        else:
1076            # Key generated externally - return certificate chain only
1077            creds = pemCerts
1078
1079       
1080        return tuple(creds)
1081
1082    def getDelegation(self, *arg, **kw):
1083        """Retrieve proxy cert for user - same as logon"""
1084        return self.logon(*arg, **kw)
Note: See TracBrowser for help on using the repository browser.