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

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

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

  • comment out Twisted from install - won't do egg install
  • updated long description

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

  • added verifyingCertFilePath keyword to SignatureHandler? initialisation
  • added SSL capability

python/conf/attAuthorityProperties.xml,
python/ndg.security.test/ndg/security/test/AttAuthority/siteAAttAuthorityProperties.xml,
python/ndg.security.test/ndg/security/test/AttAuthority/siteBAttAuthorityProperties.xml,
python/ndg.security.server/ndg/security/server/AttAuthority/init.py:
added element names for reading SSL settings from properties file.

python/ndg.security.server/ndg/security/server/SessionMgr/server-config.tac:
added verifyingCertFilePath keyword to SignatureHandler? initialisation

python/conf/sessionMgrProperties.xml,
python/ndg.security.test/ndg/security/test/SessionMgr/sessionMgrProperties.xml,
python/ndg.security.server/ndg/security/server/SessionMgr/init.py:
added clntCertFile properties file element name for setting certificate for
verifying incoming SOAP messages.

python/ndg.security.server/ndg/security/server/SessionMgr/Makefile:
corrected typo.

python/ndg.security.server/ndg/security/server/MyProxy.py:
Put OpenSSLConfig and OpenSSLConfigError classes into their own package
'openssl' so that they can also be used by the Certificate Authority client.

python/www/html/certificateAuthority.wsdl,
python/ndg.security.server/ndg/security/server/ca/CertificateAuthority_services_server.py,
python/ndg.security.common/ndg/security/common/ca/CertificateAuthority_services_types.py,
python/ndg.security.common/ndg/security/common/ca/CertificateAuthority_services.py: updated operations to issueCert, revokeCert and getCRL.

python/ndg.security.test/ndg/security/test/AttAuthority/attAuthorityClientTest.cfg: changed address of service to connect to.

python/ndg.security.test/ndg/security/test/SessionMgr/sessionMgrClientTest.cfg:
alternative username connection settings

python/ndg.security.common/ndg/security/common/AttAuthority/init.py:
fixed typos in error message and comments.

ython/ndg.security.common/ndg/security/common/XMLSec.py: changed call to
getAttributeNodeNS to getAttributeNode for retrieving reference element URI
attribute.

python/ndg.security.common/ndg/security/common/ca/init.py: code for
Certificate Authority client

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

  • tidied up imports
  • added properties for setting keywords to reference and SignedInfo? C14N
  • changed sign method so that it is truely configurable allow use of inclusive or exclusive C14N based on the keywords set for reference and SignedInfo? C14N calls.
  • swapped calls to getAttributeNodeNS with getAttributeNode where appropriate.

java/DEWS/AttAuthority/appClientModule/META-INF/ibm-webservicesclient-bnd.xmi,
java/DEWS/AttAuthority/build/classes/META-INF/ibm-webservicesclient-bnd.xmi:
updated to that request generator correctly places X.509 cert in
BinarySecurityToken? element.

java/DEWS/AttAuthority/appClientModule/Main.java,
java/DEWS/AttAuthority/appClientjava/DEWS/AttAuthority/appClientModule/META-INF/ibm-webservicesclient-bnd.xmiModule/Main.java:
include calls to getX509Cert and getAttCert methods.

java/DEWS/SessionMgr/build/classes/META-INF/ibm-webservicesclient-bnd.xmi,
java/DEWS/SessionMgr/appClientModule/META-INF/ibm-webservicesclient-bnd.xmi:
updates for testing Session MAnager client

java/DEWS/SessionMgr/appClientModule/Main.java: switched username setting.

  • 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    _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#_____________________________________________________________________________   
