1 | """MyProxy Client interface |
---|
2 | |
---|
3 | NERC Data Grid Project |
---|
4 | |
---|
5 | Major re-write of an original class. This updated version implements methods |
---|
6 | with SSL calls with M2Crypto rather use calls to myproxy client executables as |
---|
7 | in the original. This version is adapted and extended from an original |
---|
8 | program myproxy_logon by Tom Uram <turam@mcs.anl.gov> |
---|
9 | """ |
---|
10 | __author__ = "P J Kershaw" |
---|
11 | __date__ = "02/06/05" |
---|
12 | __copyright__ = "(C) 2007 STFC & NERC" |
---|
13 | __license__ = \ |
---|
14 | """This software may be distributed under the terms of the Q Public |
---|
15 | License, version 1.0 or later. |
---|
16 | |
---|
17 | For myproxy_logon see Access Grid Toolkit Public License (AGTPL): |
---|
18 | |
---|
19 | http://www-unix.mcs.anl.gov/fl/research/accessgrid/about/license.html |
---|
20 | |
---|
21 | This product includes software developed by and/or derived from the Access |
---|
22 | Grid Project (http://www.accessgrid.org) to which the U.S. Government retains |
---|
23 | certain rights.""" |
---|
24 | __contact__ = "Philip.Kershaw@stfc.ac.uk" |
---|
25 | __revision__ = '$Id: MyProxy.py 3196 2008-01-10 14:45:59Z pjkersha $' |
---|
26 | |
---|
27 | import sys, os |
---|
28 | import socket |
---|
29 | from M2Crypto import X509, RSA, EVP, m2, BIO, SSL |
---|
30 | |
---|
31 | import base64 |
---|
32 | |
---|
33 | # For parsing of properties file |
---|
34 | try: # python 2.5 |
---|
35 | from xml.etree import cElementTree as ElementTree |
---|
36 | except ImportError: |
---|
37 | # if you've installed it yourself it comes this way |
---|
38 | import cElementTree as ElementTree |
---|
39 | |
---|
40 | from ndg.security.common.openssl import OpenSSLConfig, OpenSSLConfigError |
---|
41 | |
---|
42 | class MyProxyClientError(Exception): |
---|
43 | """Catch all exception class""" |
---|
44 | |
---|
45 | class GetError(Exception): |
---|
46 | """Exceptions arising from get request to server""" |
---|
47 | |
---|
48 | class RetrieveError(Exception): |
---|
49 | """Error recovering a response from MyProxy""" |
---|
50 | |
---|
51 | class _HostCheck(SSL.Checker.Checker): |
---|
52 | """Override SSL.Checker.Checker to allow additional check of MyProxy |
---|
53 | server identity. If hostname doesn't match, allow match of host's |
---|
54 | Distinguished Name against MYPROXY_SERVER_DN setting""" |
---|
55 | |
---|
56 | def __init__(self, |
---|
57 | myProxyServerDN=os.environ.get('MYPROXY_SERVER_DN'), |
---|
58 | cnHostPfx='host/', |
---|
59 | **kw): |
---|
60 | """Override parent class __init__ to enable setting of myProxyServerDN |
---|
61 | setting |
---|
62 | |
---|
63 | @type myProxyServerDN: string |
---|
64 | @param myProxyServerDN: Set the expected Distinguished Name of the |
---|
65 | MyProxy server to avoid errors matching hostnames. This is useful |
---|
66 | where the hostname is not fully qualified |
---|
67 | |
---|
68 | @type cnHostPfx: string |
---|
69 | @param cnHostPfx: globus host certificates are |
---|
70 | generated by default with a 'host/' prefix to the host name. Set |
---|
71 | this keyword to '' or None to override and omit the prefix""" |
---|
72 | |
---|
73 | SSL.Checker.Checker.__init__(self, **kw) |
---|
74 | |
---|
75 | self.myProxyServerDN = myProxyServerDN |
---|
76 | self.cnHostPfx = cnHostPfx |
---|
77 | |
---|
78 | |
---|
79 | def __call__(self, peerCert, host=None): |
---|
80 | """Carry out checks on server ID |
---|
81 | @param peerCert: MyProxy server host certificate as M2Crypto.X509.X509 |
---|
82 | instance |
---|
83 | @param host: name of host to check |
---|
84 | """ |
---|
85 | |
---|
86 | # Globus host certificate has a "host/" prefix - see explanation in |
---|
87 | # __init__.__doc__ |
---|
88 | cnHostPfx = isinstance(self.cnHostPfx, basestring) \ |
---|
89 | and self.cnHostPfx or '' |
---|
90 | host = None or cnHostPfx + self.host |
---|
91 | |
---|
92 | try: |
---|
93 | SSL.Checker.Checker.__call__(self, peerCert, host=host) |
---|
94 | |
---|
95 | except SSL.Checker.WrongHost, e: |
---|
96 | # Try match against DN set from MYPROXY_SERVER_DN / config |
---|
97 | # file setting |
---|
98 | peerCertDN = '/' + \ |
---|
99 | peerCert.get_subject().as_text().replace(', ', '/') |
---|
100 | if peerCertDN != self.myProxyServerDN: |
---|
101 | # They match - drop the exception and return all OK instead |
---|
102 | raise e |
---|
103 | |
---|
104 | return True |
---|
105 | |
---|
106 | |
---|
107 | class MyProxyClient(object): |
---|
108 | """MyProxy client interface |
---|
109 | |
---|
110 | Based on protocol definitions in: |
---|
111 | |
---|
112 | http://grid.ncsa.uiuc.edu/myproxy/protocol/ |
---|
113 | |
---|
114 | @type __getCmd: string |
---|
115 | @cvar __getCmd: get command string |
---|
116 | |
---|
117 | @type __infoCmd: string |
---|
118 | @cvar __infoCmd: info command string |
---|
119 | |
---|
120 | @type __destroyCmd: string |
---|
121 | @cvar __destroyCmd: destroy command string |
---|
122 | |
---|
123 | @type __changePassphrase: string |
---|
124 | @cvar __changePassphrase: command string to change cred pass-phrase |
---|
125 | |
---|
126 | @type __storeCmd: string |
---|
127 | @cvar __storeCmd: store command string |
---|
128 | |
---|
129 | @type _hostCertSubDirPath: string |
---|
130 | @cvar _hostCertSubDirPath: sub-directory path host certificate (as tuple) |
---|
131 | |
---|
132 | @type _hostKeySubDirPath: string |
---|
133 | @cvar _hostKeySubDirPath: sub-directory path to host key (as tuple) |
---|
134 | |
---|
135 | @type __validKeys: tuple |
---|
136 | @cvar __validKeys: sets permissable element names for MyProxy XML config |
---|
137 | file |
---|
138 | """ |
---|
139 | |
---|
140 | __getCmd="""VERSION=MYPROXYv2 |
---|
141 | COMMAND=0 |
---|
142 | USERNAME=%s |
---|
143 | PASSPHRASE=%s |
---|
144 | LIFETIME=%d""" |
---|
145 | |
---|
146 | __infoCmd="""VERSION=MYPROXYv2 |
---|
147 | COMMAND=2 |
---|
148 | USERNAME=%s |
---|
149 | PASSPHRASE=PASSPHRASE |
---|
150 | LIFETIME=0""" |
---|
151 | |
---|
152 | __destroyCmd="""VERSION=MYPROXYv2 |
---|
153 | COMMAND=3 |
---|
154 | USERNAME=%s |
---|
155 | PASSPHRASE=PASSPHRASE |
---|
156 | LIFETIME=0""" |
---|
157 | |
---|
158 | __changePassphraseCmd="""VERSION=MYPROXYv2 |
---|
159 | COMMAND=4 |
---|
160 | USERNAME=%s |
---|
161 | PASSPHRASE=%s |
---|
162 | NEW_PHRASE=%s |
---|
163 | LIFETIME=0""" |
---|
164 | |
---|
165 | __storeCmd="""VERSION=MYPROXYv2 |
---|
166 | COMMAND=5 |
---|
167 | USERNAME=%s |
---|
168 | PASSPHRASE= |
---|
169 | LIFETIME=%d""" |
---|
170 | |
---|
171 | _hostCertSubDirPath = ('etc', 'hostcert.pem') |
---|
172 | _hostKeySubDirPath = ('etc', 'hostkey.pem') |
---|
173 | |
---|
174 | # valid configuration property keywords |
---|
175 | __validKeys = ('hostname', |
---|
176 | 'port', |
---|
177 | 'serverDN', |
---|
178 | 'serverCNprefix', |
---|
179 | 'gridSecurityDir', |
---|
180 | 'openSSLConfFilePath', |
---|
181 | 'tmpDir', |
---|
182 | 'proxyCertMaxLifetime', |
---|
183 | 'proxyCertLifetime', |
---|
184 | 'caCertFile') |
---|
185 | |
---|
186 | # Work out default location of proxy file if it exists. This is set if a |
---|
187 | # call has been made previously to logon / get-delegation |
---|
188 | defProxyFile = sys.platform == 'win32' and 'proxy' or \ |
---|
189 | sys.platform in ('linux2', 'darwin') and '/tmp/x509up_u%s'%(os.getuid()) \ |
---|
190 | or None |
---|
191 | |
---|
192 | @classmethod |
---|
193 | def writeProxyFile(cls, proxyCert, proxyPriKey, userX509Cert, filePath=None): |
---|
194 | """Write out proxy cert to file in the same way as myproxy-logon - |
---|
195 | proxy cert, private key, user cert. Nb. output from logon can be |
---|
196 | passed direct into this method |
---|
197 | |
---|
198 | @type proxyCert: string |
---|
199 | @param proxyCert: proxy certificate |
---|
200 | @type proxyPriKey: string |
---|
201 | @param proxyPriKey: private key for proxy |
---|
202 | @type userX509Cert: string |
---|
203 | @param userX509Cert: user certificate which issued the proxy |
---|
204 | @type filePath: string |
---|
205 | @param filePath: set to override the default filePath""" |
---|
206 | |
---|
207 | if filePath is None: |
---|
208 | filePath = MyProxyClient.defProxyFile |
---|
209 | |
---|
210 | if filePath is None: |
---|
211 | MyProxyClientError, \ |
---|
212 | "Error setting proxy file path - invalid platform?" |
---|
213 | |
---|
214 | outStr = proxyCert + proxyPriKey + userX509Cert |
---|
215 | open(MyProxyClient.defProxyFile, 'w').write(outStr) |
---|
216 | try: |
---|
217 | # Make sure permssions are set correctly |
---|
218 | os.chmod(MyProxyClient.defProxyFile, 0600) |
---|
219 | except Exception: |
---|
220 | # Don't leave the file lying around if couldn't change it's |
---|
221 | # permissions |
---|
222 | os.unlink(MyProxyClient.defProxyFile) |
---|
223 | |
---|
224 | raise MyProxyClientError, \ |
---|
225 | 'Unable to set 0600 permissions for proxy file "%s": %s' % \ |
---|
226 | (MyProxyClient.defProxyFile, e) |
---|
227 | |
---|
228 | @classmethod |
---|
229 | def readProxyFile(cls, filePath=None): |
---|
230 | """Read proxy cert file following the format used by myproxy-logon - |
---|
231 | proxy, cert, private key, user cert. |
---|
232 | |
---|
233 | @rtype: tuple |
---|
234 | @return: tuple containing proxy cert, private key, user cert""" |
---|
235 | if filePath is None: |
---|
236 | filePath = MyProxyClient.defProxyFile |
---|
237 | |
---|
238 | proxy = open(MyProxyClient.defProxyFile).read() |
---|
239 | |
---|
240 | # Split certs and key into separate tuple items |
---|
241 | return tuple(['-----BEGIN'+i for i in proxy.split('-----BEGIN')[1:]]) |
---|
242 | |
---|
243 | |
---|
244 | def __init__(self, propFilePath=None, **prop): |
---|
245 | """Make any initial settings for client connections to MyProxy |
---|
246 | |
---|
247 | Settings are held in a dictionary which can be set from **prop, |
---|
248 | a call to setProperties() or by passing settings in an XML file |
---|
249 | given by propFilePath |
---|
250 | |
---|
251 | @param propFilePath: set properties via a configuration file |
---|
252 | @param **prop: set properties via keywords - see __validKeys |
---|
253 | class variable for a list of these |
---|
254 | """ |
---|
255 | |
---|
256 | # Check for parameter names set from input |
---|
257 | #self.certReqDNparam = None |
---|
258 | |
---|
259 | # settings dictionary |
---|
260 | self.__prop = {} |
---|
261 | |
---|
262 | # Server host name - take from environment variable if available |
---|
263 | if 'MYPROXY_SERVER' in os.environ: |
---|
264 | self.__prop['hostname'] = os.environ['MYPROXY_SERVER'] |
---|
265 | |
---|
266 | # ... and port number |
---|
267 | if 'MYPROXY_SERVER_PORT' in os.environ: |
---|
268 | self.__prop['port'] = int(os.environ['MYPROXY_SERVER_PORT']) |
---|
269 | else: |
---|
270 | # Usual default is ... |
---|
271 | self.__prop['port'] = 7512 |
---|
272 | |
---|
273 | self.__prop['proxyCertLifetime'] = 43200 |
---|
274 | self.__prop['proxyCertMaxLifetime'] = 43200 |
---|
275 | |
---|
276 | # Configuration file used to get default subject when generating a |
---|
277 | # new proxy certificate request |
---|
278 | self.__openSSLConf = OpenSSLConfig() |
---|
279 | |
---|
280 | # Properties set via input keywords |
---|
281 | self.setProperties(**prop) |
---|
282 | |
---|
283 | # If properties file is set any parameters settings in file will |
---|
284 | # override those set by input keyword |
---|
285 | if propFilePath is not None: |
---|
286 | self.readProperties(propFilePath) |
---|
287 | |
---|
288 | |
---|
289 | #_________________________________________________________________________ |
---|
290 | def setProperties(self, **prop): |
---|
291 | """Update existing properties from an input dictionary |
---|
292 | Check input keys are valid names""" |
---|
293 | |
---|
294 | invalidKeys = [key for key in prop if key not in self.__validKeys] |
---|
295 | if invalidKeys: |
---|
296 | raise MyProxyClientError, 'Invalid property name(s) set: "%s"' % \ |
---|
297 | '", "'.join(invalidKeys) |
---|
298 | |
---|
299 | self.__prop.update(prop) |
---|
300 | |
---|
301 | # Update openssl conf file path |
---|
302 | # |
---|
303 | # Check 'prop' to see if they've been in THIS update |
---|
304 | # Check 'self.__prop' to ensure both are present in |
---|
305 | # order to construct a file path |
---|
306 | if 'openSSLConfFilePath' in prop: |
---|
307 | self.__openSSLConf.filePath = self.__prop['openSSLConfFilePath'] |
---|
308 | self.__openSSLConf.read() |
---|
309 | |
---|
310 | |
---|
311 | #_________________________________________________________________________ |
---|
312 | def readProperties(self, propFilePath=None, propElem=None): |
---|
313 | """Read XML properties from a file or cElementTree node |
---|
314 | |
---|
315 | propFilePath|propertiesElem |
---|
316 | |
---|
317 | @type propFilePath: string |
---|
318 | @param propFilePath: set to read from the specified file |
---|
319 | |
---|
320 | @type propElem: ElementTree node |
---|
321 | @param propElem: set to read beginning from a cElementTree node |
---|
322 | """ |
---|
323 | |
---|
324 | if propFilePath is not None: |
---|
325 | try: |
---|
326 | tree = ElementTree.parse(propFilePath) |
---|
327 | propElem = tree.getroot() |
---|
328 | |
---|
329 | except IOError, e: |
---|
330 | raise MyProxyClientError, \ |
---|
331 | "Error parsing properties file \"%s\": %s" % \ |
---|
332 | (e.filename, e.strerror) |
---|
333 | except Exception, e: |
---|
334 | raise MyProxyClientError, \ |
---|
335 | "Error parsing properties file: %s" % str(e) |
---|
336 | |
---|
337 | if propElem is None: |
---|
338 | raise MyProxyClientError, \ |
---|
339 | "Root element for parsing properties file is not defined" |
---|
340 | |
---|
341 | |
---|
342 | # Get properties as a data dictionary |
---|
343 | prop = {} |
---|
344 | try: |
---|
345 | for elem in propElem: |
---|
346 | # Check for string type to avoid exceptions from isdigit and |
---|
347 | # expandvars |
---|
348 | if isinstance(elem.text, basestring): |
---|
349 | if elem.text.isdigit(): |
---|
350 | prop[elem.tag] = int(elem.text) |
---|
351 | else: |
---|
352 | prop[elem.tag] = os.path.expandvars(elem.text) |
---|
353 | else: |
---|
354 | prop[elem.tag] = elem.text |
---|
355 | |
---|
356 | except Exception, e: |
---|
357 | raise SessionManagerError, \ |
---|
358 | "Error parsing tag \"%s\" in properties file" % elem.tag |
---|
359 | |
---|
360 | self.setProperties(**prop) |
---|
361 | |
---|
362 | |
---|
363 | #_________________________________________________________________________ |
---|
364 | def __getOpenSSLConfig(self): |
---|
365 | "Get OpenSSLConfig object property method" |
---|
366 | return self.__openSSLConfig |
---|
367 | |
---|
368 | openSSLConfig = property(fget=__getOpenSSLConfig, |
---|
369 | doc="OpenSSLConfig object") |
---|
370 | |
---|
371 | |
---|
372 | #_________________________________________________________________________ |
---|
373 | def _initConnection(self, |
---|
374 | ownerCertFile=None, |
---|
375 | ownerKeyFile=None, |
---|
376 | ownerPassphrase=None): |
---|
377 | """Initialise connection setting up SSL context and client and |
---|
378 | server side identity checks |
---|
379 | |
---|
380 | @param ownerCertFile: client certificate and owner of credential |
---|
381 | to be acted on. Can be a proxy cert + proxy's signing cert. Cert |
---|
382 | and private key are not necessary for getDelegation / logon calls |
---|
383 | @param ownerKeyFile: client private key file |
---|
384 | @param ownerPassphrase: pass-phrase protecting private key if set - |
---|
385 | not needed in the case of a proxy private key |
---|
386 | """ |
---|
387 | |
---|
388 | # Must be version 3 for MyProxy |
---|
389 | context = SSL.Context(protocol='sslv3') |
---|
390 | |
---|
391 | if 'caCertFile' in self.__prop: |
---|
392 | context.load_verify_locations(cafile=self.__prop['caCertFile']) |
---|
393 | |
---|
394 | # Stop if peer's certificate can't be verified |
---|
395 | context.set_allow_unknown_ca(False) |
---|
396 | else: |
---|
397 | context.set_allow_unknown_ca(True) |
---|
398 | |
---|
399 | if ownerCertFile: |
---|
400 | try: |
---|
401 | context.load_cert_chain(ownerCertFile, |
---|
402 | keyfile=ownerKeyFile, |
---|
403 | callback=lambda *ar, **kw: ownerPassphrase) |
---|
404 | except Exception, e: |
---|
405 | raise MyProxyClientError, \ |
---|
406 | "Error loading CA cert., cert. and key for SSL connection: %s" % e |
---|
407 | |
---|
408 | # Verify peer's certificate |
---|
409 | context.set_verify(SSL.verify_peer, 1) |
---|
410 | |
---|
411 | |
---|
412 | # Disable for compatibility with myproxy server (er, globus) |
---|
413 | # globus doesn't handle this case, apparently, and instead |
---|
414 | # chokes in proxy delegation code |
---|
415 | context.set_options(m2.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
---|
416 | |
---|
417 | # connect to myproxy server |
---|
418 | conn = SSL.Connection(context, sock=socket.socket()) |
---|
419 | |
---|
420 | # Check server host identity - if host doesn't match use explicit |
---|
421 | # 'serverDN' |
---|
422 | # host/<hostname> one |
---|
423 | hostCheck = _HostCheck(host=self.__prop['hostname'], |
---|
424 | myProxyServerDN=self.__prop.get('serverDN'), |
---|
425 | cnHostPfx=self.__prop.get('serverCNprefix')) |
---|
426 | conn.set_post_connection_check_callback(hostCheck) |
---|
427 | |
---|
428 | return conn |
---|
429 | |
---|
430 | |
---|
431 | #_________________________________________________________________________ |
---|
432 | def _createCertReq(self, CN, nBitsForKey=1024, messageDigest="md5"): |
---|
433 | """ |
---|
434 | Create a certificate request. |
---|
435 | |
---|
436 | @param CN: Common Name for certificate - effectively the same as the |
---|
437 | username for the MyProxy credential |
---|
438 | @param nBitsForKey: number of bits for private key generation - |
---|
439 | default is 1024 |
---|
440 | @param messageDigest: message disgest type - default is MD5 |
---|
441 | @return tuple of certificate request PEM text and private key PEM text |
---|
442 | """ |
---|
443 | |
---|
444 | # Check all required certifcate request DN parameters are set |
---|
445 | # Create certificate request |
---|
446 | req = X509.Request() |
---|
447 | |
---|
448 | # Generate keys |
---|
449 | key = RSA.gen_key(nBitsForKey, m2.RSA_F4) |
---|
450 | |
---|
451 | # Create public key object |
---|
452 | pubKey = EVP.PKey() |
---|
453 | pubKey.assign_rsa(key) |
---|
454 | |
---|
455 | # Add the public key to the request |
---|
456 | req.set_version(0) |
---|
457 | req.set_pubkey(pubKey) |
---|
458 | |
---|
459 | defaultReqDN = self.__openSSLConf.reqDN |
---|
460 | |
---|
461 | # Set DN |
---|
462 | x509Name = X509.X509_Name() |
---|
463 | x509Name.CN = CN |
---|
464 | |
---|
465 | if defaultReqDN: |
---|
466 | x509Name.OU = defaultReqDN['OU'] |
---|
467 | x509Name.O = defaultReqDN['O'] |
---|
468 | |
---|
469 | req.set_subject_name(x509Name) |
---|
470 | |
---|
471 | req.sign(pubKey, messageDigest) |
---|
472 | |
---|
473 | return (req.as_der(), key.as_pem(cipher=None)) |
---|
474 | |
---|
475 | |
---|
476 | #_________________________________________________________________________ |
---|
477 | def _deserializeResponse(self, msg, *fieldNames): |
---|
478 | """ |
---|
479 | Deserialize a MyProxy server response |
---|
480 | |
---|
481 | @param msg: string response message from MyProxy server |
---|
482 | @*fieldNames: the content of additional fields can be returned by |
---|
483 | specifying the field name or names as additional arguments e.g. info |
---|
484 | method passes 'CRED_START_TIME', 'CRED_END_TIME' and 'CRED_OWNER' |
---|
485 | field names. The content of fields is returned as an extra element |
---|
486 | in the tuple response. This element is itself a dictionary indexed |
---|
487 | by field name. |
---|
488 | @return tuple of integer response and errorTxt string (if any) |
---|
489 | """ |
---|
490 | |
---|
491 | lines = msg.split('\n') |
---|
492 | |
---|
493 | # get response value |
---|
494 | responselines = filter(lambda x: x.startswith('RESPONSE'), lines) |
---|
495 | responseline = responselines[0] |
---|
496 | respCode = int(responseline.split('=')[1]) |
---|
497 | |
---|
498 | # get error text |
---|
499 | errorTxt = "" |
---|
500 | errorlines = filter(lambda x: x.startswith('ERROR'), lines) |
---|
501 | for e in errorlines: |
---|
502 | etext = e.split('=', 1)[1] |
---|
503 | errorTxt += os.linesep + etext |
---|
504 | |
---|
505 | if fieldNames: |
---|
506 | fields = {} |
---|
507 | |
---|
508 | for fieldName in fieldNames: |
---|
509 | fieldlines = filter(lambda x: x.startswith(fieldName), lines) |
---|
510 | try: |
---|
511 | # Nb. '1' arg to split ensures owner DN is not split up. |
---|
512 | field = fieldlines[0].split('=', 1)[1] |
---|
513 | fields[fieldName]=field.isdigit() and int(field) or field |
---|
514 | |
---|
515 | except IndexError: |
---|
516 | # Ignore fields that aren't found |
---|
517 | pass |
---|
518 | |
---|
519 | return respCode, errorTxt, fields |
---|
520 | else: |
---|
521 | return respCode, errorTxt |
---|
522 | |
---|
523 | |
---|
524 | #_________________________________________________________________________ |
---|
525 | def _deserializeCerts(self, inputDat): |
---|
526 | """Unpack certificates returned from a get delegation call to the |
---|
527 | server |
---|
528 | |
---|
529 | @param inputDat: string containing the proxy cert and private key |
---|
530 | and signing cert all in DER format |
---|
531 | |
---|
532 | @return list containing the equivalent to the input in PEM format""" |
---|
533 | pemCerts = [] |
---|
534 | dat = inputDat |
---|
535 | |
---|
536 | while dat: |
---|
537 | # find start of cert, get length |
---|
538 | ind = dat.find('\x30\x82') |
---|
539 | if ind < 0: |
---|
540 | break |
---|
541 | |
---|
542 | len = 256*ord(dat[ind+2]) + ord(dat[ind+3]) |
---|
543 | |
---|
544 | # extract der-format cert, and convert to pem |
---|
545 | derCert = dat[ind:ind+len+4] |
---|
546 | |
---|
547 | x509 = X509.load_cert_der_string(derCert) |
---|
548 | pemCert = x509.as_pem() |
---|
549 | |
---|
550 | pemCerts.append(pemCert) |
---|
551 | |
---|
552 | # trim cert from data |
---|
553 | dat = dat[ind + len + 4:] |
---|
554 | |
---|
555 | return pemCerts |
---|
556 | |
---|
557 | |
---|
558 | #_________________________________________________________________________ |
---|
559 | def info(self, |
---|
560 | username, |
---|
561 | ownerCertFile=None, |
---|
562 | ownerKeyFile=None, |
---|
563 | ownerPassphrase=None): |
---|
564 | """return True/False whether credentials exist on the server for a |
---|
565 | given username |
---|
566 | |
---|
567 | Exceptions: GetError, RetrieveError |
---|
568 | |
---|
569 | @type username: string |
---|
570 | @param username: username selected for credential |
---|
571 | @type ownerCertFile: string |
---|
572 | @param ownerCertFile: certificate used for client authentication with |
---|
573 | the MyProxy server SSL connection. This ID will be set as the owner |
---|
574 | of the stored credentials. Only the owner can later remove |
---|
575 | credentials with myproxy-destroy or the destroy method. If not set, |
---|
576 | this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem |
---|
577 | @type ownerKeyFile: string |
---|
578 | @param ownerKeyFile: corresponding private key file. See explanation |
---|
579 | for ownerCertFile |
---|
580 | @type ownerPassphrase: string |
---|
581 | @param ownerPassphrase: passphrase for ownerKeyFile. Omit if the |
---|
582 | private key is not password protected. |
---|
583 | """ |
---|
584 | globusLoc = os.environ.get('GLOBUS_LOCATION') |
---|
585 | if not ownerCertFile: |
---|
586 | if globusLoc: |
---|
587 | ownerCertFile = os.path.join(globusLoc, |
---|
588 | *MyProxyClient._hostCertSubDirPath) |
---|
589 | ownerKeyFile = os.path.join(globusLoc, |
---|
590 | *MyProxyClient._hostKeySubDirPath) |
---|
591 | else: |
---|
592 | raise MyProxyClientError, \ |
---|
593 | "No client authentication cert. and private key file were given" |
---|
594 | |
---|
595 | # Set-up SSL connection |
---|
596 | conn = self._initConnection(ownerCertFile=ownerCertFile, |
---|
597 | ownerKeyFile=ownerKeyFile, |
---|
598 | ownerPassphrase=ownerPassphrase) |
---|
599 | |
---|
600 | conn.connect((self.__prop['hostname'], self.__prop['port'])) |
---|
601 | |
---|
602 | # send globus compatibility stuff |
---|
603 | conn.write('0') |
---|
604 | |
---|
605 | # send info command - ensure conversion from unicode before writing |
---|
606 | cmd = MyProxyClient.__infoCmd % username |
---|
607 | conn.write(str(cmd)) |
---|
608 | |
---|
609 | # process server response |
---|
610 | dat = conn.recv(8192) |
---|
611 | |
---|
612 | # Pass in the names of fields to return in the dictionary 'field' |
---|
613 | respCode, errorTxt, field = self._deserializeResponse(dat, |
---|
614 | 'CRED_START_TIME', |
---|
615 | 'CRED_END_TIME', |
---|
616 | 'CRED_OWNER') |
---|
617 | |
---|
618 | return not bool(respCode), errorTxt, field |
---|
619 | |
---|
620 | |
---|
621 | #_________________________________________________________________________ |
---|
622 | def changePassphrase(self, |
---|
623 | username, |
---|
624 | passphrase, |
---|
625 | newPassphrase, |
---|
626 | ownerCertFile=None, |
---|
627 | ownerKeyFile=None, |
---|
628 | ownerPassphrase=None): |
---|
629 | """change pass-phrase protecting the credentials for a given username |
---|
630 | |
---|
631 | Exceptions: GetError, RetrieveError |
---|
632 | |
---|
633 | @param username: username of credential |
---|
634 | @param passphrase: existing pass-phrase for credential |
---|
635 | @param newPassphrase: new pass-phrase to replace the existing one. |
---|
636 | @param ownerCertFile: certificate used for client authentication with |
---|
637 | the MyProxy server SSL connection. This ID will be set as the owner |
---|
638 | of the stored credentials. Only the owner can later remove |
---|
639 | credentials with myproxy-destroy or the destroy method. If not set, |
---|
640 | this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem |
---|
641 | @param ownerKeyFile: corresponding private key file. See explanation |
---|
642 | for ownerCertFile |
---|
643 | @param ownerPassphrase: passphrase for ownerKeyFile. Omit if the |
---|
644 | private key is not password protected. |
---|
645 | @return none |
---|
646 | """ |
---|
647 | globusLoc = os.environ.get('GLOBUS_LOCATION') |
---|
648 | if not ownerCertFile or not ownerKeyFile: |
---|
649 | if globusLoc: |
---|
650 | ownerCertFile = os.path.join(globusLoc, |
---|
651 | *MyProxyClient._hostCertSubDirPath) |
---|
652 | ownerKeyFile = os.path.join(globusLoc, |
---|
653 | *MyProxyClient._hostKeySubDirPath) |
---|
654 | else: |
---|
655 | raise MyProxyClientError, \ |
---|
656 | "No client authentication cert. and private key file were given" |
---|
657 | |
---|
658 | # Set-up SSL connection |
---|
659 | conn = self._initConnection(ownerCertFile=ownerCertFile, |
---|
660 | ownerKeyFile=ownerKeyFile, |
---|
661 | ownerPassphrase=ownerPassphrase) |
---|
662 | |
---|
663 | conn.connect((self.__prop['hostname'], self.__prop['port'])) |
---|
664 | |
---|
665 | # send globus compatibility stuff |
---|
666 | conn.write('0') |
---|
667 | |
---|
668 | # send command - ensure conversion from unicode before writing |
---|
669 | cmd = MyProxyClient.__changePassphraseCmd % (username, |
---|
670 | passphrase, |
---|
671 | newPassphrase) |
---|
672 | conn.write(str(cmd)) |
---|
673 | |
---|
674 | # process server response |
---|
675 | dat = conn.recv(8192) |
---|
676 | |
---|
677 | respCode, errorTxt = self._deserializeResponse(dat) |
---|
678 | if respCode: |
---|
679 | raise GetError, errorTxt |
---|
680 | |
---|
681 | |
---|
682 | #_________________________________________________________________________ |
---|
683 | def destroy(self, |
---|
684 | username, |
---|
685 | ownerCertFile=None, |
---|
686 | ownerKeyFile=None, |
---|
687 | ownerPassphrase=None): |
---|
688 | """destroy credentials from the server for a given username |
---|
689 | |
---|
690 | Exceptions: GetError, RetrieveError |
---|
691 | |
---|
692 | @param username: username selected for credential |
---|
693 | @param ownerCertFile: certificate used for client authentication with |
---|
694 | the MyProxy server SSL connection. This ID will be set as the owner |
---|
695 | of the stored credentials. Only the owner can later remove |
---|
696 | credentials with myproxy-destroy or the destroy method. If not set, |
---|
697 | this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem |
---|
698 | @param ownerKeyFile: corresponding private key file. See explanation |
---|
699 | for ownerCertFile |
---|
700 | @param ownerPassphrase: passphrase for ownerKeyFile. Omit if the |
---|
701 | private key is not password protected. |
---|
702 | @return none |
---|
703 | """ |
---|
704 | globusLoc = os.environ.get('GLOBUS_LOCATION') |
---|
705 | if not ownerCertFile or not ownerKeyFile: |
---|
706 | if globusLoc: |
---|
707 | ownerCertFile = os.path.join(globusLoc, |
---|
708 | *MyProxyClient._hostCertSubDirPath) |
---|
709 | ownerKeyFile = os.path.join(globusLoc, |
---|
710 | *MyProxyClient._hostKeySubDirPath) |
---|
711 | else: |
---|
712 | raise MyProxyClientError, \ |
---|
713 | "No client authentication cert. and private key file were given" |
---|
714 | |
---|
715 | # Set-up SSL connection |
---|
716 | conn = self._initConnection(ownerCertFile=ownerCertFile, |
---|
717 | ownerKeyFile=ownerKeyFile, |
---|
718 | ownerPassphrase=ownerPassphrase) |
---|
719 | |
---|
720 | conn.connect((self.__prop['hostname'], self.__prop['port'])) |
---|
721 | |
---|
722 | # send globus compatibility stuff |
---|
723 | conn.write('0') |
---|
724 | |
---|
725 | # send destroy command - ensure conversion from unicode before writing |
---|
726 | cmd = MyProxyClient.__destroyCmd % username |
---|
727 | conn.write(str(cmd)) |
---|
728 | |
---|
729 | # process server response |
---|
730 | dat = conn.recv(8192) |
---|
731 | |
---|
732 | respCode, errorTxt = self._deserializeResponse(dat) |
---|
733 | if respCode: |
---|
734 | raise GetError, errorTxt |
---|
735 | |
---|
736 | |
---|
737 | #_________________________________________________________________________ |
---|
738 | def store(self, |
---|
739 | username, |
---|
740 | passphrase, |
---|
741 | certFile, |
---|
742 | keyFile, |
---|
743 | ownerCertFile=None, |
---|
744 | ownerKeyFile=None, |
---|
745 | ownerPassphrase=None, |
---|
746 | lifetime=None, |
---|
747 | force=True): |
---|
748 | """Upload credentials to the server |
---|
749 | |
---|
750 | @raise GetError: |
---|
751 | @raise RetrieveError: |
---|
752 | |
---|
753 | @type username: string |
---|
754 | @param username: username selected for new credential |
---|
755 | @type passphrase: string |
---|
756 | @param passphrase: pass-phrase for new credential. This is the pass |
---|
757 | phrase which protects keyfile. |
---|
758 | @type certFile: string |
---|
759 | @param certFile: user's X.509 certificate in PEM format |
---|
760 | @type keyFile: string |
---|
761 | @param keyFile: equivalent private key file in PEM format |
---|
762 | @type ownerCertFile: string |
---|
763 | @param ownerCertFile: certificate used for client authentication with |
---|
764 | the MyProxy server SSL connection. This ID will be set as the owner |
---|
765 | of the stored credentials. Only the owner can later remove |
---|
766 | credentials with myproxy-destroy or the destroy method. If not set, |
---|
767 | this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem or if this |
---|
768 | is not set, certFile |
---|
769 | @type ownerKeyFile: string |
---|
770 | @param ownerKeyFile: corresponding private key file. See explanation |
---|
771 | for ownerCertFile |
---|
772 | @type ownerPassphrase: string |
---|
773 | @param ownerPassphrase: passphrase for ownerKeyFile. Omit if the |
---|
774 | private key is not password protected. Nb. keyFile is expected to |
---|
775 | be passphrase protected as this will be the passphrase used for |
---|
776 | logon / getDelegation. |
---|
777 | @type Force: bool |
---|
778 | @param force: set to True to overwrite any existing creds with the |
---|
779 | same username. If, force=False a check is made with a call to info. |
---|
780 | If creds already, exist exit without proceeding |
---|
781 | """ |
---|
782 | |
---|
783 | lifetime = lifetime or self.__prop['proxyCertMaxLifetime'] |
---|
784 | |
---|
785 | # Inputs must be string type otherwise server will reject the request |
---|
786 | if isinstance(username, unicode): |
---|
787 | username = str(username) |
---|
788 | |
---|
789 | if isinstance(passphrase, unicode): |
---|
790 | passphrase = str(passphrase) |
---|
791 | |
---|
792 | globusLoc = os.environ.get('GLOBUS_LOCATION') |
---|
793 | if not ownerCertFile or not ownerKeyFile: |
---|
794 | if globusLoc: |
---|
795 | ownerCertFile = os.path.join(globusLoc, |
---|
796 | *MyProxyClient._hostCertSubDirPath) |
---|
797 | ownerKeyFile = os.path.join(globusLoc, |
---|
798 | *MyProxyClient._hostKeySubDirPath) |
---|
799 | else: |
---|
800 | # Default so that the owner is the same as the ID of the |
---|
801 | # credentials to be uploaded. |
---|
802 | ownerCertFile = certFile |
---|
803 | ownerKeyFile = keyFile |
---|
804 | ownerPassphrase = passphrase |
---|
805 | |
---|
806 | if not force: |
---|
807 | # Check credentials don't already exist |
---|
808 | if self.info(username, |
---|
809 | ownerCertFile=ownerCertFile, |
---|
810 | ownerKeyFile=ownerKeyFile, |
---|
811 | ownerPassphrase=ownerPassphrase)[0]: |
---|
812 | raise MyProxyClientError, \ |
---|
813 | "Credentials already exist for user: %s" % username |
---|
814 | |
---|
815 | # Set up SSL connection |
---|
816 | conn = self._initConnection(ownerCertFile=ownerCertFile, |
---|
817 | ownerKeyFile=ownerKeyFile, |
---|
818 | ownerPassphrase=ownerPassphrase) |
---|
819 | |
---|
820 | conn.connect((self.__prop['hostname'], self.__prop['port'])) |
---|
821 | |
---|
822 | # send globus compatibility stuff |
---|
823 | conn.write('0') |
---|
824 | |
---|
825 | # send store command - ensure conversion from unicode before writing |
---|
826 | cmd = MyProxyClient.__storeCmd % (username, lifetime) |
---|
827 | conn.write(str(cmd)) |
---|
828 | |
---|
829 | # process server response |
---|
830 | dat = conn.recv(8192) |
---|
831 | |
---|
832 | respCode, errorTxt = self._deserializeResponse(dat) |
---|
833 | if respCode: |
---|
834 | raise GetError, errorTxt |
---|
835 | |
---|
836 | # Send certificate and private key |
---|
837 | certTxt = X509.load_cert(certFile).as_pem() |
---|
838 | keyTxt = open(keyFile).read() |
---|
839 | |
---|
840 | conn.send(certTxt + keyTxt) |
---|
841 | |
---|
842 | |
---|
843 | # process server response |
---|
844 | resp = conn.recv(8192) |
---|
845 | respCode, errorTxt = self._deserializeResponse(resp) |
---|
846 | if respCode: |
---|
847 | raise RetrieveError, errorTxt |
---|
848 | |
---|
849 | |
---|
850 | #_________________________________________________________________________ |
---|
851 | def logon(self, username, passphrase, lifetime=None): |
---|
852 | """Retrieve a proxy credential from a MyProxy server |
---|
853 | |
---|
854 | Exceptions: GetError, RetrieveError |
---|
855 | |
---|
856 | @type username: basestring |
---|
857 | @param username: username of credential |
---|
858 | |
---|
859 | @type passphrase: basestring |
---|
860 | @param passphrase: pass-phrase for private key of credential held on |
---|
861 | server |
---|
862 | |
---|
863 | @type lifetime: int |
---|
864 | @param lifetime: lifetime for generated certificate |
---|
865 | |
---|
866 | @rtype: tuple |
---|
867 | @return credentials as strings in PEM format: the |
---|
868 | user certificate, it's private key and the issuing certificate. The |
---|
869 | issuing certificate is only set if the user certificate is a proxy |
---|
870 | """ |
---|
871 | |
---|
872 | lifetime = lifetime or self.__prop['proxyCertLifetime'] |
---|
873 | |
---|
874 | # Generate certificate request here - any errors will be thrown |
---|
875 | # prior to making the connection and so not upsetting the server |
---|
876 | # |
---|
877 | # - The client will generate a public/private key pair and send a |
---|
878 | # NULL-terminated PKCS#10 certificate request to the server. |
---|
879 | certReq, priKey = self._createCertReq(username) |
---|
880 | |
---|
881 | # Set-up SSL connection |
---|
882 | conn = self._initConnection() |
---|
883 | conn.connect((self.__prop['hostname'], self.__prop['port'])) |
---|
884 | |
---|
885 | # send globus compatibility stuff |
---|
886 | conn.write('0') |
---|
887 | |
---|
888 | # send get command - ensure conversion from unicode before writing |
---|
889 | cmd = MyProxyClient.__getCmd % (username, passphrase, lifetime) |
---|
890 | conn.write(str(cmd)) |
---|
891 | |
---|
892 | # process server response |
---|
893 | dat = conn.recv(8192) |
---|
894 | respCode, errorTxt = self._deserializeResponse(dat) |
---|
895 | if respCode: |
---|
896 | raise GetError, errorTxt |
---|
897 | |
---|
898 | # Send certificate request |
---|
899 | conn.send(certReq) |
---|
900 | |
---|
901 | # process certificates |
---|
902 | # - 1st byte , number of certs |
---|
903 | dat = conn.recv(1) |
---|
904 | nCerts = ord(dat[0]) |
---|
905 | |
---|
906 | # - n certs |
---|
907 | dat = conn.recv(8192) |
---|
908 | |
---|
909 | # process server response |
---|
910 | resp = conn.recv(8192) |
---|
911 | respCode, errorTxt = self._deserializeResponse(resp) |
---|
912 | if respCode: |
---|
913 | raise RetrieveError, errorTxt |
---|
914 | |
---|
915 | # deserialize certs from received cert data |
---|
916 | pemCerts = self._deserializeCerts(dat) |
---|
917 | if len(pemCerts) != nCerts: |
---|
918 | RetrieveError, "%d certs expected, %d received" % \ |
---|
919 | (nCerts, len(pemCerts)) |
---|
920 | |
---|
921 | # Return certs and private key |
---|
922 | # - proxy cert |
---|
923 | # - private key |
---|
924 | # - rest of cert chain |
---|
925 | creds = [pemCerts[0], priKey] |
---|
926 | creds.extend(pemCerts[1:]) |
---|
927 | |
---|
928 | return tuple(creds) |
---|
929 | |
---|
930 | |
---|
931 | def getDelegation(self, *arg, **kw): |
---|
932 | """Retrieve proxy cert for user - same as logon""" |
---|
933 | return self.logon(*arg, **kw) |
---|
934 | |
---|
935 | |
---|
936 | #_____________________________________________________________________________ |
---|
937 | def main(): |
---|
938 | import traceback |
---|
939 | import pdb;pdb.set_trace() |
---|
940 | try: |
---|
941 | CmdLineClient() |
---|
942 | except Exception, e: |
---|
943 | print "Error: ", e |
---|
944 | print traceback.print_exc() |
---|
945 | sys.exit(1) |
---|
946 | |
---|
947 | # help="check whether a credential exists") |
---|
948 | # |
---|
949 | # help="destroy credential") |
---|
950 | # |
---|
951 | # help="change pass-phrase protecting credential") |
---|
952 | |
---|
953 | import optparse |
---|
954 | import getpass |
---|
955 | |
---|
956 | class CmdLineClientError(Exception): |
---|
957 | """Errors related to CmdLineClient class""" |
---|
958 | |
---|
959 | class CmdLineClient(object): |
---|
960 | """Command line interface to MyProxyClient class. Where possible it |
---|
961 | supports the same options as the Globus myproxy-* client commands""" |
---|
962 | |
---|
963 | run = { |
---|
964 | 'info': 'runInfo', |
---|
965 | 'logon': 'runLogon', |
---|
966 | 'get-delegation': 'runGetDelegation', |
---|
967 | 'destroy': 'runDestroy', |
---|
968 | 'change-pass': 'runChangePassphrase', |
---|
969 | 'store': 'runStore' |
---|
970 | } |
---|
971 | |
---|
972 | initOpts = { |
---|
973 | 'info': '_addInfoOpts', |
---|
974 | 'logon': '_addLogonOpts', |
---|
975 | 'get-delegation': '_addGetDelegationOpts', |
---|
976 | 'destroy': '_addDestroyOpts', |
---|
977 | 'change-pass': '_addChangePassphraseOpts', |
---|
978 | 'store': '_addStoreOpts' |
---|
979 | } |
---|
980 | |
---|
981 | cmdUsage = { |
---|
982 | 'info': "usage: %prog info arg1 arg2", |
---|
983 | 'logon': "usage: %prog logon arg1 arg2", |
---|
984 | 'get-delegation': "usage: %prog get-delegation arg1 arg2", |
---|
985 | 'destroy': "usage: %prog destroy arg1 arg2", |
---|
986 | 'change-pass': "usage: %prog change-pass arg1 arg2", |
---|
987 | 'store': "usage: %prog store arg1 arg2" |
---|
988 | } |
---|
989 | |
---|
990 | # Keep '%prog' in a separate string otherwise it confuses the % operator |
---|
991 | usage = "usage: %prog" + " [%s] arg1 arg2" % '|'.join(run.keys()) |
---|
992 | version = "%prog 0.8.7" |
---|
993 | |
---|
994 | def __init__(self): |
---|
995 | """Parse the command line and run the appropriate command""" |
---|
996 | |
---|
997 | self.parser = optparse.OptionParser(usage=self.__class__.usage, |
---|
998 | version=self.__class__.version) |
---|
999 | |
---|
1000 | # Get command - expected to be 1st arg |
---|
1001 | if len(sys.argv) > 1: |
---|
1002 | cmd = sys.argv[1] |
---|
1003 | if cmd not in self.__class__.run: |
---|
1004 | self.parser.error('"%s" command option not recognised' % cmd) |
---|
1005 | else: |
---|
1006 | self.parser.error('No command option set') |
---|
1007 | |
---|
1008 | # Ammend usage string for specific command |
---|
1009 | self.parser.set_usage(self.__class__.cmdUsage[cmd]) |
---|
1010 | |
---|
1011 | # Add options based on the command set |
---|
1012 | self._addGenericOpts() |
---|
1013 | getattr(self, self.initOpts[cmd])() |
---|
1014 | |
---|
1015 | # Omit command options as parser won't understand it |
---|
1016 | (self.opts, args) = self.parser.parse_args(args=sys.argv[2:]) |
---|
1017 | |
---|
1018 | # Process generic options |
---|
1019 | |
---|
1020 | # Not all commands require username option - for those that do, check |
---|
1021 | # to see if it was set or omitted |
---|
1022 | if hasattr(self.opts, 'username') and not self.opts.username: |
---|
1023 | if sys.platform == 'win32': |
---|
1024 | self.opts.username = os.environ["USERNAME"] |
---|
1025 | else: |
---|
1026 | import pwd |
---|
1027 | self.opts.username = pwd.getpwuid(os.geteuid())[0] |
---|
1028 | |
---|
1029 | self.myProxy = MyProxyClient(hostname=self.opts.host, |
---|
1030 | port=self.opts.port)#, |
---|
1031 | # O='NDG', |
---|
1032 | # OU='BADC') |
---|
1033 | |
---|
1034 | # Run the command |
---|
1035 | getattr(self, self.run[cmd])() |
---|
1036 | |
---|
1037 | |
---|
1038 | def _addGenericOpts(self): |
---|
1039 | """Generic options applying to all commands""" |
---|
1040 | self.parser.add_option("-s", |
---|
1041 | "--pshost", |
---|
1042 | dest="host", |
---|
1043 | default=os.environ.get('MYPROXY_SERVER'), |
---|
1044 | help="The hostname of the MyProxy server to contact") |
---|
1045 | |
---|
1046 | defPort = int(os.environ.get('MYPROXY_SERVER_PORT') or '7512') |
---|
1047 | self.parser.add_option("-p", |
---|
1048 | "--psport", |
---|
1049 | dest="port", |
---|
1050 | default=defPort, |
---|
1051 | type="int", |
---|
1052 | help="The port of the MyProxy server to contact") |
---|
1053 | |
---|
1054 | def _addInfoOpts(self): |
---|
1055 | """Add command line options for info""" |
---|
1056 | self.parser.add_option("-l", |
---|
1057 | "--username", |
---|
1058 | dest="username", |
---|
1059 | help="Username for the delegated proxy") |
---|
1060 | |
---|
1061 | self.parser.add_option("-C", |
---|
1062 | "--owner-certfile", |
---|
1063 | dest="ownerCertFile", |
---|
1064 | help=\ |
---|
1065 | """Certificate for owner of credentials to be queried. If omitted, it |
---|
1066 | defaults to $GLOBUS_LOCATION/etc/hostcert.pem.""") |
---|
1067 | |
---|
1068 | self.parser.add_option("-Y", |
---|
1069 | "--owner-keyfile", |
---|
1070 | dest="ownerKeyFile", |
---|
1071 | default=None, |
---|
1072 | help=\ |
---|
1073 | """Private key for owner of credentials to be stored. If omitted, it defaults |
---|
1074 | to $GLOBUS_LOCATION/etc/hostkey.pem.""") |
---|
1075 | |
---|
1076 | self.parser.add_option("-w", |
---|
1077 | "--owner-keyfile-passphrase", |
---|
1078 | dest="ownerPassphrase", |
---|
1079 | default=None, |
---|
1080 | help=\ |
---|
1081 | "Pass-phrase for Private key used for SSL client") |
---|
1082 | |
---|
1083 | def _addLogonOpts(self): |
---|
1084 | """Add command line options for logon""" |
---|
1085 | self.parser.add_option("-l", |
---|
1086 | "--username", |
---|
1087 | dest="username", |
---|
1088 | help="Username for the delegated proxy") |
---|
1089 | |
---|
1090 | self.parser.add_option("-t", |
---|
1091 | "--proxy_lifetime", |
---|
1092 | dest="lifetime", |
---|
1093 | default=43200, # = 12 hours in seconds |
---|
1094 | type="int", |
---|
1095 | help=\ |
---|
1096 | "Lifetime of proxies delegated by the server (default 12 hours)") |
---|
1097 | |
---|
1098 | self.parser.add_option("-o", |
---|
1099 | "--out", |
---|
1100 | dest="outfile", |
---|
1101 | help="Location of delegated proxy") |
---|
1102 | |
---|
1103 | def _addGetDelegationOpts(self): |
---|
1104 | """Add command line options for Get Delegation""" |
---|
1105 | self._addLogonOpts() |
---|
1106 | |
---|
1107 | def _addDestroyOpts(self): |
---|
1108 | """Add command line options for destroy""" |
---|
1109 | self.parser.add_option("-l", |
---|
1110 | "--username", |
---|
1111 | dest="username", |
---|
1112 | help=\ |
---|
1113 | "Username corresponding to credentials to be removed") |
---|
1114 | |
---|
1115 | def _addChangePassphraseOpts(self): |
---|
1116 | """Add command line options for change pass-phrase""" |
---|
1117 | self.parser.add_option("-l", |
---|
1118 | "--username", |
---|
1119 | dest="username", |
---|
1120 | help="Username for the target proxy") |
---|
1121 | |
---|
1122 | def _addStoreOpts(self): |
---|
1123 | """Add command line options for store""" |
---|
1124 | self.parser.add_option("-l", |
---|
1125 | "--username", |
---|
1126 | dest="username", |
---|
1127 | help=\ |
---|
1128 | "Username corresponding to credentials to be stored") |
---|
1129 | |
---|
1130 | self.parser.add_option("-c", |
---|
1131 | "--certfile", |
---|
1132 | dest="certFile", |
---|
1133 | default=None, |
---|
1134 | help="Certificate to be stored") |
---|
1135 | |
---|
1136 | self.parser.add_option("-y", |
---|
1137 | "--keyfile", |
---|
1138 | dest="keyFile", |
---|
1139 | default=None, |
---|
1140 | help="Private key to be stored") |
---|
1141 | |
---|
1142 | self.parser.add_option("-C", |
---|
1143 | "--owner-certfile", |
---|
1144 | dest="ownerCertFile", |
---|
1145 | help=\ |
---|
1146 | """Cert. for owner of credentials to be stored. If omitted, it defaults to |
---|
1147 | $GLOBUS_LOCATION/etc/hostcert.pem. If this is not readable, the cert. to be |
---|
1148 | stored is set as the owner.""") |
---|
1149 | |
---|
1150 | self.parser.add_option("-Y", |
---|
1151 | "--owner-keyfile", |
---|
1152 | dest="ownerKeyFile", |
---|
1153 | default=None, |
---|
1154 | help=\ |
---|
1155 | """Private key for owner of credentials to be stored. If omitted, it defaults |
---|
1156 | to $GLOBUS_LOCATION/etc/hostcert.pem.""") |
---|
1157 | |
---|
1158 | self.parser.add_option("-w", |
---|
1159 | "--owner-keyfile-passphrase", |
---|
1160 | dest="ownerPassphrase", |
---|
1161 | default=None, |
---|
1162 | help=\ |
---|
1163 | "Pass-phrase for Private key used for SSL client") |
---|
1164 | |
---|
1165 | self.parser.add_option("-t", |
---|
1166 | "--proxy_lifetime", |
---|
1167 | dest="lifetime", |
---|
1168 | default=43200, # = 12 hours in seconds |
---|
1169 | type="int", |
---|
1170 | help=\ |
---|
1171 | "Lifetime of proxies delegated by the server (default 12 hours)") |
---|
1172 | |
---|
1173 | def runGetDelegation(self): |
---|
1174 | """Call MyProxyClient.getDelegation""" |
---|
1175 | self.runLogon() |
---|
1176 | |
---|
1177 | def runLogon(self): |
---|
1178 | """Call MyProxyClient.logon""" |
---|
1179 | outfile = self.opts.outfile or MyProxyClient.defProxyFile |
---|
1180 | |
---|
1181 | # Get MyProxy password |
---|
1182 | passphrase = getpass.getpass() |
---|
1183 | |
---|
1184 | # Retrieve proxy cert |
---|
1185 | creds = self.myProxy.logon(self.opts.username, |
---|
1186 | passphrase, |
---|
1187 | lifetime=self.opts.lifetime) |
---|
1188 | MyProxyClient.writeProxyFile(*creds) |
---|
1189 | |
---|
1190 | print "A proxy has been received for user %s in %s." % \ |
---|
1191 | (self.opts.username, outfile) |
---|
1192 | |
---|
1193 | def runChangePassphrase(self): |
---|
1194 | """Call MyProxyClient.changePassphrase""" |
---|
1195 | |
---|
1196 | # Get MyProxy password |
---|
1197 | passphrase = getpass.getpass(\ |
---|
1198 | prompt='Enter (current) MyProxy pass phrase: ') |
---|
1199 | |
---|
1200 | newPassphrase = getpass.getpass(\ |
---|
1201 | prompt='Enter new MyProxy pass phrase: ') |
---|
1202 | |
---|
1203 | if newPassphrase != getpass.getpass(\ |
---|
1204 | prompt='Verifying - Enter new MyProxy pass phrase: '): |
---|
1205 | raise Exception, "Pass-phrases entered don't match" |
---|
1206 | |
---|
1207 | self.myProxy.changePassphrase(username, |
---|
1208 | passphrase, |
---|
1209 | newPassphrase, |
---|
1210 | self.opts.certFile, |
---|
1211 | self.opts.keyFile, |
---|
1212 | ownerPassphrase=open('../tmp2').read().strip()) |
---|
1213 | |
---|
1214 | def runInfo(self): |
---|
1215 | """Call MyProxyClient.info""" |
---|
1216 | if not self.opts.ownerCertFile or not self.opts.ownerKeyFile: |
---|
1217 | # self.opts.ownerCertFile = MyProxyClient.defProxyFile |
---|
1218 | # Look for proxy file stored from previous call to logon/ |
---|
1219 | # get-delegation |
---|
1220 | try: |
---|
1221 | creds = MyProxyClient.readProxyFile() |
---|
1222 | |
---|
1223 | # Copy proxy and user certificates |
---|
1224 | self.opts.ownerCertFile = './proxy-cert.pem' |
---|
1225 | self.opts.ownerKeyFile = './proxy-key.pem' |
---|
1226 | open(self.opts.ownerCertFile, 'w').write(creds[0] + ''.join(creds[2:])) |
---|
1227 | open(self.opts.ownerKeyFile, 'w').write(creds[1]) |
---|
1228 | except IOError: |
---|
1229 | # No such file - try proceeding regardless |
---|
1230 | pass |
---|
1231 | |
---|
1232 | credExists, errorTxt, fields = self.myProxy.info(self.opts.username, |
---|
1233 | ownerCertFile=self.opts.ownerCertFile, |
---|
1234 | ownerKeyFile=self.opts.ownerKeyFile, |
---|
1235 | ownerPassphrase=self.opts.ownerPassphrase) |
---|
1236 | if errorTxt: |
---|
1237 | raise CmdLineClientError, "User %s: %s" % (self.opts.username, |
---|
1238 | errorTxt) |
---|
1239 | |
---|
1240 | elif credExists: |
---|
1241 | print "username: %s" % self.opts.username |
---|
1242 | print "owner: %s" % fields['CRED_OWNER'] |
---|
1243 | print " time left: %d" % \ |
---|
1244 | (fields['CRED_END_TIME'] - fields['CRED_START_TIME']) |
---|
1245 | else: |
---|
1246 | ownerCert = ''#X509.load_cert(self.opts.ownerCertFile) |
---|
1247 | ownerCertDN = ''#'/' + \ |
---|
1248 | #ownerCert.get_subject().as_text().replace(', ', '/') |
---|
1249 | print "no credentials found for user %s, owner \"%s\"" % \ |
---|
1250 | (self.opts.username, ownerCertDN) |
---|
1251 | |
---|
1252 | def runDestroy(self): |
---|
1253 | """Call MyProxyClient.destroy""" |
---|
1254 | self.myProxy.destroy(self.opts.username, |
---|
1255 | ownerCertFile=self.opts.certFile, |
---|
1256 | ownerKeyFile=self.opts.keyFile, |
---|
1257 | ownerPassphrase=open('../tmp2').read().strip()) |
---|
1258 | |
---|
1259 | def runStore(self): |
---|
1260 | self.myProxy.store(self.opts.username, |
---|
1261 | self.opts.certFile, |
---|
1262 | self.opts.keyFile, |
---|
1263 | ownerCertFile=self.opts.certFile, |
---|
1264 | ownerKeyFile=self.opts.keyFile, |
---|
1265 | ownerPassphrase=open('./tmp').read().strip(), |
---|
1266 | lifetime=self.opts.lifetime) |
---|
1267 | |
---|
1268 | |
---|
1269 | if __name__ == "__main__": |
---|
1270 | main() |
---|