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

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

python/ndg.security.server/ndg/security/server/ca/server-config.tac: added file copied
from Session Manager equivalent

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

  • added ability to generate a certificate request using M2Crypto
  • added properties for running web service over SSL + PKI settings
  • properties file path can be set vai the NDGSEC_CA_PROPFILEPATH environment variable

python/ndg.security.server/ndg/security/server/ca/start-container.sh: script to run
service with twistd.

python/ndg.security.server/ndg/security/server/ca/Makefile: calls to wsdl2dispatch to
generate server side stubs.

python/ndg.security.server/ndg/security/server/SessionMgr/server-config.tac: fixed typo

  • ref to Attribute Authority instead of Session Manager.

python/ndg.security.server/ndg/security/server/MyProxy.py: simplified use of OpenSSLConfig
class.

python/conf/sessionMgrProperties.xml: removed duplicate lines.

python/conf/simpleCAProperties.xml: re-added - for some reason not previously stored in
repository.

python/ndg.security.test/ndg/security/test/ca/server.sh: adapted from Session Manager
version.

python/ndg.security.test/ndg/security/test/ca/caClientTest.cfg: added settings for
issueCert unit test to configure certificate request.

python/ndg.security.test/ndg/security/test/ca/caClientTest.py: setting up
test1IssueCert unit test.

python/ndg.security.test/ndg/security/test/ca/simpleCAProperties.xml: added settings for
SSL and PKI.

python/ndgSetup.sh: set up GRID_SECURITY_DIR environment variable

python/ndg.security.common/ndg/security/common/ca/init.py: Certificate Authority
web service client - updated settings for OpenSSLConfig object and issueCert method.

python/ndg.security.common/ndg/security/common/ca/CertReq.py: old code from alpha version
of NDG-Security.

python/ndg.security.common/ndg/security/common/ca/Makefile: generates client and server
side stubs for Certificate Authority web service.

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

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

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