source: TI12-security/trunk/python/ndg.security.server/ndg/security/server/MyProxy.py @ 2170

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.server/ndg/security/server/MyProxy.py@2170
Revision 2170, 38.3 KB checked in by pjkersha, 14 years ago (diff)

python/ndg.security.server/setup.py: tidied up package dependencies list

python/ndg.security.server/ndg/security/server/MyProxy.py: replaced old ref to
self.env with os.environ

python/ndg.security.common/setup.py:

  • made ElementTree install dependent on Python version - 2.5 doesn't need it
  • tidied up package dependencies list

python/ndg.security.test/ndg/security/test/AttAuthority/siteAAttAuthorityProperties.xml,
python/ndg.security.test/ndg/security/test/AttAuthority/siteBAttAuthorityProperties.xml:
set useSSL to False.

python/ndg.security.test/ndg/security/test/AttAuthority/attAuthorityClientTest.cfg:
connect to local service.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1"""MyProxy Client interface
2
3Based on original program myproxy_logon Tom Uram <turam@mcs.anl.gov>
4
5Also contains OpenSSLConfig class, a wrapper to the openssl configuration
6file.
7
8NERC Data Grid Project
9
10@author P J Kershaw 02/06/05
11
12Major re-write to implement methods with SSL calls with M2Crypto rather use
13calls to myproxy client executables
14
15@copyright (C) 2006 CCLRC & NERC
16
17@license This software may be distributed under the terms of the Q Public
18License, version 1.0 or later.
19"""
20reposID = '$Id$'
21
22import os
23import socket
24from M2Crypto import X509, RSA, EVP, m2, BIO, SSL
25
26import base64
27
28
29# For parsing of properties file
30import cElementTree as ElementTree
31
32from ndg.security.common.openssl import OpenSSLConfig, OpenSSLConfigError
33
34
35class MyProxyClientError(Exception):
36    """Catch all exception class"""
37   
38class GetError(Exception):
39    """Exceptions arising from get request to server"""
40   
41class RetrieveError(Exception):
42    """Error recovering a response from MyProxy"""
43
44class _HostCheck(SSL.Checker.Checker):
45    """Override SSL.Checker.Checker to allow additional check of MyProxy
46    server identity.  If hostname doesn't match, allow match of host's 
47    Distinguished Name against MYPROXY_SERVER_DN setting"""
48
49    def __init__(self, 
50                 myProxyServerDN=os.environ.get('MYPROXY_SERVER_DN'),
51                 cnHostPfx='host/',
52                 **kw):
53        """Override parent class __init__ to enable setting of myProxyServerDN
54        setting
55       
56        @type myProxyServerDN: string
57        @keyword myProxyServerDN: Set the expected Distinguished Name of the
58        MyProxy server to avoid errors matching hostnames.  This is useful
59        where the hostname is not fully qualified
60       
61        @type cnHostPfx: string
62        @keyword cnHostPfx: globus host certificates are
63        generated by default with a 'host/' prefix to the host name.  Set
64        this keyword to '' or None to override and omit the prefix"""
65       
66        SSL.Checker.Checker.__init__(self, **kw)
67       
68        self.myProxyServerDN = myProxyServerDN
69        self.cnHostPfx = cnHostPfx
70       
71       
72    def __call__(self, peerCert, host=None):
73        """Carry out checks on server ID
74        @param peerCert: MyProxy server host certificate as M2Crypto.X509.X509
75        instance
76        @keyword host: name of host to check
77        """
78       
79        # Globus host certificate has a "host/" prefix - see explanation in
80        # __init__.__doc__
81        host = None or self.cnHostPfx + self.host
82       
83        try:
84            SSL.Checker.Checker.__call__(self, peerCert, host=host)
85           
86        except SSL.Checker.WrongHost, e:
87            # Try match against DN set from MYPROXY_SERVER_DN / config
88            # file setting
89            peerCertDN = '/' + \
90                    peerCert.get_subject().as_text().replace(', ', '/')
91            if peerCertDN != self.myProxyServerDN:
92                # They match - drop the exception and return all OK instead
93                raise e
94           
95        return True
96           
97       
98class MyProxyClient(object):
99    """MyProxy client interface
100   
101    Based on protocol definitions in:
102   
103    http://grid.ncsa.uiuc.edu/myproxy/protocol/
104   
105    @cvar __getCmd: get command string
106    @cvar __infoCmd: info command string
107    @cvar __destroyCmd: destroy command string
108    @cvar __changePassphrase: command string to change cred pass-phrase
109    @cvar __storeCmd: store command string
110    @cvar _hostCertSubDirPath: sub-directory path host certificate (as tuple)
111    @cvar _hostKeySubDirPath: sub-directory path to host key (as tuple)
112    @cvar _certReqDNparamName: names of parameters needed to generate a
113    certificate request e.g. CN, OU etc.
114    @cvar __validKeys: sets permissable element names for MyProxy XML config
115    file
116    """
117     
118    __getCmd="""VERSION=MYPROXYv2
119COMMAND=0
120USERNAME=%s
121PASSPHRASE=%s
122LIFETIME=%d\0"""
123 
124    __infoCmd="""VERSION=MYPROXYv2
125COMMAND=2
126USERNAME=%s
127PASSPHRASE=PASSPHRASE
128LIFETIME=0"""
129 
130    __destroyCmd="""VERSION=MYPROXYv2
131COMMAND=3
132USERNAME=%s
133PASSPHRASE=PASSPHRASE
134LIFETIME=0"""
135
136    __changePassphraseCmd="""VERSION=MYPROXYv2
137 COMMAND=4
138 USERNAME=%s
139 PASSPHRASE=%s
140 NEW_PHRASE=%s
141 LIFETIME=0"""
142   
143    __storeCmd="""VERSION=MYPROXYv2
144COMMAND=5
145USERNAME=%s
146PASSPHRASE=
147LIFETIME=%d\0"""
148
149    _hostCertSubDirPath = ('etc', 'hostcert.pem')
150    _hostKeySubDirPath = ('etc', 'hostkey.pem')
151   
152    # valid configuration property keywords
153    __validKeys = ('hostname',
154                   'port',
155                   'serverDN',
156                   'serverCNprefix',
157                   'gridSecurityDir',
158                   'openSSLConfFileName',
159                   'tmpDir',
160                   'proxyCertMaxLifetime',
161                   'proxyCertLifetime',
162                   'caCertFile')
163
164    #_________________________________________________________________________           
165    def __init__(self, propFilePath=None, **prop):
166        """Make an initial settings for client connections to MyProxy
167       
168        Settings are held in a dictionary which can be set from **prop,
169        a call to setProperties() or by passing settings in an XML file
170        given by propFilePath
171       
172        @param propFilePath:   set properties via a configuration file
173        @param **prop:         set properties via keywords - see __validKeys
174        class variable for a list of these
175        """
176       
177        # Check for parameter names set from input
178        #self.certReqDNparam = None
179
180        # settings dictionary
181        self.__prop = {}
182       
183        # Server host name - take from environment variable if available
184        if 'MYPROXY_SERVER' in os.environ:
185            self.__prop['hostname'] = os.environ['MYPROXY_SERVER']
186           
187        # ... and port number
188        if 'MYPROXY_SERVER_PORT' in os.environ:
189            self.__prop['port'] = int(os.environ['MYPROXY_SERVER_PORT'])
190        else:
191            # Usual default is ...
192            self.__prop['port'] = 7512
193           
194        self.__prop['proxyCertLifetime'] = 43200
195        self.__prop['proxyCertMaxLifetime'] = 43200
196       
197        # Configuration file used to get default subject when generating a
198        # new proxy certificate request
199        self.__openSSLConf = OpenSSLConfig()
200
201       
202        # Properties set via input keywords
203        self.setProperties(**prop)
204
205        # If properties file is set any parameters settings in file will
206        # override those set by input keyword
207        if propFilePath is not None:
208            self.readProperties(propFilePath)
209       
210
211        # Grid security directory - environment variable setting overrides
212        if 'GRID_SECURITY_DIR' in os.environ:
213            self.__prop['gridSecurityDir'] = os.environ['GRID_SECURITY_DIR']           
214
215            self.__openSSLConf.filePath = \
216                            os.path.join(self.__prop['gridSecurityDir'],
217                                         self.__prop['openSSLConfFileName'])
218            self.__openSSLConf.read()
219
220
221    #_________________________________________________________________________
222    def setProperties(self, **prop):
223        """Update existing properties from an input dictionary
224        Check input keys are valid names"""
225       
226        invalidKeys = [key for key in prop if key not in self.__validKeys]
227        if invalidKeys:
228            raise MyProxyClientError, 'Invalid property name(s) set: "%s"' % \
229                                    '", "'.join(invalidKeys)
230               
231        self.__prop.update(prop)
232
233        # Update openssl conf file path
234        if 'gridSecurityDir' in prop or 'openSSLConfFileName' in prop:           
235            self.__openSSLConf.filePath = \
236                            os.path.join(self.__prop['gridSecurityDir'],
237                                         self.__prop['openSSLConfFileName'])
238            self.__openSSLConf.read()
239           
240
241    #_________________________________________________________________________
242    def readProperties(self, propFilePath=None, propElem=None):
243        """Read XML properties from a file or cElementTree node
244       
245        propFilePath|propertiesElem
246
247        @type propFilePath: string
248        @keyword propFilePath: set to read from the specified file
249       
250        @type propElem: ElementTree node
251        @keyword propElem: set to read beginning from a cElementTree node
252        """
253
254        if propFilePath is not None:
255            try:
256                tree = ElementTree.parse(propFilePath)
257                propElem = tree.getroot()
258               
259            except IOError, e:
260                raise MyProxyClientError, \
261                                "Error parsing properties file \"%s\": %s" % \
262                                (e.filename, e.strerror)           
263            except Exception, e:
264                raise MyProxyClientError, \
265                                "Error parsing properties file: %s" % str(e)
266                               
267        if propElem is None:
268            raise MyProxyClientError, \
269                    "Root element for parsing properties file is not defined"
270
271
272        # Get properties as a data dictionary
273        prop = {}
274        try:
275            for elem in propElem:
276                # Check for string type to avoid exceptions from isdigit and
277                # expandvars
278                if isinstance(elem.text, basestring):
279                    if elem.text.isdigit():
280                        prop[elem.tag] = int(elem.text)
281                    else:
282                        prop[elem.tag] = os.path.expandvars(elem.text)
283                else:
284                    prop[elem.tag] = elem.text
285                   
286        except Exception, e:
287            raise SessionMgrError, \
288                "Error parsing tag \"%s\" in properties file" % elem.tag
289           
290        self.setProperties(**prop)
291
292
293    #_________________________________________________________________________
294    def __getOpenSSLConfig(self):
295        "Get OpenSSLConfig object property method"
296        return self.__openSSLConfig
297   
298    openSSLConfig = property(fget=__getOpenSSLConfig,
299                             doc="OpenSSLConfig object")
300
301           
302    #_________________________________________________________________________       
303    def _initConnection(self, 
304                        ownerCertFile=None, 
305                        ownerKeyFile=None,
306                        ownerPassphrase=None):
307        """Initialise connection setting up SSL context and client and
308        server side identity checks
309       
310        @param ownerCertFile: client certificate and owner of credential
311        to be acted on.  Can be a proxy cert + proxy's signing cert.  Cert
312        and private key are not necessary for getDelegation / logon calls
313        @param ownerKeyFile: client private key file
314        @param ownerPassphrase: pass-phrase protecting private key if set -
315        not needed in the case of a proxy private key
316        """
317
318        # Must be version 3 for MyProxy
319        context = SSL.Context(protocol='sslv3')
320        context.load_verify_locations(cafile=self.__prop['caCertFile'])
321       
322        if ownerCertFile and ownerKeyFile:
323            context.load_cert_chain(ownerCertFile,
324                                keyfile=ownerKeyFile,
325                                callback=lambda *ar, **kw: ownerPassphrase)
326               
327            # Stop if peer's certificate can't be verified
328            context.set_allow_unknown_ca(False)
329           
330            # Verify peer's certificate
331            context.set_verify(SSL.verify_peer, 1) 
332       
333           
334        # Disable for compatibility with myproxy server (er, globus)
335        # globus doesn't handle this case, apparently, and instead
336        # chokes in proxy delegation code
337        context.set_options(m2.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS)
338       
339        # connect to myproxy server
340        conn = SSL.Connection(context, sock=socket.socket())
341       
342        # Check server host identity - if host doesn't match use explicit
343        # 'serverDN'
344        # host/<hostname> one
345        hostCheck = _HostCheck(host=self.__prop['hostname'],
346                               myProxyServerDN=self.__prop.get('serverDN'),
347                               cnHostPfx=self.__prop.get('serverCNprefix'))
348        conn.set_post_connection_check_callback(hostCheck)
349       
350        return conn
351   
352           
353    #_________________________________________________________________________       
354    def _createCertReq(self, CN, nBitsForKey=1024, messageDigest="md5"):
355        """
356        Create a certificate request.
357       
358        @param CN: Common Name for certificate - effectively the same as the
359        username for the MyProxy credential
360        @param nBitsForKey: number of bits for private key generation -
361        default is 1024
362        @param messageDigest: message disgest type - default is MD5
363        @return tuple of certificate request PEM text and private key PEM text
364        """
365       
366        # Check all required certifcate request DN parameters are set               
367        # Create certificate request
368        req = X509.Request()
369   
370        # Generate keys
371        key = RSA.gen_key(nBitsForKey, m2.RSA_F4)
372   
373        # Create public key object
374        pubKey = EVP.PKey()
375        pubKey.assign_rsa(key)
376       
377        # Add the public key to the request
378        req.set_version(0)
379        req.set_pubkey(pubKey)
380       
381        defaultReqDN = self.__openSSLConf.reqDN
382             
383        # Set DN
384        x509Name = X509.X509_Name()
385        x509Name.CN = CN
386        x509Name.OU = defaultReqDN['OU']
387        x509Name.O = defaultReqDN['O']
388                       
389        req.set_subject_name(x509Name)
390       
391        req.sign(pubKey, messageDigest)
392       
393        return (req.as_asn1(), key.as_pem(cipher=None))
394   
395   
396    #_________________________________________________________________________           
397    def _deserializeResponse(self, msg, *fieldNames):
398        """
399        Deserialize a MyProxy server response
400       
401        @param msg: string response message from MyProxy server
402        @*fieldNames: the content of additional fields can be returned by
403        specifying the field name or names as additional arguments e.g. info
404        method passes 'CRED_START_TIME', 'CRED_END_TIME' and 'CRED_OWNER'
405        field names.  The content of fields is returned as an extra element
406        in the tuple response.  This element is itself a dictionary indexed
407        by field name.
408        @return tuple of integer response and errorTxt string (if any)
409        """
410       
411        lines = msg.split('\n')
412       
413        # get response value
414        responselines = filter(lambda x: x.startswith('RESPONSE'), lines)
415        responseline = responselines[0]
416        respCode = int(responseline.split('=')[1])
417       
418        # get error text
419        errorTxt = ""
420        errorlines = filter(lambda x: x.startswith('ERROR'), lines)
421        for e in errorlines:
422            etext = e.split('=', 1)[1]
423            errorTxt += os.linesep + etext
424       
425        if fieldNames:
426            fields = {}
427                       
428            for fieldName in fieldNames:
429                fieldlines = filter(lambda x: x.startswith(fieldName), lines)
430                try:
431                    # Nb. '1' arg to split ensures owner DN is not split up.
432                    field = fieldlines[0].split('=', 1)[1]
433                    fields[fieldName]=field.isdigit() and int(field) or field
434
435                except IndexError:
436                    # Ignore fields that aren't found
437                    pass
438               
439            return respCode, errorTxt, fields
440        else:
441            return respCode, errorTxt
442   
443 
444    #_________________________________________________________________________             
445    def _deserializeCerts(self, inputDat):
446        """Unpack certificates returned from a get delegation call to the
447        server
448       
449        @param inputDat: string containing the proxy cert and private key
450        and signing cert all in DER format
451       
452        @return list containing the equivalent to the input in PEM format"""
453        pemCerts = []       
454        dat = inputDat
455       
456        while dat:   
457            # find start of cert, get length       
458            ind = dat.find('\x30\x82')
459            if ind < 0:
460                break
461               
462            len = 256*ord(dat[ind+2]) + ord(dat[ind+3])
463   
464            # extract der-format cert, and convert to pem
465            derCert = dat[ind:ind+len+4]
466           
467            x509 = X509.load_cert_string(derCert, type=X509.TYPE_ASN1)
468            pemCert = x509.as_pem()
469           
470            pemCerts.append(pemCert)
471   
472            # trim cert from data
473            dat = dat[ind + len + 4:]
474           
475        return pemCerts
476
477
478    #_________________________________________________________________________   
479    def info(self,
480             username, 
481             ownerCertFile=None,
482             ownerKeyFile=None,
483             ownerPassphrase=None):
484        """return True/False whether credentials exist on the server for a
485        given username
486       
487        Exceptions:  GetError, RetrieveError
488       
489        @param username: username selected for credential
490        @param ownerCertFile: certificate used for client authentication with
491        the MyProxy server SSL connection.  This ID will be set as the owner
492        of the stored credentials.  Only the owner can later remove
493        credentials with myproxy-destroy or the destroy method.  If not set,
494        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
495        @param ownerKeyFile: corresponding private key file.  See explanation
496        for ownerCertFile
497        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
498        private key is not password protected.
499        @return none
500        """
501        globusLoc = os.environ.get('GLOBUS_LOCATION')
502        if not ownerCertFile or not ownerKeyFile:
503            if globusLoc:
504                ownerCertFile = os.path.join(globusLoc, 
505                                         *MyProxyClient._hostCertSubDirPath)
506                ownerKeyFile = os.path.join(globusLoc, 
507                                         *MyProxyClient._hostKeySubDirPath)
508            else:
509                raise MyProxyClientError, \
510            "No client authentication cert. and private key file were given"
511
512        # Set-up SSL connection
513        conn = self._initConnection(ownerCertFile=ownerCertFile,
514                                    ownerKeyFile=ownerKeyFile,
515                                    ownerPassphrase=ownerPassphrase)
516       
517        conn.connect((self.__prop['hostname'], self.__prop['port']))
518       
519        # send globus compatibility stuff
520        conn.write('0')
521   
522        # send info command - ensure conversion from unicode before writing
523        cmd = MyProxyClient.__infoCmd % username
524        conn.write(str(cmd))
525   
526        # process server response
527        dat = conn.recv(8192)
528         
529        # Pass in the names of fields to return in the dictionary 'field'
530        respCode, errorTxt, field = self._deserializeResponse(dat, 
531                                                         'CRED_START_TIME', 
532                                                         'CRED_END_TIME', 
533                                                         'CRED_OWNER')
534
535        return not bool(respCode), errorTxt, field
536
537
538    #_________________________________________________________________________   
539    def changePassphrase(self,
540                         username, 
541                         passphrase,
542                         newPassphrase,
543                         ownerCertFile=None,
544                         ownerKeyFile=None,
545                         ownerPassphrase=None):
546        """change pass-phrase protecting the credentials for a given username
547       
548        Exceptions:  GetError, RetrieveError
549       
550        @param username: username of credential
551        @param passphrase: existing pass-phrase for credential
552        @param newPassphrase: new pass-phrase to replace the existing one.
553        @param ownerCertFile: certificate used for client authentication with
554        the MyProxy server SSL connection.  This ID will be set as the owner
555        of the stored credentials.  Only the owner can later remove
556        credentials with myproxy-destroy or the destroy method.  If not set,
557        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
558        @param ownerKeyFile: corresponding private key file.  See explanation
559        for ownerCertFile
560        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
561        private key is not password protected. 
562        @return none
563        """
564        globusLoc = os.environ.get('GLOBUS_LOCATION')
565        if not ownerCertFile or not ownerKeyFile:
566            if globusLoc:
567                ownerCertFile = os.path.join(globusLoc, 
568                                         *MyProxyClient._hostCertSubDirPath)
569                ownerKeyFile = os.path.join(globusLoc, 
570                                         *MyProxyClient._hostKeySubDirPath)
571            else:
572                raise MyProxyClientError, \
573            "No client authentication cert. and private key file were given"
574       
575        # Set-up SSL connection
576        conn = self._initConnection(ownerCertFile=ownerCertFile,
577                                    ownerKeyFile=ownerKeyFile,
578                                    ownerPassphrase=ownerPassphrase)
579
580        conn.connect((self.__prop['hostname'], self.__prop['port']))
581       
582        # send globus compatibility stuff
583        conn.write('0')
584   
585        # send command - ensure conversion from unicode before writing
586        cmd = MyProxyClient.__changePassphraseCmd % (username, 
587                                                     passphrase,
588                                                     newPassphrase)
589        conn.write(str(cmd))
590   
591        # process server response
592        dat = conn.recv(8192)
593           
594        respCode, errorTxt = self._deserializeResponse(dat)
595        if respCode:
596            raise GetError, errorTxt
597
598
599    #_________________________________________________________________________   
600    def destroy(self,
601                username, 
602                ownerCertFile=None,
603                ownerKeyFile=None,
604                ownerPassphrase=None):
605        """destroy credentials from the server for a given username
606       
607        Exceptions:  GetError, RetrieveError
608       
609        @param username: username selected for credential
610        @param ownerCertFile: certificate used for client authentication with
611        the MyProxy server SSL connection.  This ID will be set as the owner
612        of the stored credentials.  Only the owner can later remove
613        credentials with myproxy-destroy or the destroy method.  If not set,
614        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
615        @param ownerKeyFile: corresponding private key file.  See explanation
616        for ownerCertFile
617        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
618        private key is not password protected. 
619        @return none
620        """
621        globusLoc = os.environ.get('GLOBUS_LOCATION')
622        if not ownerCertFile or not ownerKeyFile:
623            if globusLoc:
624                ownerCertFile = os.path.join(globusLoc, 
625                                         *MyProxyClient._hostCertSubDirPath)
626                ownerKeyFile = os.path.join(globusLoc, 
627                                         *MyProxyClient._hostKeySubDirPath)
628            else:
629                raise MyProxyClientError, \
630            "No client authentication cert. and private key file were given"
631       
632        # Set-up SSL connection
633        conn = self._initConnection(ownerCertFile=ownerCertFile,
634                                    ownerKeyFile=ownerKeyFile,
635                                    ownerPassphrase=ownerPassphrase)
636
637        conn.connect((self.__prop['hostname'], self.__prop['port']))
638       
639        # send globus compatibility stuff
640        conn.write('0')
641   
642        # send destroy command - ensure conversion from unicode before writing
643        cmd = MyProxyClient.__destroyCmd % username
644        conn.write(str(cmd))
645   
646        # process server response
647        dat = conn.recv(8192)
648           
649        respCode, errorTxt = self._deserializeResponse(dat)
650        if respCode:
651            raise GetError, errorTxt
652
653
654    #_________________________________________________________________________   
655    def store(self,
656              username,
657              passphrase, 
658              certFile,
659              keyFile,
660              ownerCertFile=None,
661              ownerKeyFile=None,
662              ownerPassphrase=None,
663              lifetime=None,
664              force=True):
665        """Upload credentials to the server
666       
667        Exceptions:  GetError, RetrieveError
668       
669        @param username: username selected for new credential
670        @param passphrase: pass-phrase for new credential.  This is the pass
671        phrase which protects keyfile.
672        @param certFile: user's X.509 certificate in PEM format
673        @param keyFile: equivalent private key file in PEM format
674        @param ownerCertFile: certificate used for client authentication with
675        the MyProxy server SSL connection.  This ID will be set as the owner
676        of the stored credentials.  Only the owner can later remove
677        credentials with myproxy-destroy or the destroy method.  If not set,
678        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem or if this
679        is not set, certFile
680        @param ownerKeyFile: corresponding private key file.  See explanation
681        for ownerCertFile
682        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
683        private key is not password protected.  Nb. keyFile is expected to
684        be passphrase protected as this will be the passphrase used for
685        logon / getDelegation.
686        @param force: set to True to overwrite any existing creds with the
687        same username.  If, force=False a check is made with a call to info.
688        If creds already, exist exit without proceeding
689        @return none
690        """
691       
692        lifetime = lifetime or self.__prop['proxyCertMaxLifetime']
693
694        # Inputs must be string type otherwise server will reject the request
695        if isinstance(username, unicode):
696            username = str(username)
697           
698        if isinstance(passphrase, unicode):
699            passphrase = str(passphrase)
700       
701        globusLoc = os.environ.get('GLOBUS_LOCATION')
702        if not ownerCertFile or not ownerKeyFile:
703            if globusLoc:
704                ownerCertFile = os.path.join(globusLoc, 
705                                         *MyProxyClient._hostCertSubDirPath)
706                ownerKeyFile = os.path.join(globusLoc, 
707                                         *MyProxyClient._hostKeySubDirPath)
708            else:
709                # Default so that the owner is the same as the ID of the
710                # credentials to be uploaded.
711                ownerCertFile = certFile
712                ownerKeyFile = keyFile
713                ownerPassphrase = passphrase
714               
715        if not force:
716            # Check credentials don't already exist
717            if self.info(username,
718                         ownerCertFile=ownerCertFile,
719                         ownerKeyFile=ownerKeyFile,
720                         ownerPassphrase=ownerPassphrase)[0]:
721                raise MyProxyClientError, \
722                        "Credentials already exist for user: %s" % username
723
724        # Set up SSL connection
725        conn = self._initConnection(ownerCertFile=ownerCertFile,
726                                    ownerKeyFile=ownerKeyFile,
727                                    ownerPassphrase=ownerPassphrase)
728       
729        conn.connect((self.__prop['hostname'], self.__prop['port']))
730       
731        # send globus compatibility stuff
732        conn.write('0')
733   
734        # send store command - ensure conversion from unicode before writing
735        cmd = MyProxyClient.__storeCmd % (username, lifetime)
736        conn.write(str(cmd))
737   
738        # process server response
739        dat = conn.recv(8192)
740           
741        respCode, errorTxt = self._deserializeResponse(dat)
742        if respCode:
743            raise GetError, errorTxt
744       
745        # Send certificate and private key
746        certTxt = X509.load_cert(certFile).as_pem()
747        keyTxt = open(keyFile).read()
748       
749        conn.send(certTxt + keyTxt)
750   
751   
752        # process server response
753        resp = conn.recv(8192)
754        respCode, errorTxt = self._deserializeResponse(resp)
755        if respCode:
756            raise RetrieveError, errorTxt
757       
758       
759    #_________________________________________________________________________           
760    def logon(self, username, passphrase, lifetime=None):
761        """Retrieve a proxy credential from a MyProxy server
762       
763        Exceptions:  GetError, RetrieveError
764       
765        @param username: username of credential
766        @param passphrase: pass-phrase for private key of credential held on
767        server
768        @return list containing the credentials as strings in PEM format: the
769        proxy certificate, it's private key and the signing certificate.
770        """
771       
772        lifetime = lifetime or self.__prop['proxyCertLifetime']
773
774        # Generate certificate request here - any errors will be thrown
775        # prior to making the connection and so not upsetting the server
776        #
777        # - The client will generate a public/private key pair and send a
778        #   NULL-terminated PKCS#10 certificate request to the server.
779        certReq, priKey = self._createCertReq(username)
780
781        # Set-up SSL connection
782        conn = self._initConnection()
783        conn.connect((self.__prop['hostname'], self.__prop['port']))
784       
785        # send globus compatibility stuff
786        conn.write('0')
787   
788        # send get command - ensure conversion from unicode before writing
789        cmd = MyProxyClient.__getCmd % (username, passphrase, lifetime)
790        conn.write(str(cmd))
791   
792        # process server response
793        dat = conn.recv(8192)
794        respCode, errorTxt = self._deserializeResponse(dat)
795        if respCode:
796            raise GetError, errorTxt
797       
798        # Send certificate request
799        conn.send(certReq)
800   
801        # process certificates
802        # - 1st byte , number of certs
803        dat = conn.recv(1)
804        nCerts = ord(dat[0])
805       
806        # - n certs
807        dat = conn.recv(8192)
808   
809        # process server response
810        resp = conn.recv(8192)
811        respCode, errorTxt = self._deserializeResponse(resp)
812        if respCode:
813            raise RetrieveError, errorTxt
814   
815        # deserialize certs from received cert data
816        pemCerts = self._deserializeCerts(dat)
817        if len(pemCerts) != nCerts:
818            RetrieveError, "%d certs expected, %d received" % \
819                                                    (nCerts, len(pemCerts))
820   
821        # Return certs and private key
822        # - proxy cert
823        # - private key
824        # - rest of cert chain
825        creds = [pemCerts[0], priKey]
826        creds.extend(pemCerts[1:])
827       
828        return tuple(creds)
829       
830
831    def getDelegation(self, *arg, **kw):
832        """Retrieve proxy cert for user - same as logon"""
833        return self.logon(*arg, **kw)
834
835
836#_____________________________________________________________________________   
837def main():
838    import sys
839    import optparse
840    import getpass
841   
842    parser = optparse.OptionParser()
843    parser.add_option("-i", 
844                      "--info", 
845                      dest="info", 
846                      default=False,
847                      action="store_true",
848                      help="check whether a credential exists")
849
850    parser.add_option("-z", 
851                      "--destroy", 
852                      dest="destroy", 
853                      default=False,
854                      action="store_true",
855                      help="destroy credential")
856
857    parser.add_option("-C", 
858                      "--change-pass-phrase", 
859                      dest="changePassphrase", 
860                      default=False,
861                      action="store_true",
862                      help="change pass-phrase protecting credential")
863
864    parser.add_option("-g", 
865                      "--get-delegation", 
866                      dest="getDelegation", 
867                      default=False,
868                      action="store_true",
869                      help="Get delegation / logon")
870   
871    parser.add_option("-c", 
872                      "--certfile", 
873                      dest="certFile", 
874                      default=None,
875                      help="Certificate to be stored")
876   
877    parser.add_option("-y", 
878                      "--keyfile", 
879                      dest="keyFile", 
880                      default=None,
881                      help="Private key to be stored")
882   
883    parser.add_option("-w", 
884                      "--keyfile-passphrase", 
885                      dest="ownerPassphrase", 
886                      default=None,
887                      help="Pass-phrase for Private key used for SSL client")
888
889    parser.add_option("-s", 
890                      "--pshost", 
891                      dest="host", 
892                      help="The hostname of the MyProxy server to contact")
893   
894    parser.add_option("-p", 
895                      "--psport", 
896                      dest="port", 
897                      default=7512,
898                      type="int",
899                      help="The port of the MyProxy server to contact")
900   
901    parser.add_option("-l", 
902                      "--username", 
903                      dest="username", 
904                      help=\
905    "The username with which the credential is stored on the MyProxy server")
906
907    parser.add_option("-o", 
908                      "--out", 
909                      dest="outfile", 
910                      help=\
911    "The username with which the credential is stored on the MyProxy server")
912
913    parser.add_option("-t", 
914                      "--proxy-lifetime", 
915                      dest="lifetime", 
916                      default=43200,
917                      type="int",
918                      help=\
919    "The username with which the credential is stored on the MyProxy server")
920
921    (options, args) = parser.parse_args()
922   
923
924    # process options   
925    username = options.username
926    if not username:
927        if sys.platform == 'win32':
928            username = os.environ["USERNAME"]
929        else:
930            import pwd
931            username = pwd.getpwuid(os.geteuid())[0]
932
933    hostname = options.host or os.environ.get('MYPROXY_SERVER')
934    myProxy = MyProxyClient(hostname=hostname,
935                            port=options.port,
936                            O='NDG',
937                            OU='BADC')
938   
939    if options.getDelegation:
940               
941        outfile = options.outfile
942        if not outfile:
943            if sys.platform == 'win32':
944                outfile = 'proxy'
945            elif sys.platform in ['linux2','darwin']:
946                outfile = '/tmp/x509up_u%s' % (os.getuid())
947   
948        # Get MyProxy password
949        passphrase = getpass.getpass()
950           
951        # Retrieve proxy cert
952        try:
953            creds = myProxy.logon(username, 
954                                  passphrase, 
955                                  lifetime=options.lifetime)
956            open(outfile, 'w').write(''.join(creds))
957            try:
958                # Make sure permssions are set correctly
959                os.chmod(outfile, 600)
960            except Exception:
961                # Don't leave the file lying around if couldn't change it's
962                # permissions
963                os.unlink(outfile)
964               
965            print "A proxy has been received for user %s in %s." % \
966                (username, outfile)
967           
968        except Exception,e:
969            print "Error:", e
970            sys.exit(1)
971           
972    elif options.changePassphrase:
973               
974        # Get MyProxy password
975        passphrase = getpass.getpass(\
976                     prompt='Enter (current) MyProxy pass phrase: ')
977       
978        newPassphrase = getpass.getpass(\
979                                 prompt='Enter new MyProxy pass phrase: ')
980       
981        if newPassphrase != getpass.getpass(\
982                     prompt='Verifying - Enter new MyProxy pass phrase: '):
983            raise Exception, "Pass-phrases entered don't match"
984       
985       
986        # Retrieve proxy cert
987        try:
988            myProxy.changePassphrase(username,
989                             passphrase,
990                             newPassphrase, 
991                             options.certFile,
992                             options.keyFile,
993                             ownerPassphrase=open('../tmp2').read().strip())           
994        except Exception,e:
995            print "Error:", e
996            sys.exit(1)
997               
998    elif options.info:
999        try:
1000            credExists, errorTxt, fields = myProxy.info(username, 
1001                             options.certFile,
1002                             options.keyFile,
1003                             ownerPassphrase=open('../tmp2').read().strip())
1004            if credExists:
1005                print "username: %s" % username
1006                print "owner: %s" % fields['CRED_OWNER']
1007                print "  time left: %d" % \
1008                        (fields['CRED_END_TIME'] - fields['CRED_START_TIME'])
1009            else:
1010                ownerCert = X509.load_cert(options.certFile)
1011                ownerCertDN = '/' + \
1012                    ownerCert.get_subject().as_text().replace(', ', '/')
1013                print "no credentials found for user %s, owner \"%s\"" % \
1014                    (username, ownerCertDN)
1015
1016        except Exception, e:
1017            print "Error:", e
1018            sys.exit(1)
1019               
1020    elif options.destroy:
1021        try:
1022            myProxy.destroy(username, 
1023                            ownerCertFile=options.certFile,
1024                            ownerKeyFile=options.keyFile,
1025                            ownerPassphrase=open('../tmp2').read().strip())
1026           
1027        except Exception, e:
1028            print "Error:", e
1029            sys.exit(1)
1030    else:
1031        try:
1032            myProxy.store(username, 
1033                          options.certFile,
1034                          options.keyFile,
1035                          ownerCertFile=options.certFile,
1036                          ownerKeyFile=options.keyFile,
1037                          ownerPassphrase=open('../tmp2').read().strip(),
1038                          lifetime=options.lifetime)
1039           
1040        except Exception, e:
1041            print "Error:", e
1042            sys.exit(1)
1043   
Note: See TracBrowser for help on using the repository browser.