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

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/MyProxyClient/myproxy/client.py@6919
Revision 6919, 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    __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           
708        # Verify peer's (MyProxy server) certificate
709        context.set_verify(SSL.VERIFY_PEER, self.__serverSSLCertVerify)
710             
711        if certFile:
712            try:
713                context.use_certificate_chain_file(certFile)
714                def pwdCallback(passphraseMaxLen, 
715                                promptPassphraseTwice,
716                                passphrase):
717                    """Private key file password callback function"""
718                    if len(passphrase) > passphraseMaxLen:
719                        log.error('Passphrase length %d is greater than the '
720                                  'maximum length allowed %d',
721                                  len(passphrase), passphraseMaxLen)
722                        return ''
723                       
724                    return passphrase
725                   
726                if keyFilePassphrase is not None:
727                    context.set_passwd_cb(pwdCallback, keyFilePassphrase)
728                   
729                context.use_privatekey_file(keyFile)
730            except Exception:
731                raise MyProxyClientConfigError("Loading certificate "
732                                               "and private key for SSL "
733                                               "connection [also check CA "
734                                               "certificate settings]: %s" % 
735                                               traceback.format_exc())
736           
737        # Disable for compatibility with myproxy server (er, globus)
738        # globus doesn't handle this case, apparently, and instead
739        # chokes in proxy delegation code
740        context.set_options(SSL.OP_DONT_INSERT_EMPTY_FRAGMENTS)
741       
742        # connect to myproxy server
743        conn = SSL.Connection(context, socket.socket())
744       
745        return conn
746       
747    def _createKeyPair(self, nBitsForKey=PRIKEY_NBITS):
748        """Generate key pair and return as PEM encoded string
749        @type nBitsForKey: int
750        @param nBitsForKey: number of bits for private key generation -
751        default is 2048
752        @rtype: OpenSSL.crypto.PKey
753        @return: public/private key pair
754        """
755        keyPair = crypto.PKey()
756        keyPair.generate_key(crypto.TYPE_RSA, nBitsForKey)
757       
758        return keyPair
759           
760    def _createCertReq(self, CN, keyPair, messageDigest=MESSAGE_DIGEST_TYPE):
761        """Create a certificate request.
762       
763        @type CN: basestring
764        @param CN: Common Name for certificate - effectively the same as the
765        username for the MyProxy credential
766        @type keyPair: string/None
767        @param keyPair: public/private key pair
768        @type messageDigest: basestring
769        @param messageDigest: message digest type - default is MD5
770        @rtype: base string
771        @return certificate request PEM text and private key PEM text
772        """
773       
774        # Check all required certifcate request DN parameters are set               
775        # Create certificate request
776        certReq = crypto.X509Req()
777       
778        # Create public key object
779        certReq.set_pubkey(keyPair)
780       
781        # Add the public key to the request
782        certReq.sign(keyPair, messageDigest)
783       
784        derCertReq = crypto.dump_certificate_request(crypto.FILETYPE_ASN1, 
785                                                     certReq)
786
787        return derCertReq
788   
789    def _deserializeResponse(self, msg, *fieldNames):
790        """
791        Deserialize a MyProxy server response
792       
793        @param msg: string response message from MyProxy server
794        @return: tuple of integer response and errorTxt string (if any) and all
795        the fields parsed.  fields is a list of two element, field name, field
796        value tuples.
797        @rtype: tuple
798        """ 
799        lines = msg.split('\n')
800        fields = [tuple(line.split('=', 1)) for line in lines][:-1]
801       
802        # get response value
803        respCode = [int(v) for k, v in fields if k == 'RESPONSE'][0]
804
805        # get error text
806        errorTxt = os.linesep.join([v for k, v in fields if k == 'ERROR'])
807       
808        # Check for custom fields requested by caller to this method
809        if fieldNames:
810            fieldsDict = {}
811            for k, v in fields:
812                names = [name for name in fieldNames if k.startswith(name)]
813                if len(names) == 0:
814                    continue
815                else:
816                    if v.isdigit():
817                        fieldsDict[k] = int(v)
818                    else:
819                        fieldsDict[k] = v
820             
821            # Return additional dict item in tuple 
822            return respCode, errorTxt, fieldsDict
823        else:
824            return respCode, errorTxt   
825 
826    def _deserializeCerts(self, inputDat):
827        """Unpack certificates returned from a get delegation call to the
828        server
829       
830        @param inputDat: string containing the proxy cert and private key
831        and signing cert all in DER format
832       
833        @return list containing the equivalent to the input in PEM format"""
834        pemCerts = []
835        dat = inputDat
836       
837        while dat:
838            # find start of cert, get length       
839            ind = dat.find('\x30\x82')
840            if ind < 0:
841                break
842               
843            len = 256*ord(dat[ind+2]) + ord(dat[ind+3])
844   
845            # extract der-format cert, and convert to pem
846            derCert = dat[ind:ind+len+4]
847            x509Cert = crypto.load_certificate(crypto.FILETYPE_ASN1, derCert)
848            pemCert = crypto.dump_certificate(crypto.FILETYPE_PEM, x509Cert)       
849            pemCerts.append(pemCert)
850   
851            # trim cert from data
852            dat = dat[ind + len + 4:]
853           
854        return pemCerts
855       
856    @classmethod
857    def writeProxyFile(cls, proxyCert, proxyPriKey, userX509Cert, 
858                       filePath=None):
859        """Write out proxy cert to file in the same way as myproxy-logon -
860        proxy cert, private key, user cert.  Nb. output from logon can be
861        passed direct into this method
862       
863        @type proxyCert: string
864        @param proxyCert: proxy certificate
865        @type proxyPriKey: string
866        @param proxyPriKey: private key for proxy
867        @type userX509Cert: string
868        @param userX509Cert: user certificate which issued the proxy
869        @type filePath: string
870        @param filePath: set to override the default filePath"""
871       
872        if filePath is None:
873            filePath = MyProxyClient.DEF_PROXY_FILEPATH
874           
875        if filePath is None:
876            MyProxyClientConfigError("Error setting proxy file path - invalid "
877                                     "platform?")
878       
879        outStr = proxyCert + proxyPriKey + userX509Cert       
880        open(MyProxyClient.DEF_PROXY_FILEPATH, 'w').write(outStr)
881        try:
882            # Make sure permissions are set correctly
883            os.chmod(MyProxyClient.DEF_PROXY_FILEPATH, 
884                     MyProxyClient.PROXY_FILE_PERMISSIONS)
885        except Exception, e:
886            # Don't leave the file lying around if couldn't change it's
887            # permissions
888            os.unlink(MyProxyClient.DEF_PROXY_FILEPATH)
889           
890            log.error('Unable to set %o permissions for proxy file "%s": %s'% 
891                      (MyProxyClient.PROXY_FILE_PERMISSIONS,
892                       MyProxyClient.DEF_PROXY_FILEPATH, e))
893            raise
894
895    @classmethod
896    def readProxyFile(cls, filePath=None):
897        """Read proxy cert file following the format used by myproxy-logon -
898        proxy, cert, private key, user cert.
899       
900        @rtype: tuple
901        @return: tuple containing proxy cert, private key, user cert"""
902        if filePath is None:
903            filePath = MyProxyClient.DEF_PROXY_FILEPATH
904           
905        if filePath is None:
906            MyProxyClientConfigError("Error setting proxy file path - invalid "
907                                     "platform?")
908               
909        proxy = open(MyProxyClient.DEF_PROXY_FILEPATH).read()
910       
911        # Split certs and key into separate tuple items
912        return tuple(['-----BEGIN'+i for i in proxy.split('-----BEGIN')[1:]])
913
914    def put(self,
915            username,
916            passphrase,
917            userCertFile,
918            userKeyFile, 
919            lifetime=None,
920            sslCertFile=None,
921            sslKeyFile=None,
922            sslKeyFilePassphrase=None):
923        """Store a proxy credential on the server
924       
925        Unfortunately this method is not implemented as it requires the creation
926        of a proxy certificate by the client but PyOpenSSL doesn't currently
927        support the required proxyCertInfo X.509 certificate extension
928       
929        @raise NotImplementedError: see above
930       
931        @type username: string
932        @param username: username selected for new credential
933        @type passphrase: string
934        @param passphrase: pass-phrase for new credential.  This will be used
935        by the server to authenticate later requests.  IT must be at least
936        6 characters.  The server may impose other restrictions too depending
937        on its configuration.
938        @type certFile: string
939        @param certFile: user's X.509 proxy certificate in PEM format
940        @type keyFile: string
941        @param keyFile: equivalent private key file in PEM format
942        @type sslCertFile: string
943        @param sslCertFile: certificate used for client authentication with
944        the MyProxy server SSL connection.  If not set,
945        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
946        @type sslKeyFile: string
947        @param sslKeyFile: corresponding private key file.  See explanation
948        for sslCertFile
949        @type sslKeyFilePassphrase: string
950        @param sslKeyFilePassphrase: passphrase for sslKeyFile.  Omit if the
951        private key is not password protected. 
952        @type lifetime: int / None
953        @param lifetime: the maximum lifetime allowed for retrieved proxy
954        credentials in seconds. defaults to proxyCertMaxLifetime attribute value
955        """
956        raise NotImplementedError('put method is not currently implemented.  '
957                                  'It requires the creation of a proxy '
958                                  'certificate by the client but PyOpenSSL '
959                                  'doesn\'t currently support the required '
960                                  'proxyCertInfo X.509 certificate extension.')
961
962    def info(self,
963             username, 
964             sslCertFile=None,
965             sslKeyFile=None,
966             sslKeyFilePassphrase=None):
967        """return True/False whether credentials exist on the server for a
968        given username
969       
970        @raise MyProxyClientGetError:
971        @raise MyProxyClientRetrieveError:
972       
973        @type username: string
974        @param username: username selected for credential
975        @type sslCertFile: string
976        @param sslCertFile: certificate used for client authentication with
977        the MyProxy server SSL connection.  This ID will be set as the owner
978        of the stored credentials.  Only the owner can later remove
979        credentials with myproxy-destroy or the destroy method.  If not set,
980        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
981        @type sslKeyFile: string
982        @param sslKeyFile: corresponding private key file.  See explanation
983        for sslCertFile
984        @type sslKeyFilePassphrase: string
985        @param sslKeyFilePassphrase: passphrase for sslKeyFile.  Omit if the
986        private key is not password protected.
987        """
988        globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME)
989        if not sslCertFile:
990            if globusLoc:
991                sslCertFile = os.path.join(globusLoc, 
992                                            *MyProxyClient.HOSTCERT_SUBDIRPATH)
993                sslKeyFile = os.path.join(globusLoc, 
994                                            *MyProxyClient.HOSTKEY_SUBDIRPATH)
995            else:
996                raise MyProxyClientError(
997            "No client authentication cert. and private key file were given")
998
999        # Set-up SSL connection
1000        conn = self._initConnection(certFile=sslCertFile,
1001                                    keyFile=sslKeyFile,
1002                                    keyFilePassphrase=sslKeyFilePassphrase)
1003       
1004        conn.connect((self.hostname, self.port))
1005       
1006        # send globus compatibility stuff
1007        conn.write('0')
1008   
1009        # send info command - ensure conversion from unicode before writing
1010        cmd = MyProxyClient.INFO_CMD % username
1011        conn.write(str(cmd))
1012   
1013        # process server response
1014        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1015         
1016        # Pass in the names of fields to return in the dictionary 'field'
1017        respCode, errorTxt, field = self._deserializeResponse(dat, 
1018                                                              'CRED_START_TIME', 
1019                                                              'CRED_END_TIME', 
1020                                                              'CRED_OWNER')
1021
1022        return not bool(respCode), errorTxt, field
1023
1024    def changePassphrase(self,
1025                         username, 
1026                         passphrase,
1027                         newPassphrase,
1028                         sslCertFile=None,
1029                         sslKeyFile=None,
1030                         sslKeyFilePassphrase=None):
1031        """change pass-phrase protecting the credentials for a given username
1032       
1033        @raise MyProxyClientGetError:
1034        @raise MyProxyClientRetrieveError:
1035       
1036        @param username: username of credential
1037        @param passphrase: existing pass-phrase for credential
1038        @param newPassphrase: new pass-phrase to replace the existing one.
1039        @param sslCertFile: certificate used for client authentication with
1040        the MyProxy server SSL connection.  This ID will be set as the owner
1041        of the stored credentials.  Only the owner can later remove
1042        credentials with myproxy-destroy or the destroy method.  If not set,
1043        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
1044        @param sslKeyFile: corresponding private key file.  See explanation
1045        for sslCertFile
1046        @param sslKeyFilePassphrase: passphrase for sslKeyFile.  Omit if the
1047        private key is not password protected. 
1048        @return none
1049        """
1050        globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME)
1051        if not sslCertFile or not sslKeyFile:
1052            if globusLoc:
1053                sslCertFile = os.path.join(globusLoc, 
1054                                           *MyProxyClient.HOSTCERT_SUBDIRPATH)
1055                sslKeyFile = os.path.join(globusLoc, 
1056                                          *MyProxyClient.HOSTKEY_SUBDIRPATH)
1057            else:
1058                raise MyProxyClientError(
1059            "No client authentication cert. and private key file were given")
1060       
1061        # Set-up SSL connection
1062        conn = self._initConnection(certFile=sslCertFile,
1063                                    keyFile=sslKeyFile,
1064                                    keyFilePassphrase=sslKeyFilePassphrase)
1065
1066        conn.connect((self.hostname, self.port))
1067       
1068        # send globus compatibility stuff
1069        conn.write('0')
1070   
1071        # send command - ensure conversion from unicode before writing
1072        cmd = MyProxyClient.CHANGE_PASSPHRASE_CMD % (username, 
1073                                                     passphrase,
1074                                                     newPassphrase)
1075        conn.write(str(cmd))
1076   
1077        # process server response
1078        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1079           
1080        respCode, errorTxt = self._deserializeResponse(dat)
1081        if respCode:
1082            raise MyProxyClientGetError(errorTxt)
1083
1084    def destroy(self,
1085                username, 
1086                sslCertFile=None,
1087                sslKeyFile=None,
1088                sslKeyFilePassphrase=None):
1089        """destroy credentials from the server for a given username
1090       
1091        @raise MyProxyClientGetError:
1092        @raise MyProxyClientRetrieveError:
1093       
1094        @param username: username selected for credential
1095        @param sslCertFile: certificate used for client authentication with
1096        the MyProxy server SSL connection.  This ID will be set as the owner
1097        of the stored credentials.  Only the owner can later remove
1098        credentials with myproxy-destroy or the destroy method.  If not set,
1099        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
1100        @param sslKeyFile: corresponding private key file.  See explanation
1101        for sslCertFile
1102        @param sslKeyFilePassphrase: passphrase for sslKeyFile.  Omit if the
1103        private key is not password protected. 
1104        @return none
1105        """
1106        globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME)
1107        if not sslCertFile or not sslKeyFile:
1108            if globusLoc:
1109                sslCertFile = os.path.join(globusLoc, 
1110                                         *MyProxyClient.HOSTCERT_SUBDIRPATH)
1111                sslKeyFile = os.path.join(globusLoc, 
1112                                         *MyProxyClient.HOSTKEY_SUBDIRPATH)
1113            else:
1114                raise MyProxyClientError(
1115            "No client authentication cert. and private key file were given")
1116       
1117        # Set-up SSL connection
1118        conn = self._initConnection(certFile=sslCertFile,
1119                                    keyFile=sslKeyFile,
1120                                    keyFilePassphrase=sslKeyFilePassphrase)
1121
1122        conn.connect((self.hostname, self.port))
1123       
1124        # send globus compatibility stuff
1125        conn.write('0')
1126   
1127        # send destroy command - ensure conversion from unicode before writing
1128        cmd = MyProxyClient.DESTROY_CMD % username
1129        conn.write(str(cmd))
1130   
1131        # process server response
1132        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1133           
1134        respCode, errorTxt = self._deserializeResponse(dat)
1135        if respCode:
1136            raise MyProxyClientGetError(errorTxt)
1137
1138    def store(self,
1139              username,
1140              passphrase, 
1141              certFile,
1142              keyFile,
1143              sslCertFile=None,
1144              sslKeyFile=None,
1145              sslKeyFilePassphrase=None,
1146              lifetime=None,
1147              force=True):
1148        """Upload credentials to the server
1149       
1150        @raise MyProxyClientGetError:
1151        @raise MyProxyClientRetrieveError:
1152       
1153        @type username: string
1154        @param username: username selected for new credential
1155        @type passphrase: string
1156        @param passphrase: pass-phrase for new credential.  This is the pass
1157        phrase which protects keyfile.
1158        @type certFile: string
1159        @param certFile: user's X.509 certificate in PEM format
1160        @type keyFile: string
1161        @param keyFile: equivalent private key file in PEM format
1162        @type sslCertFile: string
1163        @param sslCertFile: certificate used for client authentication with
1164        the MyProxy server SSL connection.  This ID will be set as the owner
1165        of the stored credentials.  Only the owner can later remove
1166        credentials with myproxy-destroy or the destroy method.  If not set,
1167        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem or if this
1168        is not set, certFile
1169        @type sslKeyFile: string
1170        @param sslKeyFile: corresponding private key file.  See explanation
1171        for sslCertFile
1172        @type sslKeyFilePassphrase: string
1173        @param sslKeyFilePassphrase: passphrase for sslKeyFile.  Omit if the
1174        private key is not password protected.  Nb. keyFile is expected to
1175        be passphrase protected as this will be the passphrase used for
1176        logon / getDelegation.
1177        @type Force: bool
1178        @param force: set to True to overwrite any existing creds with the
1179        same username.  If, force=False a check is made with a call to info.
1180        If creds already, exist exit without proceeding
1181        """
1182       
1183        lifetime = lifetime or self.proxyCertMaxLifetime
1184
1185        # Inputs must be string type otherwise server will reject the request
1186        if isinstance(username, unicode):
1187            username = str(username)
1188           
1189        if isinstance(passphrase, unicode):
1190            passphrase = str(passphrase)
1191       
1192        globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME)
1193        if not sslCertFile or not sslKeyFile:
1194            if globusLoc:
1195                sslCertFile = os.path.join(globusLoc, 
1196                                           *MyProxyClient.HOSTCERT_SUBDIRPATH)
1197                sslKeyFile = os.path.join(globusLoc, 
1198                                          *MyProxyClient.HOSTKEY_SUBDIRPATH)
1199            else:
1200                # Default so that the owner is the same as the ID of the
1201                # credentials to be uploaded.
1202                sslCertFile = certFile
1203                sslKeyFile = keyFile
1204                sslKeyFilePassphrase = passphrase
1205               
1206        if not force:
1207            # Check credentials don't already exist
1208            if self.info(username,
1209                         sslCertFile=sslCertFile,
1210                         sslKeyFile=sslKeyFile,
1211                         sslKeyFilePassphrase=sslKeyFilePassphrase)[0]:
1212                raise MyProxyCredentialsAlreadyExist(
1213                        "Credentials already exist for user: %s" % username)
1214
1215        # Set up SSL connection
1216        conn = self._initConnection(certFile=sslCertFile,
1217                                    keyFile=sslKeyFile,
1218                                    keyFilePassphrase=sslKeyFilePassphrase)
1219       
1220        conn.connect((self.hostname, self.port))
1221       
1222        # send globus compatibility stuff
1223        conn.write('0')
1224   
1225        # send store command - ensure conversion from unicode before writing
1226        cmd = MyProxyClient.STORE_CMD % (username, lifetime)
1227        conn.write(str(cmd))
1228   
1229        # process server response
1230        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1231           
1232        respCode, errorTxt = self._deserializeResponse(dat)
1233        if respCode:
1234            raise MyProxyClientGetError(errorTxt)
1235       
1236        # Send certificate and private key
1237        certTxt = open(certFile).read()
1238        keyTxt = open(keyFile).read()
1239       
1240        conn.send(certTxt + keyTxt)
1241   
1242   
1243        # process server response
1244        resp = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1245        respCode, errorTxt = self._deserializeResponse(resp)
1246        if respCode:
1247            raise MyProxyClientRetrieveError(errorTxt)
1248       
1249    def logon(self, username, passphrase, lifetime=None, keyPair=None, 
1250              certReq=None, nBitsForKey=PRIKEY_NBITS, bootstrap=False,
1251              updateTrustRoots=False):
1252        """Retrieve a proxy credential from a MyProxy server
1253       
1254        Exceptions:  MyProxyClientGetError, MyProxyClientRetrieveError
1255       
1256        @type username: basestring
1257        @param username: username of credential
1258       
1259        @type passphrase: basestring
1260        @param passphrase: pass-phrase for private key of credential held on
1261        server
1262       
1263        @type lifetime: int
1264        @param lifetime: lifetime for generated certificate
1265       
1266        @type keyPair: OpenSSL.crypto.PKey
1267        @param keyPair: Public/Private key pair.  This is ignored if a
1268        certificate request is passed via the certReq keyword
1269       
1270        @type certReq: string
1271        @param certReq: ASN1 format certificate request, if none set, one is
1272        created along with a key pair
1273       
1274        @type nBitsForKey: int
1275        @param nBitsForKey: number of bits to use when generating key pair,
1276        defaults to the PRIKEY_NBITS class variable setting.  This keyword is
1277        ignored if a key pair is passed in from an external source via the
1278        keyPair keyword
1279       
1280        @rtype: tuple
1281        @return credentials as strings in PEM format: the
1282        user certificate, it's private key and the issuing certificate.  The
1283        issuing certificate is only set if the user certificate is a proxy
1284       
1285        @type bootstrap: bool
1286        @param bootstrap: If set to True, bootstrap trust roots i.e. connect to
1287        MyProxy server without verification of the server's SSL certificate
1288        against any CA certificates.  Set to False, for default behaviour:
1289        verify server SSL certificate against CA certificates held in location
1290        set by the "caCertDir" attribute.  If bootstrap is set, updateTrustRoots
1291        will be forced to True also
1292       
1293        @type updateTrustRoots: bool
1294        @param updateTrustRoots: set to True to update the trust roots
1295        """
1296        if bootstrap:
1297            log.info('Bootstrapping MyProxy server root of trust.')
1298           
1299            # Bootstrap implies update to trust roots
1300            updateTrustRoots = True
1301       
1302        if updateTrustRoots:
1303            self.getTrustRoots(username, 
1304                               passphrase, 
1305                               writeToCACertDir=True, 
1306                               bootstrap=bootstrap)
1307           
1308        lifetime = lifetime or self.proxyCertLifetime
1309
1310        # Certificate request may be passed as an input but if not generate it
1311        # here request here
1312        if certReq is None:
1313            # If no key pair was passed, generate here
1314            if keyPair is None:
1315                keyPair = self._createKeyPair(nBitsForKey=nBitsForKey)
1316               
1317            certReq = self._createCertReq(username, keyPair)
1318
1319        if keyPair is not None: 
1320            pemKeyPair = crypto.dump_privatekey(crypto.FILETYPE_PEM, keyPair)
1321       
1322        # Set-up SSL connection
1323        conn = self._initConnection()
1324        conn.connect((self.hostname, self.port))
1325       
1326        # send globus compatibility stuff
1327        conn.write('0')
1328   
1329        # send get command - ensure conversion from unicode before writing
1330        cmd = MyProxyClient.GET_CMD % (username, passphrase, lifetime)
1331           
1332        conn.write(str(cmd))
1333       
1334        # process server response
1335        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1336                   
1337        respCode, errorTxt = self._deserializeResponse(dat)
1338        if respCode:
1339            raise MyProxyClientGetError(errorTxt)
1340       
1341        # Send certificate request
1342        conn.send(certReq)
1343   
1344        # process certificates
1345        # - 1st byte , number of certs
1346        dat = conn.recv(1)
1347        nCerts = ord(dat[0])
1348       
1349        # - n certs
1350        dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1351   
1352        # process server response
1353        resp = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1354        respCode, errorTxt = self._deserializeResponse(resp)
1355        if respCode:
1356            raise MyProxyClientRetrieveError(errorTxt)     
1357   
1358        # deserialize certs from received cert data
1359        pemCerts = self._deserializeCerts(dat)
1360        if len(pemCerts) != nCerts:
1361            MyProxyClientRetrieveError("%d certs expected, %d received" % 
1362                                       (nCerts, len(pemCerts)))
1363   
1364        if keyPair is not None:
1365            # Return certs and private key
1366            # - proxy or dynamically issued certificate (MyProxy CA mode)
1367            # - private key
1368            # - rest of cert chain if proxy cert issued
1369            creds = [pemCerts[0], pemKeyPair]
1370            creds.extend(pemCerts[1:])
1371        else:
1372            # Key generated externally - return certificate chain only
1373            creds = pemCerts
1374
1375       
1376        return tuple(creds)
1377
1378    def getDelegation(self, *arg, **kw):
1379        """Retrieve proxy cert for user - same as logon"""
1380        return self.logon(*arg, **kw)
1381   
1382    def getTrustRoots(self, 
1383                      username='', 
1384                      passphrase='', 
1385                      writeToCACertDir=False,
1386                      bootstrap=False):
1387        """Get trust roots for the given MyProxy server
1388       
1389        @type username: basestring
1390        @param username: username (optional)
1391       
1392        @type passphrase: basestring
1393        @param passphrase: pass-phrase (optional)
1394        server
1395       
1396        @type writeToCACertDir: bool
1397        @param writeToCACertDir: if set to True, write the retrieved trust roots
1398        out to the directory specified by the "caCertDir" attribute
1399       
1400        @type bootstrap: bool
1401        @param bootstrap: If set to True, bootstrap trust roots i.e. connect to
1402        MyProxy server without verification of the server's SSL certificate
1403        against any CA certificates.  Set to False, for default behaviour:
1404        verify server SSL certificate against CA certificates held in location
1405        set by the "caCertDir" attribute.
1406       
1407        @return: trust root files as a dictionary keyed by file name with each
1408        item value set to the file contents
1409        @rtype: dict
1410        """
1411        if bootstrap:
1412            log.info('Bootstrapping MyProxy server root of trust.')
1413           
1414        # Set-up SSL connection
1415        conn = self._initConnection(verifyPeerWithTrustRoots=(not bootstrap))
1416        conn.connect((self.hostname, self.port))
1417       
1418        # send globus compatibility stuff
1419        conn.write('0')
1420   
1421        # send get command - ensure conversion from unicode before writing
1422        cmd = MyProxyClient.GET_TRUST_ROOTS_CMD % (username, passphrase)
1423        conn.write(str(cmd))
1424   
1425        # process server response chunks until all consumed
1426        dat = ''
1427        tries = 0
1428        try:
1429            for tries in range(MyProxyClient.MAX_RECV_TRIES):
1430                dat += conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
1431        except SSL.SysCallError:
1432            # Expect this exception when response content exhausted
1433            pass
1434       
1435        # Precaution
1436        if tries == MyProxyClient.MAX_RECV_TRIES:
1437            log.warning('Maximum %d tries reached for getTrustRoots response '
1438                        'block retrieval with block size %d', 
1439                        MyProxyClient.MAX_RECV_TRIES,
1440                        MyProxyClient.SERVER_RESP_BLK_SIZE)
1441         
1442        fieldName = MyProxyClient.TRUSTED_CERTS_FIELDNAME
1443        prefix = MyProxyClient.TRUSTED_CERTS_FILEDATA_FIELDNAME_PREFIX 
1444        respCode, errorTxt, fileData = self._deserializeResponse(dat, 
1445                                                                 fieldName,
1446                                                                 prefix)
1447        if respCode:
1448            raise MyProxyClientGetTrustRootsError(errorTxt)
1449       
1450        filesDict = dict([(k.split(prefix, 1)[1], base64.b64decode(v)) 
1451                          for k, v in fileData.items() if k != fieldName])
1452       
1453        if writeToCACertDir:
1454            for fileName, fileContents in filesDict.items():
1455                filePath = os.path.join(self.caCertDir, fileName)
1456                open(filePath, 'wb').write(fileContents)
1457               
1458        return filesDict
1459       
Note: See TracBrowser for help on using the repository browser.