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

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

ndg.security.server/setup.py:

ndg.security.server/setup.cfg:

  • removed EasyInstall? and build sections
  • reinstated tag_build - set to '_dews' - and tag_svn_revision

ndg.security.server/ndg/security/server/AttAuthority/server-config.tac:

  • removed socket import and added os
  • added M2Crypto SSL support - works with Python client unit tests (required setting SSL v2 and 3 support)

but problems with WebSphere? client

ndg.security.server/ndg/security/server/AttAuthority/init.py,
ndg.security.server/ndg/security/server/conf/attAuthorityProperties.xml,
ndg.security.test/ndg/security/test/AttAuthority/siteAAttAuthorityProperties.xml,
ndg.security.test/ndg/security/test/AttAuthority/siteBAttAuthorityProperties.xml:

  • added sslKeyPwd setting for properties

ndg.security.server/ndg/security/server/MyProxy.py:

  • ensure cnHostPfx is reinitialised to if equal to None

ndg.security.common/setup.py:

  • added M2Crypto, ZSI and 4Suite to dependencies
  • revised dependency links to use NDG site, http://ndg.nerc.ac.uk/dist and ZSI sourceforge link taken

from pyGridWare settings. Latter won't work for PyXML but does work from command line ??

ndg.security.common/ndg/security/common/wsSecurity.py:

  • IMPORTANT FIX * - removed strip() from signed info digest calc - NOT needed and caused some problems

with verify.

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