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

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

New MyProxyClient? release 1.1.0

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 False
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 False
122                   
123                cn = self.cnPrefix + self.hostname
124                if peerCertSubj.commonName == cn:
125                    return True
126                else:
127                    log.error('Peer certificate CN %r doesn\'t match the '
128                              'expected CN %r', peerCertSubj.commonName, cn)
129                    return False
130            else:
131                if peerCertDN == self.certDN:
132                    return True
133                else:
134                    log.error('Peer certificate DN %r doesn\'t match the '
135                              'expected DN %r', peerCertDN, self.certDN)
136                    return False
137        else:
138            return True
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    del k
412    __slots__ += ('__openSSLConfig', '__cfg', '__serverSSLCertVerify')
413
414    def __init__(self, cfgFilePath=None, **prop):
415        """Make any initial settings for client connections to MyProxy
416       
417        Settings are held in a dictionary which can be set from **prop,
418        a call to setProperties() or by passing settings in an XML file
419        given by cfgFilePath
420       
421        @param cfgFilePath: set properties via a configuration file
422        @type cfgFilePath: basestring
423        @param **prop: set properties via keywords - see
424        PROPERTY_DEFAULTS class variable for a list of these
425        @type **prop: dict
426        """       
427        self.__serverSSLCertVerify = MyProxyServerSSLCertVerification()
428        self.__hostname = None
429        self.__port = None
430        self.__serverDN = None
431        self.__openSSLConfFilePath = None
432        self.__proxyCertMaxLifetime = MyProxyClient.PROPERTY_DEFAULTS[
433                                                        'proxyCertMaxLifetime']
434        self.__proxyCertLifetime = MyProxyClient.PROPERTY_DEFAULTS[
435                                                        'proxyCertLifetime']
436        self.__caCertDir = None
437       
438        self.__cfg = None
439
440        # Configuration file used to get default subject when generating a
441        # new proxy certificate request
442        self.__openSSLConfig = OpenSSLConfig()
443
444        # Server host name - take from environment variable if available
445        self.hostname = os.environ.get(MyProxyClient.MYPROXY_SERVER_ENVVARNAME,
446                                    MyProxyClient.PROPERTY_DEFAULTS['hostname'])
447           
448        # ... and port number
449        self.port = int(os.environ.get(
450                                MyProxyClient.MYPROXY_SERVER_PORT_ENVVARNAME, 
451                                MyProxyClient.PROPERTY_DEFAULTS['port']))
452
453        # Server Distinguished Name
454        serverDN = os.environ.get(MyProxyClient.MYPROXY_SERVER_DN_ENVVARNAME,
455                                  MyProxyClient.PROPERTY_DEFAULTS['serverDN'])
456        if serverDN is not None:
457            self.serverDN = serverDN
458       
459        # Set trust root - the directory containing the CA certificates for
460        # verifying the MyProxy server's SSL certificate
461        self.setDefaultCACertDir()
462       
463        # Any keyword settings override the defaults above
464        for opt, val in prop.items():
465            setattr(self, opt, val)
466       
467        # If properties file is set any parameters settings in file will
468        # override those set by input keyword or the defaults
469        if cfgFilePath is not None:
470            self.parseConfig(cfg=cfgFilePath)
471           
472    def setDefaultCACertDir(self):
473        '''Make default trust root setting - the directory containing the CA
474        certificates for verifying the MyProxy server's SSL certificate.
475       
476        The setting is made by using standard Globus defined locations and
477        environment variable settings
478        '''
479       
480        # Check for X509_CERT_DIR environment variable
481        x509CertDir = os.environ.get(MyProxyClient.X509_CERT_DIR_ENVVARNAME)
482        if x509CertDir is not None:
483            self.caCertDir = x509CertDir
484           
485        # Check for running as root user
486        elif os.environ.get(MyProxyClient.ROOT_USERNAME) is not None:
487            self.caCertDir = MyProxyClient.ROOT_TRUSTROOT_DIR
488           
489        # Default to non-root standard location
490        else:
491            self.caCertDir = os.path.expanduser(
492                                            MyProxyClient.USER_TRUSTROOT_DIR)
493
494    def _getServerSSLCertVerify(self):
495        return self.__serverSSLCertVerify
496
497    def _setServerSSLCertVerify(self, value):
498        if not isinstance(value, MyProxyServerSSLCertVerification):
499            raise TypeError('Expecting %r derived type for '
500                            '"serverSSLCertVerify" attribute; got %r' %
501                            MyProxyServerSSLCertVerification,
502                            value)
503        self.__serverSSLCertVerify = value
504
505    serverSSLCertVerify = property(_getServerSSLCertVerify, 
506                                   _setServerSSLCertVerify, 
507                                   doc="Class with a __call__ method which is "
508                                       "passed to the SSL context to verify "
509                                       "the peer (MyProxy server) certificate "
510                                       "in the SSL handshake between this "
511                                       "client and the MyProxy server")
512
513    def parseConfig(self, cfg, section='DEFAULT'):
514        '''Extract parameters from _cfg config object'''
515       
516        if isinstance(cfg, basestring):
517            cfgFilePath = os.path.expandvars(cfg)
518            self.__cfg = CaseSensitiveConfigParser()
519            self.__cfg.read(cfgFilePath)
520        else:
521            cfgFilePath = None
522            self.__cfg = cfg
523       
524        for key, val in self.__cfg.items(section):
525            setattr(self, key, val)
526       
527    # Get/Set Property methods
528    def _getHostname(self):
529        return self.__hostname
530   
531    def _setHostname(self, val):
532        """Also sets SSL Certificate verification object property!"""
533        if not isinstance(val, basestring):
534            raise TypeError("Expecting string type for hostname "
535                                 "attribute")
536        self.__hostname = val
537        self.__serverSSLCertVerify.hostname = val
538       
539    hostname = property(fget=_getHostname,
540                        fset=_setHostname,
541                        doc="hostname of MyProxy server")
542   
543    def _getPort(self):
544        return self.__port
545   
546    def _setPort(self, val):
547        if isinstance(val, basestring):
548            self.__port = int(val)
549        elif isinstance(val, int):
550            self.__port = val
551        else:
552            raise TypeError("Expecting int type for port attribute")
553   
554    port = property(fget=_getPort,
555                    fset=_setPort,
556                    doc="Port number for MyProxy server")
557   
558    def _getServerDN(self):
559        return self.__serverDN
560   
561    def _setServerDN(self, val):
562        """Also sets SSL Certificate verification object property!"""
563        if not isinstance(val, basestring):
564            raise TypeError("Expecting string type for serverDN attribute")
565       
566        self.__serverDN = val
567        self.__serverSSLCertVerify.certDN = val
568   
569    serverDN = property(fget=_getServerDN,
570                        fset=_setServerDN,
571                        doc="Distinguished Name for MyProxy Server "
572                            "Certificate")
573   
574    def _getServerCNPrefix(self):
575        """References SSL Certificate verification object property!"""
576        return self.__serverSSLCertVerify.cnPrefix
577   
578    def _setServerCNPrefix(self, val):
579        """Sets SSL Certificate verification object property!"""
580        self.__serverSSLCertVerify.cnPrefix = val
581   
582    serverCNPrefix = property(fget=_getServerCNPrefix,
583                              fset=_setServerCNPrefix,
584                              doc="Prefix for MyProxy Server Certificate "
585                                  "Distinguished Name CoomonName field; "
586                                  "usually set to host/ for Globus host "
587                                  "certificates")
588       
589    def _getOpenSSLConfFilePath(self):
590        return self.__openSSLConfFilePath
591   
592    def _setOpenSSLConfFilePath(self, val):
593        if not isinstance(val, basestring):
594            raise TypeError('Expecting string type for "openSSLConfFilePath" '
595                            'attribute')
596           
597        self.__openSSLConfFilePath = os.path.expandvars(val)
598        self.__openSSLConfig.filePath = self.__openSSLConfFilePath
599        self.__openSSLConfig.read() 
600   
601    openSSLConfFilePath = property(fget=_getOpenSSLConfFilePath,
602                                   fset=_setOpenSSLConfFilePath,
603                                   doc="file path for OpenSSL config file")
604   
605    def _getProxyCertMaxLifetime(self):
606        return self.__proxyCertMaxLifetime
607   
608    def _setProxyCertMaxLifetime(self, val):
609        if isinstance(val, basestring):
610            self.__proxyCertMaxLifetime = int(val)
611           
612        elif isinstance(val, int):
613            self.__proxyCertMaxLifetime = val
614        else:
615            raise TypeError("Expecting int type for proxyCertMaxLifetime "
616                            "attribute")
617   
618    proxyCertMaxLifetime = property(fget=_getProxyCertMaxLifetime,
619                                    fset=_setProxyCertMaxLifetime,
620                                    doc="Default max. lifetime allowed for "
621                                        "Proxy Certificate retrieved - used "
622                                        "by store method")
623   
624    def _getProxyCertLifetime(self):
625        return self.__proxyCertLifetime
626   
627    def _setProxyCertLifetime(self, val):
628        if isinstance(val, basestring):
629            self.__proxyCertLifetime = int(val)
630        elif isinstance(val, int):
631            self.__proxyCertLifetime = val
632        else:
633            raise TypeError("Expecting int type for proxyCertLifetime "
634                            "attribute")
635   
636    proxyCertLifetime = property(fget=_getProxyCertLifetime,
637                                 fset=_setProxyCertLifetime,
638                                 doc="Default proxy cert. lifetime used in "
639                                     "logon request")
640
641    def _getCACertDir(self):
642        return self.__caCertDir
643
644    def _setCACertDir(self, val):
645        '''Specify a directory containing PEM encoded CA certs. used for
646        validation of MyProxy server certificate.
647       
648        Set to None to make OpenSSL.SSL.Context.load_verify_locations ignore
649        this parameter
650       
651        @type val: basestring/None
652        @param val: directory path'''
653       
654        if isinstance(val, basestring):
655            if val == '':
656                self.__caCertDir = None
657            else:
658                self.__caCertDir = os.path.expandvars(val)
659               
660        elif isinstance(val, None):
661            self.__caCertDir = val   
662        else:
663            raise TypeError("Expecting string or None type for caCertDir "
664                            "attribute")
665       
666    caCertDir = property(fget=_getCACertDir,
667                         fset=_setCACertDir,
668                         doc="trust roots directory containing PEM encoded CA "
669                             "certificates to validate MyProxy server "
670                             "certificate")
671
672
673    def _getOpenSSLConfig(self):
674        "Get OpenSSLConfig object property method"
675        return self.__openSSLConfig
676   
677    openSSLConfig = property(fget=_getOpenSSLConfig, doc="OpenSSLConfig object")
678
679    def _initConnection(self, 
680                        certFile=None, 
681                        keyFile=None,
682                        keyFilePassphrase=None,
683                        verifyPeerWithTrustRoots=True):
684        """Initialise connection setting up SSL context and client and
685        server side identity checks
686       
687        @type sslCertFile: basestring
688        @param sslCertFile: certificate for SSL client authentication.  It may
689        be owner of a credential to be acted on or the concatenated proxy
690        certificate + proxy's signing cert.  SSL client authentication is not
691        necessary for getDelegation / logon calls
692        @type sslKeyFile: basestring
693        @param sslKeyFile: client private key file
694        @type keyFilePassphrase: basestring
695        @param keyFilePassphrase: pass-phrase protecting private key if set
696        @type verifyPeerWithTrustRoots: bool
697        @param verifyPeerWithTrustRoots: verify MyProxy server's SSL certificate
698        against a list of trusted CA certificates in the CA certificate
699        directory set by the "CaCertDir" attribute.  This should always be set
700        to True for MyProxy client calls unless using the 'bootstrap' trust
701        roots mode available with logon and get trust roots calls
702        """
703        # Must be version 3 for MyProxy
704        context = SSL.Context(SSL.SSLv3_METHOD)
705       
706        if verifyPeerWithTrustRoots:
707            context.load_verify_locations(None, self.caCertDir)
708           
709        # Verify peer's (MyProxy server) certificate
710        context.set_verify(SSL.VERIFY_PEER, self.__serverSSLCertVerify)
711             
712        if certFile:
713            try:
714                context.use_certificate_chain_file(certFile)
715                def pwdCallback(passphraseMaxLen, 
716                                promptPassphraseTwice,
717                                passphrase):
718                    """Private key file password callback function"""
719                    if len(passphrase) > passphraseMaxLen:
720                        log.error('Passphrase length %d is greater than the '
721                                  'maximum length allowed %d',
722                                  len(passphrase), passphraseMaxLen)
723                        return ''
724                       
725                    return passphrase
726                   
727                if keyFilePassphrase is not None:
728                    context.set_passwd_cb(pwdCallback, keyFilePassphrase)
729                   
730                context.use_privatekey_file(keyFile)
731            except Exception:
732                raise MyProxyClientConfigError("Loading certificate "
733                                               "and private key for SSL "
734                                               "connection [also check CA "
735                                               "certificate settings]: %s" % 
736                                               traceback.format_exc())
737           
738        # Disable for compatibility with myproxy server (er, globus)
739        # globus doesn't handle this case, apparently, and instead
740        # chokes in proxy delegation code
741        context.set_options(SSL.OP_DONT_INSERT_EMPTY_FRAGMENTS)
742       
743        # connect to myproxy server
744        conn = SSL.Connection(context, socket.socket())
745       
746        return conn
747       
748    def _createKeyPair(self, nBitsForKey=PRIKEY_NBITS):
749        """Generate key pair and return as PEM encoded string
750        @type nBitsForKey: int
751        @param nBitsForKey: number of bits for private key generation -
752        default is 2048
753        @rtype: OpenSSL.crypto.PKey
754        @return: public/private key pair
755        """
756        keyPair = crypto.PKey()
757        keyPair.generate_key(crypto.TYPE_RSA, nBitsForKey)
758       
759        return keyPair
760           
761    def _createCertReq(self, CN, keyPair, messageDigest=MESSAGE_DIGEST_TYPE):
762        """Create a certificate request.
763       
764        @type CN: basestring
765        @param CN: Common Name for certificate - effectively the same as the
766        username for the MyProxy credential
767        @type keyPair: string/None
768        @param keyPair: public/private key pair
769        @type messageDigest: basestring
770        @param messageDigest: message digest type - default is MD5
771        @rtype: base string
772        @return certificate request PEM text and private key PEM text
773        """
774       
775        # Check all required certifcate request DN parameters are set               
776        # Create certificate request
777        certReq = crypto.X509Req()
778       
779        # Create public key object
780        certReq.set_pubkey(keyPair)
781       
782        # Add the public key to the request
783        certReq.sign(keyPair, messageDigest)
784       
785        derCertReq = crypto.dump_certificate_request(crypto.FILETYPE_ASN1, 
786                                                     certReq)
787
788        return derCertReq
789   
790    def _deserializeResponse(self, msg, *fieldNames):
791        """
792        Deserialize a MyProxy server response
793       
794        @param msg: string response message from MyProxy server
795        @return: tuple of integer response and errorTxt string (if any) and all
796        the fields parsed.  fields is a list of two element, field name, field
797        value tuples.
798        @rtype: tuple
799        """ 
800        lines = msg.split('\n')
801        fields = [tuple(line.split('=', 1)) for line in lines][:-1]
802       
803        # get response value
804        respCode = [int(v) for k, v in fields if k == 'RESPONSE'][0]
805
806        # get error text
807        errorTxt = os.linesep.join([v for k, v in fields if k == 'ERROR'])
808       
809        # Check for custom fields requested by caller to this method
810        if fieldNames:
811            fieldsDict = {}
812            for k, v in fields:
813                names = [name for name in fieldNames if k.startswith(name)]
814                if len(names) == 0:
815                    continue
816                else:
817                    if v.isdigit():
818                        fieldsDict[k] = int(v)
819                    else:
820                        fieldsDict[k] = v
821             
822            # Return additional dict item in tuple 
823            return respCode, errorTxt, fieldsDict
824        else:
825            return respCode, errorTxt   
826 
827    def _deserializeCerts(self, inputDat):
828        """Unpack certificates returned from a get delegation call to the
829        server
830       
831        @param inputDat: string containing the proxy cert and private key
832        and signing cert all in DER format
833       
834        @return list containing the equivalent to the input in PEM format"""
835        pemCerts = []
836        dat = inputDat
837       
838        while dat:
839            # find start of cert, get length       
840            ind = dat.find('\x30\x82')
841            if ind < 0:
842                break
843               
844            len = 256*ord(dat[ind+2]) + ord(dat[ind+3])
845   
846            # extract der-format cert, and convert to pem
847            derCert = dat[ind:ind+len+4]
848            x509Cert = crypto.load_certificate(crypto.FILETYPE_ASN1, derCert)
849            pemCert = crypto.dump_certificate(crypto.FILETYPE_PEM, x509Cert)       
850            pemCerts.append(pemCert)
851   
852            # trim cert from data
853            dat = dat[ind + len + 4:]
854           
855        return pemCerts
856       
857    @classmethod
858    def writeProxyFile(cls, proxyCert, proxyPriKey, userX509Cert, 
859                       filePath=None):
860        """Write out proxy cert to file in the same way as myproxy-logon -
861        proxy cert, private key, user cert.  Nb. output from logon can be
862        passed direct into this method
863       
864        @type proxyCert: string
865        @param proxyCert: proxy certificate
866        @type proxyPriKey: string
867        @param proxyPriKey: private key for proxy
868        @type userX509Cert: string
869        @param userX509Cert: user certificate which issued the proxy
870        @type filePath: string
871        @param filePath: set to override the default filePath"""
872       
873        if filePath is None:
874            filePath = MyProxyClient.DEF_PROXY_FILEPATH
875           
876        if filePath is None:
877            MyProxyClientConfigError("Error setting proxy file path - invalid "
878                                     "platform?")
879       
880        outStr = proxyCert + proxyPriKey + userX509Cert       
881        open(MyProxyClient.DEF_PROXY_FILEPATH, 'w').write(outStr)
882        try:
883            # Make sure permissions are set correctly
884            os.chmod(MyProxyClient.DEF_PROXY_FILEPATH, 
885                     MyProxyClient.PROXY_FILE_PERMISSIONS)
886        except Exception, e:
887            # Don't leave the file lying around if couldn't change it's
888            # permissions
889            os.unlink(MyProxyClient.DEF_PROXY_FILEPATH)
890           
891            log.error('Unable to set %o permissions for proxy file "%s": %s'% 
892                      (MyProxyClient.PROXY_FILE_PERMISSIONS,
893                       MyProxyClient.DEF_PROXY_FILEPATH, e))
894            raise
895
896    @classmethod
897    def readProxyFile(cls, filePath=None):
898        """Read proxy cert file following the format used by myproxy-logon -
899        proxy, cert, private key, user cert.
900       
901        @rtype: tuple
902        @return: tuple containing proxy cert, private key, user cert"""
903        if filePath is None:
904            filePath = MyProxyClient.DEF_PROXY_FILEPATH
905           
906        if filePath is None:
907            MyProxyClientConfigError("Error setting proxy file path - invalid "
908                                     "platform?")
909               
910        proxy = open(MyProxyClient.DEF_PROXY_FILEPATH).read()
911       
912        # Split certs and key into separate tuple items
913        return tuple(['-----BEGIN'+i for i in proxy.split('-----BEGIN')[1:]])
914
915    def put(self,
916            username,
917            passphrase,
918            userCertFile,
919            userKeyFile, 
920            lifetime=None,
921            sslCertFile=None,
922            sslKeyFile=None,
923            sslKeyFilePassphrase=None):
924        """Store a proxy credential on the server
925       
926        Unfortunately this method is not implemented as it requires the creation
927        of a proxy certificate by the client but PyOpenSSL doesn't currently
928        support the required proxyCertInfo X.509 certificate extension
929       
930        @raise NotImplementedError: see above
931       
932        @type username: string
933        @param username: username selected for new credential
934        @type passphrase: string
935        @param passphrase: pass-phrase for new credential.  This will be used
936        by the server to authenticate later requests.  IT must be at least
937        6 characters.  The server may impose other restrictions too depending
938        on its configuration.
939        @type certFile: string
940        @param certFile: user's X.509 proxy certificate in PEM format
941        @type keyFile: string
942        @param keyFile: equivalent private key file in PEM format
943        @type sslCertFile: string
944        @param sslCertFile: certificate used for client authentication with
945        the MyProxy server SSL connection.  If not set,
946        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
947        @type sslKeyFile: string
948        @param sslKeyFile: corresponding private key file.  See explanation
949        for sslCertFile
950        @type sslKeyFilePassphrase: string
951        @param sslKeyFilePassphrase: passphrase for sslKeyFile.  Omit if the
952        private key is not password protected. 
953        @type lifetime: int / None
954        @param lifetime: the maximum lifetime allowed for retrieved proxy
955        credentials in seconds. defaults to proxyCertMaxLifetime attribute value
956        """
957        raise NotImplementedError('put method is not currently implemented.  '
958                                  'It requires the creation of a proxy '
959                                  'certificate by the client but PyOpenSSL '
960                                  'doesn\'t currently support the required '
961                                  'proxyCertInfo X.509 certificate extension.')
962
963    def info(self,
964             username, 
965             sslCertFile=None,
966             sslKeyFile=None,
967             sslKeyFilePassphrase=None):
968        """return True/False whether credentials exist on the server for a
969        given username
970       
971        @raise MyProxyClientGetError:
972        @raise MyProxyClientRetrieveError:
973       
974        @type username: string
975        @param username: username selected for credential
976        @type sslCertFile: string
977        @param sslCertFile: certificate used for client authentication with
978        the MyProxy server SSL connection.  This ID will be set as the owner
979        of the stored credentials.  Only the owner can later remove
980        credentials with myproxy-destroy or the destroy method.  If not set,
981        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
982        @type sslKeyFile: string
983        @param sslKeyFile: corresponding private key file.  See explanation
984        for sslCertFile
985        @type sslKeyFilePassphrase: string
986        @param sslKeyFilePassphrase: passphrase for sslKeyFile.  Omit if the
987        private key is not password protected.
988        """
989        globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME)
990        if not sslCertFile:
991            if globusLoc:
992                sslCertFile = os.path.join(globusLoc, 
993                                            *MyProxyClient.HOSTCERT_SUBDIRPATH)
994                sslKeyFile = os.path.join(globusLoc, 
995                                            *MyProxyClient.HOSTKEY_SUBDIRPATH)
996            else:
997                raise MyProxyClientError(
998            "No client authentication cert. and private key file were given")
999
1000        # Set-up SSL connection
1001        conn = self._initConnection(certFile=sslCertFile,
1002                                    keyFile=sslKeyFile,
1003                                    keyFilePassphrase=sslKeyFilePassphrase)
1004       
1005        conn.connect((self.hostname, self.port))
1006       
1007        # send globus compatibility stuff
1008        conn.write('0')
1009   
1010        # send info command - ensure conversion from unicode before writing
1011        cmd = MyProxyClient.INFO_CMD % username
1012        conn.write(str(cmd))
1013   
1014        # process server response
1015        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1016         
1017        # Pass in the names of fields to return in the dictionary 'field'
1018        respCode, errorTxt, field = self._deserializeResponse(dat, 
1019                                                              'CRED_START_TIME', 
1020                                                              'CRED_END_TIME', 
1021                                                              'CRED_OWNER')
1022
1023        return not bool(respCode), errorTxt, field
1024
1025    def changePassphrase(self,
1026                         username, 
1027                         passphrase,
1028                         newPassphrase,
1029                         sslCertFile=None,
1030                         sslKeyFile=None,
1031                         sslKeyFilePassphrase=None):
1032        """change pass-phrase protecting the credentials for a given username
1033       
1034        @raise MyProxyClientGetError:
1035        @raise MyProxyClientRetrieveError:
1036       
1037        @param username: username of credential
1038        @param passphrase: existing pass-phrase for credential
1039        @param newPassphrase: new pass-phrase to replace the existing one.
1040        @param sslCertFile: certificate used for client authentication with
1041        the MyProxy server SSL connection.  This ID will be set as the owner
1042        of the stored credentials.  Only the owner can later remove
1043        credentials with myproxy-destroy or the destroy method.  If not set,
1044        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
1045        @param sslKeyFile: corresponding private key file.  See explanation
1046        for sslCertFile
1047        @param sslKeyFilePassphrase: passphrase for sslKeyFile.  Omit if the
1048        private key is not password protected. 
1049        @return none
1050        """
1051        globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME)
1052        if not sslCertFile or not sslKeyFile:
1053            if globusLoc:
1054                sslCertFile = os.path.join(globusLoc, 
1055                                           *MyProxyClient.HOSTCERT_SUBDIRPATH)
1056                sslKeyFile = os.path.join(globusLoc, 
1057                                          *MyProxyClient.HOSTKEY_SUBDIRPATH)
1058            else:
1059                raise MyProxyClientError(
1060            "No client authentication cert. and private key file were given")
1061       
1062        # Set-up SSL connection
1063        conn = self._initConnection(certFile=sslCertFile,
1064                                    keyFile=sslKeyFile,
1065                                    keyFilePassphrase=sslKeyFilePassphrase)
1066
1067        conn.connect((self.hostname, self.port))
1068       
1069        # send globus compatibility stuff
1070        conn.write('0')
1071   
1072        # send command - ensure conversion from unicode before writing
1073        cmd = MyProxyClient.CHANGE_PASSPHRASE_CMD % (username, 
1074                                                     passphrase,
1075                                                     newPassphrase)
1076        conn.write(str(cmd))
1077   
1078        # process server response
1079        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1080           
1081        respCode, errorTxt = self._deserializeResponse(dat)
1082        if respCode:
1083            raise MyProxyClientGetError(errorTxt)
1084
1085    def destroy(self,
1086                username, 
1087                sslCertFile=None,
1088                sslKeyFile=None,
1089                sslKeyFilePassphrase=None):
1090        """destroy credentials from the server for a given username
1091       
1092        @raise MyProxyClientGetError:
1093        @raise MyProxyClientRetrieveError:
1094       
1095        @param username: username selected for credential
1096        @param sslCertFile: certificate used for client authentication with
1097        the MyProxy server SSL connection.  This ID will be set as the owner
1098        of the stored credentials.  Only the owner can later remove
1099        credentials with myproxy-destroy or the destroy method.  If not set,
1100        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
1101        @param sslKeyFile: corresponding private key file.  See explanation
1102        for sslCertFile
1103        @param sslKeyFilePassphrase: passphrase for sslKeyFile.  Omit if the
1104        private key is not password protected. 
1105        @return none
1106        """
1107        globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME)
1108        if not sslCertFile or not sslKeyFile:
1109            if globusLoc:
1110                sslCertFile = os.path.join(globusLoc, 
1111                                         *MyProxyClient.HOSTCERT_SUBDIRPATH)
1112                sslKeyFile = os.path.join(globusLoc, 
1113                                         *MyProxyClient.HOSTKEY_SUBDIRPATH)
1114            else:
1115                raise MyProxyClientError(
1116            "No client authentication cert. and private key file were given")
1117       
1118        # Set-up SSL connection
1119        conn = self._initConnection(certFile=sslCertFile,
1120                                    keyFile=sslKeyFile,
1121                                    keyFilePassphrase=sslKeyFilePassphrase)
1122
1123        conn.connect((self.hostname, self.port))
1124       
1125        # send globus compatibility stuff
1126        conn.write('0')
1127   
1128        # send destroy command - ensure conversion from unicode before writing
1129        cmd = MyProxyClient.DESTROY_CMD % username
1130        conn.write(str(cmd))
1131   
1132        # process server response
1133        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1134           
1135        respCode, errorTxt = self._deserializeResponse(dat)
1136        if respCode:
1137            raise MyProxyClientGetError(errorTxt)
1138
1139    def store(self,
1140              username,
1141              passphrase, 
1142              certFile,
1143              keyFile,
1144              sslCertFile=None,
1145              sslKeyFile=None,
1146              sslKeyFilePassphrase=None,
1147              lifetime=None,
1148              force=True):
1149        """Upload credentials to the server
1150       
1151        @raise MyProxyClientGetError:
1152        @raise MyProxyClientRetrieveError:
1153       
1154        @type username: string
1155        @param username: username selected for new credential
1156        @type passphrase: string
1157        @param passphrase: pass-phrase for new credential.  This is the pass
1158        phrase which protects keyfile.
1159        @type certFile: string
1160        @param certFile: user's X.509 certificate in PEM format
1161        @type keyFile: string
1162        @param keyFile: equivalent private key file in PEM format
1163        @type sslCertFile: string
1164        @param sslCertFile: certificate used for client authentication with
1165        the MyProxy server SSL connection.  This ID will be set as the owner
1166        of the stored credentials.  Only the owner can later remove
1167        credentials with myproxy-destroy or the destroy method.  If not set,
1168        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem or if this
1169        is not set, certFile
1170        @type sslKeyFile: string
1171        @param sslKeyFile: corresponding private key file.  See explanation
1172        for sslCertFile
1173        @type sslKeyFilePassphrase: string
1174        @param sslKeyFilePassphrase: passphrase for sslKeyFile.  Omit if the
1175        private key is not password protected.  Nb. keyFile is expected to
1176        be passphrase protected as this will be the passphrase used for
1177        logon / getDelegation.
1178        @type Force: bool
1179        @param force: set to True to overwrite any existing creds with the
1180        same username.  If, force=False a check is made with a call to info.
1181        If creds already, exist exit without proceeding
1182        """
1183       
1184        lifetime = lifetime or self.proxyCertMaxLifetime
1185
1186        # Inputs must be string type otherwise server will reject the request
1187        if isinstance(username, unicode):
1188            username = str(username)
1189           
1190        if isinstance(passphrase, unicode):
1191            passphrase = str(passphrase)
1192       
1193        globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME)
1194        if not sslCertFile or not sslKeyFile:
1195            if globusLoc:
1196                sslCertFile = os.path.join(globusLoc, 
1197                                           *MyProxyClient.HOSTCERT_SUBDIRPATH)
1198                sslKeyFile = os.path.join(globusLoc, 
1199                                          *MyProxyClient.HOSTKEY_SUBDIRPATH)
1200            else:
1201                # Default so that the owner is the same as the ID of the
1202                # credentials to be uploaded.
1203                sslCertFile = certFile
1204                sslKeyFile = keyFile
1205                sslKeyFilePassphrase = passphrase
1206               
1207        if not force:
1208            # Check credentials don't already exist
1209            if self.info(username,
1210                         sslCertFile=sslCertFile,
1211                         sslKeyFile=sslKeyFile,
1212                         sslKeyFilePassphrase=sslKeyFilePassphrase)[0]:
1213                raise MyProxyCredentialsAlreadyExist(
1214                        "Credentials already exist for user: %s" % username)
1215
1216        # Set up SSL connection
1217        conn = self._initConnection(certFile=sslCertFile,
1218                                    keyFile=sslKeyFile,
1219                                    keyFilePassphrase=sslKeyFilePassphrase)
1220       
1221        conn.connect((self.hostname, self.port))
1222       
1223        # send globus compatibility stuff
1224        conn.write('0')
1225   
1226        # send store command - ensure conversion from unicode before writing
1227        cmd = MyProxyClient.STORE_CMD % (username, lifetime)
1228        conn.write(str(cmd))
1229   
1230        # process server response
1231        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1232           
1233        respCode, errorTxt = self._deserializeResponse(dat)
1234        if respCode:
1235            raise MyProxyClientGetError(errorTxt)
1236       
1237        # Send certificate and private key
1238        certTxt = open(certFile).read()
1239        keyTxt = open(keyFile).read()
1240       
1241        conn.send(certTxt + keyTxt)
1242   
1243   
1244        # process server response
1245        resp = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1246        respCode, errorTxt = self._deserializeResponse(resp)
1247        if respCode:
1248            raise MyProxyClientRetrieveError(errorTxt)
1249       
1250    def logon(self, username, passphrase, lifetime=None, keyPair=None, 
1251              certReq=None, nBitsForKey=PRIKEY_NBITS, bootstrap=False,
1252              updateTrustRoots=False):
1253        """Retrieve a proxy credential from a MyProxy server
1254       
1255        Exceptions:  MyProxyClientGetError, MyProxyClientRetrieveError
1256       
1257        @type username: basestring
1258        @param username: username of credential
1259       
1260        @type passphrase: basestring
1261        @param passphrase: pass-phrase for private key of credential held on
1262        server
1263       
1264        @type lifetime: int
1265        @param lifetime: lifetime for generated certificate
1266       
1267        @type keyPair: OpenSSL.crypto.PKey
1268        @param keyPair: Public/Private key pair.  This is ignored if a
1269        certificate request is passed via the certReq keyword
1270       
1271        @type certReq: string
1272        @param certReq: ASN1 format certificate request, if none set, one is
1273        created along with a key pair
1274       
1275        @type nBitsForKey: int
1276        @param nBitsForKey: number of bits to use when generating key pair,
1277        defaults to the PRIKEY_NBITS class variable setting.  This keyword is
1278        ignored if a key pair is passed in from an external source via the
1279        keyPair keyword
1280       
1281        @rtype: tuple
1282        @return credentials as strings in PEM format: the
1283        user certificate, it's private key and the issuing certificate.  The
1284        issuing certificate is only set if the user certificate is a proxy
1285       
1286        @type bootstrap: bool
1287        @param bootstrap: If set to True, bootstrap trust roots i.e. connect to
1288        MyProxy server without verification of the server's SSL certificate
1289        against any CA certificates.  Set to False, for default behaviour:
1290        verify server SSL certificate against CA certificates held in location
1291        set by the "caCertDir" attribute.  If bootstrap is set, updateTrustRoots
1292        will be forced to True also
1293       
1294        @type updateTrustRoots: bool
1295        @param updateTrustRoots: set to True to update the trust roots
1296        """
1297        if bootstrap:
1298            log.info('Bootstrapping MyProxy server root of trust.')
1299           
1300            # Bootstrap implies update to trust roots
1301            updateTrustRoots = True
1302       
1303        if updateTrustRoots:
1304            self.getTrustRoots(username, 
1305                               passphrase, 
1306                               writeToCACertDir=True, 
1307                               bootstrap=bootstrap)
1308           
1309        lifetime = lifetime or self.proxyCertLifetime
1310
1311        # Certificate request may be passed as an input but if not generate it
1312        # here request here
1313        if certReq is None:
1314            # If no key pair was passed, generate here
1315            if keyPair is None:
1316                keyPair = self._createKeyPair(nBitsForKey=nBitsForKey)
1317               
1318            certReq = self._createCertReq(username, keyPair)
1319
1320        if keyPair is not None: 
1321            pemKeyPair = crypto.dump_privatekey(crypto.FILETYPE_PEM, keyPair)
1322       
1323        # Set-up SSL connection
1324        conn = self._initConnection()
1325        conn.connect((self.hostname, self.port))
1326       
1327        # send globus compatibility stuff
1328        conn.write('0')
1329   
1330        # send get command - ensure conversion from unicode before writing
1331        cmd = MyProxyClient.GET_CMD % (username, passphrase, lifetime)
1332           
1333        conn.write(str(cmd))
1334       
1335        # process server response
1336        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1337                   
1338        respCode, errorTxt = self._deserializeResponse(dat)
1339        if respCode:
1340            raise MyProxyClientGetError(errorTxt)
1341       
1342        # Send certificate request
1343        conn.send(certReq)
1344   
1345        # process certificates
1346        # - 1st byte , number of certs
1347        dat = conn.recv(1)
1348        nCerts = ord(dat[0])
1349       
1350        # - n certs
1351        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1352   
1353        # process server response
1354        resp = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1355        respCode, errorTxt = self._deserializeResponse(resp)
1356        if respCode:
1357            raise MyProxyClientRetrieveError(errorTxt)     
1358   
1359        # deserialize certs from received cert data
1360        pemCerts = self._deserializeCerts(dat)
1361        if len(pemCerts) != nCerts:
1362            MyProxyClientRetrieveError("%d certs expected, %d received" % 
1363                                       (nCerts, len(pemCerts)))
1364   
1365        if keyPair is not None:
1366            # Return certs and private key
1367            # - proxy or dynamically issued certificate (MyProxy CA mode)
1368            # - private key
1369            # - rest of cert chain if proxy cert issued
1370            creds = [pemCerts[0], pemKeyPair]
1371            creds.extend(pemCerts[1:])
1372        else:
1373            # Key generated externally - return certificate chain only
1374            creds = pemCerts
1375
1376       
1377        return tuple(creds)
1378
1379    def getDelegation(self, *arg, **kw):
1380        """Retrieve proxy cert for user - same as logon"""
1381        return self.logon(*arg, **kw)
1382   
1383    def getTrustRoots(self, 
1384                      username='', 
1385                      passphrase='', 
1386                      writeToCACertDir=False,
1387                      bootstrap=False):
1388        """Get trust roots for the given MyProxy server
1389       
1390        @type username: basestring
1391        @param username: username (optional)
1392       
1393        @type passphrase: basestring
1394        @param passphrase: pass-phrase (optional)
1395        server
1396       
1397        @type writeToCACertDir: bool
1398        @param writeToCACertDir: if set to True, write the retrieved trust roots
1399        out to the directory specified by the "caCertDir" attribute
1400       
1401        @type bootstrap: bool
1402        @param bootstrap: If set to True, bootstrap trust roots i.e. connect to
1403        MyProxy server without verification of the server's SSL certificate
1404        against any CA certificates.  Set to False, for default behaviour:
1405        verify server SSL certificate against CA certificates held in location
1406        set by the "caCertDir" attribute.
1407       
1408        @return: trust root files as a dictionary keyed by file name with each
1409        item value set to the file contents
1410        @rtype: dict
1411        """
1412        if bootstrap:
1413            log.info('Bootstrapping MyProxy server root of trust.')
1414           
1415        # Set-up SSL connection
1416        conn = self._initConnection(verifyPeerWithTrustRoots=(not bootstrap))
1417        conn.connect((self.hostname, self.port))
1418       
1419        # send globus compatibility stuff
1420        conn.write('0')
1421   
1422        # send get command - ensure conversion from unicode before writing
1423        cmd = MyProxyClient.GET_TRUST_ROOTS_CMD % (username, passphrase)
1424        conn.write(str(cmd))
1425   
1426        # process server response chunks until all consumed
1427        dat = ''
1428        tries = 0
1429        try:
1430            for tries in range(MyProxyClient.MAX_RECV_TRIES):
1431                dat += conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1432        except SSL.SysCallError:
1433            # Expect this exception when response content exhausted
1434            pass
1435       
1436        # Precaution
1437        if tries == MyProxyClient.MAX_RECV_TRIES:
1438            log.warning('Maximum %d tries reached for getTrustRoots response '
1439                        'block retrieval with block size %d', 
1440                        MyProxyClient.MAX_RECV_TRIES,
1441                        MyProxyClient.SERVER_RESP_BLK_SIZE)
1442         
1443        fieldName = MyProxyClient.TRUSTED_CERTS_FIELDNAME
1444        prefix = MyProxyClient.TRUSTED_CERTS_FILEDATA_FIELDNAME_PREFIX 
1445        respCode, errorTxt, fileData = self._deserializeResponse(dat, 
1446                                                                 fieldName,
1447                                                                 prefix)
1448        if respCode:
1449            raise MyProxyClientGetTrustRootsError(errorTxt)
1450       
1451        filesDict = dict([(k.split(prefix, 1)[1], base64.b64decode(v)) 
1452                          for k, v in fileData.items() if k != fieldName])
1453       
1454        if writeToCACertDir:
1455            for fileName, fileContents in filesDict.items():
1456                filePath = os.path.join(self.caCertDir, fileName)
1457                open(filePath, 'wb').write(fileContents)
1458               
1459        return filesDict
1460       
Note: See TracBrowser for help on using the repository browser.