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

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

python/ndg.security.server/ndg/security/server/SessionMgr/server-config.tac:
update return from SessionMgr?.connect call

python/ndg.security.server/ndg/security/server/SessionMgr/init.py:
fix to SessionMgr?.createCookie + updated header

python/ndg.security.server/ndg/security/server/MyProxy.py:
!! Changed type of all commands sent to server so that they explicitly set to
string type. Sending uencoded strings causes errors.

python/ndg.security.test/ndg/security/test/MyProxy/myProxyClientTest.cfg:
change user settings for tests.

python/ndg.security.test/ndg/security/test/SessionMgr/SessionMgrClientTest.py:

  • fix to setUp so that not tested for password if left blank in config file -

useful for private keys without password protection.

  • updates to test2CookieConnect and test3ProxyCertConnect for testing

connect WS operation.

python/ndg.security.test/ndg/security/test/SessionMgr/sessionMgrProperties.xml:
Add in MyProxy? config details.

python/ndg.security.test/ndg/security/test/SessionMgr/sessionMgrClientTest.cfg:
updates to settings for tests.

  • 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 re
27import base64
28
29
30# For parsing of properties file
31import cElementTree as ElementTree
32
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    _certReqDNparamName = ('O', 'OU')
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        # Set-up parameter names for certificate request
180        self.__certReqDNparam = {}
181       
182        # Check for parameter names set from input
183        #self.certReqDNparam = None
184
185        # settings dictionary
186        self.__prop = {}
187       
188        # Server host name - take from environment variable if available
189        if 'MYPROXY_SERVER' in os.environ:
190            self.__prop['hostname'] = os.environ['MYPROXY_SERVER']
191           
192        # ... and port number
193        if 'MYPROXY_SERVER_PORT' in os.environ:
194            self.__prop['port'] = int(os.environ['MYPROXY_SERVER_PORT'])
195        else:
196            # Usual default is ...
197            self.__prop['port'] = 7512
198           
199        self.__prop['proxyCertLifetime'] = 43200
200        self.__prop['proxyCertMaxLifetime'] = 43200
201       
202        # Configuration file used to get default subject when generating a
203        # new proxy certificate request
204        self.__openSSLConf = OpenSSLConfig()
205
206       
207        # Properties set via input keywords
208        self.setProperties(**prop)
209
210        # If properties file is set any parameters settings in file will
211        # override those set by input keyword
212        if propFilePath is not None:
213            self.readProperties(propFilePath)
214       
215
216        # Grid security directory - environment variable setting overrides
217        if 'GRID_SECURITY_DIR' in os.environ:
218            self.__prop['gridSecurityDir'] = self.__env['GRID_SECURITY_DIR']           
219
220            self.__openSSLConf.filePath = \
221                            os.path.join(self.__prop['gridSecurityDir'],
222                                         self.__prop['openSSLConfFileName'])
223
224    #_________________________________________________________________________
225    def setProperties(self, **prop):
226        """Update existing properties from an input dictionary
227        Check input keys are valid names"""
228       
229        invalidKeys = [key for key in prop if key not in self.__validKeys]
230        if invalidKeys:
231            raise MyProxyClientError, 'Invalid property name(s) set: "%s"' % \
232                                    '", "'.join(invalidKeys)
233               
234        self.__prop.update(prop)
235
236        # Update openssl conf file path
237        if 'gridSecurityDir' in prop or 'openSSLConfFileName' in prop:           
238            self.__openSSLConf.filePath = \
239                            os.path.join(self.__prop['gridSecurityDir'],
240                                         self.__prop['openSSLConfFileName'])
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        @param propFilePath: set to read from the specified file
250        @param propertiesElem: set to read beginning from a cElementTree node
251        @return None
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 __setCertReqDNparam(self, dict):
295        '''certReqDNparam property set method - forces setting of certificate
296        request parameter names to valid values
297       
298        @param dict: dictionary of parameters'''
299       
300        invalidKw = [k for k in dict \
301                     if k not in MyProxyClient._certReqDNparamName]
302        if invalidKw:
303            raise MyProxyClientError, \
304    "Invalid certificate request keyword(s): %s.  Valid keywords are: %s" % \
305    (', '.join(invalidKw), ', '.join(MyProxyClient._certReqDNparamName))
306   
307        self.__certReqDNparam.update(dict)
308
309    #_________________________________________________________________________       
310    def __getCertReqDNparam(self):
311        """certReqDNparam property set method - for Certificate request
312        parameters dict"""
313        return self.__certReqDNparam
314   
315   
316    certReqDNparam = property(fset=__setCertReqDNparam,
317                            fget=__getCertReqDNparam,
318                            doc="Dictionary of parameters for cert. request")
319           
320    #_________________________________________________________________________       
321    def _initConnection(self, 
322                        ownerCertFile=None, 
323                        ownerKeyFile=None,
324                        ownerPassphrase=None):
325        """Initialise connection setting up SSL context and client and
326        server side identity checks
327       
328        @param ownerCertFile: client certificate and owner of credential
329        to be acted on.  Can be a proxy cert + proxy's signing cert.  Cert
330        and private key are not necessary for getDelegation / logon calls
331        @param ownerKeyFile: client private key file
332        @param ownerPassphrase: pass-phrase protecting private key if set -
333        not needed in the case of a proxy private key
334        """
335
336        # Must be version 3 for MyProxy
337        context = SSL.Context(protocol='sslv3')
338        context.load_verify_locations(cafile=self.__prop['caCertFile'])
339       
340        if ownerCertFile and ownerKeyFile:
341            context.load_cert_chain(ownerCertFile,
342                                keyfile=ownerKeyFile,
343                                callback=lambda *ar, **kw: ownerPassphrase)
344               
345            # Stop if peer's certificate can't be verified
346            context.set_allow_unknown_ca(False)
347           
348            # Verify peer's certificate
349            context.set_verify(SSL.verify_peer, 1) 
350       
351           
352        # Disable for compatibility with myproxy server (er, globus)
353        # globus doesn't handle this case, apparently, and instead
354        # chokes in proxy delegation code
355        context.set_options(m2.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS)
356       
357        # connect to myproxy server
358        conn = SSL.Connection(context, sock=socket.socket())
359       
360        # Check server host identity - if host doesn't match use explicit
361        # 'serverDN'
362        # host/<hostname> one
363        hostCheck = _HostCheck(host=self.__prop['hostname'],
364                               myProxyServerDN=self.__prop.get('serverDN'),
365                               cnHostPfx=self.__prop.get('serverCNprefix'))
366        conn.set_post_connection_check_callback(hostCheck)
367       
368        return conn
369   
370           
371    #_________________________________________________________________________       
372    def _createCertReq(self, CN, nBitsForKey=1024, messageDigest="md5"):
373        """
374        Create a certificate request.
375       
376        @param CN: Common Name for certificate - effectively the same as the
377        username for the MyProxy credential
378        @param nBitsForKey: number of bits for private key generation -
379        default is 1024
380        @param messageDigest: message disgest type - default is MD5
381        @return tuple of certificate request PEM text and private key PEM text
382        """
383       
384        # Check all required certifcate request DN parameters are set               
385        # Create certificate request
386        req = X509.Request()
387   
388        # Generate keys
389        key = RSA.gen_key(nBitsForKey, m2.RSA_F4)
390   
391        # Create public key object
392        pubKey = EVP.PKey()
393        pubKey.assign_rsa(key)
394       
395        # Add the public key to the request
396        req.set_version(0)
397        req.set_pubkey(pubKey)
398       
399        if self.__certReqDNparam:
400            certReqDNparam = self.__certReqDNparam
401        else:
402            defaultReqDN = self.__openSSLConf.getReqDN()
403           
404            certReqDNparam = {}
405            certReqDNparam['O'] = defaultReqDN['0.organizationName']
406            certReqDNparam['OU'] = defaultReqDN['0.organizationalUnitName']
407           
408        # Set DN
409        x509Name = X509.X509_Name()
410        x509Name.CN = CN
411        x509Name.OU = certReqDNparam['OU']
412        x509Name.O = certReqDNparam['O']
413        req.set_subject_name(x509Name)
414       
415        req.sign(pubKey, messageDigest)
416       
417        return (req.as_asn1(), key.as_pem(cipher=None))
418   
419   
420    #_________________________________________________________________________           
421    def _deserializeResponse(self, msg, *fieldNames):
422        """
423        Deserialize a MyProxy server response
424       
425        @param msg: string response message from MyProxy server
426        @*fieldNames: the content of additional fields can be returned by
427        specifying the field name or names as additional arguments e.g. info
428        method passes 'CRED_START_TIME', 'CRED_END_TIME' and 'CRED_OWNER'
429        field names.  The content of fields is returned as an extra element
430        in the tuple response.  This element is itself a dictionary indexed
431        by field name.
432        @return tuple of integer response and errorTxt string (if any)
433        """
434       
435        lines = msg.split('\n')
436       
437        # get response value
438        responselines = filter(lambda x: x.startswith('RESPONSE'), lines)
439        responseline = responselines[0]
440        respCode = int(responseline.split('=')[1])
441       
442        # get error text
443        errorTxt = ""
444        errorlines = filter(lambda x: x.startswith('ERROR'), lines)
445        for e in errorlines:
446            etext = e.split('=', 1)[1]
447            errorTxt += os.linesep + etext
448       
449        if fieldNames:
450            fields = {}
451                       
452            for fieldName in fieldNames:
453                fieldlines = filter(lambda x: x.startswith(fieldName), lines)
454                try:
455                    # Nb. '1' arg to split ensures owner DN is not split up.
456                    field = fieldlines[0].split('=', 1)[1]
457                    fields[fieldName]=field.isdigit() and int(field) or field
458
459                except IndexError:
460                    # Ignore fields that aren't found
461                    pass
462               
463            return respCode, errorTxt, fields
464        else:
465            return respCode, errorTxt
466   
467 
468    #_________________________________________________________________________             
469    def _deserializeCerts(self, inputDat):
470        """Unpack certificates returned from a get delegation call to the
471        server
472       
473        @param inputDat: string containing the proxy cert and private key
474        and signing cert all in DER format
475       
476        @return list containing the equivalent to the input in PEM format"""
477        pemCerts = []       
478        dat = inputDat
479       
480        while dat:   
481            # find start of cert, get length       
482            ind = dat.find('\x30\x82')
483            if ind < 0:
484                break
485               
486            len = 256*ord(dat[ind+2]) + ord(dat[ind+3])
487   
488            # extract der-format cert, and convert to pem
489            derCert = dat[ind:ind+len+4]
490           
491            x509 = X509.load_cert_string(derCert, type=X509.TYPE_ASN1)
492            pemCert = x509.as_pem()
493           
494            pemCerts.append(pemCert)
495   
496            # trim cert from data
497            dat = dat[ind + len + 4:]
498           
499        return pemCerts
500
501
502    #_________________________________________________________________________   
503    def info(self,
504             username, 
505             ownerCertFile=None,
506             ownerKeyFile=None,
507             ownerPassphrase=None):
508        """return True/False whether credentials exist on the server for a
509        given username
510       
511        Exceptions:  GetError, RetrieveError
512       
513        @param username: username selected for credential
514        @param ownerCertFile: certificate used for client authentication with
515        the MyProxy server SSL connection.  This ID will be set as the owner
516        of the stored credentials.  Only the owner can later remove
517        credentials with myproxy-destroy or the destroy method.  If not set,
518        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
519        @param ownerKeyFile: corresponding private key file.  See explanation
520        for ownerCertFile
521        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
522        private key is not password protected.
523        @return none
524        """
525        globusLoc = os.environ.get('GLOBUS_LOCATION')
526        if not ownerCertFile or not ownerKeyFile:
527            if globusLoc:
528                ownerCertFile = os.path.join(globusLoc, 
529                                         *MyProxyClient._hostCertSubDirPath)
530                ownerKeyFile = os.path.join(globusLoc, 
531                                         *MyProxyClient._hostKeySubDirPath)
532            else:
533                raise MyProxyClientError, \
534            "No client authentication cert. and private key file were given"
535
536        # Set-up SSL connection
537        conn = self._initConnection(ownerCertFile=ownerCertFile,
538                                    ownerKeyFile=ownerKeyFile,
539                                    ownerPassphrase=ownerPassphrase)
540       
541        conn.connect((self.__prop['hostname'], self.__prop['port']))
542       
543        # send globus compatibility stuff
544        conn.write('0')
545   
546        # send info command - ensure conversion from unicode before writing
547        cmd = MyProxyClient.__infoCmd % username
548        conn.write(str(cmd))
549   
550        # process server response
551        dat = conn.recv(8192)
552         
553        # Pass in the names of fields to return in the dictionary 'field'
554        respCode, errorTxt, field = self._deserializeResponse(dat, 
555                                                         'CRED_START_TIME', 
556                                                         'CRED_END_TIME', 
557                                                         'CRED_OWNER')
558
559        return not bool(respCode), errorTxt, field
560
561
562    #_________________________________________________________________________   
563    def changePassphrase(self,
564                         username, 
565                         passphrase,
566                         newPassphrase,
567                         ownerCertFile=None,
568                         ownerKeyFile=None,
569                         ownerPassphrase=None):
570        """change pass-phrase protecting the credentials for a given username
571       
572        Exceptions:  GetError, RetrieveError
573       
574        @param username: username of credential
575        @param passphrase: existing pass-phrase for credential
576        @param newPassphrase: new pass-phrase to replace the existing one.
577        @param ownerCertFile: certificate used for client authentication with
578        the MyProxy server SSL connection.  This ID will be set as the owner
579        of the stored credentials.  Only the owner can later remove
580        credentials with myproxy-destroy or the destroy method.  If not set,
581        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
582        @param ownerKeyFile: corresponding private key file.  See explanation
583        for ownerCertFile
584        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
585        private key is not password protected. 
586        @return none
587        """
588        globusLoc = os.environ.get('GLOBUS_LOCATION')
589        if not ownerCertFile or not ownerKeyFile:
590            if globusLoc:
591                ownerCertFile = os.path.join(globusLoc, 
592                                         *MyProxyClient._hostCertSubDirPath)
593                ownerKeyFile = os.path.join(globusLoc, 
594                                         *MyProxyClient._hostKeySubDirPath)
595            else:
596                raise MyProxyClientError, \
597            "No client authentication cert. and private key file were given"
598       
599        # Set-up SSL connection
600        conn = self._initConnection(ownerCertFile=ownerCertFile,
601                                    ownerKeyFile=ownerKeyFile,
602                                    ownerPassphrase=ownerPassphrase)
603
604        conn.connect((self.__prop['hostname'], self.__prop['port']))
605       
606        # send globus compatibility stuff
607        conn.write('0')
608   
609        # send command - ensure conversion from unicode before writing
610        cmd = MyProxyClient.__changePassphraseCmd % (username, 
611                                                     passphrase,
612                                                     newPassphrase)
613        conn.write(str(cmd))
614   
615        # process server response
616        dat = conn.recv(8192)
617           
618        respCode, errorTxt = self._deserializeResponse(dat)
619        if respCode:
620            raise GetError, errorTxt
621
622
623    #_________________________________________________________________________   
624    def destroy(self,
625                username, 
626                ownerCertFile=None,
627                ownerKeyFile=None,
628                ownerPassphrase=None):
629        """destroy credentials from the server for a given username
630       
631        Exceptions:  GetError, RetrieveError
632       
633        @param username: username selected for credential
634        @param ownerCertFile: certificate used for client authentication with
635        the MyProxy server SSL connection.  This ID will be set as the owner
636        of the stored credentials.  Only the owner can later remove
637        credentials with myproxy-destroy or the destroy method.  If not set,
638        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
639        @param ownerKeyFile: corresponding private key file.  See explanation
640        for ownerCertFile
641        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
642        private key is not password protected. 
643        @return none
644        """
645        globusLoc = os.environ.get('GLOBUS_LOCATION')
646        if not ownerCertFile or not ownerKeyFile:
647            if globusLoc:
648                ownerCertFile = os.path.join(globusLoc, 
649                                         *MyProxyClient._hostCertSubDirPath)
650                ownerKeyFile = os.path.join(globusLoc, 
651                                         *MyProxyClient._hostKeySubDirPath)
652            else:
653                raise MyProxyClientError, \
654            "No client authentication cert. and private key file were given"
655       
656        # Set-up SSL connection
657        conn = self._initConnection(ownerCertFile=ownerCertFile,
658                                    ownerKeyFile=ownerKeyFile,
659                                    ownerPassphrase=ownerPassphrase)
660
661        conn.connect((self.__prop['hostname'], self.__prop['port']))
662       
663        # send globus compatibility stuff
664        conn.write('0')
665   
666        # send destroy command - ensure conversion from unicode before writing
667        cmd = MyProxyClient.__destroyCmd % username
668        conn.write(str(cmd))
669   
670        # process server response
671        dat = conn.recv(8192)
672           
673        respCode, errorTxt = self._deserializeResponse(dat)
674        if respCode:
675            raise GetError, errorTxt
676
677
678    #_________________________________________________________________________   
679    def store(self,
680              username,
681              passphrase, 
682              certFile,
683              keyFile,
684              ownerCertFile=None,
685              ownerKeyFile=None,
686              ownerPassphrase=None,
687              lifetime=None,
688              force=True):
689        """Upload credentials to the server
690       
691        Exceptions:  GetError, RetrieveError
692       
693        @param username: username selected for new credential
694        @param passphrase: pass-phrase for new credential.  This is the pass
695        phrase which protects keyfile.
696        @param certFile: user's X.509 certificate in PEM format
697        @param keyFile: equivalent private key file in PEM format
698        @param ownerCertFile: certificate used for client authentication with
699        the MyProxy server SSL connection.  This ID will be set as the owner
700        of the stored credentials.  Only the owner can later remove
701        credentials with myproxy-destroy or the destroy method.  If not set,
702        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem or if this
703        is not set, certFile
704        @param ownerKeyFile: corresponding private key file.  See explanation
705        for ownerCertFile
706        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
707        private key is not password protected.  Nb. keyFile is expected to
708        be passphrase protected as this will be the passphrase used for
709        logon / getDelegation.
710        @param force: set to True to overwrite any existing creds with the
711        same username.  If, force=False a check is made with a call to info.
712        If creds already, exist exit without proceeding
713        @return none
714        """
715       
716        lifetime = lifetime or self.__prop['proxyCertMaxLifetime']
717
718        # Inputs must be string type otherwise server will reject the request
719        if isinstance(username, unicode):
720            username = str(username)
721           
722        if isinstance(passphrase, unicode):
723            passphrase = str(passphrase)
724       
725        globusLoc = os.environ.get('GLOBUS_LOCATION')
726        if not ownerCertFile or not ownerKeyFile:
727            if globusLoc:
728                ownerCertFile = os.path.join(globusLoc, 
729                                         *MyProxyClient._hostCertSubDirPath)
730                ownerKeyFile = os.path.join(globusLoc, 
731                                         *MyProxyClient._hostKeySubDirPath)
732            else:
733                # Default so that the owner is the same as the ID of the
734                # credentials to be uploaded.
735                ownerCertFile = certFile
736                ownerKeyFile = keyFile
737                ownerPassphrase = passphrase
738               
739        if not force:
740            # Check credentials don't already exist
741            if self.info(username,
742                         ownerCertFile=ownerCertFile,
743                         ownerKeyFile=ownerKeyFile,
744                         ownerPassphrase=ownerPassphrase)[0]:
745                raise MyProxyClientError, \
746                        "Credentials already exist for user: %s" % username
747
748        # Set up SSL connection
749        conn = self._initConnection(ownerCertFile=ownerCertFile,
750                                    ownerKeyFile=ownerKeyFile,
751                                    ownerPassphrase=ownerPassphrase)
752       
753        conn.connect((self.__prop['hostname'], self.__prop['port']))
754       
755        # send globus compatibility stuff
756        conn.write('0')
757   
758        # send store command - ensure conversion from unicode before writing
759        cmd = MyProxyClient.__storeCmd % (username, lifetime)
760        conn.write(str(cmd))
761   
762        # process server response
763        dat = conn.recv(8192)
764           
765        respCode, errorTxt = self._deserializeResponse(dat)
766        if respCode:
767            raise GetError, errorTxt
768       
769        # Send certificate and private key
770        certTxt = X509.load_cert(certFile).as_pem()
771        keyTxt = open(keyFile).read()
772       
773        conn.send(certTxt + keyTxt)
774   
775   
776        # process server response
777        resp = conn.recv(8192)
778        respCode, errorTxt = self._deserializeResponse(resp)
779        if respCode:
780            raise RetrieveError, errorTxt
781       
782       
783    #_________________________________________________________________________           
784    def logon(self, username, passphrase, lifetime=None):
785        """Retrieve a proxy credential from a MyProxy server
786       
787        Exceptions:  GetError, RetrieveError
788       
789        @param username: username of credential
790        @param passphrase: pass-phrase for private key of credential held on
791        server
792        @return list containing the credentials as strings in PEM format: the
793        proxy certificate, it's private key and the signing certificate.
794        """
795       
796        lifetime = lifetime or self.__prop['proxyCertLifetime']
797
798        # Generate certificate request here - any errors will be thrown
799        # prior to making the connection and so not upsetting the server
800        #
801        # - The client will generate a public/private key pair and send a
802        #   NULL-terminated PKCS#10 certificate request to the server.
803        certReq, priKey = self._createCertReq(username)
804
805        # Set-up SSL connection
806        conn = self._initConnection()
807        conn.connect((self.__prop['hostname'], self.__prop['port']))
808       
809        # send globus compatibility stuff
810        conn.write('0')
811   
812        # send get command - ensure conversion from unicode before writing
813        cmd = MyProxyClient.__getCmd % (username, passphrase, lifetime)
814        conn.write(str(cmd))
815   
816        # process server response
817        dat = conn.recv(8192)
818        respCode, errorTxt = self._deserializeResponse(dat)
819        if respCode:
820            raise GetError, errorTxt
821       
822        # Send certificate request
823        conn.send(certReq)
824   
825        # process certificates
826        # - 1st byte , number of certs
827        dat = conn.recv(1)
828        nCerts = ord(dat[0])
829       
830        # - n certs
831        dat = conn.recv(8192)
832   
833        # process server response
834        resp = conn.recv(8192)
835        respCode, errorTxt = self._deserializeResponse(resp)
836        if respCode:
837            raise RetrieveError, errorTxt
838   
839        # deserialize certs from received cert data
840        pemCerts = self._deserializeCerts(dat)
841        if len(pemCerts) != nCerts:
842            RetrieveError, "%d certs expected, %d received" % \
843                                                    (nCerts, len(pemCerts))
844   
845        # Return certs and private key
846        # - proxy cert
847        # - private key
848        # - rest of cert chain
849        creds = [pemCerts[0], priKey]
850        creds.extend(pemCerts[1:])
851       
852        return tuple(creds)
853       
854
855    def getDelegation(self, *arg, **kw):
856        """Retrieve proxy cert for user - same as logon"""
857        return self.logon(*arg, **kw)
858
859
860#_____________________________________________________________________________       
861class OpenSSLConfigError(Exception):
862    """Exceptions related to OpenSSLConfig class"""   
863
864
865#_____________________________________________________________________________       
866class OpenSSLConfig(object):
867    """Wrapper to OpenSSL Configuration file to allow extraction of
868    required distinguished name used for making certificate requests
869   
870    @cvar __reqDnRE: regular expression pattern for locating required
871    distinguished name from the config file"""
872   
873    __reqDnRE = '\[ req_distinguished_name \].*\['
874   
875    def __init__(self, filePath=None):
876        """Initial OpenSSL configuration optionally setting a file path to
877        read from
878       
879        @param filePath: path to OpenSSL configuration file"""
880       
881        # Content of file
882        self.__fileTxt = None
883        self.__setFilePath(filePath)
884
885           
886    def __setFilePath(self, filePath):
887        """Set property method
888        @param filePath: path for OpenSSL configuration file"""
889        if filePath is not None:
890            if not isinstance(filePath, basestring):
891                raise OpenSSLConfigError, \
892                    "Input OpenSSL config file path must be a string"
893
894            self.__filePath = filePath
895                   
896            try:
897                if not os.access(self.__filePath, os.R_OK):
898                    raise OpenSSLConfigError, "not found or no read access"
899                                         
900            except Exception, e:
901                raise OpenSSLConfigError, \
902                    "OpenSSL config file path is not valid: \"%s\": %s" % \
903                    (self.__filePath, str(e))
904
905
906    def __getFilePath(self):
907        """Get property method
908        @param filePath: file path for OpenSSL configuration file"""
909        return self.__filePath
910
911    filePath = property(fget=__getFilePath,
912                        fset=__setFilePath,
913                        doc="file path for configuration file")
914   
915    def __getFileTxt(self):
916        """Get content of file in call to getReqDN
917        @return string content of file"""
918        return self.__fileTxt
919   
920    def __setFileTxt(self, input):
921        """Set content of file
922        @param input: string content of  file. - Set to None to re-read
923        file content in call to getReqDN"""
924        self.__fileTxt = input
925   
926   
927    fileTxt = property(fset=__setFileTxt,
928                       fget=__getFileTxt,
929                       doc="Content of SSL file")
930   
931    def _read(self):
932        """Read OpenSSL configuration file and return as string
933       
934        @return fileTxt: content of the file"""
935
936        self.__fileTxt = self.__fileTxt or open(self.__filePath).read()
937        return self.__fileTxt
938
939
940    def getReqDN(self):
941        """Read Required DN parameters from the configuration file returning
942        them in a dictionary
943       
944        @return Distinguished Name OU and O defaults in a dictionary"""
945       
946        # Nb. Match over line boundaries
947        try:
948            reqDnTxt = re.findall(self.__reqDnRE, self._read(), re.S)[0]
949
950            # Separate lines
951            reqDnLines = reqDnTxt.split(os.linesep)
952           
953            # Match the '*_default' entries and make a dictionary
954            #
955            # Make sure comment lies are omitted - P J Kershaw 22/07/05
956            return dict([re.split('_default\s*=\s*', line) \
957                         for line in reqDnLines \
958                         if re.match('[^#].*_default\s*=', line)]) 
959        except Exception, e:
960            raise "Error reading content of OpenSSL config file \"%s\: %s" % \
961                                                    (self.__filePath, str(e))
962
963
964#_____________________________________________________________________________   
965def main():
966    import sys
967    import optparse
968    import getpass
969   
970    parser = optparse.OptionParser()
971    parser.add_option("-i", 
972                      "--info", 
973                      dest="info", 
974                      default=False,
975                      action="store_true",
976                      help="check whether a credential exists")
977
978    parser.add_option("-z", 
979                      "--destroy", 
980                      dest="destroy", 
981                      default=False,
982                      action="store_true",
983                      help="destroy credential")
984
985    parser.add_option("-C", 
986                      "--change-pass-phrase", 
987                      dest="changePassphrase", 
988                      default=False,
989                      action="store_true",
990                      help="change pass-phrase protecting credential")
991
992    parser.add_option("-g", 
993                      "--get-delegation", 
994                      dest="getDelegation", 
995                      default=False,
996                      action="store_true",
997                      help="Get delegation / logon")
998   
999    parser.add_option("-c", 
1000                      "--certfile", 
1001                      dest="certFile", 
1002                      default=None,
1003                      help="Certificate to be stored")
1004   
1005    parser.add_option("-y", 
1006                      "--keyfile", 
1007                      dest="keyFile", 
1008                      default=None,
1009                      help="Private key to be stored")
1010   
1011    parser.add_option("-w", 
1012                      "--keyfile-passphrase", 
1013                      dest="ownerPassphrase", 
1014                      default=None,
1015                      help="Pass-phrase for Private key used for SSL client")
1016
1017    parser.add_option("-s", 
1018                      "--pshost", 
1019                      dest="host", 
1020                      help="The hostname of the MyProxy server to contact")
1021   
1022    parser.add_option("-p", 
1023                      "--psport", 
1024                      dest="port", 
1025                      default=7512,
1026                      type="int",
1027                      help="The port of the MyProxy server to contact")
1028   
1029    parser.add_option("-l", 
1030                      "--username", 
1031                      dest="username", 
1032                      help=\
1033    "The username with which the credential is stored on the MyProxy server")
1034
1035    parser.add_option("-o", 
1036                      "--out", 
1037                      dest="outfile", 
1038                      help=\
1039    "The username with which the credential is stored on the MyProxy server")
1040
1041    parser.add_option("-t", 
1042                      "--proxy-lifetime", 
1043                      dest="lifetime", 
1044                      default=43200,
1045                      type="int",
1046                      help=\
1047    "The username with which the credential is stored on the MyProxy server")
1048
1049    (options, args) = parser.parse_args()
1050   
1051
1052    # process options   
1053    username = options.username
1054    if not username:
1055        if sys.platform == 'win32':
1056            username = os.environ["USERNAME"]
1057        else:
1058            import pwd
1059            username = pwd.getpwuid(os.geteuid())[0]
1060
1061    hostname = options.host or os.environ.get('MYPROXY_SERVER')
1062    myProxy = MyProxyClient(hostname=hostname,
1063                            port=options.port,
1064                            O='NDG',
1065                            OU='BADC')
1066   
1067    if options.getDelegation:
1068               
1069        outfile = options.outfile
1070        if not outfile:
1071            if sys.platform == 'win32':
1072                outfile = 'proxy'
1073            elif sys.platform in ['linux2','darwin']:
1074                outfile = '/tmp/x509up_u%s' % (os.getuid())
1075   
1076        # Get MyProxy password
1077        passphrase = getpass.getpass()
1078           
1079        # Retrieve proxy cert
1080        try:
1081            creds = myProxy.logon(username, 
1082                                  passphrase, 
1083                                  lifetime=options.lifetime)
1084            open(outfile, 'w').write(''.join(creds))
1085            try:
1086                # Make sure permssions are set correctly
1087                os.chmod(outfile, 600)
1088            except Exception:
1089                # Don't leave the file lying around if couldn't change it's
1090                # permissions
1091                os.unlink(outfile)
1092               
1093            print "A proxy has been received for user %s in %s." % \
1094                (username, outfile)
1095           
1096        except Exception,e:
1097            print "Error:", e
1098            sys.exit(1)
1099           
1100    elif options.changePassphrase:
1101               
1102        # Get MyProxy password
1103        passphrase = getpass.getpass(\
1104                     prompt='Enter (current) MyProxy pass phrase: ')
1105       
1106        newPassphrase = getpass.getpass(\
1107                                 prompt='Enter new MyProxy pass phrase: ')
1108       
1109        if newPassphrase != getpass.getpass(\
1110                     prompt='Verifying - Enter new MyProxy pass phrase: '):
1111            raise Exception, "Pass-phrases entered don't match"
1112       
1113       
1114        # Retrieve proxy cert
1115        try:
1116            myProxy.changePassphrase(username,
1117                             passphrase,
1118                             newPassphrase, 
1119                             options.certFile,
1120                             options.keyFile,
1121                             ownerPassphrase=open('../tmp2').read().strip())           
1122        except Exception,e:
1123            print "Error:", e
1124            sys.exit(1)
1125               
1126    elif options.info:
1127        try:
1128            credExists, errorTxt, fields = myProxy.info(username, 
1129                             options.certFile,
1130                             options.keyFile,
1131                             ownerPassphrase=open('../tmp2').read().strip())
1132            if credExists:
1133                print "username: %s" % username
1134                print "owner: %s" % fields['CRED_OWNER']
1135                print "  time left: %d" % \
1136                        (fields['CRED_END_TIME'] - fields['CRED_START_TIME'])
1137            else:
1138                ownerCert = X509.load_cert(options.certFile)
1139                ownerCertDN = '/' + \
1140                    ownerCert.get_subject().as_text().replace(', ', '/')
1141                print "no credentials found for user %s, owner \"%s\"" % \
1142                    (username, ownerCertDN)
1143
1144        except Exception, e:
1145            print "Error:", e
1146            sys.exit(1)
1147               
1148    elif options.destroy:
1149        try:
1150            myProxy.destroy(username, 
1151                            ownerCertFile=options.certFile,
1152                            ownerKeyFile=options.keyFile,
1153                            ownerPassphrase=open('../tmp2').read().strip())
1154           
1155        except Exception, e:
1156            print "Error:", e
1157            sys.exit(1)
1158    else:
1159        try:
1160            myProxy.store(username, 
1161                          options.certFile,
1162                          options.keyFile,
1163                          ownerCertFile=options.certFile,
1164                          ownerKeyFile=options.keyFile,
1165                          ownerPassphrase=open('../tmp2').read().strip(),
1166                          lifetime=options.lifetime)
1167           
1168        except Exception, e:
1169            print "Error:", e
1170            sys.exit(1)
1171   
Note: See TracBrowser for help on using the repository browser.