861def main():
862    import sys
863    import optparse
864    import getpass
865   
866    parser = optparse.OptionParser()
867    parser.add_option("-i", 
868                      "--info", 
869                      dest="info", 
870                      default=False,
871                      action="store_true",
872                      help="check whether a credential exists")
873
874    parser.add_option("-z", 
875                      "--destroy", 
876                      dest="destroy", 
877                      default=False,
878                      action="store_true",
879                      help="destroy credential")
880
881    parser.add_option("-C", 
882                      "--change-pass-phrase", 
883                      dest="changePassphrase", 
884                      default=False,
885                      action="store_true",
886                      help="change pass-phrase protecting credential")
887
888    parser.add_option("-g", 
889                      "--get-delegation", 
890                      dest="getDelegation", 
891                      default=False,
892                      action="store_true",
893                      help="Get delegation / logon")
894   
895    parser.add_option("-c", 
896                      "--certfile", 
897                      dest="certFile", 
898                      default=None,
899                      help="Certificate to be stored")
900   
901    parser.add_option("-y", 
902                      "--keyfile", 
903                      dest="keyFile", 
904                      default=None,
905                      help="Private key to be stored")
906   
907    parser.add_option("-w", 
908                      "--keyfile-passphrase", 
909                      dest="ownerPassphrase", 
910                      default=None,
911                      help="Pass-phrase for Private key used for SSL client")
912
913    parser.add_option("-s", 
914                      "--pshost", 
915                      dest="host", 
916                      help="The hostname of the MyProxy server to contact")
917   
918    parser.add_option("-p", 
919                      "--psport", 
920                      dest="port", 
921                      default=7512,
922                      type="int",
923                      help="The port of the MyProxy server to contact")
924   
925    parser.add_option("-l", 
926                      "--username", 
927                      dest="username", 
928                      help=\
929    "The username with which the credential is stored on the MyProxy server")
930
931    parser.add_option("-o", 
932                      "--out", 
933                      dest="outfile", 
934                      help=\
935    "The username with which the credential is stored on the MyProxy server")
936
937    parser.add_option("-t", 
938                      "--proxy-lifetime", 
939                      dest="lifetime", 
940                      default=43200,
941                      type="int",
942                      help=\
943    "The username with which the credential is stored on the MyProxy server")
944
945    (options, args) = parser.parse_args()
946   
947
948    # process options   
949    username = options.username
950    if not username:
951        if sys.platform == 'win32':
952            username = os.environ["USERNAME"]
953        else:
954            import pwd
955            username = pwd.getpwuid(os.geteuid())[0]
956
957    hostname = options.host or os.environ.get('MYPROXY_SERVER')
958    myProxy = MyProxyClient(hostname=hostname,
959                            port=options.port,
960                            O='NDG',
961                            OU='BADC')
962   
963    if options.getDelegation:
964               
965        outfile = options.outfile
966        if not outfile:
967            if sys.platform == 'win32':
968                outfile = 'proxy'
969            elif sys.platform in ['linux2','darwin']:
970                outfile = '/tmp/x509up_u%s' % (os.getuid())
971   
972        # Get MyProxy password
973        passphrase = getpass.getpass()
974           
975        # Retrieve proxy cert
976        try:
977            creds = myProxy.logon(username, 
978                                  passphrase, 
979                                  lifetime=options.lifetime)
980            open(outfile, 'w').write(''.join(creds))
981            try:
982                # Make sure permssions are set correctly
983                os.chmod(outfile, 600)
984            except Exception:
985                # Don't leave the file lying around if couldn't change it's
986                # permissions
987                os.unlink(outfile)
988               
989            print "A proxy has been received for user %s in %s." % \
990                (username, outfile)
991           
992        except Exception,e:
993            print "Error:", e
994            sys.exit(1)
995           
996    elif options.changePassphrase:
997               
998        # Get MyProxy password
999        passphrase = getpass.getpass(\
1000                     prompt='Enter (current) MyProxy pass phrase: ')
1001       
1002        newPassphrase = getpass.getpass(\
1003                                 prompt='Enter new MyProxy pass phrase: ')
1004       
1005        if newPassphrase != getpass.getpass(\
1006                     prompt='Verifying - Enter new MyProxy pass phrase: '):
1007            raise Exception, "Pass-phrases entered don't match"
1008       
1009       
1010        # Retrieve proxy cert
1011        try:
1012            myProxy.changePassphrase(username,
1013                             passphrase,
1014                             newPassphrase, 
1015                             options.certFile,
1016                             options.keyFile,
1017                             ownerPassphrase=open('../tmp2').read().strip())           
1018        except Exception,e:
1019            print "Error:", e
1020            sys.exit(1)
1021               
1022    elif options.info:
1023        try:
1024            credExists, errorTxt, fields = myProxy.info(username, 
1025                             options.certFile,
1026                             options.keyFile,
1027                             ownerPassphrase=open('../tmp2').read().strip())
1028            if credExists:
1029                print "username: %s" % username
1030                print "owner: %s" % fields['CRED_OWNER']
1031                print "  time left: %d" % \
1032                        (fields['CRED_END_TIME'] - fields['CRED_START_TIME'])
1033            else:
1034                ownerCert = X509.load_cert(options.certFile)
1035                ownerCertDN = '/' + \
1036                    ownerCert.get_subject().as_text().replace(', ', '/')
1037                print "no credentials found for user %s, owner \"%s\"" % \
1038                    (username, ownerCertDN)
1039
1040        except Exception, e:
1041            print "Error:", e
1042            sys.exit(1)
1043               
1044    elif options.destroy:
1045        try:
1046            myProxy.destroy(username, 
1047                            ownerCertFile=options.certFile,
1048                            ownerKeyFile=options.keyFile,
1049                            ownerPassphrase=open('../tmp2').read().strip())
1050           
1051        except Exception, e:
1052            print "Error:", e
1053            sys.exit(1)
1054    else:
1055        try:
1056            myProxy.store(username, 
1057                          options.certFile,
1058                          options.keyFile,
1059                          ownerCertFile=options.certFile,
1060                          ownerKeyFile=options.keyFile,
1061                          ownerPassphrase=open('../tmp2').read().strip(),
1062                          lifetime=options.lifetime)
1063           
1064        except Exception, e:
1065            print "Error:", e
1066            sys.exit(1)
1067   
Note: See TracBrowser for help on using the repository browser.