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

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

python/ndg.security.server/ndg/security/server/ca/server-config.tac:

  • added check to ensure CA pass-phrase is set and if not prompt for from command line.
  • do a get call for 'clntCertFile' dict key so that it can be optional

python/ndg.security.server/ndg/security/server/ca/init.py:

  • use $HOME/.globus/simpleCA/grid-ca-ssl.conf as the default SSL config file
  • key access methods raise KeyError? on exception
  • PassPhrase? -> passphrase

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

  • certReqDNParam attribute is no longer needed - use openSSLConfig.reqDN instead.

python/conf/simpleCAProperties.xml,
python/ndg.security.test/ndg/security/test/ca/simpleCAProperties.xml:
explanation about default openSSLConfigFilePath setting

python/ndg.security.common/ndg/security/common/wsSecurity.py: check X.509 cert text
on 64th char for newline not 65th.

python/ndg.security.common/ndg/security/common/openssl.py: fix to error reading file
exception message.

  • 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'] = self.__env['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.get('0.organizationalUnitName') or \
387                        defaultReqDN['OU']
388        x509Name.O = defaultReqDN.get('0.organizationName') or \
389                        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.