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

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

Incomplete - task 5: MyProxy? Logon HTTPS Interface

  • Updated MyProxyClient with bootstrap capability for get trust roots and logon methods.
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 PyOpenSSL 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) 2010 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
28import base64
29import re
30import traceback
31
32from OpenSSL import crypto, SSL
33
34from myproxy.utils.openssl import OpenSSLConfig
35from myproxy.utils import CaseSensitiveConfigParser
36
37
38class MyProxyServerSSLCertVerification(object):
39    """Check MyProxy server identity.  If hostname doesn't match, allow match of
40    host's Distinguished Name against MYPROXY_SERVER_DN setting"""
41    DN_LUT = {
42        'commonName':               'CN',
43        'organisationalUnitName':   'OU',
44        'organisation':             'O',
45        'countryName':              'C',
46        'emailAddress':             'EMAILADDRESS',
47        'localityName':             'L',
48        'stateOrProvinceName':      'ST',
49        'streetAddress':            'STREET',
50        'domainComponent':          'DC',
51        'userid':                   'UID'
52    }
53    PARSER_RE_STR = '/(%s)=' % '|'.join(DN_LUT.keys() + DN_LUT.values())
54    PARSER_RE = re.compile(PARSER_RE_STR)   
55       
56    SERVER_CN_PREFIX = 'host/'
57
58    __slots__ = ('__hostname', '__cnPrefix', '__certDN')
59   
60    def __init__(self, 
61                 certDN=None,
62                 hostname=None,
63                 cnPrefix=SERVER_CN_PREFIX):
64        """Override parent class __init__ to enable setting of certDN
65        setting
66       
67        @type certDN: string
68        @param certDN: Set the expected Distinguished Name of the
69        MyProxy server to avoid errors matching hostnames.  This is useful
70        where the hostname is not fully qualified
71        """
72        self.__cnPrefix = None
73        self.__certDN = None
74        self.__hostname = None
75       
76        if certDN is not None:
77            self.certDN = certDN
78           
79        if hostname is not None:
80            self.hostname = hostname
81           
82        self.cnPrefix = cnPrefix
83       
84    def __call__(self, connection, peerCert, errorStatus, errorDepth, 
85                 successStatus):
86        """Verify MyProxy server certificate
87       
88        @type connection: OpenSSL.SSL.Connection
89        @param connection: SSL connection object
90        @type peerCert: basestring
91        @param peerCert: MyProxy server host certificate as OpenSSL.crypto.X509
92        instance
93        @type errorStatus: int
94        @param errorStatus: error code to return if verification fails
95        @type errorDepth: int
96        @param errorDepth:
97        @type successStatus: int
98        @param successStatus:
99        @rtype: int
100        @return: status code
101        """
102        if peerCert.has_expired():
103            # Any expired certificate in the chain should result in an error
104            log.error('Certificate %r in peer certificate chain has expired', 
105                      peerCert.get_subject())
106               
107            return errorStatus
108           
109        elif errorDepth == 0:
110            # Only interested in DN of last certificate in the chain - this must
111            # match the expected MyProxy Server DN setting
112            peerCertSubj = peerCert.get_subject()
113            peerCertDN = peerCertSubj.get_components()
114            peerCertDN.sort()
115
116            if self.certDN is None:
117                # Check hostname against peer certificate CN field instead:
118                if self.hostname is None:
119                    log.error('No "hostname" or "certDN" set to check peer '
120                              'certificate against')
121                    return errorStatus
122                   
123                cn = self.cnPrefix + self.hostname
124                if peerCertSubj.commonName == cn:
125                    return successStatus
126                else:
127                    log.error('Peer certificate CN %r doesn\'t match the '
128                              'expected CN %r', peerCertSubj.commonName, cn)
129                    return errorStatus
130            else:
131                if peerCertDN == self.certDN:
132                    return successStatus
133                else:
134                    log.error('Peer certificate DN %r doesn\'t match the '
135                              'expected DN %r', peerCertDN, self.certDN)
136                    return errorStatus
137        else:
138            return successStatus
139             
140    def _getCertDN(self):
141        return self.__certDN
142   
143    def _setCertDN(self, val):
144        if isinstance(val, basestring):
145            # Allow for quoted DN
146            certDN = val.strip('"')
147           
148            dnFields = self.__class__.PARSER_RE.split(certDN)
149            if len(dnFields) < 2:
150                raise TypeError('Error parsing DN string: "%s"' % certDN)
151   
152            self.__certDN = zip(dnFields[1::2], dnFields[2::2])
153            self.__certDN.sort()
154           
155        elif not isinstance(val, list):
156            for i in val:
157                if not len(i) == 2:
158                    raise TypeError('Expecting list of two element DN field, '
159                                    'DN field value pairs for "certDN" '
160                                    'attribute')
161            self.__certDN = val
162        else:
163            raise TypeError('Expecting list or string type for "certDN" '
164                            'attribute')
165       
166    certDN = property(fget=_getCertDN,
167                      fset=_setCertDN,
168                      doc="Distinguished Name for MyProxy Server Certificate")
169       
170    # Get/Set Property methods
171    def _getHostname(self):
172        return self.__hostname
173   
174    def _setHostname(self, val):
175        if not isinstance(val, basestring):
176            raise TypeError("Expecting string type for hostname "
177                                 "attribute")
178        self.__hostname = val
179       
180    hostname = property(fget=_getHostname,
181                        fset=_setHostname,
182                        doc="hostname of MyProxy server")
183   
184    def _getCNPrefix(self):
185        """References SSL Certificate verification object property!"""
186        return self.__cnPrefix
187   
188    def _setCNPrefix(self, val):
189        """Sets SSL Certificate verification object property!"""
190        if not isinstance(val, basestring):
191            raise TypeError("Expecting string type for hostname "
192                                 "attribute")
193        self.__cnPrefix = val
194   
195    cnPrefix = property(fget=_getCNPrefix,
196                        fset=_setCNPrefix,
197                        doc="Prefix for MyProxy Server Certificate "
198                            "Distinguished Name CommonName field; usually set "
199                            "to 'host/' for Globus host certificates")
200                   
201   
202class MyProxyClientError(Exception):
203    """Base exception class for MyProxyClient exceptions"""
204
205
206class MyProxyClientConfigError(MyProxyClientError):
207    """Error with configuration"""
208     
209     
210class MyProxyClientGetError(MyProxyClientError):
211    """Exceptions arising from get request to server"""
212   
213   
214class MyProxyClientRetrieveError(MyProxyClientError):
215    """Error recovering a response from MyProxy"""
216
217
218class MyProxyCredentialsAlreadyExist(MyProxyClientError):
219    """Attempting to upload credentials to the server which already exist.  -
220    See MyProxyClient.store
221    """
222   
223   
224class MyProxyClientGetTrustRootsError(MyProxyClientError):
225    """Error retrieving trust roots"""
226           
227       
228class MyProxyClient(object):
229    """MyProxy client interface
230   
231    Based on protocol definitions in:
232   
233    http://grid.ncsa.uiuc.edu/myproxy/protocol/
234
235    @type MYPROXY_SERVER_ENVVARNAME: string
236    @cvar MYPROXY_SERVER_ENVVARNAME: server environment variable name
237   
238    @type MYPROXY_SERVER_PORT_ENVVARNAME: string
239    @cvar MYPROXY_SERVER_PORT_ENVVARNAME: port environment variable name
240   
241    @type MYPROXY_SERVER_DN_ENVVARNAME: string
242    @cvar MYPROXY_SERVER_DN_ENVVARNAME: server certificate Distinguished Name
243    environment variable name
244   
245    @type GLOBUS_LOCATION_ENVVARNAME: string
246    @param GLOBUS_LOCATION_ENVVARNAME: 'GLOBUS_LOCATION' environment variable
247    name
248   
249    @type GET_CMD: string
250    @cvar GET_CMD: get command string
251   
252    @type INFO_CMD: string
253    @cvar INFO_CMD: info command string
254   
255    @type DESTROY_CMD: string
256    @cvar DESTROY_CMD: destroy command string
257   
258    @type CHANGE_PASSPHRASE_CMD: string
259    @cvar CHANGE_PASSPHRASE_CMD: command string to change cred pass-phrase
260   
261    @type STORE_CMD: string
262    @cvar STORE_CMD: store command string
263   
264    @type GET_TRUST_ROOTS_CMD: string
265    @cvar GET_TRUST_ROOTS_CMD: get trust roots command string
266   
267    @type TRUSTED_CERTS_FIELDNAME: string
268    @param TRUSTED_CERTS_FIELDNAME: field name in get trust roots response for
269    trusted certificate file names
270   
271    @type TRUSTED_CERTS_FILEDATA_FIELDNAME_PREFIX: string
272    @param TRUSTED_CERTS_FILEDATA_FIELDNAME_PREFIX: field name prefix in get
273    trust roots response for trusted certificate file contents
274   
275    @type HOSTCERT_SUBDIRPATH: string
276    @cvar HOSTCERT_SUBDIRPATH: sub-directory path host certificate (as tuple)
277   
278    @type HOSTKEY_SUBDIRPATH: string
279    @cvar HOSTKEY_SUBDIRPATH: sub-directory path to host key (as tuple)
280   
281    @type PRIKEY_NBITS: int
282    @cvar PRIKEY_NBITS: default number of bits for private key generated
283   
284    @type MESSAGE_DIGEST_TYPE: string
285    @cvar MESSAGE_DIGEST_TYPE: message digest type is MD5
286   
287    @type SERVER_RESP_BLK_SIZE: int
288    @cvar SERVER_RESP_BLK_SIZE: block size for retrievals from server
289   
290    @type MAX_RECV_TRIES: int
291    @cvar MAX_RECV_TRIES: maximum number of retrievals of size
292    SERVER_RESP_BLK_SIZE before this client gives up
293   
294    @type DEF_PROXY_FILEPATH: string
295    @cvar DEF_PROXY_FILEPATH: default location for proxy file to be written to
296   
297    @type PROXY_FILE_PERMISSIONS: int
298    @cvar PROXY_FILE_PERMISSIONS: file permissions returned proxy file is
299    created with
300   
301    @type PROPERTY_DEFAULTS: tuple
302    @cvar PROPERTY_DEFAULTS: sets permissable element names for MyProxy config
303    file
304
305    @type ROOT_USERNAME: string
306    @cvar ROOT_USERNAME: root username - used to determine output directory
307    for trust roots
308
309    @type ROOT_TRUSTROOT_DIR: string
310    @param ROOT_TRUSTROOT_DIR: default trust root directory if running as root
311    user
312
313    @type USER_TRUSTROOT_DIR: string
314    @param USER_TRUSTROOT_DIR: default trust root directory for users other
315    than root
316   
317    @type X509_CERT_DIR_ENVVARNAME: string
318    @param X509_CERT_DIR_ENVVARNAME: environment variable name 'X509_CERT_DIR',
319    which if set points to the location of the trust roots
320    """
321    MYPROXY_SERVER_ENVVARNAME = 'MYPROXY_SERVER'
322    MYPROXY_SERVER_PORT_ENVVARNAME = 'MYPROXY_SERVER_PORT'
323    MYPROXY_SERVER_DN_ENVVARNAME = 'MYPROXY_SERVER_DN'
324   
325    GLOBUS_LOCATION_ENVVARNAME = 'GLOBUS_LOCATION'
326   
327    GET_CMD="""VERSION=MYPROXYv2
328COMMAND=0
329USERNAME=%s
330PASSPHRASE=%s
331LIFETIME=%d"""
332
333    PUT_CMD="""VERSION=MYPROXYv2
334COMMAND=1
335USERNAME=%s
336PASSPHRASE=<pass phrase>
337LIFETIME=%d"""
338     
339    INFO_CMD="""VERSION=MYPROXYv2
340COMMAND=2
341USERNAME=%s
342PASSPHRASE=PASSPHRASE
343LIFETIME=0"""
344 
345    DESTROY_CMD="""VERSION=MYPROXYv2
346COMMAND=3
347USERNAME=%s
348PASSPHRASE=PASSPHRASE
349LIFETIME=0"""
350
351    CHANGE_PASSPHRASE_CMD="""VERSION=MYPROXYv2
352 COMMAND=4
353 USERNAME=%s
354 PASSPHRASE=%s
355 NEW_PHRASE=%s
356 LIFETIME=0"""
357   
358    STORE_CMD="""VERSION=MYPROXYv2
359COMMAND=5
360USERNAME=%s
361PASSPHRASE=
362LIFETIME=%d"""
363
364    GET_TRUST_ROOTS_CMD="""VERSION=MYPROXYv2
365COMMAND=7
366USERNAME=%s
367PASSPHRASE=%s
368LIFETIME=0
369TRUSTED_CERTS=1"""
370
371    TRUSTED_CERTS_FIELDNAME = 'TRUSTED_CERTS'
372    TRUSTED_CERTS_FILEDATA_FIELDNAME_PREFIX = 'FILEDATA_'
373   
374    HOSTCERT_SUBDIRPATH = ('etc', 'hostcert.pem')
375    HOSTKEY_SUBDIRPATH = ('etc', 'hostkey.pem')
376   
377    PROXY_FILE_PERMISSIONS = 0600
378   
379    # Work out default location of proxy file if it exists.  This is set if a
380    # call has been made previously to logon / get-delegation
381    DEF_PROXY_FILEPATH = sys.platform == ('win32' and 'proxy' or 
382                                    sys.platform in ('linux2', 'darwin') and 
383                                    '/tmp/x509up_u%s' % (os.getuid()) 
384                                    or None) 
385   
386    PRIKEY_NBITS = 4096
387    MESSAGE_DIGEST_TYPE = "md5"
388    SERVER_RESP_BLK_SIZE = 8192
389    MAX_RECV_TRIES = 1024
390   
391    # valid configuration property keywords
392    PROPERTY_DEFAULTS = {
393       'hostname':              'localhost',
394       'port':                  7512,
395       'cnPrefix':          MyProxyServerSSLCertVerification.SERVER_CN_PREFIX,
396       'serverDN':              None,
397       'openSSLConfFilePath':   '',
398       'proxyCertMaxLifetime':  43200,
399       'proxyCertLifetime':     43200,
400       'caCertDir':             None
401    }
402
403    ROOT_USERNAME = 'root'
404    ROOT_TRUSTROOT_DIR = '/etc/grid-security/certificates'
405    USER_TRUSTROOT_DIR = '~/.globus/certificates'   
406    X509_CERT_DIR_ENVVARNAME = 'X509_CERT_DIR'
407   
408    # Restrict attributes to the above properties, their equivalent
409    # protected values + extra OpenSSL config object.
410    __slots__ = tuple(['__' + k for k in PROPERTY_DEFAULTS.keys()])
411    __slots__ += ('__openSSLConfig', '__cfg', '__serverSSLCertVerify')
412
413    def __init__(self, cfgFilePath=None, **prop):
414        """Make any initial settings for client connections to MyProxy
415       
416        Settings are held in a dictionary which can be set from **prop,
417        a call to setProperties() or by passing settings in an XML file
418        given by cfgFilePath
419       
420        @param cfgFilePath: set properties via a configuration file
421        @type cfgFilePath: basestring
422        @param **prop: set properties via keywords - see
423        PROPERTY_DEFAULTS class variable for a list of these
424        @type **prop: dict
425        """       
426        self.__serverSSLCertVerify = MyProxyServerSSLCertVerification()
427        self.__hostname = None
428        self.__port = None
429        self.__serverDN = None
430        self.__openSSLConfFilePath = None
431        self.__proxyCertMaxLifetime = MyProxyClient.PROPERTY_DEFAULTS[
432                                                        'proxyCertMaxLifetime']
433        self.__proxyCertLifetime = MyProxyClient.PROPERTY_DEFAULTS[
434                                                        'proxyCertLifetime']
435        self.__caCertDir = None
436       
437        self.__cfg = None
438
439        # Configuration file used to get default subject when generating a
440        # new proxy certificate request
441        self.__openSSLConfig = OpenSSLConfig()
442
443        # Server host name - take from environment variable if available
444        self.hostname = os.environ.get(MyProxyClient.MYPROXY_SERVER_ENVVARNAME,
445                                    MyProxyClient.PROPERTY_DEFAULTS['hostname'])
446           
447        # ... and port number
448        self.port = int(os.environ.get(
449                                MyProxyClient.MYPROXY_SERVER_PORT_ENVVARNAME, 
450                                MyProxyClient.PROPERTY_DEFAULTS['port']))
451
452        # Server Distinguished Name
453        serverDN = os.environ.get(MyProxyClient.MYPROXY_SERVER_DN_ENVVARNAME,
454                                  MyProxyClient.PROPERTY_DEFAULTS['serverDN'])
455        if serverDN is not None:
456            self.serverDN = serverDN
457       
458        # Set trust root - the directory containing the CA certificates for
459        # verifying the MyProxy server's SSL certificate
460        self.setDefaultCACertDir()
461       
462        # Any keyword settings override the defaults above
463        for opt, val in prop.items():
464            setattr(self, opt, val)
465       
466        # If properties file is set any parameters settings in file will
467        # override those set by input keyword or the defaults
468        if cfgFilePath is not None:
469            self.parseConfig(cfg=cfgFilePath)
470           
471    def setDefaultCACertDir(self):
472        '''Make default trust root setting - the directory containing the CA
473        certificates for verifying the MyProxy server's SSL certificate.
474       
475        The setting is made by using standard Globus defined locations and
476        environment variable settings
477        '''
478       
479        # Check for X509_CERT_DIR environment variable
480        x509CertDir = os.environ.get(MyProxyClient.X509_CERT_DIR_ENVVARNAME)
481        if x509CertDir is not None:
482            self.caCertDir = x509CertDir
483           
484        # Check for running as root user
485        elif os.environ.get(MyProxyClient.ROOT_USERNAME) is not None:
486            self.caCertDir = MyProxyClient.ROOT_TRUSTROOT_DIR
487           
488        # Default to non-root standard location
489        else:
490            self.caCertDir = os.path.expanduser(
491                                            MyProxyClient.USER_TRUSTROOT_DIR)
492
493    def _getServerSSLCertVerify(self):
494        return self.__serverSSLCertVerify
495
496    def _setServerSSLCertVerify(self, value):
497        if not isinstance(value, MyProxyServerSSLCertVerification):
498            raise TypeError('Expecting %r derived type for '
499                            '"serverSSLCertVerify" attribute; got %r' %
500                            MyProxyServerSSLCertVerification,
501                            value)
502        self.__serverSSLCertVerify = value
503
504    serverSSLCertVerify = property(_getServerSSLCertVerify, 
505                                   _setServerSSLCertVerify, 
506                                   doc="Class with a __call__ method which is "
507                                       "passed to the SSL context to verify "
508                                       "the peer (MyProxy server) certificate "
509                                       "in the SSL handshake between this "
510                                       "client and the MyProxy server")
511
512    def parseConfig(self, cfg, section='DEFAULT'):
513        '''Extract parameters from _cfg config object'''
514       
515        if isinstance(cfg, basestring):
516            cfgFilePath = os.path.expandvars(cfg)
517            self.__cfg = CaseSensitiveConfigParser()
518            self.__cfg.read(cfgFilePath)
519        else:
520            cfgFilePath = None
521            self.__cfg = cfg
522       
523        for key, val in self.__cfg.items(section):
524            setattr(self, key, val)
525       
526    # Get/Set Property methods
527    def _getHostname(self):
528        return self.__hostname
529   
530    def _setHostname(self, val):
531        """Also sets SSL Certificate verification object property!"""
532        if not isinstance(val, basestring):
533            raise TypeError("Expecting string type for hostname "
534                                 "attribute")
535        self.__hostname = val
536        self.__serverSSLCertVerify.hostname = val
537       
538    hostname = property(fget=_getHostname,
539                        fset=_setHostname,
540                        doc="hostname of MyProxy server")
541   
542    def _getPort(self):
543        return self.__port
544   
545    def _setPort(self, val):
546        if isinstance(val, basestring):
547            self.__port = int(val)
548        elif isinstance(val, int):
549            self.__port = val
550        else:
551            raise TypeError("Expecting int type for port attribute")
552   
553    port = property(fget=_getPort,
554                    fset=_setPort,
555                    doc="Port number for MyProxy server")
556   
557    def _getServerDN(self):
558        return self.__serverDN
559   
560    def _setServerDN(self, val):
561        """Also sets SSL Certificate verification object property!"""
562        if not isinstance(val, basestring):
563            raise TypeError("Expecting string type for serverDN attribute")
564       
565        self.__serverDN = val
566        self.__serverSSLCertVerify.certDN = val
567   
568    serverDN = property(fget=_getServerDN,
569                        fset=_setServerDN,
570                        doc="Distinguished Name for MyProxy Server "
571                            "Certificate")
572   
573    def _getServerCNPrefix(self):
574        """References SSL Certificate verification object property!"""
575        return self.__serverSSLCertVerify.cnPrefix
576   
577    def _setServerCNPrefix(self, val):
578        """Sets SSL Certificate verification object property!"""
579        self.__serverSSLCertVerify.cnPrefix = val
580   
581    serverCNPrefix = property(fget=_getServerCNPrefix,
582                              fset=_setServerCNPrefix,
583                              doc="Prefix for MyProxy Server Certificate "
584                                  "Distinguished Name CoomonName field; "
585                                  "usually set to host/ for Globus host "
586                                  "certificates")
587       
588    def _getOpenSSLConfFilePath(self):
589        return self.__openSSLConfFilePath
590   
591    def _setOpenSSLConfFilePath(self, val):
592        if not isinstance(val, basestring):
593            raise TypeError('Expecting string type for "openSSLConfFilePath" '
594                            'attribute')
595           
596        self.__openSSLConfFilePath = os.path.expandvars(val)
597        self.__openSSLConfig.filePath = self.__openSSLConfFilePath
598        self.__openSSLConfig.read() 
599   
600    openSSLConfFilePath = property(fget=_getOpenSSLConfFilePath,
601                                   fset=_setOpenSSLConfFilePath,
602                                   doc="file path for OpenSSL config file")
603   
604    def _getProxyCertMaxLifetime(self):
605        return self.__proxyCertMaxLifetime
606   
607    def _setProxyCertMaxLifetime(self, val):
608        if isinstance(val, basestring):
609            self.__proxyCertMaxLifetime = int(val)
610           
611        elif isinstance(val, int):
612            self.__proxyCertMaxLifetime = val
613        else:
614            raise TypeError("Expecting int type for proxyCertMaxLifetime "
615                            "attribute")
616   
617    proxyCertMaxLifetime = property(fget=_getProxyCertMaxLifetime,
618                                    fset=_setProxyCertMaxLifetime,
619                                    doc="Default max. lifetime allowed for "
620                                        "Proxy Certificate retrieved - used "
621                                        "by store method")
622   
623    def _getProxyCertLifetime(self):
624        return self.__proxyCertLifetime
625   
626    def _setProxyCertLifetime(self, val):
627        if isinstance(val, basestring):
628            self.__proxyCertLifetime = int(val)
629        elif isinstance(val, int):
630            self.__proxyCertLifetime = val
631        else:
632            raise TypeError("Expecting int type for proxyCertLifetime "
633                            "attribute")
634   
635    proxyCertLifetime = property(fget=_getProxyCertLifetime,
636                                 fset=_setProxyCertLifetime,
637                                 doc="Default proxy cert. lifetime used in "
638                                     "logon request")
639
640    def _getCACertDir(self):
641        return self.__caCertDir
642
643    def _setCACertDir(self, val):
644        '''Specify a directory containing PEM encoded CA certs. used for
645        validation of MyProxy server certificate.
646       
647        Set to None to make OpenSSL.SSL.Context.load_verify_locations ignore
648        this parameter
649       
650        @type val: basestring/None
651        @param val: directory path'''
652       
653        if isinstance(val, basestring):
654            if val == '':
655                self.__caCertDir = None
656            else:
657                self.__caCertDir = os.path.expandvars(val)
658               
659        elif isinstance(val, None):
660            self.__caCertDir = val   
661        else:
662            raise TypeError("Expecting string or None type for caCertDir "
663                            "attribute")
664       
665    caCertDir = property(fget=_getCACertDir,
666                         fset=_setCACertDir,
667                         doc="trust roots directory containing PEM encoded CA "
668                             "certificates to validate MyProxy server "
669                             "certificate")
670
671
672    def _getOpenSSLConfig(self):
673        "Get OpenSSLConfig object property method"
674        return self.__openSSLConfig
675   
676    openSSLConfig = property(fget=_getOpenSSLConfig, doc="OpenSSLConfig object")
677
678    def _initConnection(self, 
679                        certFile=None, 
680                        keyFile=None,
681                        keyFilePassphrase=None,
682                        verifyPeerWithTrustRoots=True):
683        """Initialise connection setting up SSL context and client and
684        server side identity checks
685       
686        @type sslCertFile: basestring
687        @param sslCertFile: certificate for SSL client authentication.  It may
688        be owner of a credential to be acted on or the concatenated proxy
689        certificate + proxy's signing cert.  SSL client authentication is not
690        necessary for getDelegation / logon calls
691        @type sslKeyFile: basestring
692        @param sslKeyFile: client private key file
693        @type keyFilePassphrase: basestring
694        @param keyFilePassphrase: pass-phrase protecting private key if set
695        @type verifyPeerWithTrustRoots: bool
696        @param verifyPeerWithTrustRoots: verify MyProxy server's SSL certificate
697        against a list of trusted CA certificates in the CA certificate
698        directory set by the "CaCertDir" attribute.  This should always be set
699        to True for MyProxy client calls unless using the 'bootstrap' trust
700        roots mode available with logon and get trust roots calls
701        """
702        # Must be version 3 for MyProxy
703        context = SSL.Context(SSL.SSLv3_METHOD)
704       
705        if verifyPeerWithTrustRoots:
706            context.load_verify_locations(None, self.caCertDir)
707            verifyMode = SSL.VERIFY_PEER|SSL.VERIFY_FAIL_IF_NO_PEER_CERT
708        else:
709            log.warning("SSL Context verify mode set to SSL.VERIFY_NONE")
710            verifyMode = SSL.VERIFY_NONE
711           
712        # Verify peer's (MyProxy server) certificate
713        context.set_verify(verifyMode, self.__serverSSLCertVerify)
714             
715        if certFile:
716            try:
717                context.use_certificate_chain_file(certFile)
718                def pwdCallback(passphraseMaxLen, 
719                                promptPassphraseTwice,
720                                passphrase):
721                    """Private key file password callback function"""
722                    if len(passphrase) > passphraseMaxLen:
723                        log.error('Passphrase length %d is greater than the '
724                                  'maximum length allowed %d',
725                                  len(passphrase), passphraseMaxLen)
726                        return ''
727                       
728                    return passphrase
729                   
730                if keyFilePassphrase is not None:
731                    context.set_passwd_cb(pwdCallback, keyFilePassphrase)
732                   
733                context.use_privatekey_file(keyFile)
734            except Exception:
735                raise MyProxyClientConfigError("Loading certificate "
736                                               "and private key for SSL "
737                                               "connection [also check CA "
738                                               "certificate settings]: %s" % 
739                                               traceback.format_exc())
740           
741        # Disable for compatibility with myproxy server (er, globus)
742        # globus doesn't handle this case, apparently, and instead
743        # chokes in proxy delegation code
744        context.set_options(SSL.OP_DONT_INSERT_EMPTY_FRAGMENTS)
745       
746        # connect to myproxy server
747        conn = SSL.Connection(context, socket.socket())
748       
749        return conn
750       
751    def _createKeyPair(self, nBitsForKey=PRIKEY_NBITS):
752        """Generate key pair and return as PEM encoded string
753        @type nBitsForKey: int
754        @param nBitsForKey: number of bits for private key generation -
755        default is 2048
756        @rtype: OpenSSL.crypto.PKey
757        @return: public/private key pair
758        """
759        keyPair = crypto.PKey()
760        keyPair.generate_key(crypto.TYPE_RSA, nBitsForKey)
761       
762        return keyPair
763           
764    def _createCertReq(self, CN, keyPair, messageDigest=MESSAGE_DIGEST_TYPE):
765        """Create a certificate request.
766       
767        @type CN: basestring
768        @param CN: Common Name for certificate - effectively the same as the
769        username for the MyProxy credential
770        @type keyPair: string/None
771        @param keyPair: public/private key pair
772        @type messageDigest: basestring
773        @param messageDigest: message digest type - default is MD5
774        @rtype: base string
775        @return certificate request PEM text and private key PEM text
776        """
777       
778        # Check all required certifcate request DN parameters are set               
779        # Create certificate request
780        certReq = crypto.X509Req()
781       
782        # Create public key object
783        certReq.set_pubkey(keyPair)
784       
785        # Add the public key to the request
786        certReq.sign(keyPair, messageDigest)
787       
788        derCertReq = crypto.dump_certificate_request(crypto.FILETYPE_ASN1, 
789                                                     certReq)
790
791        return derCertReq
792   
793    def _deserializeResponse(self, msg, *fieldNames):
794        """
795        Deserialize a MyProxy server response
796       
797        @param msg: string response message from MyProxy server
798        @return: tuple of integer response and errorTxt string (if any) and all
799        the fields parsed.  fields is a list of two element, field name, field
800        value tuples.
801        @rtype: tuple
802        """ 
803        lines = msg.split('\n')
804        fields = [tuple(line.split('=', 1)) for line in lines][:-1]
805       
806        # get response value
807        respCode = [int(v) for k, v in fields if k == 'RESPONSE'][0]
808
809        # get error text
810        errorTxt = os.linesep.join([v for k, v in fields if k == 'ERROR'])
811       
812        # Check for custom fields requested by caller to this method
813        if fieldNames:
814            fieldsDict = {}
815            for k, v in fields:
816                names = [name for name in fieldNames if k.startswith(name)]
817                if len(names) == 0:
818                    continue
819                else:
820                    if v.isdigit():
821                        fieldsDict[k] = int(v)
822                    else:
823                        fieldsDict[k] = v
824             
825            # Return additional dict item in tuple 
826            return respCode, errorTxt, fieldsDict
827        else:
828            return respCode, errorTxt   
829 
830    def _deserializeCerts(self, inputDat):
831        """Unpack certificates returned from a get delegation call to the
832        server
833       
834        @param inputDat: string containing the proxy cert and private key
835        and signing cert all in DER format
836       
837        @return list containing the equivalent to the input in PEM format"""
838        pemCerts = []
839        dat = inputDat
840       
841        while dat:
842            # find start of cert, get length       
843            ind = dat.find('\x30\x82')
844            if ind < 0:
845                break
846               
847            len = 256*ord(dat[ind+2]) + ord(dat[ind+3])
848   
849            # extract der-format cert, and convert to pem
850            derCert = dat[ind:ind+len+4]
851            x509Cert = crypto.load_certificate(crypto.FILETYPE_ASN1, derCert)
852            pemCert = crypto.dump_certificate(crypto.FILETYPE_PEM, x509Cert)       
853            pemCerts.append(pemCert)
854   
855            # trim cert from data
856            dat = dat[ind + len + 4:]
857           
858        return pemCerts
859       
860    @classmethod
861    def writeProxyFile(cls, proxyCert, proxyPriKey, userX509Cert, 
862                       filePath=None):
863        """Write out proxy cert to file in the same way as myproxy-logon -
864        proxy cert, private key, user cert.  Nb. output from logon can be
865        passed direct into this method
866       
867        @type proxyCert: string
868        @param proxyCert: proxy certificate
869        @type proxyPriKey: string
870        @param proxyPriKey: private key for proxy
871        @type userX509Cert: string
872        @param userX509Cert: user certificate which issued the proxy
873        @type filePath: string
874        @param filePath: set to override the default filePath"""
875       
876        if filePath is None:
877            filePath = MyProxyClient.DEF_PROXY_FILEPATH
878           
879        if filePath is None:
880            MyProxyClientConfigError("Error setting proxy file path - invalid "
881                                     "platform?")
882       
883        outStr = proxyCert + proxyPriKey + userX509Cert       
884        open(MyProxyClient.DEF_PROXY_FILEPATH, 'w').write(outStr)
885        try:
886            # Make sure permissions are set correctly
887            os.chmod(MyProxyClient.DEF_PROXY_FILEPATH, 
888                     MyProxyClient.PROXY_FILE_PERMISSIONS)
889        except Exception, e:
890            # Don't leave the file lying around if couldn't change it's
891            # permissions
892            os.unlink(MyProxyClient.DEF_PROXY_FILEPATH)
893           
894            log.error('Unable to set %o permissions for proxy file "%s": %s'% 
895                      (MyProxyClient.PROXY_FILE_PERMISSIONS,
896                       MyProxyClient.DEF_PROXY_FILEPATH, e))
897            raise
898
899    @classmethod
900    def readProxyFile(cls, filePath=None):
901        """Read proxy cert file following the format used by myproxy-logon -
902        proxy, cert, private key, user cert.
903       
904        @rtype: tuple
905        @return: tuple containing proxy cert, private key, user cert"""
906        if filePath is None:
907            filePath = MyProxyClient.DEF_PROXY_FILEPATH
908           
909        if filePath is None:
910            MyProxyClientConfigError("Error setting proxy file path - invalid "
911                                     "platform?")
912               
913        proxy = open(MyProxyClient.DEF_PROXY_FILEPATH).read()
914       
915        # Split certs and key into separate tuple items
916        return tuple(['-----BEGIN'+i for i in proxy.split('-----BEGIN')[1:]])
917
918    def put(self,
919            username,
920            passphrase,
921            userCertFile,
922            userKeyFile, 
923            lifetime=None,
924            sslCertFile=None,
925            sslKeyFile=None,
926            sslKeyFilePassphrase=None):
927        """Store a proxy credential on the server
928       
929        Unfortunately this method is not implemented as it requires the creation
930        of a proxy certificate by the client but PyOpenSSL doesn't currently
931        support the required proxyCertInfo X.509 certificate extension
932       
933        @raise NotImplementedError: see above
934       
935        @type username: string
936        @param username: username selected for new credential
937        @type passphrase: string
938        @param passphrase: pass-phrase for new credential.  This will be used
939        by the server to authenticate later requests.  IT must be at least
940        6 characters.  The server may impose other restrictions too depending
941        on its configuration.
942        @type certFile: string
943        @param certFile: user's X.509 proxy certificate in PEM format
944        @type keyFile: string
945        @param keyFile: equivalent private key file in PEM format
946        @type sslCertFile: string
947        @param sslCertFile: certificate used for client authentication with
948        the MyProxy server SSL connection.  If not set,
949        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
950        @type sslKeyFile: string
951        @param sslKeyFile: corresponding private key file.  See explanation
952        for sslCertFile
953        @type sslKeyFilePassphrase: string
954        @param sslKeyFilePassphrase: passphrase for sslKeyFile.  Omit if the
955        private key is not password protected. 
956        @type lifetime: int / None
957        @param lifetime: the maximum lifetime allowed for retrieved proxy
958        credentials in seconds. defaults to proxyCertMaxLifetime attribute value
959        """
960        raise NotImplementedError('put method is not currently implemented.  '
961                                  'It requires the creation of a proxy '
962                                  'certificate by the client but PyOpenSSL '
963                                  'doesn\'t currently support the required '
964                                  'proxyCertInfo X.509 certificate extension.')
965
966    def info(self,
967             username, 
968             sslCertFile=None,
969             sslKeyFile=None,
970             sslKeyFilePassphrase=None):
971        """return True/False whether credentials exist on the server for a
972        given username
973       
974        @raise MyProxyClientGetError:
975        @raise MyProxyClientRetrieveError:
976       
977        @type username: string
978        @param username: username selected for credential
979        @type sslCertFile: string
980        @param sslCertFile: certificate used for client authentication with
981        the MyProxy server SSL connection.  This ID will be set as the owner
982        of the stored credentials.  Only the owner can later remove
983        credentials with myproxy-destroy or the destroy method.  If not set,
984        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
985        @type sslKeyFile: string
986        @param sslKeyFile: corresponding private key file.  See explanation
987        for sslCertFile
988        @type sslKeyFilePassphrase: string
989        @param sslKeyFilePassphrase: passphrase for sslKeyFile.  Omit if the
990        private key is not password protected.
991        """
992        globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME)
993        if not sslCertFile:
994            if globusLoc:
995                sslCertFile = os.path.join(globusLoc, 
996                                            *MyProxyClient.HOSTCERT_SUBDIRPATH)
997                sslKeyFile = os.path.join(globusLoc, 
998                                            *MyProxyClient.HOSTKEY_SUBDIRPATH)
999            else:
1000                raise MyProxyClientError(
1001            "No client authentication cert. and private key file were given")
1002
1003        # Set-up SSL connection
1004        conn = self._initConnection(certFile=sslCertFile,
1005                                    keyFile=sslKeyFile,
1006                                    keyFilePassphrase=sslKeyFilePassphrase)
1007       
1008        conn.connect((self.hostname, self.port))
1009       
1010        # send globus compatibility stuff
1011        conn.write('0')
1012   
1013        # send info command - ensure conversion from unicode before writing
1014        cmd = MyProxyClient.INFO_CMD % username
1015        conn.write(str(cmd))
1016   
1017        # process server response
1018        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1019         
1020        # Pass in the names of fields to return in the dictionary 'field'
1021        respCode, errorTxt, field = self._deserializeResponse(dat, 
1022                                                              'CRED_START_TIME', 
1023                                                              'CRED_END_TIME', 
1024                                                              'CRED_OWNER')
1025
1026        return not bool(respCode), errorTxt, field
1027
1028    def changePassphrase(self,
1029                         username, 
1030                         passphrase,
1031                         newPassphrase,
1032                         sslCertFile=None,
1033                         sslKeyFile=None,
1034                         sslKeyFilePassphrase=None):
1035        """change pass-phrase protecting the credentials for a given username
1036       
1037        @raise MyProxyClientGetError:
1038        @raise MyProxyClientRetrieveError:
1039       
1040        @param username: username of credential
1041        @param passphrase: existing pass-phrase for credential
1042        @param newPassphrase: new pass-phrase to replace the existing one.
1043        @param sslCertFile: certificate used for client authentication with
1044        the MyProxy server SSL connection.  This ID will be set as the owner
1045        of the stored credentials.  Only the owner can later remove
1046        credentials with myproxy-destroy or the destroy method.  If not set,
1047        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
1048        @param sslKeyFile: corresponding private key file.  See explanation
1049        for sslCertFile
1050        @param sslKeyFilePassphrase: passphrase for sslKeyFile.  Omit if the
1051        private key is not password protected. 
1052        @return none
1053        """
1054        globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME)
1055        if not sslCertFile or not sslKeyFile:
1056            if globusLoc:
1057                sslCertFile = os.path.join(globusLoc, 
1058                                           *MyProxyClient.HOSTCERT_SUBDIRPATH)
1059                sslKeyFile = os.path.join(globusLoc, 
1060                                          *MyProxyClient.HOSTKEY_SUBDIRPATH)
1061            else:
1062                raise MyProxyClientError(
1063            "No client authentication cert. and private key file were given")
1064       
1065        # Set-up SSL connection
1066        conn = self._initConnection(certFile=sslCertFile,
1067                                    keyFile=sslKeyFile,
1068                                    keyFilePassphrase=sslKeyFilePassphrase)
1069
1070        conn.connect((self.hostname, self.port))
1071       
1072        # send globus compatibility stuff
1073        conn.write('0')
1074   
1075        # send command - ensure conversion from unicode before writing
1076        cmd = MyProxyClient.CHANGE_PASSPHRASE_CMD % (username, 
1077                                                     passphrase,
1078                                                     newPassphrase)
1079        conn.write(str(cmd))
1080   
1081        # process server response
1082        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1083           
1084        respCode, errorTxt = self._deserializeResponse(dat)
1085        if respCode:
1086            raise MyProxyClientGetError(errorTxt)
1087
1088    def destroy(self,
1089                username, 
1090                sslCertFile=None,
1091                sslKeyFile=None,
1092                sslKeyFilePassphrase=None):
1093        """destroy credentials from the server for a given username
1094       
1095        @raise MyProxyClientGetError:
1096        @raise MyProxyClientRetrieveError:
1097       
1098        @param username: username selected for credential
1099        @param sslCertFile: certificate used for client authentication with
1100        the MyProxy server SSL connection.  This ID will be set as the owner
1101        of the stored credentials.  Only the owner can later remove
1102        credentials with myproxy-destroy or the destroy method.  If not set,
1103        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
1104        @param sslKeyFile: corresponding private key file.  See explanation
1105        for sslCertFile
1106        @param sslKeyFilePassphrase: passphrase for sslKeyFile.  Omit if the
1107        private key is not password protected. 
1108        @return none
1109        """
1110        globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME)
1111        if not sslCertFile or not sslKeyFile:
1112            if globusLoc:
1113                sslCertFile = os.path.join(globusLoc, 
1114                                         *MyProxyClient.HOSTCERT_SUBDIRPATH)
1115                sslKeyFile = os.path.join(globusLoc, 
1116                                         *MyProxyClient.HOSTKEY_SUBDIRPATH)
1117            else:
1118                raise MyProxyClientError(
1119            "No client authentication cert. and private key file were given")
1120       
1121        # Set-up SSL connection
1122        conn = self._initConnection(certFile=sslCertFile,
1123                                    keyFile=sslKeyFile,
1124                                    keyFilePassphrase=sslKeyFilePassphrase)
1125
1126        conn.connect((self.hostname, self.port))
1127       
1128        # send globus compatibility stuff
1129        conn.write('0')
1130   
1131        # send destroy command - ensure conversion from unicode before writing
1132        cmd = MyProxyClient.DESTROY_CMD % username
1133        conn.write(str(cmd))
1134   
1135        # process server response
1136        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1137           
1138        respCode, errorTxt = self._deserializeResponse(dat)
1139        if respCode:
1140            raise MyProxyClientGetError(errorTxt)
1141
1142    def store(self,
1143              username,
1144              passphrase, 
1145              certFile,
1146              keyFile,
1147              sslCertFile=None,
1148              sslKeyFile=None,
1149              sslKeyFilePassphrase=None,
1150              lifetime=None,
1151              force=True):
1152        """Upload credentials to the server
1153       
1154        @raise MyProxyClientGetError:
1155        @raise MyProxyClientRetrieveError:
1156       
1157        @type username: string
1158        @param username: username selected for new credential
1159        @type passphrase: string
1160        @param passphrase: pass-phrase for new credential.  This is the pass
1161        phrase which protects keyfile.
1162        @type certFile: string
1163        @param certFile: user's X.509 certificate in PEM format
1164        @type keyFile: string
1165        @param keyFile: equivalent private key file in PEM format
1166        @type sslCertFile: string
1167        @param sslCertFile: certificate used for client authentication with
1168        the MyProxy server SSL connection.  This ID will be set as the owner
1169        of the stored credentials.  Only the owner can later remove
1170        credentials with myproxy-destroy or the destroy method.  If not set,
1171        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem or if this
1172        is not set, certFile
1173        @type sslKeyFile: string
1174        @param sslKeyFile: corresponding private key file.  See explanation
1175        for sslCertFile
1176        @type sslKeyFilePassphrase: string
1177        @param sslKeyFilePassphrase: passphrase for sslKeyFile.  Omit if the
1178        private key is not password protected.  Nb. keyFile is expected to
1179        be passphrase protected as this will be the passphrase used for
1180        logon / getDelegation.
1181        @type Force: bool
1182        @param force: set to True to overwrite any existing creds with the
1183        same username.  If, force=False a check is made with a call to info.
1184        If creds already, exist exit without proceeding
1185        """
1186       
1187        lifetime = lifetime or self.proxyCertMaxLifetime
1188
1189        # Inputs must be string type otherwise server will reject the request
1190        if isinstance(username, unicode):
1191            username = str(username)
1192           
1193        if isinstance(passphrase, unicode):
1194            passphrase = str(passphrase)
1195       
1196        globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME)
1197        if not sslCertFile or not sslKeyFile:
1198            if globusLoc:
1199                sslCertFile = os.path.join(globusLoc, 
1200                                           *MyProxyClient.HOSTCERT_SUBDIRPATH)
1201                sslKeyFile = os.path.join(globusLoc, 
1202                                          *MyProxyClient.HOSTKEY_SUBDIRPATH)
1203            else:
1204                # Default so that the owner is the same as the ID of the
1205                # credentials to be uploaded.
1206                sslCertFile = certFile
1207                sslKeyFile = keyFile
1208                sslKeyFilePassphrase = passphrase
1209               
1210        if not force:
1211            # Check credentials don't already exist
1212            if self.info(username,
1213                         sslCertFile=sslCertFile,
1214                         sslKeyFile=sslKeyFile,
1215                         sslKeyFilePassphrase=sslKeyFilePassphrase)[0]:
1216                raise MyProxyCredentialsAlreadyExist(
1217                        "Credentials already exist for user: %s" % username)
1218
1219        # Set up SSL connection
1220        conn = self._initConnection(certFile=sslCertFile,
1221                                    keyFile=sslKeyFile,
1222                                    keyFilePassphrase=sslKeyFilePassphrase)
1223       
1224        conn.connect((self.hostname, self.port))
1225       
1226        # send globus compatibility stuff
1227        conn.write('0')
1228   
1229        # send store command - ensure conversion from unicode before writing
1230        cmd = MyProxyClient.STORE_CMD % (username, lifetime)
1231        conn.write(str(cmd))
1232   
1233        # process server response
1234        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1235           
1236        respCode, errorTxt = self._deserializeResponse(dat)
1237        if respCode:
1238            raise MyProxyClientGetError(errorTxt)
1239       
1240        # Send certificate and private key
1241        certTxt = open(certFile).read()
1242        keyTxt = open(keyFile).read()
1243       
1244        conn.send(certTxt + keyTxt)
1245   
1246   
1247        # process server response
1248        resp = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1249        respCode, errorTxt = self._deserializeResponse(resp)
1250        if respCode:
1251            raise MyProxyClientRetrieveError(errorTxt)
1252       
1253    def logon(self, username, passphrase, lifetime=None, keyPair=None, 
1254              certReq=None, nBitsForKey=PRIKEY_NBITS, bootstrap=False,
1255              updateTrustRoots=False):
1256        """Retrieve a proxy credential from a MyProxy server
1257       
1258        Exceptions:  MyProxyClientGetError, MyProxyClientRetrieveError
1259       
1260        @type username: basestring
1261        @param username: username of credential
1262       
1263        @type passphrase: basestring
1264        @param passphrase: pass-phrase for private key of credential held on
1265        server
1266       
1267        @type lifetime: int
1268        @param lifetime: lifetime for generated certificate
1269       
1270        @type keyPair: OpenSSL.crypto.PKey
1271        @param keyPair: Public/Private key pair.  This is ignored if a
1272        certificate request is passed via the certReq keyword
1273       
1274        @type certReq: string
1275        @param certReq: ASN1 format certificate request, if none set, one is
1276        created along with a key pair
1277       
1278        @type nBitsForKey: int
1279        @param nBitsForKey: number of bits to use when generating key pair,
1280        defaults to the PRIKEY_NBITS class variable setting.  This keyword is
1281        ignored if a key pair is passed in from an external source via the
1282        keyPair keyword
1283       
1284        @rtype: tuple
1285        @return credentials as strings in PEM format: the
1286        user certificate, it's private key and the issuing certificate.  The
1287        issuing certificate is only set if the user certificate is a proxy
1288       
1289        @type bootstrap: bool
1290        @param bootstrap: If set to True, bootstrap trust roots i.e. connect to
1291        MyProxy server without verification of the server's SSL certificate
1292        against any CA certificates.  Set to False, for default behaviour:
1293        verify server SSL certificate against CA certificates held in location
1294        set by the "caCertDir" attribute.  If bootstrap is set, updateTrustRoots
1295        will be forced to True also
1296       
1297        @type updateTrustRoots: bool
1298        @param updateTrustRoots: set to True to update the trust roots
1299        """
1300        if bootstrap:
1301            log.info('Bootstrapping MyProxy server root of trust.')
1302           
1303            # Bootstrap implies update to trust roots
1304            updateTrustRoots = True
1305       
1306        if updateTrustRoots:
1307            self.getTrustRoots(username, 
1308                               passphrase, 
1309                               writeToCACertDir=True, 
1310                               bootstrap=bootstrap)
1311           
1312        lifetime = lifetime or self.proxyCertLifetime
1313
1314        # Certificate request may be passed as an input but if not generate it
1315        # here request here
1316        if certReq is None:
1317            # If no key pair was passed, generate here
1318            if keyPair is None:
1319                keyPair = self._createKeyPair(nBitsForKey=nBitsForKey)
1320               
1321            certReq = self._createCertReq(username, keyPair)
1322
1323        if keyPair is not None: 
1324            pemKeyPair = crypto.dump_privatekey(crypto.FILETYPE_PEM, keyPair)
1325       
1326        # Set-up SSL connection
1327        conn = self._initConnection()
1328        conn.connect((self.hostname, self.port))
1329       
1330        # send globus compatibility stuff
1331        conn.write('0')
1332   
1333        # send get command - ensure conversion from unicode before writing
1334        cmd = MyProxyClient.GET_CMD % (username, passphrase, lifetime)
1335           
1336        conn.write(str(cmd))
1337       
1338        # process server response
1339        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1340                   
1341        respCode, errorTxt = self._deserializeResponse(dat)
1342        if respCode:
1343            raise MyProxyClientGetError(errorTxt)
1344       
1345        # Send certificate request
1346        conn.send(certReq)
1347   
1348        # process certificates
1349        # - 1st byte , number of certs
1350        dat = conn.recv(1)
1351        nCerts = ord(dat[0])
1352       
1353        # - n certs
1354        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1355   
1356        # process server response
1357        resp = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1358        respCode, errorTxt = self._deserializeResponse(resp)
1359        if respCode:
1360            raise MyProxyClientRetrieveError(errorTxt)     
1361   
1362        # deserialize certs from received cert data
1363        pemCerts = self._deserializeCerts(dat)
1364        if len(pemCerts) != nCerts:
1365            MyProxyClientRetrieveError("%d certs expected, %d received" % 
1366                                       (nCerts, len(pemCerts)))
1367   
1368        if keyPair is not None:
1369            # Return certs and private key
1370            # - proxy or dynamically issued certificate (MyProxy CA mode)
1371            # - private key
1372            # - rest of cert chain if proxy cert issued
1373            creds = [pemCerts[0], pemKeyPair]
1374            creds.extend(pemCerts[1:])
1375        else:
1376            # Key generated externally - return certificate chain only
1377            creds = pemCerts
1378
1379       
1380        return tuple(creds)
1381
1382    def getDelegation(self, *arg, **kw):
1383        """Retrieve proxy cert for user - same as logon"""
1384        return self.logon(*arg, **kw)
1385   
1386    def getTrustRoots(self, 
1387                      username='', 
1388                      passphrase='', 
1389                      writeToCACertDir=False,
1390                      bootstrap=False):
1391        """Get trust roots for the given MyProxy server
1392       
1393        @type username: basestring
1394        @param username: username (optional)
1395       
1396        @type passphrase: basestring
1397        @param passphrase: pass-phrase (optional)
1398        server
1399       
1400        @type writeToCACertDir: bool
1401        @param writeToCACertDir: if set to True, write the retrieved trust roots
1402        out to the directory specified by the "caCertDir" attribute
1403       
1404        @type bootstrap: bool
1405        @param bootstrap: If set to True, bootstrap trust roots i.e. connect to
1406        MyProxy server without verification of the server's SSL certificate
1407        against any CA certificates.  Set to False, for default behaviour:
1408        verify server SSL certificate against CA certificates held in location
1409        set by the "caCertDir" attribute.
1410       
1411        @return: trust root files as a dictionary keyed by file name with each
1412        item value set to the file contents
1413        @rtype: dict
1414        """
1415        if bootstrap:
1416            log.info('Bootstrapping MyProxy server root of trust.')
1417           
1418        # Set-up SSL connection
1419        conn = self._initConnection(verifyPeerWithTrustRoots=(not bootstrap))
1420        conn.connect((self.hostname, self.port))
1421       
1422        # send globus compatibility stuff
1423        conn.write('0')
1424   
1425        # send get command - ensure conversion from unicode before writing
1426        cmd = MyProxyClient.GET_TRUST_ROOTS_CMD % (username, passphrase)
1427        conn.write(str(cmd))
1428   
1429        # process server response chunks until all consumed
1430        dat = ''
1431        tries = 0
1432        try:
1433            for tries in range(MyProxyClient.MAX_RECV_TRIES):
1434                dat += conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1435        except SSL.SysCallError:
1436            # Expect this exception when response content exhausted
1437            pass
1438       
1439        # Precaution
1440        if tries == MyProxyClient.MAX_RECV_TRIES:
1441            log.warning('Maximum %d tries reached for getTrustRoots response '
1442                        'block retrieval with block size %d', 
1443                        MyProxyClient.MAX_RECV_TRIES,
1444                        MyProxyClient.SERVER_RESP_BLK_SIZE)
1445         
1446        fieldName = MyProxyClient.TRUSTED_CERTS_FIELDNAME
1447        prefix = MyProxyClient.TRUSTED_CERTS_FILEDATA_FIELDNAME_PREFIX 
1448        respCode, errorTxt, fileData = self._deserializeResponse(dat, 
1449                                                                 fieldName,
1450                                                                 prefix)
1451        if respCode:
1452            raise MyProxyClientGetTrustRootsError(errorTxt)
1453       
1454        filesDict = dict([(k.split(prefix, 1)[1], base64.b64decode(v)) 
1455                          for k, v in fileData.items() if k != fieldName])
1456       
1457        if writeToCACertDir:
1458            for fileName, fileContents in filesDict.items():
1459                filePath = os.path.join(self.caCertDir, fileName)
1460                open(filePath, 'wb').write(fileContents)
1461               
1462        return filesDict
1463       
Note: See TracBrowser for help on using the repository browser.