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

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

MyProxy? Client 1.1.2 release

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